本节书摘来异步社区《Unity 5.x游戏开发实战》一书中的第2章,第2.6节,作者: Alan Thorn 译者: 李华峰 责编: 胡俊英,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.6 定时器和倒计时
现在已经拥有一个具备场景和金币的关卡了。凭借新添加进来的Coin.cs脚本,现在的金币也可以被正常计数和采集了。不过对于玩家来说,现在的关卡中仍然没有挑战性,因为他们既赢不了,也输不了。在这个游戏中,玩家没有需要达成的目标。因此时间限制也就成为了游戏中的一个重要条件:它定义了玩家胜利和失败的关键。也就是说,在游戏时间结束前,玩家采集到了所有的金币,就可以看作玩家胜利;玩家没有采集到所有的金币,则玩家失败。所以需要为这个关卡创建一个倒计时的计数器。首先选择菜单栏上的“GameObject | Create Empty”创建一个新的空游戏对象,并为其起名为“Level Timer”,如图2.25所示。
注意
记住,游戏开始时,玩家是看不到任何空游戏对象的,因为这些对象身上没有任何的网格渲染组件。但是这些对象在用来创建一些不直接对应到物理的和可见实体上的功能和行为时,就显得特别有用了,例如定时器、管理器和游戏逻辑控制器之类。
接下来,创建一个名为“Timer.cs”的新脚本文件,然后将这个脚本添加到场景中的LevelTimer 对象上。完成这个操作以后,在这个场景中,定时器就开始起作用了。不过需要确定的是,这个定时脚本仅仅被添加到了场景中的一个物体上,而不是多个物体上。否则,在同一场景中可能就会出现多个相互矛盾的定时器。可以使用层次(Hierarchy)面板来查找整个场景中特定类型的组件。首先在层次(Hierarchy)查找框中输入“t:Timer”,然后按下键盘上的回车键开始查找。这样将会查找到场景中所有带有定时器类型组件的对象,并将在层次(Hierarchy)面板中显示查找的结果。另外,在这个层次(Hierarchy)面板中只会显示匹配的结果。在查找时输入的“t”表示这次查找的条件是类型,如图2.26所示。
如果在查找过程中停止这次查找,并返回层次(Hierarchy)面板的初始状态,则可以通过单击查找对话框右侧的“×”号图标按钮来实现。不过这个按钮很难发现,如图2.27所示。
如果希望Timer脚本生效,那么必须在里面编写相应的代码。下面的代码示例2.6给出了Timer.cs中的完整代码。如果读者之前从未在Unity中进行过系统的编码,那么这段代码将会是非常有学习价值的。这段代码给出了很多十分重要的特点,代码中的注释给出了详细的说明。
代码示例2.6:
//-------------------------
using UnityEngine;
using System.Collections;
//-------------------------
public class Timer : MonoBehaviour
{
//-------------------------
//完成关卡的时间 (单位为秒)
public float MaxTime = 60f;
//-------------------------
/倒计时
[SerializeField]
private float CountDown = 0;
//-------------------------
// start()是初始化函数
void Start ()
{
CountDown = MaxTime;
}
//-------------------------
// 每一帧都会调用update
void Update ()
{
//时间减少
CountDown -= Time.deltaTime;
//当时间耗尽之后重启关卡t
if(CountDown <= 0)
{
//重置金币的数量
Coin.CoinCount=0;
Application.LoadLevel(Application.loadedLevel);
}
}
//-------------------------
}
//-------------------------
现在对上面的代码进行如下总结。
- 在Unity中,将类变量声明为公共变量(例如public float MaxTime)之后,会作为一个可以编辑的字段显示在Object Inspector处。虽然这并不支持所有的数据类型,但是却是相当有用的一个功能。这意味着开发人员可以直接从Inspector处监视公共变量的值,或者设置公共变量的值,而不是每次都要对代码进行修改和编译。默认情况下,在Inspector处私有变量是看不到的。不过,如果需要,可以使用SerializeField属性强制使它们可见。如果私有变量前面加上了这个属性,例如CountDown变量,就会在不改变变量的作用范围前提下,像一个公共变量一样显示在对象Inspector中。
- 所有由MonoBehaviour派生出来的类都支持Update()函数,Scene中所有Active的游戏对象在每一帧中都会自动调用这个函数。简而言之,Update()函数会在每秒执行很多次。游戏的FPS是一个每秒钟执行次数的指标。而实际这个数字可能在每秒钟都会发生变化,Update函数在随着时间的变化去改变对象时相当有用。例如在使用CountDown类时,追踪时间的变化是相当有用的。如果想了解更多的关于Update函数功能,可以访问http://docs.Unity3d.com/ScriptReference/ MonoBehaviour. Update.html这个网址上的Unity在线文档。
注意
除了在每一帧都会被调用的Update()函数,Unity中还支持两个相关的函数,即FixedUpdate()和LateUpdate()。FixedUpdate()主要用来实现物理编码,稍后将看到,这个函数在每一帧都会被调用相同的次数。LateUpdate()函数会被每个活动(Active)的对象在每一帧调用一次,但是LateUpdate()函数的调用总是发生在Update()事件之后。因此,它总是发生在Update()周期之后,使它成为一个后期Update。在本书的后面将介绍这个后期Update()存在的特殊用途。如果想了解更多的关于FixedUpdate()函数功能,可以访问http://docs.Unity3d. com/ScriptReference/MonoBehaviour.Fixed Update.html.在线文档。同样,如果想了解更多的关于LateUpdate函数功能,可以访问http://docs.Unity3d.com/ScriptReference/ MonoBehaviour.LateUpdate.html.在线文档。
- 当进行编程时,Time.deltaTime是一个被Unity不断更新的静态变量。它用来描述自从上一帧结束之后到现在的时间量(以秒为单位)。例如游戏的帧速率是2FPS(这是一个非常低的值),deltaTime的值就是0.5。这是因为在每一秒钟将会有两帧,因此每一帧就是半秒。deltaTime是非常有用的,因为随着时间的推移,这个值会体现出自从游戏开始到现在经过了多少时间。deltaTime作为一个浮点型变量,主要用在Update函数中,用来从总时间中减去已经经过的时间。如果想了解更多关于deltaTime的信息,可以访问http://docs.Unity3d.com/ScriptReference/Time-delta Time.html上的在线文档。
- 静态函数Application.LoadLevel可以在代码的任何地方被调用,用来改变在运行时的场景。这个函数在将游戏从一个关卡切换到另一个关卡时是相当有效的,它将会使Unity终止当前活动的场景,销毁其中的所有内容,然后加载一个新的场景。另外,这个函数也可以通过再次加载当前活动的关卡来重新启动这个场景。Application.LoadLevel十分适合于那些明确定义了关卡的游戏,这些游戏的开始与结局都有明确的分界线。不过它并不适合于那种大型的开放性的游戏,这种游戏的世界看起来都是无限伸展的,看起来没有任何的断裂。关于Application.LoadLevel的更多信息可以在http://docs.Unity3d.com/ScriptReference/Application.LoadLevel.html上的Unity在线文档中找到。
完成Timer脚本的创建工作之后,在场景中选中LevelTimer对象。从对象的检查(Inspector)面板中就可以看到玩家在当前关卡完成任务所允许的最大时间限制(以秒为单位),如图2.28所示。这里将时间设置为60秒。这意味着玩家需要在关卡开始之后的60秒之内完成所有金币的采集工作。如果定时器中的时间耗尽,那么这个关卡将会重启。
现在有了一个具备倒计时功能的完整关卡,可以完成金币的采集,而且定时器也可以耗尽时间。总的来说,现在的游戏已经初具规模了,不过这里还有一个更深入的问题,后面内容将进行讲解。