分享

unity3d延时调用

 3dC 2016-08-12

一.协程

协程是什么?

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

协程 不是新线程 ,是u3d提供的一种迭代器管理机制,例如常见的:

void Start () {

  print (0);

  StartCoroutine (wait (3));

  print (1);

}

IEnumerator wait(float s) {

  print (2);

  yield return new WaitForSeconds (s);

  print (3);

}
// => 0 2 1 [3s later] 3

StartCoroutine 传入 wait 返回的迭代器,协程内部就可以控制迭代器内部的执行,类似于:

IEnumerator iter;

void Start () {

  print (0);

  iter = wait ();

  print (1);

}

IEnumerator wait() {

  print (2);

  yield return 1;

  print (3);

}
// => 0 1

为什么没有输出2和3?

因为 wait 只是创建了一个迭代器,我们知道迭代器不调用 next() 方法(C#是 MoveNext ,js是 next )就不会执行。嗯,接着尝试:

IEnumerator iter;

void Start () {

  print (0);

  iter = wait ();

  print (1);

  iter.MoveNext ();

  iter.MoveNext ();

}

IEnumerator wait() {

  print (2);

  yield return 1;

  print (3);

}

// => 0 1 2 3

手动调用迭代器的 MoveNext() 方法,结果和我们想的一样,这就是 StartCoroutine 的参数, StartCoroutine 内部拿到迭代器的引用就可以控制什么时候 MoveNext ,什么时候停下来。所以我们看到的效果是:代码在分段执行(由yield return语句分开)这个表象

还不太明白?没关系,我们接着看:

IEnumerator iter;

void Start () {

  print (0);

  iter = wait (3);

  print (1);

  iter.MoveNext ();

  iter.MoveNext ();

}

IEnumerator wait(float s) {

  print (2);

  yield return new WaitForSeconds (s);

  print (3);

}

// => 0 1 2 3


等等,为什么输出2后没有等待3秒?

因为我们现在是在手动管理迭代器,第一次 MoveNext() 之后 yield return 返回的 WaitForSeconds 类型对象我们并没有做相应处理,所以不会等待10s。

StartCoroutine 内部可能做了这样的处理:

  1. 拿到参数(迭代器)后,在某个时机( 不是立即 ,至于是什么时候,我们待会儿再议)调用迭代器的 MoveNext()

  2. 如果发现 MoveNext() 的返回值(也就是 yield return 后面的东西)是一个 WaitForSeconds 对象,就s秒后再执行 MoveNext()

最终我们看到的直接结果就是延时s秒执行 yield return 下面的东西。好了,到这里就差不多弄清楚了,最后一个例子:

IEnumerator iter;
void Start () {
  print (0);
  StartCoroutine (iter = wait (3));
  print (1);
  iter.MoveNext ();
}
IEnumerator wait(float s) {
  print (2);
  yield return new WaitForSeconds (s);
  print (3);
}
// => 0 2 1 3

猜猜输出2之后有没有等待3秒?

没有,因为我们手动 MoveNext () 了,协程第一次调用 MoveNext () 的时候迭代器已经走了一步了,协程根本没有看到 WaitForSeconds ,所以协程拿到迭代器后不是立即执行,而是等待某个属于协程的时间段(据说是在 Update 之后,有兴趣的话可以找找u3d函数执行顺序图,当然,这不重要)

协程是什么?答案是开篇提到的:

协程是u3d提供的一种迭代器管理机制

P.S. 前辈博文 说,“协程其实就是一个IEnumerator(迭代器)”,二者哪个更对,不用争辩了吧

二.协程实现的延时调动

看到一种很灵活的延时调用,如下:

using UnityEngine;
using System.Collections;
using System;

public class Delay {
    public static IEnumerator run(Action action, float delaySeconds) {
        yield return new WaitForSeconds(delaySeconds);
        action();
    }
}

代码修改自 Unity 延迟执行一段代码的实现比较好的方式

静态方法,方便调用,可以作为工具函数,不需要与任何物体绑定,调用方式比较丑,如下:

void Start() {
  print (1);
  StartCoroutine(Delay.run (() => {
    print (2);
  }, 3));
}
// => 1 [3s later] 3

特别注意 :有2种情况会导致延时调用失败

  • StartCoroutine 后面有切换场景( Application.LoadLevel

    因为切换场景后协程停止执行,延时调用就失败了

  • StartCoroutine 后面有 Destroy 当前物体或者当前物体的祖先物体

    因为物体被销毁后,该物体身上的所有脚本通过 StartCoroutine 添加的协程任务都会停止执行,所以延时调用失败

三.Invoke的延时调用

Invoke 类似于js的 setTimeout ,还有类似于 setIntervalInvokeRepeating ,但远不如js强大, Invoke 系列 只能 接受字符串形式的方法名,按名延时调用,例如:

int arg;
void Start() {
  arg = 1;
  Invoke ("doSth", 3);
}
void doSth() {
  print (arg);
}
// => [3s later] 1

因为字符串形式不能传参,所以用了全局变量来传,语法很简洁,也没有让人迷惑的地方

特别注意 :上面提到的2种延时调用失败的情况仍然存在

四.Time.time实现延时调用

比较笨的方法,但能够避免切换场景和销毁物体导致延时调用失效的问题,代码如下:

using UnityEngine;
using System.Collections;
using System;
public class Wait : MonoBehaviour {
  static Action _action;
  static float time;
  static float delayTime;
  // Use this for initialization
  void Start () {
    // 切换场景时不销毁该物体
    DontDestroyOnLoad (gameObject);
    reset ();
  }
  // Update is called once per frame
  void Update () {
    if (Time.time > time + delayTime) {
      _action();
      reset();
    }
  }
  void reset() {
    time = 0;
    delayTime = int.MaxValue;
  }
  public static void runAfterSec(Action action, float s) {
    _action = action;
    time = Time.time;
    delayTime = s;
  }
}

用法有讲究:

  1. 创建以上脚本

  2. 创建空物体,并绑定以上脚本

然后就可以在任意地方调用了,例如:

void Start() {
  int arg = 1;
  Wait.runAfterSec (() => {
    print (arg);
  }, 3);
  Application.LoadLevel("scene1");
}
// => 场景跳转 [3s later] 1

嫌功能弱的话,可以改成用队列存放 action 等等,自行扩展

五.总结

推荐使用第一种方式,即用协程实现延时调用,要求可控性比较高的话,可以采用第三种方式,情景比较简单的话可以用第二种方式,毕竟要传参、定义函数,不很方便

参考资料

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多