Unity中的协程是模拟操作系统线程的(名称很相近),使用的感觉也相近。不同之处是:线程是操作系统管理的,而协程是Unity管理并运行在主线程中(每帧运行一次,每秒运行30或60次);
协程的使用方便且强大,在游戏编程中很常见。游戏编程的很多逻辑需要跨帧才能完成(尤其涉及到动画表现相关的逻辑),在不用协程的情况下,可能需要在Update的逻辑中进行(其中逻辑状态需要存放为类变量);而改用协程,代码就会变得很简单,且状态只需要维护为方法变量;举例:
1)Update的方法:
public class MyAnim1 : MonoBehaviour {
public Vector3 startPos, endPos;
public float speed;
// 状态
private float progress;
void Start () {
progress = 0;
}
void Update () {
progress += Time.deltaTime * speed;
transform.position = Vector3.Lerp(startPos, endPos, progress);
}
}
2)用协程的方法:
public class MyAnim2 : MonoBehaviour {
public Vector3 startPos, endPos;
public float speed;
void Start() {
StartCoroutine(doAnim()); // 启动协程
}
private IEnumerator doAnim() {
var progress = 0f; // 状态
while (true) {
yield return null; // 等待一帧
progress += Time.deltaTime * speed;
transform.position = Vector3.Lerp(startPos, endPos, progress);
}
}
}
这个例子虽然简单,但是Update的方法的progress的维护要分散在代码的3处,而协程就在一个方法内,这就体现了协程的优势;如果动画有多个的话,使用协程的方便性和可维护性就会更加明显。
Unity管理协程的方式基于两点:1)游戏引擎每秒更新30帧(或则60帧等);2)C#语言对协程的支持;
在游戏开发中,有些时候需要自己管理协程。运用场景举例:
1)一些帧同步的网游,驱动逻辑更新的源头是网络上过来的“逻辑帧”(非游戏刷新帧),网络上每到来一“逻辑帧”,逻辑就需要更新一次(帧同步频率一般会低于游戏刷新帧---比如帧同步频率是15帧/秒。但由于网络卡顿延迟,会把延后“逻辑帧”积累起来一起发送,这样游戏的一个刷新帧内会到来多个“逻辑帧”)。
2)自己想用协程的方式来管理一些游戏功能,比如状态机的简化处理等(以后有时间会写一些相关文章);
在项目中,有的时候需要手动控制协程的更新,也就是重写StartCoroutine方法。具体如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
public class CoroutineManager {
public Clock clock { get; private set; }
private HashSet cIters ;
private List itAddTemp;
private List itDelTemp;
private CoroutineManager() {
cIters = new HashSet();
itAddTemp = new List();
itDelTemp = new List();
clock = new Clock();
}
private static CoroutineManager instance = null;
public static CoroutineManager Instance {
get {
if (instance == null) {
instance = new CoroutineManager();
}
return instance;
}
}
public static float time {
get {
return Instance.clock.time;
}
}
public CoroutineIter StartCoroutine(IEnumerator _it) {
var iter = new CoroutineIter(_it);
itAddTemp.Add(iter);
return iter;
}
public void StopCoroutine(CoroutineIter cIter) {
if (cIter != null) {
itDelTemp.Add(cIter);
}
}
public bool doUpdate(float dt) {
if (itAddTemp.Count > 0) { // 添加
itAddTemp.ForEach(it => { cIters.Add(it); });
itAddTemp.Clear();
}
if (clock.tick(dt) && cIters.Count > 0) {
foreach (var i in cIters) {
i.doUpdate(dt);
}
cIters.RemoveWhere(i => i.isEnd);
}
if (itDelTemp.Count > 0) { // 删除
itDelTemp.ForEach(it => { cIters.Remove(it); });
itDelTemp.Clear();
}
return cIters.Count > 0;
}
public class Clock {
public int frame { get; private set; }
public float time { get; private set; }
public float dt { get; private set; }
public void reset() {
time = dt = 0;
}
public bool tick(float _dt) {
if (frame < Time.frameCount) {
dt = _dt;
time += _dt;
frame = Time.frameCount;
return true;
}
return false;
}
}
}
public class CoroutineIter {
public bool isEnd { get; private set; }
Stack stack = new Stack();
IEnumerator it;
public CoroutineIter(IEnumerator _it) {
it = _it;
isEnd = it == null;
}
public void doUpdate(float dt) {
if (!isEnd) {
if (it.MoveNext()) {
dealCurrent(it.Current);
} else {
it = stack.Count > 0 ? stack.Pop() : null;
}
isEnd = it == null;
}
}
private void dealCurrent(object cur) {
if (it.Current is IEnumerator) {
stack.Push(it);
it = it.Current as IEnumerator;
} else if (it.Current is WaitForSeconds) {
stack.Push(it);
it = new MyWaitForSecond(it.Current as WaitForSeconds);
}
}
}
class MyWaitForSecond : CustomYieldInstruction {
private float duration;
private float startTime;
CoroutineManager cm;
public MyWaitForSecond(WaitForSeconds wfs) {
duration = GetPrivateFieldValue(wfs, "m_Seconds");
cm = CoroutineManager.Instance;
startTime = cm.clock.time;
}
public override bool keepWaiting {
get {
return (cm.clock.time - startTime) < duration;
}
}
private static T GetPrivateFieldValue(object obj, string propName) {
if (obj == null)
throw new ArgumentNullException("obj");
Type t = obj.GetType();
FieldInfo fi = null;
while (fi == null && t != null) {
fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
t = t.BaseType;
}
if (fi == null)
throw new ArgumentOutOfRangeException("propName",
string.Format("Field {0} was not found in Type {1}", propName,
obj.GetType().FullName));
return (T)fi.GetValue(obj);
}
}
using System;
using System.Collections;
using UnityEngine;
public class Testtest : MonoBehaviour {
CoroutineIter iter;
void Start() {
CoroutineManager.Instance.StartCoroutine(testx0());
//StartCoroutine(test0());
}
void Update() {
if (!CoroutineManager.Instance.doUpdate(Time.deltaTime)) {
Debug.LogError("=======EEEEEEEE=======");
}
}
private IEnumerator testx0() {
Debug.Log("======={{{{ =======");
yield return null;
var handler = CoroutineManager.Instance.StartCoroutine(testx1());
yield return new WaitForSeconds(3f);
CoroutineManager.Instance.StopCoroutine(handler);
Debug.Log("======= }}}} =======");
yield return new WaitForSeconds(1f);
Debug.Log("======= End =======");
}
private IEnumerator testx1() {
int i = 0;
while (true) {
yield return new WaitForSeconds(0.3f);
Debug.Log(">>> " + (i++));
}
}
private IEnumerator test0() {
yield return test1();
Debug.Log("000000000000");
yield return test2();
Debug.Log("000000000000");
}
private IEnumerator test1() {
Debug.Log("111111 : " + Time.time);
yield return new WaitForSeconds(1f);
Debug.Log("111111 : " + Time.time);
}
private IEnumerator test2() {
Debug.Log("222222 : " + Time.time);
yield return new WaitForSeconds(2f);
Debug.Log("22222 : " + Time.time);
}
private IEnumerator testA() {
for (int i = 0; i < 3; i++) {
yield return new WaitForSeconds(i);
Debug.Log("-1- " + CoroutineManager.time);
}
Debug.Log("-2-");
yield return null;
Debug.Log("-3-");
yield return new CC();
Debug.Log("-4- " + CoroutineManager.time);
yield return new WaitUntil(delegate {
return CoroutineManager.time > 5;
});
Debug.Log("-5- "+ CoroutineManager.time);
yield return new WaitWhile(delegate {
return CoroutineManager.time < 8;
});
Debug.Log("-6- "+ CoroutineManager.time);
}
class CC : CustomYieldInstruction {
int i = 0;
public override bool keepWaiting {
get {
Debug.Log("CC > " + i);
return i++ < 10;
}
}
}
}
另外,协程遍历的方法,其实是一个树的深度遍历:
using UnityEngine;
using UnityEditor;
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
public class NewEditorTest {
[Test]
public void EditorTest() {
//Iterate (test01 (0));
Iterate(testA(), obj=>{
if(obj is YieldInstruction){
Debug.LogFormat("!!YI: " + obj.GetType().Name);
} else if(obj is CustomYieldInstruction) {
Debug.LogFormat("!!CYI: {0}, isIt? {1} ", obj.GetType().Name, obj is IEnumerator);
}
});
}
private void Iterate(IEnumerator _it, System.Action