当前位置:   article > 正文

Unity 3D脚本编程与游戏开发(1.2)_unity 3d脚本编程与游戏开发 配套工程

unity 3d脚本编程与游戏开发 配套工程

1.2 触发器事件

        知道了物体如何移动,下⼀步就得知道如何判断⼀个物体是否碰到了另⼀个物体,这也是绝⼤部分游戏都需要⽤到的功能。例如,在经典游戏PONG中,球碰到边界和挡板会反弹;《超级⻢⾥奥兄弟》中的⻢⾥奥碰到蘑菇就会变⼤。
        实际上,抛开⾼级游戏引擎提供的各种技术,直接判断物体之间的距离就⾜以实现碰撞检测,即两个物体之间的距离⼩于某个值,就是碰到了。早期的游戏就是这么做的,即便在今天,对某些需要特别优化的功能也可以⽤这种⽅法。
        不过Unity等现代游戏引擎给出了更统⼀、更简便的⽅法——使⽤触发器。
        触发器是⼀个组件,它定义了⼀个范围。当其他带有碰撞体组件的物体进⼊了这个范围时,就会产⽣⼀个触发事件,脚本捕捉到这个事件的时候,就可以做出相应的处理。

1.2.1 创建触发器

        在之前的⼩球旁边创建⼀个⽴⽅体。创建的⽴⽅体已经⾃带碰撞体,即Box Collider,可以在Inspector窗⼝中看到,默认该碰撞体的范围就是⽴⽅体的范围,如图1-10所⽰。

        在Unity中,触发器和碰撞体共⽤了同⼀种组件——Collider,实际上两者是不同的概念。勾选Box Collider⾯板中的Is Trigger选项,碰撞体就变成了同样外形的触发器,如图1-11所⽰。

        并不是任何物体进⼊触发器的范围都会产⽣触发事件。产⽣触发事件的具体要求将在第3章物理系统中进⾏讨论。这⾥需要做的是,给⼩球添加⼀个Rigidbody(刚体)组件,并勾选Is Kinematic(动⼒学)选项,其他选项不重要,如图1-12所⽰。

        做完以上步骤,碰撞的场景设置就完成了,接下来讲解如何编写脚本来处理触发事件。

1.2.2 触发器事件函数

        碰撞和触发总是发⽣在两个物体之间,所以根据不同情况,可以选择其中⼀个物体进⾏碰撞或触发的处理。当前按照常规思路,被碰到的物体应该是⽴⽅体,因此给⽴⽅体新建⼀个脚本并挂载,取名Coin,⽤于处理碰撞。
        触发事件实际上有3种,即开始触发(OnTriggerEnter)、触发持续中(OnTriggerStay)及结束触发(OnTriggerExit),分别代表另⼀个物体进⼊触发范围、在触发范围内、离开触发范围这3个阶段。这⾥只介绍开始触发事件,Coin脚本内容如下。

  1. using UnityEngine;
  2. public class Coin : MonoBehaviour {
  3. // 触发开始事件OnTriggerEnter,参数为碰撞体信息,即另⼀个
  4. 进⼊了该触发区域的物体的碰撞体
  5. private void OnTriggerEnter(Collider other)
  6. {
  7. Debug.Log(other.name + " 碰到了我");
  8. }
  9. }

提⽰

删除不必要的事件函数
可以看到左边代码⾥不必要的Start()函数、Update()函数等已经
通通删除了。这是⼀种良好的编码习惯,可以减少不必要的函数执
⾏。即使是函数体为空的函数,执⾏时依然会消耗CPU资源。

        以上代码会接收到其他碰撞体进⼊触发区域的事件,且能获得该碰撞体的信息。上⾯的代码利⽤other.name输出了进⼊触发范围的物体名称。
        现在运⾏游戏进⾏测试。测试⽅法是先运⾏游戏,再直接在场景窗⼝中拖曳球体,让它和⽴⽅体接触,然后观察Console窗⼝是否出现了消息提⽰,如图1-13所⽰。

        可以看到,触发时Console窗⼝输出了消息,⽽且获得了物体名称Sphere。

1.2.3 实例:吃⾦币

        通过控制物体的运动和触发器,很容易做出游戏中吃⾦币和⾦币消失的效果,下⾯就来实际操作⼀下。

01 为更好地测试,先在前⾯例⼦的基础上调整镜头位置,变成俯视⾓投。操作⽅法提⽰:摄像机位置归0,沿x轴转90°,然后适当沿y轴升⾼。摄像机的参考位置和旋转⾓度如图1-14所⽰。

