项目效果演示:

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

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,用于控制脚本的跳转。

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:

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,并挂载在物体上。

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,用于垃圾分类逻辑处理。

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,用于区别题型和设置答案:

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,用于实现判断对错、交互的功能:

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属性进行赋值,如下图所示:

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

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

垃圾分类项目分享

通过百度网盘分享的文件:
链接:https://pan.baidu.com/s/1k4yHJMtkrdQRLj6D5zit9g?pwd=koan
提取码:koan
--来自百度网盘超级会员V4的分享

Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