- Unity 3D脚本编程与游戏开发
- 马遥 沈琰编著
- 2942字
- 2021-08-20 17:24:59
1.3 制作第一个游戏:3D滚球跑酷
本节利用前面的知识来实现第一个较为完整的小游戏,如图1-21所示。
图1-21 3D滚球跑酷游戏完成效果
1.3.1 游戏设计
1. 功能点分析
游戏中的小球会以恒定速度向前移动,而玩家控制着小球左右移动来躲避跑道中的黄色障碍物。如果玩家能控制小球在跑道上移动一定距离则视为玩家通过关卡,触碰到障碍物或从跑道上掉落则视为失败。我们需要实现的功能点概括来说分为主角的运动、摄像机的移动和过关与失败的检测等。
2. 场景搭建
01 创建项目。打开Unity Hub或者单独的Unity,初始模板选择3D,如图1-22所示。建议使用Unity 2018.3以后的版本,这里使用的Unity版本为2019.2.13f1。
图1-22 创建3D工程
02 创建场景内物体。在场景中新建一个Cube(立方体)作为跑道,将其长度改为1000,宽度改为8(即立方体z轴和x轴的scale),然后将其位置沿z轴前移480,如图1-23所示。
图1-23 创建跑道
03 新建一个Sphere(球体)作为玩家,重置它的初始位置(选择Transform组件右上角菜单中的Reset选项),按住Ctrl键拖曳球体的y轴,使其刚好移动到跑道上。
04 新建若干个Cube(立方体)作为障碍物,用上面的方法铺在跑道上面。为了便于区分,新建两个材质分别作为跑道和障碍物的材质,调整好颜色后直接拖曳到物体上即可,如图1-24所示。
图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所示。
图1-25 将摄像机作为球体的子物体
小提示
Unity中的父子关系
父子关系是Unity中相当重要的一个概念,此处可以不用深究,在第2章会详细说明。
1.3.3 游戏机制
1. 游戏失败
有两种情况会导致游戏失败,一种是碰到了障碍物,另一种是小球从跑道边缘掉落。
碰撞到障碍物与吃金币实例中的原理类似,将障碍物碰撞体上的Is Trigger选项勾选上,然后在障碍物脚本里的OnTriggerEnter()函数中检测碰撞。
不过游戏中的障碍物可能会有许多个,如果要一个个地分别做上述修改显然很麻烦,还容易遗漏。因此正确的做法是把其中一个障碍物设为Prefab(预制体),后面添加的障碍物都以这个Prefab为模板复制即可。
最后注意,要检测物理碰撞还需要在小球上添加Rigidbody组件。为了避免不必要的物理计算消耗,在这个游戏中完全用代码控制小球的移动,物理系统仅做检测碰撞使用,因此小球Rigidbody组件上的Is Kinematic选项要勾选上。障碍物脚本里的代码如下。
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所示。终点物体脚本的代码如下。
图1-26 在终点创建触发器
public class End : MonoBehaviour { private void OnTriggerEnter(Collider other) { if (other.name == "Player") { Debug.Log(" 过关 "); Time.timeScale = 0; } } }
至此,这个小游戏的基本代码就完成了,之后会对其进行适当的修改,使其更完整。
1.3.4 完成和完善游戏
1. 测试自己的游戏
这时候可以开始测试自己设置的关卡难度了,一个好的游戏应当有一个合理的难度曲线。有一个小技巧可以提高这一步的效率,即单击场景视窗右上角的坐标轴图标,让场景摄像机迅速切换为对应轴方向的视角,而单击下面的Persp或Iso则分别代表切换摄像机为透视模式或正交模式,如图1-27所示。
图1-27 场景可切换为正交模式显示
小提示
场景摄像机不会影响实际游戏画面
场景摄像机指的是在Scene(场景视窗)里的、仅在编辑模式可用的摄像机。Hierarchy窗口中的摄像机决定在Game窗口里看到的实际游戏画面。注意不要将两者混淆。
这里可以切换场景摄像机为y轴方向正交,善用复制与按住Ctrl键拖曳功能搭建关卡。
2. 加入通关UI
在Hierarchy窗口中单击鼠标右键,通过选择UI→Panel选项创建一个UI面板,并以Panel为父节点创建一个Text组件,在Text组件中输入过关的信息,同时调整字体大小、位置等设置,如图1-28和图1-29所示。
图1-28 创建Text组件
图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所示。
图1-30 到达终点时的显示效果
3. 加入摄像机运动效果
最后添加一个好玩的扩展功能:当控制小球左右移动时让摄像机往对应方向倾斜。具体的做法会涉及一些3D数学知识,会在后续章节中介绍。简单思路为:在Player脚本中使用获取的横向输入,以此控制摄像机的倾斜角度。在Player中添加如下代码,效果如图1-31所示。
图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); }
可以在此基础上加入更多细节,如音乐、音效和特效等。合适的音乐和特效可以让简单的游戏更吸引人。