02 新建球体,位置归0。挂载之前⽤过的Ball脚本,然后添加Rigidbody组件,并勾选Is Kinematic选项,与第1.2.1⼩节设置相同。
03 新建⽴⽅体,位置归0,向右平移摆放到球体旁边。
04 为了让“⾦币”更醒⽬,给⽴⽅体添加⼀个⾦⻩⾊材质。先在Project窗⼝中新建⼀个Material(材质),如图1-15所⽰。

05 将材质命名为matCoin,如图1-16所⽰。

06 选中新建的材质,在Inspector窗⼝中将它的颜⾊修改为明亮的⻩⾊,如图1-17所⽰。


 07 将材质⽂件直接拖曳到场景中的⽴⽅体上,⽴⽅体就变成了⻩⾊。这时选中⽴⽅体,在Inspector窗⼝中可以看到材质名称已经变为matCoin了,如图1-18所⽰。

完成以上操作后,在Game窗⼝中看到的效果如图1-19所⽰。

        由于球体已挂载了之前的Ball脚本,因此可以根据输⼊进⾏移动。注意⽴⽅体要勾选Is Trigger选项,并挂载Coin脚本,设置与1.2.1⼩节相同。
        由于⾦币被碰撞到以后就应该消失,因此修改的Coin脚本如下。

  1. using UnityEngine;
  2. public class Coin : MonoBehaviour {
  3. // 触发开始事件OnTriggerEnter,参数为碰撞体信息,即另⼀个进⼊了该
  4. 触发区域的物体的碰撞体
  5. private void OnTriggerEnter(Collider other)
  6. {
  7. Debug.Log(other.name + " 碰到了我");
  8. // 销毁⾃⼰
  9. Destroy(gameObject);
  10. }
  11. }

        此处仅添加了⼀个Destroy()函数调⽤,该函数⽤于销毁物体,⽽参数gameObject指代的正是脚本所挂载到的物体。如何从物体找到组件,如何从组件找到物体,这些都会在第2章中详细讲解。
        测试游戏,⽤键盘控制⼩球,检查⼩球碰到⽴⽅体后,⽴⽅体是否消失。
        如果测试正常,就可以多复制⼏个⽴⽅体。选中⽴⽅体,按Ctrl+D快捷键就可以复制⽴⽅体,然后调整位置,如图1-20所⽰。

         如果读者阅读到了这⾥,且跟着指引完成了演⽰案例,那么就能明⽩编写脚本的基本流程,并理解移动、碰撞等基本操作中每⼀句代码的含义。如此⼀来,利⽤这些知识就能制作⼀个相对完整的⼩游戏了。

1.3 制作第⼀个游戏:3D滚球跑酷

        本节利⽤前⾯的知识来实现第⼀个较为完整的⼩游戏,如图1-21所⽰。

1.3.1 游戏设计

1. 功能点分析
        游戏中的⼩球会以恒定速度向前移动,⽽玩家控制着⼩球左右移动来躲避跑道中的⻩⾊障碍物。如果玩家能控制⼩球在跑道上移动⼀定距离则视为玩家通过关卡,触碰到障碍物或从跑道上掉落则视为失败。我们需要实现的功能点概括来说分为主⾓的运动、摄像机的移动和过关与失败的检测等。
2. 场景搭建
        01 创建项⽬。打开Unity Hub或者单独的Unity,初始模板选择3D,如图1-22所⽰。建议使⽤Unity 2018.3以后的版本,这⾥使⽤的Unity版本为2019.2.13f1。

02 创建场景内物体。在场景中新建⼀个Cube(⽴⽅体)作为跑道,将其⻓度改为1000,宽度改为8(即⽴⽅体z轴和x轴的scale),然后将其位置沿z轴前移480,如图1-23所⽰。

        03 新建⼀个Sphere(球体)作为玩家,重置它的初始位置(选择Transform组件右上⾓菜单中的Reset选项),按住Ctrl键拖曳球体的y轴,使其刚好移动到跑道上。
        04 新建若⼲个Cube(⽴⽅体)作为障碍物,⽤上⾯的⽅法铺在跑道上⾯。为了便于区分,新建两个材质分别作为跑道和障碍物的材质,调整好颜⾊后直接拖曳到物体上即可,如图1-24所⽰。

⼩知识
按住Ctrl键拖曳物体的作⽤按住Ctrl键拖曳物体会让其以⼀个固定值移动(可以在Edit→Snap Settings中修改这个固定值),调整物体的旋转和缩放时也是同理。这个功能在搭建场景时可以很⽅便地对⻬物体的位置,特别是当物体为同⼀规格⼤⼩时。Unity中还有很多类似的很⽅便的
快捷键,在⽤到时会介绍。

1.3.2 功能实现

