前言
Unity 3D的学习,相比系统的按部就班的学习,我更希望直接通过做一些小的项目来实现。这样效率更高,因为毕竟自己的空闲时间不多。所以这几天在网上看了一些其他人用Unity制作游戏流程的演示视屏,同时也为拟定了一个小游戏作为自己的第一个Unity项目。
第一个3D游戏的目标与计划
以下为我拟定的游戏目标:
游戏类型: 3D赛车竞技游戏
游戏评判标准: 完成一次比赛的最终得分
记分规则:
- 比赛用时: 时间越短,分数越高
- 奖励: 赛道上的奖励物品,得到的越多分数越高
- 陷阱与障碍: 赛道上的陷阱和障碍需要避开,触碰到的越多,分数越少
作为Unity初学者我的第一个游戏,很显然是无法指望一下就做得很完善好玩的。所以其他的一些细节我都先不考虑,做出一个最简单最基本都游戏原型来再说,然后再进一步完善方案。
考虑游戏需要的一些基本元素,用C#编写脚本,我将制作方案简化如下:
场景——马路
整个场景的主体就是马路,且不考虑其他复杂类型,直接做成一条水平平行且笔直的马路。
Player——小球
赛车游戏的player主题本来是赛车的,但是其建模和动力系统的控制都太复杂,所以这里直接用简单的球体来代替。
键盘输入来控制运动
将小球加入物体系统,外物受力为重力和摩擦力。小球的运动直接简化为前后左右四个方向的受力,通过键盘来控制。
计时器
赛车类的游戏最重要的就是比赛的用时,所以需要计时器来记录
起点和重点的感应
为了精确计时,应当设置起点和终点的感应,来配合定时器一起计时
菜单
一个完整的游戏的必备,负责设置游戏的一些参数,在这里就可以简化到只有游戏的暂定和继续以及游戏的退出。
创建场景和Player
用Unity内置的3D对象Plane或者Quad来做马路,用球体模型Sphere来充当Player,在透视视图中将两个调整到合适的位置。如下图为我摆放的马路、小球和主摄像机的位置:
直接将贴图推到马路上,Unity会自动创建其材质,就如上图所示。
而对于小球,为了达到方案中受力的作用,我们需要将其加入物理系统。添加小球的
Rigbody(刚体)组件:Add Component –> Physics –> Rigidbody ,在其中勾选“Use Gravity”,使其受重力的影响。
实际上在Rigbody中还可以设置如下选项(选择常用的说明):
- Mass : 物体的质量
- Drag: 空气阻力
- Angular Drag: 转动空气阻力
- Is Kinematic: 选中则物体不受物体系统的控制
脚本的运行框架
在Unity中一般会在物体对象上创建Script组件来控制对象的行为和游戏的运行流程,其中每个脚本都有共同的内置方法,其中一些重要的方法的执行顺序为:
Awake() --> Start() --> FixedUpdate() --> Update() --> LateUpdate() --> OnGUI() --> Destroy()
Awake
Awake方法是在脚本对象建立起来之后最先调用
Start
Start方法是在第一帧Updata前调用
FixedUpdate、Update、LateUpdate
这三个方法是在当MonoBehaviour启用时不断被调用的,update跟当前平台的帧数有关,而FixedUpdate是真实时间,所以处理物理逻辑的时候要把代码放在FixedUpdate而不是Update,LateUpdate是在所有Update函数调用后被调用。
Update是在每次渲染新的一帧的时候才会调用,也就是说,这个函数的更新频率和设备的性能有关以及被渲染的物体(可以认为是三角形的数量)。在性能好的机器上可能fps 30,差的可能小些。这会导致同一个游戏在不同的机器上效果不一致,有的快有的慢。因为Update的执行间隔不一样了。
而FixedUpdate,是在固定的时间间隔执行,不受游戏帧率的影响。所以处理Rigidbody的时候最好用FixedUpdate。FixedUpdate的时间间隔可以在项目设置中更改,Edit->ProjectSetting->time 找到Fixedtimestep,就可以修改了。
OnGUI
渲染和处理GUI事件时调用,UI相关的操作需要在这里面实现。
Destroy
Destroy方法在对象销毁时调用。
输入系统和运动控制
如方案所叙,小球的动力来源为,在水平面上前后左右四个方向上的外力,四个方向上的外力的有无和作用时间由键盘来控制。
在小球上添加脚本组件,通过代码检测键盘输入,从而控制外力的作用。Unity中由Input类来操作外部的输入,用w、s、a、d四个键分别控制四个方向,检测C#代码如下:
1 | bool hasUpKey = Input.GetKey(KeyCode.W); |
要产生力的作用,需要用刚体组件中的AddForce方法,获取组件的方法需要用到ameObject.GetComponent方法,如在x轴正方向上添加一个值为2的力的代码如下:
1 | gameObject.GetComponent<Rigidbody> ().AddForce (2, 0, 0); |
以上这些逻辑代码在FixedUpdate方法中实现。
计时器和触发器
Unity中提供Time类实现时间的操作,为了实现计时器的功能,只需要计算起始时间和当前时间的时间差即可,同样在FixedUpdate函数里面实现:
1 | spendTime = Time.time - startTime; |
起点和终点的触发器,可以用碰撞触发来实现。在马路的起点和终点处分别放置碰撞物体,并在其在Box Collider组件中勾选Is Tigger,下图所示的为起点触发设置:
在小球的脚本中添加OnTriggerEnter函数,当小球碰撞到了起点线就会触发这个函数,并通过参数来区分碰撞的物体,具体代码如下:
1 | void OnTriggerEnter(Collider other) |
定时时间显示可以在OnGUI方法中通过GUI.Label函数来实现,具体代码如下:
1 | GUI.color = new Color(0xff, 0xff, 0); |
菜单创建
制作菜单有如下步骤:
创建文字UI
通过创建UI –> Text来实现,在Text组件中设置文字的各种属性:如显示文字、字体、大小、颜色等等文字格式,另外还可以添加阴影等文字特效组件,如下图所示:
在这个本次制作中我制作了两个菜单项:一个为主菜单,一个为子菜单。主菜单一直三个功能:游戏的暂停、继续和推出,子菜单为推出确认菜单。将菜单拖放到合适的位置,如下图所示:
添加Button组件
在菜单中有些是提示的文字,有些是提供交互的按钮选项。对于那些需要有按钮功能的文字,要在其各自对象下添加Button组件:Add Commponent –> UI –> Button,如下图所示:
在Button组件中设置各种配置如颜色,当然最重要的是添加其点击处理函数。
添加按钮点击消息处理函数以及功能的实现
在脚本中编写各个按钮点击处理函数,并在各自的按钮组件中正确的指定。在相关按钮处理函数中实现各自的功能:
暂停/继续
同通过Time.timeScale可以控制游戏的运行快慢,将Time.timeScale 设置为0表示暂停播放,Time.timeScale设置为1为正常运行。
同时需要在逻辑计算的代码部分,加入暂停的判断。因为Time.timeScale只能显示的画面停下来,而输入、计算等仍需要手动的控制停止。
隐藏子菜单
UI Text需要用Canves对象来承载,所以Canves对象是UI Text对象的父级对象,将主菜单和子菜单的分别放在不同的Canves对象中,通过控制子菜单的Canves对象来设置隐藏,代码如下:
1
2quitMenu = quitMenu.GetComponent<Canvas> ();
quitMenu.enabled = false;
摄像机的跟随
在游戏运行时,游戏的画面都是从摄像机的拍到的,所以为保证在小球,游戏画面还能够看到比较清晰的看到小球视角,就需要将摄像机跟这小球一起移动。这里包括两方面的内容:一是位置的跟随,一是视角的跟随。
位置的跟随
位置的跟随可以先获取小球的位置,然后在设置摄像机的位置,在摄像机脚本的Update方法中,在这里我实现的是摄像机的水平高度不变,其他两个轴的位置与小球保持固定,如下代码:
1
transform.position = new Vector3(playerObj.transform.position.x + 4, transform.position.y, playerObj.transform.position.z);
视角跟随
视角跟随有非常简单的方法,可以将摄像机的视角中心对准小球:
1
transform.LookAt (playerObj.transform);