用unity XR interaction Toolkit 制作垃圾分类虚拟仿真项目

项目效果演示:

垃圾分类虚拟仿真项目演示

1.环境配置

选择universal 3D(通用渲染管道)项目(不然导入素材包会丢失材质)。

选择Window->Package Manager,安装其中的XR interaction Toolkit。

选择其中的Samples,导入Starter Assets。

选择Edit->Project Settings->XR Plugin Management进行安装。

如果要用电脑模拟器进行VR控制,需要选择Edit->Project Settings->XR interaction Toolkit,勾选 Use XR Devcie Simulator in scenes。

选择Assets\Samples\XR Interaction Toolkit\2.5.4\Starter Assets路径位置下的DemoScene场景,打开运行,观察是否能进行控制。

2.选择界面场景制作(StartScene)

主要功能:射线交互实现主场景(mainScene)和测试场景(TestScene)两个场景的跳转。

新建场景命名为"StartScene",将DemoScene场景文件拖入Hierarchy面板,把其中的XR interaction Setup拖入到StartScene场景中,点击运行,进行测试。

导入素材包Classification_Resourse.unitypackage,将Assets\Polytope Studio\Lowpoly_Demos\Environment_Free路径位置的Environment_Free场景文件中拖入Hierarchy面板。

把Environment_Free所有元素拖入到StartScene场景中并删除其中的Player游戏对象,把XR interaction Setup作为某个场景元素的子物体,对XR interaction Setup游戏对象的Transform进行reset重置,然后进行手动的坐标调整,就可以在场景中漫游。同时可以对场景元素进行删减与范围缩小,还可以通过设置下图中colliders的位置,限制主体漫游的范围。

导入素材包Classification_Resourse.unitypackage,新建画布Canvas和图像Image和按钮Button(Legacy),选择Assets\Resourse\01图片路径下中3D面板背景图片赋值给image组件,效果大致如下图:

接下来,为UI元素添加组件使其可以进行VR射线交互,为画布添加Tracked Device Graphic Raycaster组件,为按钮添加XR Poke Follow Affordance组件。

编写脚本SkipController.cs,用于控制脚本的跳转。

cs 复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.SceneManagement;

//挂载在选择界面上,用于界面的跳转。

public class SkipController : MonoBehaviour

{

    // Start is called before the first frame update

    //控制返回主界面方法

    public void LoadScene(string sceneName)

    {

        SceneManager.LoadScene(sceneName); // 加载指定名称的场景

    }

}

新建空物体,命名为SkipController,将脚本挂载到该物体上,给两个按钮添加点击事件,如下图所示:

提前创建两个场景主场景(mainScene)和测试场景(TestScene),选择File->Build Settings,将三个场景拖入到Scene In Bulid中进行Build,随后测试是否能实现界面的跳转。

3.主界面场景制作(mainScene)

主要功能:通过VR抓取进行与垃圾的交互,需要将垃圾投入到正确的垃圾桶中,通过碰撞体检测和检测类型是否匹配,从而判断否正确分类,进行记录和反馈,也可以跳转到测试界面。

打开mainScene场景文件,导入素材包LowPolyTropicalEnvironment_LITE.unitypackage,将Assets\LowPolyTropicalEnvironment_LITE\Scenes路径位置的TropicalEnvironmentLite_Demo场景文件中拖入Hierarchy面板。

把TropicalEnvironmentLite_Demo所有元素拖入到StartScene场景中并删除其中的摄像头的游戏对象,再把DemoScene中的XR interaction Setup拖入到场景中,把其作为某个场景元素的子物体,进行坐标的调整,效果如下图,进行测试。

接下来进行垃圾分类场景的搭建,将Assets\Resources\02 模型 路径位置下的垃圾桶拖入场景中,调整四个垃圾桶的位置,将Assets\Resources\01 图片 路径位置下的干垃圾、有害垃圾、湿垃圾、可回收的图片拖入场景中,作为垃圾桶的子物体,进行位置的调整。

在Project面板右击,选择create->matertial,调整材料的颜色,并把该材质拖入到Scene中的垃圾桶上(调整整体和轮子的颜色)。

最终效果如下图所示:

为垃圾桶添加碰撞体,选择4个垃圾桶,添加Mesh Collider组件,不勾选Convex选项。