1. 主⾓的移动
之前的实例中已经提到过如何控制⼩球的移动,因此此处不再赘述。与之前不同的是,在该案例的设计中,玩家只能控制⼩球左右移动,因此只需要获取横向的输⼊即可,纵向移动保持⼀个固定值。编辑好脚本后挂载在⼩球上,代码如下。

  1. public class Player : MonoBehaviour
  2. {
  3. public float speed;
  4. public float turnSpeed;
  5. void Update()
  6. {
  7. float x = Input.GetAxis("Horizontal");
  8. transform.Translate(x*turnSpeed*Time.deltaTime, 0,
  9. speed * Time.deltaTime);
  10. }
  11. }

2. 摄像机的移动
摄像机移动的⽅法可以分为两种。⼀种是像控制⼩球⼀样为摄像机挂载控制脚本,使其与⼩球保持同步运动。另⼀种则更为简单直接,即将摄像机设置为⼩球的⼦物体,此时摄像机在没有其他代码控制的情况下会与⼩球保持相对静⽌,即随着⼩球移动。这⾥选择第⼆种⽅法,当设置好⽗⼦关系后调整摄像机到合适的⾼度和⾓度,如图1-25所⽰。

⼩提⽰
Unity中的⽗⼦关系⽗⼦关系是Unity中相当重要的⼀个概念,此处可以不⽤深究,
在第2章会详细说明。

1.3.3 游戏机制

1. 游戏失败
        有两种情况会导致游戏失败,⼀种是碰到了障碍物,另⼀种是⼩球从跑道边缘掉落。
        碰撞到障碍物与吃⾦币实例中的原理类似,将障碍物碰撞体上的IsTrigger选项勾选上,然后在障碍物脚本⾥的OnTriggerEnter()函数中检测碰撞。
        不过游戏中的障碍物可能会有许多个,如果要⼀个个地分别做上述修改显然很⿇烦,还容易遗漏。因此正确的做法是把其中⼀个障碍物设为Prefab(预制体),后⾯添加的障碍物都以这个Prefab为模板复制即可。
        最后注意,要检测物理碰撞还需要在⼩球上添加Rigidbody组件。为了避免不必要的物理计算消耗,在这个游戏中完全⽤代码控制⼩球的移动,物理系统仅做检测碰撞使⽤,因此⼩球Rigidbody组件上的IsKinematic选项要勾选上。障碍物脚本⾥的代码如下。

  1. public class Barrier : MonoBehaviour
  2. {
  3. private void OnTriggerEnter(Collider other)
  4. {
  5. // 防⽌其他物体与障碍物的碰撞被检测,我们只需要障碍物与⼩球的碰
  6. 撞被检测到
  7. if(other.name=="Player")
  8. {
  9. Time.timeScale = 0;
  10. }
  11. }
  12. }

小知识:
什么是预制体?Time.timeScale是什么?
        预制体简单来说就是⼀个事先定义好的游戏物体,之后可以在游戏中反复使⽤。最简单的创建预制体的⽅法是直接将场景内的物体拖曳到Project窗⼝中,这时在Hierarchy(层级)窗⼝中所有与预制体关联的物体名称都会以蓝⾊显⽰(普通物体的名称是⿊⾊)。
关于预制体的内容会在第2章详细说明。
        Time.timeScale表⽰游戏的运⾏时间倍率,设置为0即表⽰游戏⾥的时间停滞,1即正常的时间流逝速度,2即两倍于正常的时间流逝速度,以此类推。
        ⼩球从跑道边缘掉落时也视为游戏结束。但是由于是直接⽤代码控制⼩球移动,与刚体有⼀点冲突,因此掉落部分的功能同样⽤代码来实现。在Player脚本⾥添加如下代码。

  1. void Update()
  2. {
  3. ……
  4. // ⼀旦⼩球位置超出了跑道的范围则直接下落
  5. if(transform.position.x<-4||transform.position.x>4)
  6. {
  7. transform.Translate(0, -10 * Time.deltaTime, 0);
  8. }
  9. // 下落⼀定距离之后游戏结束
  10. if(transform.position.y<-20)
  11. {
  12. Time.timeScale = 0;
  13. }
  14. }

        当游戏失败结束时应该允许玩家重新开始游戏,这⾥设置键盘上的R键为重置游戏的按键,在按R键后即可重新加载当前场景。在Player脚本⾥添加如下代码。

  1. using UnityEngine.SceneManagement;
  2. public class Player : MonoBehaviour
  3. {……
  4. void Update()
  5. {
  6. if(Input.GetKeyDown(KeyCode.R))
  7. {
  8. SceneManager.LoadScene(0);
  9. Time.timeScale = 1;
  10. return;
  11. }
  12. ……
  13. }
  14. }

