Unity 3D中不得不说的yield协程与消息传递

时间:2023-03-09 00:33:53
Unity 3D中不得不说的yield协程与消息传递

  1. 协程

  在Unity 3D中,我们刚开始写脚本的时候肯定会遇到类似下面这样的需求:每隔3秒发射一个烟花、怪物死亡后20秒再复活之类的。刚开始的时候喜欢把这些东西都塞到Update里面去,就像下面这样写。

 float nowTime = 3.0f;
bool isDead = true;
float deadTime = 20.0f; void startFireworks()
{
// 放烟花
} void revival()
{
// 复活
} void Update ()
{
if (nowTime <= )
{
startFireworks();
nowTime = Random.Range(2.5f, 3.5f);
}
nowTime -= Time.deltaTime;
if (isDead)
{
if (deadTime <= )
{
revival();
isDead = false;
deadTime = 30.0f;
}
deadTime -= Time.deltaTime;
}
}

  当这样的需求多起来时,Update中凌乱不堪,如果有需求需要添加或者修改,将显得非常麻烦。尤其是类似死亡后复活这种需求,只是在死亡后等待30秒重新复活,其他时间根本不需要去执行,这样放在Update里面还需要每一帧去判断,显得很累赘。

好在Unity 3D支持yield协程,不懂没关系,先看看下面用协程实现上面的功能。

 void Start()
{
StartCoroutine(Fireworks());
} void deadHandle()
{
StartCoroutine(Revival());
} IEnumerator Fireworks()
{
while (true)
{
startFireworks();
yield return new WaitForSeconds(Random.Range(2.5f, 3.5f));
}
} IEnumerator Revival()
{
yield return new WaitForSeconds(30.0f);
revival();
}

  上面代码中,以IEnumerator作为返回值的函数就是协程,调用StartCoroutine()开始协程,在Start函数中调用StartCoroutine(Fireworks());,说明在开始时就开始执行协程Fireworks(),在deadHandle()中调用StartCoroutine(Revival());说明是在怪物死亡时开始执行协程。

  现在再来看看协程Fireworks()和Revival()中带有yield return的语句,yield return new WaitForSeconds(30.0f);表示现在从return这个语句处中断执行,在30秒后继续执行后面的代码。

  如果想在下一帧继续执行,就应该这样写 yield return null,这样语句就会在return这里中断,等待下一帧继续执行后面的代码。就像在Fireworks()里面写的,return之后,继续进行while判断,为true则继续循环,遇到yield return中断执行,等待,反复这样运行,就像Update一样。当然while中的判断条件可以自己指定,在需要中断的时候,在外面将while中的判断条件置为false即可。

 bool isContinue = true;
void stopFireworks()
{
isContinue = false;
}
IEnumerator Fireworks()
{
while (isContinue)
{
startFireworks();
yield return new WaitForSeconds(Random.Range(2.5f, 3.5f));
}
}

  当然也可以通过有StopCoroutine来中止协程的执行,不过这个函数是有条件的,具体可以去查阅unity文档或者网上搜索一下,有很多资料,这里只是告诉大家有这么个东西可以用。

  有了协程,写起脚本来真是方便了很多。协程和Update一样,也是系统在每帧会去检测调用,因此在协程中也是可以使用Time.deltaTime的。关于协程与Update之类的执行顺序,没有测试过,网上也有一些资料,大家可以参考,不同的Unity 3D版本具体的实现可能有出入,如果某些功能确实需要知道执行顺序,那么到时候可以亲测一下。

  需要注意的一点是:WaitForSeconds是受到Time.timeScale影响的,如果将其置为0,那么协程就无法执行下去了。不过yield return null不会受到影响,因为每帧会执行,只是Time.deltaTime为0了。

  2. 消息传递

  在游戏开发中,消息传递必不可少。通常有三种方式:保存别的对象的引用、Unity自带的SendMessage和C#中的事件。

  例如一个暂停,我需要通知玩家,暂停了,不要响应键盘鼠标操作了;通知UI,显示一个暂停面板;通过所有怪物,不要动了,暂停了,休息一下。

  第一种方式是刚开始写脚本时常用的,保存所有对象的引用,这是很麻烦的事情,我需要获取玩家、UI和所有的怪物对象,然后调用其相应的暂停函数,这在程序规模变大之后,添加、修改和删除是一个很大的工作量。而且很多对象之间相互引用,耦合对也很高,用起来比较麻烦。

  第二种方式是Unity 3D提供的Messages消息机制,不过网上说这种方法有很大的缺陷,而且只能通知一个父子关系的对象,不同对象之间的消息无法传递。没有用过这个机制,所以也不是很清楚是不是像上面说的那样。

  第三种方式是C#中的委托和事件,这个方法对于消息传递来说非常好用,从设计模式的角度上来说,就是一个典型的观察者模式。如果你用过EasyTouch摇杆,那你就应该知道在OnEnable()中使用EasyJoystick.On_JoystickMove += OnJoystickMove;注册自己的Move函数,在这里就是OnJoystickMove。其实EasyTouch这个就用到了C#事件,使用+=添加自己的响应函数,当发生摇杆移动时,就会调用你自己指定的OnJoystickMove函数。具体可以参考下面给出的参考资料的链接。

  今天就写到这里,这些都是简介性质的,详细资料网上都有很多,我这些只是告诉初学者Unity 3D中有这些东西,很可能是你需要的,可以少走一些弯路。

  关于协程和C#事件,是Unity 3D中强力推荐的两个机制,它们真的非常重要,一定要善用,大家可以体会一下。

  参考资料1:【吐血推荐】简要分析unity3d中剪不断理还乱的yield

  参考资料2:C# 事件和Unity3D