同时需要在底部创建一个碰撞体,用于检测是否分类正确。在Hierarchy面板中,选择垃圾桶右击,选择3D Object->Plane,作为其子物体,编写脚本Trash Can.cs:

cs 复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

//用枚举类型区分垃圾类型

public enum GarbageType

{

    recyclable,//可回收


    wet,//湿垃圾

    dry,//干垃圾

    hazardous//有害垃圾

}

//挂载在垃圾桶上

public class TrashCan : MonoBehaviour

{

    // Start is called before the first frame update

    void Start()

    {

        

    }

    public GarbageType type; // 垃圾桶类型

    //垃圾桶底部设置碰撞体,用于碰撞检测,返回垃圾桶本身和垃圾的类型给控制器进行逻辑判断。

    private void OnCollisionEnter(Collision collision)

    {

        Garbage garbage = collision.collider.GetComponent<Garbage>();

        if (garbage != null)

        {

            ClassificationManager.Instance.OnGarbageCollision(garbage.type, type);

            Debug.Log("当前垃圾桶的类型为:" + type + "    当前垃圾桶的类型为: " + garbage.type);

            if (type != garbage.type)

            {

                garbage.ResetToStartPosition();

            }

        }


    }

}

然后挂载在该物体上,同时Plane需要进行调整位置、颜色,设置对应的类型。

将Assets\Resources\02 模型 路径位置下的"桌子"模型拖入场景中,调整到合适的大小和为止,为其添加Box Collider组件,点击Edit Collider,调整其碰撞体大小,同时添加Rigidbody组件,勾选Is Kinematic选项,使其不会受碰撞影响。

将Assets\Resources\02 模型 路径位置下的"大猩猩手办"、"可乐罐"、"药品2"、"蛋糕"、"陶瓷杯"、"电池"的模型拖入场景中,部分模型需要手动拖动material进行填色。

随后选中所有物体,为其添加Box Collider、Rigidbody、XR Grab Interactable组件,在Box Collider组件中点击Edit Collider,单独调整其碰撞体大小,并将物体放置在桌面上。

新建脚本Garbage.cs,并挂载在物体上。

cs 复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

//挂载在垃圾类上

public class Garbage : MonoBehaviour

{

    public GarbageType type; // 垃圾类型

    private Vector3 originalPosition; // 原始位置

    private Rigidbody rb;


    private void Start()

    {

        rb = GetComponent<Rigidbody>();

        originalPosition = transform.position;

    }

    //当被错误分类时调用,用于把垃圾重新放回桌子上

    public void ResetToStartPosition()

    {

        transform.position = originalPosition;

        rb.velocity = Vector3.zero;

        rb.angularVelocity = Vector3.zero;

    }

}

在Garbage(Script)组件中设置每个物体的类型Type,类型如下表:

|-------|--------|-----------|
| 模型 | 实际垃圾类型 | 设置的Type |
| 大猩猩手办 | 可回收 | Recycle |
| 可乐罐 | 可回收 | Recycle |
| 药品 | 有害 | Hazardous |
| 蛋糕 | 湿垃圾 | Wet |
| 陶瓷杯 | 干垃圾 | Dry |
| 电池 | 有害 | Hazardous |

创建脚本ClassificationManager.cs,用于垃圾分类逻辑处理。

cs 复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

using UnityEngine.SceneManagement;

using UnityEngine.Events;

//挂载在主界面(垃圾分类)上的控制器

public class ClassificationManager : MonoBehaviour

{

    public static ClassificationManager Instance; // 单例模式

    public GameObject correctUI;//正确UI

    public GameObject incorrectUI;//错误UI

    public AudioSource audiosource;//播放器

    public AudioClip correctSound;//正确音效

    public AudioClip incorrectSound;//错误音效

    public GameObject CountUI;//用于统计正确分类的个数

    private int Count=0;//个数

    private void Start()

    {

        correctUI.SetActive(false);

        incorrectUI.SetActive(false);

        CountUI.SetActive(true);

        CountUI.GetComponent<Text>().text = "你目前正确分类的物体个数为:" + Count;

    }

    //用于判断垃圾和垃圾桶是否是同一类别的

    public void OnGarbageCollision(GarbageType garbageType, GarbageType trashCanType)

