介绍

Unity 中的协程本质上是一个状态机,使用 C# yield 语法实现的一个迭代器,在启动协程时将其加入到管理器中,在合适的时机不断调用。

环境

  • Unity 5.6.6f2

动机

由于原有的资源管理代码使用协程处理资源加载问题,因此会导致资源管理协程与逻辑代码协程执行先后顺序不确定。

如果资源管理协程在逻辑代码协程之后执行,就会出现资源加载后未设置就显示并直到下一帧才设置更新,出现一帧错误的画面。

因此需要将资源管理协程的执行顺序手动移动到逻辑代码协程之前,为了最大程度减少对原有资源管理代码的改动,尝试使用自定义协程管理器来驱动原有资源管理协程。

需求

嵌套

支持协程嵌套,即 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 子类。

递归方法处理嵌套协程

自定义包装协程类