Unity如何在Editor下执行协程(coroutine)

在处理Unity5新的AssetBundle的时候,我有一个需求,需要在Editor下(比如一个menuitem的处理函数中,游戏没有运行,也没有MonoBehaviour)加载AssetBundle。而加载AssetBundle的时候又需要使用yield return www;这样的协程用法。

所以就有了一个需求,在Editor下执行协程。我从网上找到一个EditorCoroutine,其代码如下:

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

public static class EditorCoroutineRunner
{
private class EditorCoroutine : IEnumerator
{
private Stack<IEnumerator> executionStack;

public EditorCoroutine(IEnumerator iterator)
{
this.executionStack = new Stack<IEnumerator>();
this.executionStack.Push(iterator);
}

public bool MoveNext()
{
IEnumerator i = this.executionStack.Peek();

if (i.MoveNext())
{
object result = i.Current;
if (result != null && result is IEnumerator)
{
this.executionStack.Push((IEnumerator)result);
}

return true;
}
else
{
if (this.executionStack.Count > 1)
{
this.executionStack.Pop();
return true;
}
}

return false;
}

public void Reset()
{
throw new System.NotSupportedException("This Operation Is Not Supported.");
}

public object Current
{
get { return this.executionStack.Peek().Current; }
}

public bool Find(IEnumerator iterator)
{
return this.executionStack.Contains(iterator);
}
}

private static List<EditorCoroutine> editorCoroutineList;
private static List<IEnumerator> buffer;

public static IEnumerator StartEditorCoroutine(IEnumerator iterator)
{
if (editorCoroutineList == null)
{
// test
editorCoroutineList = new List<EditorCoroutine>();
}
if (buffer == null)
{
buffer = new List<IEnumerator>();
}
if (editorCoroutineList.Count == 0)
{
EditorApplication.update += Update;
}

// add iterator to buffer first
buffer.Add(iterator);

return iterator;
}

private static bool Find(IEnumerator iterator)
{
// If this iterator is already added
// Then ignore it this time
foreach (EditorCoroutine editorCoroutine in editorCoroutineList)
{
if (editorCoroutine.Find(iterator))
{
return true;
}
}

return false;
}

private static void Update()
{
// EditorCoroutine execution may append new iterators to buffer
// Therefore we should run EditorCoroutine first
editorCoroutineList.RemoveAll
(
coroutine => { return coroutine.MoveNext() == false; }
);

// If we have iterators in buffer
if (buffer.Count > 0)
{
foreach (IEnumerator iterator in buffer)
{
// If this iterators not exists
if (!Find(iterator))
{
// Added this as new EditorCoroutine
editorCoroutineList.Add(new EditorCoroutine(iterator));
}
}

// Clear buffer
buffer.Clear();
}

// If we have no running EditorCoroutine
// Stop calling update anymore
if (editorCoroutineList.Count == 0)
{
EditorApplication.update -= Update;
}
}
}

这里需要注意几个地方:

  1. EditorApplication.update,这个是一个delegate,可以绑定一个函数,从而在编辑器下执行Update。

  2. EditorCoroutineRunner.StartEditorCoroutine(Routine1()); 这样可以在编辑器下开启一个协程。

  3. 另外一个思路是不使用协程,绑定一个Update函数,然后判断www.isDone来获取AssetBundle。这个我并没有实际验证。

  4. www可以正常的加载出AssetBundle,但是isDone的变量一直为false。额外要注意因为Editor模式下不存在退出游戏清理资源的概念,所以要注意处理已加载的assetbundle的情况,否则可能会报冲突的错误。

  5. 理论上只支持yield return null这样的情况,延时要自己处理。Unity协程的原理是引擎在特定条件下执行MoveNext运行下面的语句,在上面的代码中不管是延时还是其他的东西,都是每帧执行MoveNext,这样WaitForSeconds这样的协程是无效的。 www的情况比较特殊,虽然理论上也是会有问题的,但是确实可以正常的取到结果。

本文来自:https://blog.csdn.net/langresser_king/article/details/44244369