    {

        if (garbageType == trashCanType)

        {

            CorrectDispose();

            Count += 1;

        }

        else

        {

            IncorrectDispose();


        }

        CountUI.GetComponent<Text>().text = "你目前正确分类的物体个数为:" + Count;

        Debug.Log("Your score is: " + Count);

    }

    private void CorrectDispose()

    {

        audiosource.clip = correctSound;

        audiosource.Play();

        incorrectUI.SetActive(false);

        //控制UI先显示,2秒后消失

        correctUI.SetActive(true);

        Invoke("DisappearObject", 2f);

       

    }

    private void IncorrectDispose()

    {

        audiosource.clip = incorrectSound;

        audiosource.Play();

        correctUI.SetActive(false);

     

        incorrectUI.SetActive(true);

        Invoke("DisappearObject", 2f);

        

    }

    //挂载在按钮上,界面跳转

    public void LoadScene(string sceneName)

    {

        SceneManager.LoadScene(sceneName); // 加载指定名称的场景

    }

    private void DisappearObject()

    {

        correctUI.SetActive(false);

        incorrectUI.SetActive(false);

    }

}

在场景中创建一个空物体,命名为ClassificationManager,把创建的脚本挂载上去,为其添加Audio Source组件,对脚本中的Audiosource进行赋值,同时将Assets\Resources\03 音效 路径位置下的"正确音效"、"错误"文件拖入到Correct Sound和Incorrect Sound中进行赋值,效果如下图所示:

接下来,需要制作UI界面,需要在场景中新建画布Canvas、用于显示已分类数量的Text(Legacy)和用于跳转界面的按钮Button(Legacy)。同时新建3个Image游戏对象,用于显示背景、正确图像和错误图像,将Assets\Resources\01 图片 路径位置下"回顾3"、"正确"、"错误"的图片赋值给Image,修改UI元素名称,便于区分,调整UI元素位置,最终效果如下图所示:

为了使其能进行VR射线交互,给画布添加Tracked Device Graphic Raycaster组件,为按钮添加XR Poke Follow Affordance组件。

接下来继续给ClassificationManager物体进行参数的赋值。

进行测试,是否能进行判断垃圾分类的正误并进行反馈。

4.测试场景制作(TestScene)

主要功能:能通过射线交互,能进行选择题选项的选择和判断题的对错的选择。

首先导入素材,打开TestScene场景文件,将DemoScene场景文件拖入Hierarchy面板,把其中的XR interaction Setup拖入到StartScene场景中,将Assets\Resources\02 模型 路径位置下的房屋模型导入到场景中,调整位置,让XR interaction Setup处于房屋之中,效果大致如下图,进行测试。

接下来要进行答题界面的制作,我们可以打开将Assets\Resources\06 考题 路径位置下的垃圾分类考题文件,进行题干和选项的内容的设置。

首先新建一个画布Canvas,调整位置,为其添加TrackedDeviceGraphicRaycaster组件。新建Image子物体,设置画布的背景图片。

然后题目中有单选题和判断题,需要进行区分。创建脚本Question.cs,用于区别题型和设置答案:

cs 复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

//挂载在具体题目上(判断题、选择题)

public class Question: MonoBehaviour

{

    public Toggle[] options; // 单选题的选项

    public int correctAnswerIndex; // 单选题正确答案的索引

    public Toggle trueFalseToggle; // 判断题的Toggle

    public bool correctAnswer; // 判断题的正确答案

    public bool isChoice; // 判断是选择题还是判断题

}

接下进行题目的UI制作。

选择题的制作(以第一题为例):

创建一个空物体作为新建画布的子物体,命名为Question1。为其挂载Question脚本,并设置属性如下图所示(4个选项、D选项为正确选项,为选择题),点击Options下方"+"号,设置数量为4,设置Correct Answer Index的值为3,勾选 Is Choice选项。

同时为Question1游戏对象添加Toggle Group组件,用于控制其子对象的Toggle只能被选中一个,用于模拟单选题。同时勾选Allow Switch Off 选项,允许程序运行时,可以没有默认的勾选选项。

