Unity游戏教程初步(五):Resources与UI

前言

在上一节中我们给球体添加了纹理,并且为场景设置了一个目的区域。当球体处于目的区域内时,其将变色。在这一节里,我们将完善球体在到达目的区域时游戏的反馈,增加音乐和UI显示。

项目需求

增加小球进入目的区域的音效。

增加计分的UI界面。

增加音效

-本节相关内容请读者参考:

-https://docs.unity.cn/cn/current/ScriptReference/Resources.html,《Resources》

-https://docs.unity.cn/cn/current/Manual/class-AudioSource.html,《音频源》

我们可以用两个方法将音效素材导入到游戏。第一个就是按照上一节绑定变量的方法,将音效素材作为JudgeController的属性绑定到类中;第二个是使用Resources文件夹。同时,为了让JudgeController可以播放音乐,我们需要为其添加一个Audio Source组件。

Resources文件夹是一类文件夹,其文件夹名为Resources,位于工程目录下的Assets文件夹内(不需要位于根目录下,比如Assets\a\Resources也有效)。我们可以在脚本中使用Resources.Load来访问Resources文件夹下的文件。

第一种方法因为已经在前面使用过了,所以在这里我们不多赘述,来看第二种方法。由于unity预设建立的项目中没有Resources文件夹,我们先在Assets文件夹下建立一个Resources文件夹,然后将音效文件放进去。

然后码代码:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class JudgeController : MonoBehaviour

{

    public GameObject sphere; //球体的引用

    //近点和远点分别是目的区域离原点最近和最远的点

    public Vector3 nearP=new Vector3(2.5f,0,2.5f); //近点

    public Vector3 farP= new Vector3(5,0,5); //远点

    private AudioClip m; 

    bool hasClip=false; //标志,球体在一次进出是否播放过音效

    // Start is called before the first frame update

    void Start()

    {

        m=Resources.Load<AudioClip>("001"); //加载Assets/.../Resources/001.*

    }

    // Update is called once per frame

    void Update()

    {

        Vector3 p=sphere.GetComponent<Transform>().position; //位置

        if(p.x>nearP.x && p.x<farP.x && p.z>nearP.z &&p.z<farP.z){

            //小球中心点在目的区域内

            GetComponent<AudioSource>().clip=m;

            if(!hasClip){

                GetComponent<AudioSource>().Play(); //播放

                hasClip=true;

            }

            sphere.GetComponent<Sphere>().changeMaterial(true);  //调用Sphere的函数

        }else{

            //不在区域内,变回来

            hasClip=false;

            sphere.GetComponent<Sphere>().changeMaterial(false);

        }

    }

}

增加UI

-本节相关内容请读者参考:

-https://docs.unity.cn/cn/current/Manual/UISystem.html,《UI》

-https://docs.unity.cn/cn/current/Manual/UICanvas.html,《画布》

UI是User Interface(用户界面)的缩写。大多数游戏都会有UI,用于记录游戏的得分情况等信息。在unity中,UI必须作为Canvas(画布)的子项存在。如果直接创建UI,unity会在此之前自动创建一个Canvas并将被创建的UI作为子项。

Hierarchy->UI->Text,创建一个TextUI,我们命名为Bonus。设定初始分数为0,球体进入目的区域时分数加1。改写JudgeController的代码:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

public class JudgeController : MonoBehaviour

{

    public GameObject sphere; //球体的引用

    public GameObject bonusUI; //显示分数的UI

    //近点和远点分别是目的区域离原点最近和最远的点

    public Vector3 nearP=new Vector3(2.5f,0,2.5f); //近点

    public Vector3 farP= new Vector3(5,0,5); //远点

    private AudioClip m; //音效文件

    private int bonus; //分数

    bool hasClip=false; //标志,球体在一次进出是否播放过音效

    // Start is called before the first frame update

    void Start()

    {

        bonus=0;

        m=Resources.Load<AudioClip>("001"); //加载Assets/.../Resources/001.*

    }

    // Update is called once per frame

    void Update()

    {

        Vector3 p=sphere.GetComponent<Transform>().position; //位置

        if(p.x>nearP.x && p.x<farP.x && p.z>nearP.z &&p.z<farP.z){

            //小球中心点在目的区域内

            GetComponent<AudioSource>().clip=m;

            if(!hasClip){

                bonus++;

                bonusUI.GetComponent<Text>().text=bonus.ToString(); //更新分数

                GetComponent<AudioSource>().Play(); //播放

                hasClip=true;

            }

            sphere.GetComponent<Sphere>().changeMaterial(true);  //调用Sphere的函数

        }else{

            //不在区域内,变回来

            hasClip=false;

            sphere.GetComponent<Sphere>().changeMaterial(false);

        }

    }

}

可以看到我们增加了一个引用bonusUI,将这个变量与Bonus绑定。然后调整Bonus的大小和其Text的内容(由于后续内容由JudgeController控制,这里只需要将Text置为0即可)。

# TextMeshPro-Text

同样是UI,TextMeshPro可以被看做是Text的升级版。因为文档里没有与其相关的资料,所以在这里笔者会简单描述一下与它有关的信息。

TextMeshPro又称为TMP,一开始是一个外部插件,在最近的版本中才被包含进unity本体中。TMP采用了SDF文字渲染技术,相比原生的Text组件它能保证文字在缩放数倍后仍然保持平滑(其实就是矢量绘图)。

