介绍
Unity 中的协程本质上是一个状态机,使用 C# yield 语法实现的一个迭代器,在启动协程时将其加入到管理器中,在合适的时机不断调用。
环境
动机
由于原有的资源管理代码使用协程处理资源加载问题,因此会导致资源管理协程与逻辑代码协程执行先后顺序不确定。
如果资源管理协程在逻辑代码协程之后执行,就会出现资源加载后未设置就显示并直到下一帧才设置更新,出现一帧错误的画面。
因此需要将资源管理协程的执行顺序手动移动到逻辑代码协程之前,为了最大程度减少对原有资源管理代码的改动,尝试使用自定义协程管理器来驱动原有资源管理协程。
需求
嵌套
支持协程嵌套,即 yield return AnotherCoroutine();
实现时可以考虑使用递归方法调用与自行维护协程栈两种方法。
Unity 协程类
支持 Unity 自带 YieldInstruction
协程,如 WWW
LoadAssetAsync
LoadAssetBundleAsync
实现时需要对其进行特殊处理,因为 YieldInstruction
不是 IEnumerator
,必须手动处理。
考虑并未使用 YieldInstruction
(如:WaitForSeconds
),而只是使用了 AsyncOperation
这个子类,因此可以直接使用子类的 isDone
属性。
实现
管理器
CustomCoroutineManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CustomCoroutineManager
{
private readonly HashSet<CoroutineWrapper> currentIterators;
private readonly List<CoroutineWrapper> iteratorsToAdd;
private readonly List<CoroutineWrapper> iteratorsToRemove;
public CustomCoroutineManager()
{
currentIterators = new HashSet<CoroutineWrapper>();
iteratorsToAdd = new List<CoroutineWrapper>();
iteratorsToRemove = new List<CoroutineWrapper>();
}
public CoroutineWrapper StartCoroutine(IEnumerator enumerator)
{
var iterator = new CoroutineWrapper(enumerator);
iteratorsToAdd.Add(iterator);
return iterator;
}
public void StopCoroutine(CoroutineWrapper coroutineWrapper)
{
if (coroutineWrapper != null)
{
iteratorsToRemove.Add(coroutineWrapper);
}
}
public bool Update()
{
if (iteratorsToAdd.Count > 0)
{
// 添加
iteratorsToAdd.ForEach(it => { currentIterators.Add(it); });
iteratorsToAdd.Clear();
}
if (currentIterators.Count > 0)
{
foreach (var i in currentIterators)
{
i.Update();
}
currentIterators.RemoveWhere(i => i.isEnd);
}
if (iteratorsToRemove.Count > 0)
{
// 删除
iteratorsToRemove.ForEach(it => { currentIterators.Remove(it); });
iteratorsToRemove.Clear();
}
return currentIterators.Count > 0;
}
public class CoroutineWrapper
{
public bool isEnd { get; private set; }
readonly IEnumerator _enumerator;
public CoroutineWrapper(IEnumerator enumerator)
{
_enumerator = enumerator;
isEnd = _enumerator == null;
}
public void Update()
{
isEnd = !MoveNext(_enumerator);
}
private bool MoveNext(IEnumerator enumerator)
{
//yield return 另一个协程:递归 MoveNext
var child = enumerator.Current as IEnumerator;
if (child != null && MoveNext(child))
return true;
// 处理 Unity 内置协程:WaitForSeconds LoadAssetAsync
var asyncOperation = enumerator.Current as AsyncOperation;
if (asyncOperation != null && !asyncOperation.isDone)
return true;
if (enumerator.MoveNext())
return true;
return false;
}
}
}
|
使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private readonly CustomCoroutineManager _customCoroutineManager = new CustomCoroutineManager();
void Start()
{
_customCoroutineManager.StartCoroutine(DoSomething());
}
void Update()
{
_customCoroutineManager.Update();
}
IEnumerator DoSomething()
{
Debug.Log("1");
yield return new WaitForSeconds(1f);
Debug.Log("2");
}
|
参考资料
使用协程栈方法处理嵌套协程,同时处理了 WaitForSeconds 这种非 AsyncOperation 子类。
递归方法处理嵌套协程
自定义包装协程类