在Question1下新建Text(Lecary)和4个Toggle,作为其子物体,调整位置、颜色,并对选项进行取名(1_A,1_B,1_C,1_D),便于与后面的选择题选项区分。同时需要为每一个Toggle游戏对象添加XR Poke Follow Affordance组件。

选项新建完成后,对Question1的Question组件中的Option进行赋值,如下图所示:

这样一道选择题的设置就完成了。

判断题的制作(以第二题为例):

创建一个Toggle作为画布Panel的子物体,命名为Question2,为其挂载XR Poke Follow Affordance组件,同时挂载Question脚本,并设置属性如下图所示(答案为正确,为判断题),勾选Correct Answer选项,不勾选Is Choice。

新建一个Text(Legacy)作为Question2游戏对象的子物体,编辑其内容,显示题干,如下图所示:

这样一道判断题就编辑好了,按如下的两种方式将5道题目编辑好后(注意命名要进行区分),我们还需要提交按钮和返回主界面的按钮Button和显示分数的UI。在画布中新建两个按钮Button(Legacy),分别命名为returnButton、SubmitButton,新建Text(Legacy),命名为Score,在Hierarchy面板中将returnButton拖到到Score下,作为其子物体。

调整其位置,最后效果如下图所示:

到此为止,我们UI界面制作完毕。

接下来我们需要新建脚本TestController.cs,用于实现判断对错、交互的功能:

cs 复制代码
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

using UnityEngine.SceneManagement;

//挂载在测试界面的控制器上。

public class TestController : MonoBehaviour

{

    public List<Question> questions; // 包含所有问题的列表

    public int score = 0; // 最终得分

    public GameObject ScoreUI;

    private void Start()

    {

        ScoreUI.SetActive(false);  

    }

    // 用户点击提交按钮时调用的方法

    public void SubmitAnswers()

    {

        score = 0;

        foreach (Question question in questions)

        {      //如果是选择题

            if (question.isChoice)

            {

                // 单选题

                if (question.options[question.correctAnswerIndex].isOn)

                {

                    score++;

                }

            }

            else

            {

                // 判断题

                if (question.trueFalseToggle.isOn== question.correctAnswer)

                {

                    score++;

                }

            }

        }

        ScoreUI.SetActive(true);

        ScoreUI.GetComponent<Text>().text = "你的最终得分是:" + (score*1.0/questions.Count)*100;

        Debug.Log("Your score is: " + score);

        // 可以在这里添加代码显示得分或者转到下一个场景

    }

    //控制返回主界面方法

    public void LoadScene(string sceneName)

    {

        SceneManager.LoadScene(sceneName); // 加载指定名称的场景

    }

}

新建空物体,命名为TestController,挂载上该脚本,对Questions和Score UI属性进行赋值,如下图所示:

为两个按钮分别添加跳转场景的功能和提交答案的功能,添加点击事件,如下图所示:

最后进行测试,功能是否正常。

相关推荐
mxwin6 小时前
Unity Shader 深度写入与关闭ZWrite Off · 半透明排序 · 粒子穿插
unity·游戏引擎·shader
张老师带你学8 小时前
宇宙飞船完整Unity项目
科技·游戏·unity·游戏引擎·模型
mxwin8 小时前
Unity URP 下的流体模拟 深入解析 Navier-Stokes 方程与浅水方程的数学原理
unity·游戏引擎
mxwin12 小时前
Unity Shader 深度重建世界坐标
unity·游戏引擎·shader
雪儿waii12 小时前
Unity 中继承(父类子类)用法详解
unity·游戏引擎
总写bug的程序员13 小时前
用 AI 蒸馏球员的思维操作系统:qiuyuan-skill 技术解析
人工智能·unity·游戏引擎
Mr数据杨14 小时前
结构化表格分类建模与业务预测落地路径
人工智能·机器学习·分类·数据挖掘·数据分析·kaggle
mxwin15 小时前
Unity Shader 预乘 Alpha 完全指南 解决半透明纹理边缘黑边问题,让你的 UI 渲染更干净
unity·游戏引擎
mxwin15 小时前
Unity URP 软粒子(Soft Particles)完全指南
unity·游戏引擎·shader
Mr数据杨15 小时前
Unlearnable CIFAR 10 图像分类实战 从异常训练数据到鲁棒建模
人工智能·机器学习·分类·数据挖掘·数据分析·kaggle