⼩提⽰:
注意添加头部引⽤
        SceneManager这个类型是属于UnityEngine.SceneManagement的,因此要添加头部引⽤后才能调⽤SceneManager.LoadScene(0)⽅法。这⾥参数0表⽰场景的序号,由于游戏现在只有⼀个场景,因此表⽰加载当前场景。

2. 游戏胜利
        ⼀般来说游戏都应该有⼀个最终⽬标,达成这个⽬标则视为过关或者胜利。不过也不绝对,类似Flappy Bird这样的游戏就没有最终⽬标。这⾥还是设置⼀个完成⽬标,即玩家跑了⼀定距离就视为过关。这⾥使⽤⼀个看不⻅的触发器作为决定距离的终点,确保其范围能够覆盖跑道的宽度,当⼩球进⼊范围就表⽰游戏过关,如图1-26所⽰。终点物体脚本的代码如下。

  1. public class End : MonoBehaviour
  2. {
  3. private void OnTriggerEnter(Collider other)
  4. {
  5. if (other.name == "Player")
  6. {
  7. Debug.Log(" 过关 ");
  8. Time.timeScale = 0;
  9. }
  10. }
  11. }

        ⾄此,这个⼩游戏的基本代码就完成了,之后会对其进⾏适当的修改,使其更完整。

1.3.4 完成和完善游戏

1. 测试⾃⼰的游戏
        这时候可以开始测试⾃⼰设置的关卡难度了,⼀个好的游戏应当有⼀个合理的难度曲线。有⼀个⼩技巧可以提⾼这⼀步的效率,即单击场景视窗右上⾓的坐标轴图标,让场景摄像机迅速切换为对应轴⽅向的视⾓,⽽单击下⾯的Persp或Iso则分别代表切换摄像机为透视模式或正交模式,如图1-27所⽰。

⼩提⽰:
        场景摄像机不会影响实际游戏画⾯场景摄像机指的是在Scene(场景视窗)⾥的、仅在编辑模式可
⽤的摄像机。Hierarchy窗⼝中的摄像机决定在Game窗⼝⾥看到的实际游戏画⾯。注意不要将两者混淆。
        这⾥可以切换场景摄像机为y轴⽅向正交,善⽤复制与按住Ctrl键拖曳功能搭建关卡。
2. 加⼊通关UI
        在Hierarchy窗⼝中单击⿏标右键,通过选择UI→Panel选项创建⼀个UI⾯板,并以Panel为⽗节点创建⼀个Text组件,在Text组件中输⼊过关的信息,同时调整字体⼤⼩、位置等设置,如图1-28和图1-29所⽰。

 ⼩知识:
        只是创建了Panel,为什么⾃动添加了其他东⻄?当直接创建任何UI下的组件时都会⾃动⽣成Canvas与EventSystem组件,这两个组件分别与UI的布局和交互相关,暂时不
做深究。完整的UI系统会在后续章节介绍。
        接下来要做的是在游戏开始时隐藏UI,在⼩球触发终点物体时再显⽰。终点物体的End脚本代码如下,同时注意修改Panel的名字为EndUI。

  1. public class End : MonoBehaviour
  2. {
  3. // 声明⼀个物体变量
  4. GameObject endUI;
  5. private void Start()
  6. {
  7. // 通过物体在场景中的名字来找到这个物体
  8. endUI = GameObject.Find("EndUI");
  9. // 在场景中隐藏这个物体
  10. endUI.SetActive(false);
  11. }
  12. private void OnTriggerEnter(Collider other)
  13. {
  14. if (other.name == "Player")
  15. {
  16. Debug.Log(" 过关 ");
  17. //在场景中显⽰这个物体
  18. endUI.SetActive(true);
  19. Time.timeScale = 0;
  20. }
  21. }
  22. }

如此⼀来,当⼩球触碰到终点后,UI就会显⽰出来,如图1-30所⽰。

3. 加⼊摄像机运动效果
        最后添加⼀个好玩的扩展功能:当控制⼩球左右移动时让摄像机往对应⽅向倾斜。具体的做法会涉及⼀些3D数学知识,会在后续章节中介绍。简单思路为:在Player脚本中使⽤获取的横向输⼊,以此控制摄像机的倾斜⾓度。在Player中添加如下代码,效果如图1-31所⽰。

  1. void Update()
  2. {
  3. ……
  4. Transform c = Camera.main.transform;
  5. Quaternion cur = c.rotation;
  6. Quaternion target = cur * Quaternion.Euler(0, 0, x *
  7. 1.5f);
  8. Camera.main.transform.rotation = Quaternion.Slerp(cur,target, 0.5f);
  9. }

可以在此基础上加⼊更多细节,如⾳乐、⾳效和特效等。合适的⾳乐和特效可以让简单的游戏更吸引⼈。
 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/105915
推荐阅读
相关标签
  

闽ICP备14008679号