# Unity 协同程序 (Coroutine).

# 定义

具有多个返回点 (yield),可以在特定时机分步执行的函数。。

# 原理

协程运行在 Unity 的主线程中,Unity 每帧处理 GameObject 中的协同函数,直到函数执行完毕。

当一个协同函数启动时,本质是创建迭代器对象,调用 MoveNext () 方法,执行到 yield 暂时退出,待满足条件后再次调用 MoveNext () 方法,执行后续代码,直至遇到下一个 yield 为止,如此循环至整个函数结束。

# 作用

  • 延时调用
  • 分解操作

# 语法

通过 MonoBehaviour 中的 StartCoroutine 方法启动,StopCoroutine 方法停止。
协程函数返回值类型为 lEnumerator, 方法体中通过 yield 关键字定义返回点,通过 return XXX 对象定义继续执行的条件。

可以被 yield return 的对象:

  1. null 或数字 —— 在 Update 后执行,适合分解耗时的逻辑处理。
  2. WaitForFixedUpdate—— 在 FixedUpdate 后执行,适合分解物理操作。
  3. WaitForSeconds—— 在指定时间后执行,适合延迟调用。
    WaitForSecondsRealtime—— 同上,但不受时间缩放影响。
  4. WaitForEndOfFrame—— 在每帧结束后执行,适合相机的跟随操作。
  5. Coroutine—— 在另一个协程执行完毕后再执行。
  6. WaitUntil—— 在委托返回 true 时执行,适合等待某一操作。
    WaitWhile—— 在委托返回 false 时执行,适合等待某 - 操作。
  7. Www—— 在请求结束后执行,适合加载数据,如文件、贴图、材质等。

# 协程与线程

协程不是线程
下面给出协程与线程的对比

协程

  1. 协程在 Unity 的主线程中执行,每帧调用,所以协程可以访问 Unity 的 API
  2. 通过 StartCoroutine () 开启,通过 yield return ... 控制执行时机
  3. 共享堆,不共享栈,访问共享数据时,不存在并发冲突
  4. 性能开销小于线程

线程

  1. 为确保脚本生命周期的执行顺序,辅助线程不能访问 Unity 的 API
  2. 通过 Thread 对象的 Start () 开启,由操作系统抢占式调度,到达分配时间后暂停执行,无法预测执行时机
  3. 共享堆,不共享栈,访问共享数据时,存在并发冲突
  4. 线程具备内核对象、环境块、DLL 线程链接和线程分离通知、用户模式栈、内核模式栈等元素,所以开启或关闭线程的性能消耗较大

最直接的分辨方法:用协程和线程分别写一个死循环

# 例子

# 理清代码的执行顺序

namespace Default
{
    public class CoroutineTest : MonoBehaviour
    {
        private Coroutine coroutine;
        private void Start()
        {
            print("a:" + Time.frameCount);
            coroutine = StartCoroutine(Fun_0());
            print("b:" + Time.frameCount);
            StartCoroutine(Fun_1());
            print("c:" + Time.frameCount);
        }
        private IEnumerator Fun_0()
        {
            print("d:" + Time.frameCount);
            yield return new WaitForSeconds(2);
            print("e:" + Time.frameCount);
        }
        private IEnumerator Fun_1()
        {
            print("f:" + Time.frameCount);
            yield return coroutine;
            print("g:" + Time.frameCount);
        }
    }
}
代码的运行结果

a:1
d:1
b:1
f:1
c:1
e:544
g:544

# 利用协程实现自动寻路

namespace Default
{
    public class Coroutine_Pathfinding : MonoBehaviour
    {
        [Tooltip("移动速度")]
        public float MoveSpeed;
        [Tooltip("路线")]
        public Transform WayLine;
        private Transform[] AllWayLines;
        private bool Action;
        private void Awake()
        {
            AllWayLines = new Transform[WayLine.childCount];
        }
        private void Start()
        {
            GetWayLine();
        }
        private void OnGUI()
        {
            if (GUILayout.Button("Action"))
            {
                Action = true;
                //StartCoroutine(Pathfinding());
                StartCoroutine(Pathfinding_1());
            }
            if (GUILayout.Button("Stop"))
            {
                Action = false;
                StopAllCoroutines();
            }
        }
        /// <summary>
        /// GetWayLine
        /// </summary>
        private void GetWayLine()
        {
            for (int i = 0; i < WayLine.childCount; i++)
            {
                AllWayLines[i] = WayLine.GetChild(i);
            }
        }
        private IEnumerator Pathfinding()
        {
            int index = 0;
            while (Action)
            {
                if (Vector3.Distance(this.transform.position, AllWayLines[index].position) > 0.1f)
                {
                    this.transform.position = Vector3.MoveTowards(this.transform.position, AllWayLines[index].position, MoveSpeed);
                }
                else
                {
                    index++;
                    if (index >= WayLine.childCount)
                    {
                        index = 0;
                    }
                }
                yield return null;
            }
        }
        private IEnumerator Pathfinding_1()
        {
            int index = 0;
            while (Action)
            {
                yield return MoveTo(AllWayLines[index].position);
                index++;
                if (index >= WayLine.childCount)
                {
                    index = 0;
                }
            }
        }
        private IEnumerator MoveTo(Vector3 target)
        {
            this.transform.LookAt(target);
            while (Vector3.Distance(this.transform.position, target) > 0.1f)
            {
                this.transform.position = Vector3.MoveTowards(this.transform.position, target, MoveSpeed);
                yield return null;
            }
        }
    }
}