但是保持文字的平滑自然需要代价,TMP会为字体创建一个纹理集,而此纹理集在字体所属语言为中文的情况下会占用较大的空间。

而TMP也不只有这一个长处,TMP还可以设置文字的描边颜色渐变等,并且可以图文混用。

增加得分点

-本节相关内容请读者参考:

-https://docs.unity.cn/cn/current/ScriptReference/Random.html,《Random》

既然已经有了一套基础的得分系统,不妨在之前的基础上增加一个得分点,比如说,让平面上的正方体在进入得分区域时重置位置并且得分。

按照之前的思路,修改JudgeController,并给Cube添加脚本:

JudgeController.cs(注:前文中对于近点/远点的定义有误,在这里更正)

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

public class JudgeController : MonoBehaviour

{

    public GameObject sphere; //球体的引用

    public GameObject bonusUI; //显示分数的UI

    public GameObject cube; //正方体的引用

    //近点和远点分别是目的区域离xy最大的点和xy最小的点

    public Vector3 nearP=new Vector3(2.5f,0,2.5f); //近点

    public Vector3 farP= new Vector3(5,0,5); //远点

    private AudioClip m; //音效文件

    private int bonus; //分数

    bool hasClip=false; //标志,球体在一次进出是否播放过音效

    // Start is called before the first frame update

    void Start()

    {

        bonus=0;

        m=Resources.Load<AudioClip>("001"); //加载Assets/.../Resources/001.*

    }

    bool comPosition(Vector3 p)

    { //比较传入点与近点/远点的相对位置,内部函数

        if(p.x>nearP.x && p.x<farP.x && p.z>nearP.z &&p.z<farP.z){

            return true;

        }else{

            return false;

        }

    }

    // Update is called once per frame

    void Update()

    {

        Vector3 p=sphere.GetComponent<Transform>().position; //位置

        if(this.comPosition(p)){

            //小球中心点在目的区域内

            GetComponent<AudioSource>().clip=m;

            if(!hasClip){

                bonus++;

                bonusUI.GetComponent<Text>().text=bonus.ToString(); //更新分数

                GetComponent<AudioSource>().Play(); //播放

                hasClip=true;

            }

            sphere.GetComponent<Sphere>().changeMaterial(true);  //调用Sphere的函数

        }

        else{

            //不在区域内,变回来

            hasClip=false;

            sphere.GetComponent<Sphere>().changeMaterial(false);

        }

        Vector3 c=cube.GetComponent<Transform>().position;

        if(this.comPosition(c)){

            //一旦抵达目标地点,就开始传送,所以不需要额外标志,也没有else

            cube.GetComponent<Cube>().transmit(nearP,farP);

            bonus+=2;

            bonusUI.GetComponent<Text>().text=bonus.ToString(); //更新分数

        }

    }

}

Cube.cs

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using Random=UnityEngine.Random;

public class Cube : MonoBehaviour

{

    public GameObject plane; //平台,用于计算区域长度

    // Start is called before the first frame update

    void Start()

    {

    }

    // Update is called once per frame

    void Update()

    {

    }

    float comVxy(float far,float near,float width){ //封装内部函数

        if(far<width){ //换成near>-width也是一样的,因为目的区域必然不大于整个区域,所以只要比较一个

            if(Random.value>0.5){

                return Random.Range(far,width);

            }

            else{

                return Random.Range(-1*width,near);

            }

        }

        else{

            return Random.Range(-1*width,near);

        }

    }

    public void transmit(Vector3 near,Vector3 far){ 

        //以0,0,0为中心的情况下,给出:整个区域长度、目的区域近点和远点 将正方体传送到整个区域之内,目的区域之外

        //设定平面长宽相等,生成在与目的区域相对的区域内

        Vector3 v2=new Vector3(0,-4.5f,0);

        float myWidth=GetComponent<Transform>().localScale.x*0.5f; //由于算的是物体中心的位置,要减去到中心的距离

        float width=plane.GetComponent<Transform>().localScale.x*5-myWidth;

        v2.x=this.comVxy(far.x,near.x,width);

        v2.z=this.comVxy(far.z,near.z,width); //笔者的实例里y轴朝上

        transform.localPosition=v2; //这里是相对坐标

        print(v2);

    }

}

可以看到在Cube.cs中,我们传送物体使用的是transform.localPosition(GetComponent<Transform>().localPosition),而非position。这是因为笔者在之前建立了一个空游戏对象作为Cube的父对象,而这里需要对正方体的父对象定位(如果没有父对象localPosition就与position相同)。我们把position称为世界位置,而localPosition则是相对位置。position是游戏对象在绝对坐标系下(世界坐标系,也就是无父对象时Transform面板显示的坐标)的位置,而localPosition则是position对游戏对象的所有父对象的位置进行变换之后的位置。例如在场景下有一个根游戏对象A(1,1,1),其子游戏对象B在Transform面板里的坐标为(0,1,-1),则B在世界坐标系的坐标为A+B(1,2,0)。

由于需要判断两个物体的位置,为了节省代码量我们把判断位置部分的代码封装为一个函数comPosition(其实也就是节省了一行不到,但是也省得写了)。同时,建议读者尽量使用unity的Random来生成随机数,而不使用C#自带的Random。

举报
评论 0