# Unity 协同程序 (Coroutine).
# 定义
具有多个返回点 (yield),可以在特定时机分步执行的函数。。
# 原理
协程运行在 Unity 的主线程中,Unity 每帧处理 GameObject 中的协同函数,直到函数执行完毕。
当一个协同函数启动时,本质是创建迭代器对象,调用 MoveNext () 方法,执行到 yield 暂时退出,待满足条件后再次调用 MoveNext () 方法,执行后续代码,直至遇到下一个 yield 为止,如此循环至整个函数结束。
# 作用
- 延时调用
- 分解操作
# 语法
通过 MonoBehaviour 中的 StartCoroutine 方法启动,StopCoroutine 方法停止。
协程函数返回值类型为 lEnumerator, 方法体中通过 yield 关键字定义返回点,通过 return XXX 对象定义继续执行的条件。
可以被 yield return 的对象:
- null 或数字 —— 在 Update 后执行,适合分解耗时的逻辑处理。
- WaitForFixedUpdate—— 在 FixedUpdate 后执行,适合分解物理操作。
- WaitForSeconds—— 在指定时间后执行,适合延迟调用。
WaitForSecondsRealtime—— 同上,但不受时间缩放影响。 - WaitForEndOfFrame—— 在每帧结束后执行,适合相机的跟随操作。
- Coroutine—— 在另一个协程执行完毕后再执行。
- WaitUntil—— 在委托返回 true 时执行,适合等待某一操作。
WaitWhile—— 在委托返回 false 时执行,适合等待某 - 操作。 - Www—— 在请求结束后执行,适合加载数据,如文件、贴图、材质等。
# 协程与线程
协程不是线程
下面给出协程与线程的对比
协程
- 协程在 Unity 的主线程中执行,每帧调用,所以协程可以访问 Unity 的 API
- 通过 StartCoroutine () 开启,通过 yield return ... 控制执行时机
- 共享堆,不共享栈,访问共享数据时,不存在并发冲突
- 性能开销小于线程
线程
- 为确保脚本生命周期的执行顺序,辅助线程不能访问 Unity 的 API
- 通过 Thread 对象的 Start () 开启,由操作系统抢占式调度,到达分配时间后暂停执行,无法预测执行时机
- 共享堆,不共享栈,访问共享数据时,存在并发冲突
- 线程具备内核对象、环境块、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; | |
} | |
} | |
} | |
} |