您的位置:首页 > 移动开发 > Unity3D

Unity中协程的使用+自定义

2017-01-16 23:26 399 查看
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的协程管理

 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<CoroutineIter> cIters ;
private List<CoroutineIter> itAddTemp;
private List<CoroutineIter> itDelTemp;

private CoroutineManager() {
cIters = new HashSet<CoroutineIter>();
itAddTemp = new List<CoroutineIter>();
itDelTemp = new List<CoroutineIter>();
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<IEnumerator> stack = new Stack<IEnumerator>();
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<float>(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<T>(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<object> action = null) {
Stack<IEnumerator> stack = new Stack<IEnumerator> ();
var it = _it;
while (it != null) {
while (it.MoveNext ()) {
if (action != null)
action (it.Current);
if (it.Current is IEnumerator) {
stack.Push (it);
it = it.Current as IEnumerator;
continue;
}
}
it = stack.Count > 0 ? stack.Pop() : null;
}
}
float time = 0;
private IEnumerator testA() {
for (int i = 0; i < 3; i++) {
Debug.Log ("-1-");
yield return new WaitForSeconds (i);
}
Debug.Log ("-2-");
yield return null;
Debug.Log ("-3-");
yield return new CC ();
Debug.Log ("-4-");
yield return new WaitUntil (delegate {
time += 0.3f;
Debug.Log(">Time: " + time);
return time > 10;
});
Debug.Log ("-5-");
yield return new WaitWhile (delegate {
time += 0.3f;
Debug.Log(">Time: " + time);
return time < 15;
});
}

class CC : CustomYieldInstruction {
int i = 0;
public override bool keepWaiting {
get {
Debug.Log ("CC > " + i);
return i++ < 10;
}
}
}

private IEnumerator test01(int l) {
Debug.Log ("----test01-{{{");
print (1, l);
yield return test02 (l+1);
print (2, l);
yield return null;
print (3, l);
yield return test03(l+1);
print (4, l);
Debug.Log ("}}}test01-----");
}

private IEnumerator test02(int l) {
Debug.Log ("----test02-{{{");
print (1, l);
yield return test03 (l+1);
print (2, l);
yield return null;
print (3, l);
yield return test03(l+1);
print (4, l);
Debug.Log ("}}}test02-----");

}

private IEnumerator test03(int l) {
Debug.Log ("----test03-{{{");
print (1, l);
yield return null;
Debug.Log ("}}}test03-----");
}

private void print(int num, int level) {
string rt = "";
for (int i = 0; i < level; i++) {
rt += " ";
}
Debug.LogFormat ("{0}{1}", rt, num);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: