赞
踩
知道了物体如何移动,下⼀步就得知道如何判断⼀个物体是否碰到了另⼀个物体,这也是绝⼤部分游戏都需要⽤到的功能。例如,在经典游戏PONG中,球碰到边界和挡板会反弹;《超级⻢⾥奥兄弟》中的⻢⾥奥碰到蘑菇就会变⼤。
实际上,抛开⾼级游戏引擎提供的各种技术,直接判断物体之间的距离就⾜以实现碰撞检测,即两个物体之间的距离⼩于某个值,就是碰到了。早期的游戏就是这么做的,即便在今天,对某些需要特别优化的功能也可以⽤这种⽅法。
不过Unity等现代游戏引擎给出了更统⼀、更简便的⽅法——使⽤触发器。
触发器是⼀个组件,它定义了⼀个范围。当其他带有碰撞体组件的物体进⼊了这个范围时,就会产⽣⼀个触发事件,脚本捕捉到这个事件的时候,就可以做出相应的处理。
在之前的⼩球旁边创建⼀个⽴⽅体。创建的⽴⽅体已经⾃带碰撞体,即Box Collider,可以在Inspector窗⼝中看到,默认该碰撞体的范围就是⽴⽅体的范围,如图1-10所⽰。
在Unity中,触发器和碰撞体共⽤了同⼀种组件——Collider,实际上两者是不同的概念。勾选Box Collider⾯板中的Is Trigger选项,碰撞体就变成了同样外形的触发器,如图1-11所⽰。
并不是任何物体进⼊触发器的范围都会产⽣触发事件。产⽣触发事件的具体要求将在第3章物理系统中进⾏讨论。这⾥需要做的是,给⼩球添加⼀个Rigidbody(刚体)组件,并勾选Is Kinematic(动⼒学)选项,其他选项不重要,如图1-12所⽰。
做完以上步骤,碰撞的场景设置就完成了,接下来讲解如何编写脚本来处理触发事件。
碰撞和触发总是发⽣在两个物体之间,所以根据不同情况,可以选择其中⼀个物体进⾏碰撞或触发的处理。当前按照常规思路,被碰到的物体应该是⽴⽅体,因此给⽴⽅体新建⼀个脚本并挂载,取名Coin,⽤于处理碰撞。
触发事件实际上有3种,即开始触发(OnTriggerEnter)、触发持续中(OnTriggerStay)及结束触发(OnTriggerExit),分别代表另⼀个物体进⼊触发范围、在触发范围内、离开触发范围这3个阶段。这⾥只介绍开始触发事件,Coin脚本内容如下。
- using UnityEngine;
- public class Coin : MonoBehaviour {
- // 触发开始事件OnTriggerEnter,参数为碰撞体信息,即另⼀个
- 进⼊了该触发区域的物体的碰撞体
- private void OnTriggerEnter(Collider other)
- {
- Debug.Log(other.name + " 碰到了我");
- }
- }
⼩ 提⽰ 删除不必要的事件函数 可以看到左边代码⾥不必要的Start()函数、Update()函数等已经 通通删除了。这是⼀种良好的编码习惯,可以减少不必要的函数执 ⾏。即使是函数体为空的函数,执⾏时依然会消耗CPU资源。 |
以上代码会接收到其他碰撞体进⼊触发区域的事件,且能获得该碰撞体的信息。上⾯的代码利⽤other.name输出了进⼊触发范围的物体名称。
现在运⾏游戏进⾏测试。测试⽅法是先运⾏游戏,再直接在场景窗⼝中拖曳球体,让它和⽴⽅体接触,然后观察Console窗⼝是否出现了消息提⽰,如图1-13所⽰。
可以看到,触发时Console窗⼝输出了消息,⽽且获得了物体名称Sphere。
通过控制物体的运动和触发器,很容易做出游戏中吃⾦币和⾦币消失的效果,下⾯就来实际操作⼀下。
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脚本如下。
- using UnityEngine;
- public class Coin : MonoBehaviour {
- // 触发开始事件OnTriggerEnter,参数为碰撞体信息,即另⼀个进⼊了该
- 触发区域的物体的碰撞体
- private void OnTriggerEnter(Collider other)
- {
- Debug.Log(other.name + " 碰到了我");
- // 销毁⾃⼰
- Destroy(gameObject);
- }
- }
此处仅添加了⼀个Destroy()函数调⽤,该函数⽤于销毁物体,⽽参数gameObject指代的正是脚本所挂载到的物体。如何从物体找到组件,如何从组件找到物体,这些都会在第2章中详细讲解。
测试游戏,⽤键盘控制⼩球,检查⼩球碰到⽴⽅体后,⽴⽅体是否消失。
如果测试正常,就可以多复制⼏个⽴⽅体。选中⽴⽅体,按Ctrl+D快捷键就可以复制⽴⽅体,然后调整位置,如图1-20所⽰。
如果读者阅读到了这⾥,且跟着指引完成了演⽰案例,那么就能明⽩编写脚本的基本流程,并理解移动、碰撞等基本操作中每⼀句代码的含义。如此⼀来,利⽤这些知识就能制作⼀个相对完整的⼩游戏了。
本节利⽤前⾯的知识来实现第⼀个较为完整的⼩游戏,如图1-21所⽰。
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. 主⾓的移动
之前的实例中已经提到过如何控制⼩球的移动,因此此处不再赘述。与之前不同的是,在该案例的设计中,玩家只能控制⼩球左右移动,因此只需要获取横向的输⼊即可,纵向移动保持⼀个固定值。编辑好脚本后挂载在⼩球上,代码如下。
- public class Player : MonoBehaviour
- {
- public float speed;
- public float turnSpeed;
- void Update()
- {
- float x = Input.GetAxis("Horizontal");
- transform.Translate(x*turnSpeed*Time.deltaTime, 0,
- speed * Time.deltaTime);
- }
- }
2. 摄像机的移动
摄像机移动的⽅法可以分为两种。⼀种是像控制⼩球⼀样为摄像机挂载控制脚本,使其与⼩球保持同步运动。另⼀种则更为简单直接,即将摄像机设置为⼩球的⼦物体,此时摄像机在没有其他代码控制的情况下会与⼩球保持相对静⽌,即随着⼩球移动。这⾥选择第⼆种⽅法,当设置好⽗⼦关系后调整摄像机到合适的⾼度和⾓度,如图1-25所⽰。
⼩提⽰
Unity中的⽗⼦关系⽗⼦关系是Unity中相当重要的⼀个概念,此处可以不⽤深究,
在第2章会详细说明。
1. 游戏失败
有两种情况会导致游戏失败,⼀种是碰到了障碍物,另⼀种是⼩球从跑道边缘掉落。
碰撞到障碍物与吃⾦币实例中的原理类似,将障碍物碰撞体上的IsTrigger选项勾选上,然后在障碍物脚本⾥的OnTriggerEnter()函数中检测碰撞。
不过游戏中的障碍物可能会有许多个,如果要⼀个个地分别做上述修改显然很⿇烦,还容易遗漏。因此正确的做法是把其中⼀个障碍物设为Prefab(预制体),后⾯添加的障碍物都以这个Prefab为模板复制即可。
最后注意,要检测物理碰撞还需要在⼩球上添加Rigidbody组件。为了避免不必要的物理计算消耗,在这个游戏中完全⽤代码控制⼩球的移动,物理系统仅做检测碰撞使⽤,因此⼩球Rigidbody组件上的IsKinematic选项要勾选上。障碍物脚本⾥的代码如下。
- public class Barrier : MonoBehaviour
- {
- private void OnTriggerEnter(Collider other)
- {
- // 防⽌其他物体与障碍物的碰撞被检测,我们只需要障碍物与⼩球的碰
- 撞被检测到
- if(other.name=="Player")
- {
- Time.timeScale = 0;
- }
- }
- }
小知识:
什么是预制体?Time.timeScale是什么?
预制体简单来说就是⼀个事先定义好的游戏物体,之后可以在游戏中反复使⽤。最简单的创建预制体的⽅法是直接将场景内的物体拖曳到Project窗⼝中,这时在Hierarchy(层级)窗⼝中所有与预制体关联的物体名称都会以蓝⾊显⽰(普通物体的名称是⿊⾊)。
关于预制体的内容会在第2章详细说明。
Time.timeScale表⽰游戏的运⾏时间倍率,设置为0即表⽰游戏⾥的时间停滞,1即正常的时间流逝速度,2即两倍于正常的时间流逝速度,以此类推。
⼩球从跑道边缘掉落时也视为游戏结束。但是由于是直接⽤代码控制⼩球移动,与刚体有⼀点冲突,因此掉落部分的功能同样⽤代码来实现。在Player脚本⾥添加如下代码。
- void Update()
- {
- ……
- // ⼀旦⼩球位置超出了跑道的范围则直接下落
- if(transform.position.x<-4||transform.position.x>4)
- {
- transform.Translate(0, -10 * Time.deltaTime, 0);
- }
- // 下落⼀定距离之后游戏结束
- if(transform.position.y<-20)
- {
- Time.timeScale = 0;
- }
- }
当游戏失败结束时应该允许玩家重新开始游戏,这⾥设置键盘上的R键为重置游戏的按键,在按R键后即可重新加载当前场景。在Player脚本⾥添加如下代码。
- using UnityEngine.SceneManagement;
- public class Player : MonoBehaviour
- {……
- void Update()
- {
- if(Input.GetKeyDown(KeyCode.R))
- {
- SceneManager.LoadScene(0);
- Time.timeScale = 1;
- return;
- }
- ……
- }
- }
⼩提⽰:
注意添加头部引⽤
SceneManager这个类型是属于UnityEngine.SceneManagement的,因此要添加头部引⽤后才能调⽤SceneManager.LoadScene(0)⽅法。这⾥参数0表⽰场景的序号,由于游戏现在只有⼀个场景,因此表⽰加载当前场景。
2. 游戏胜利
⼀般来说游戏都应该有⼀个最终⽬标,达成这个⽬标则视为过关或者胜利。不过也不绝对,类似Flappy Bird这样的游戏就没有最终⽬标。这⾥还是设置⼀个完成⽬标,即玩家跑了⼀定距离就视为过关。这⾥使⽤⼀个看不⻅的触发器作为决定距离的终点,确保其范围能够覆盖跑道的宽度,当⼩球进⼊范围就表⽰游戏过关,如图1-26所⽰。终点物体脚本的代码如下。
- public class End : MonoBehaviour
- {
- private void OnTriggerEnter(Collider other)
- {
- if (other.name == "Player")
- {
- Debug.Log(" 过关 ");
- Time.timeScale = 0;
- }
- }
- }
⾄此,这个⼩游戏的基本代码就完成了,之后会对其进⾏适当的修改,使其更完整。
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。
- public class End : MonoBehaviour
- {
- // 声明⼀个物体变量
- GameObject endUI;
- private void Start()
- {
- // 通过物体在场景中的名字来找到这个物体
- endUI = GameObject.Find("EndUI");
- // 在场景中隐藏这个物体
- endUI.SetActive(false);
- }
- private void OnTriggerEnter(Collider other)
- {
- if (other.name == "Player")
- {
- Debug.Log(" 过关 ");
- //在场景中显⽰这个物体
- endUI.SetActive(true);
- Time.timeScale = 0;
- }
- }
- }
如此⼀来,当⼩球触碰到终点后,UI就会显⽰出来,如图1-30所⽰。
3. 加⼊摄像机运动效果
最后添加⼀个好玩的扩展功能:当控制⼩球左右移动时让摄像机往对应⽅向倾斜。具体的做法会涉及⼀些3D数学知识,会在后续章节中介绍。简单思路为:在Player脚本中使⽤获取的横向输⼊,以此控制摄像机的倾斜⾓度。在Player中添加如下代码,效果如图1-31所⽰。
- void Update()
- {
- ……
- Transform c = Camera.main.transform;
- Quaternion cur = c.rotation;
- Quaternion target = cur * Quaternion.Euler(0, 0, x *
- 1.5f);
- Camera.main.transform.rotation = Quaternion.Slerp(cur,target, 0.5f);
- }
可以在此基础上加⼊更多细节,如⾳乐、⾳效和特效等。合适的⾳乐和特效可以让简单的游戏更吸引⼈。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。