LoginSignup
13
10

More than 5 years have passed since last update.

複数のIEnumratorを時間管理しつつ処理するCoroutine

Last updated at Posted at 2015-09-20

作ったもの

TaskScheduler

作ろうと思ったきっかけ

ステージクリア型のパズルゲームのステージセレクト画面で縮小したマップを一気に3x4の12個動的に読み込みつつ、表示。みたいなことをStartCoroutineでやっていたらFPSがガタ落ちでUIの反応が悪くなってしまったので。
TaskScheduler.PNG

例えば

あるGameObjectはあるPrefabを10個Instantiateして子要素として付けておく必要があるとする。
しかしUnityのInstantiateは重い。 なので、StartCoroutineを使って、yield return nullしながら1個づつInstantiateすることにした。(よくある)

public class HogeObject : MonoBehaviour
{
    public GameObject prefab;
    public void Start()
    {
        StartCoroutine(CreateIterator());
    }

    private IEnumrator CreateIterator()
    {
        for(int i = 0;i < 10;++i)
        {
            var go = Instantiate(prefab) as GameObject;//heavy work
            go.transform.SetParent(this.transform,false);
            yield return null;
        }   
    }
}

しかし、このHogeObjectは画面上に100個あった!(よくある(?))
すると、StartCoroutineが100回呼ばれ、1フレームに100個Instantiateされることになってプチフリーズのような状態に。

何が悪いかって

UnityのStartCoroutineの仕組みには横の繋がりがなく、独立して動いているので、何個StartCoroutineされてようが知ったこっちゃないということ。

なので

冒頭に戻る。 自前StartCoroutineの作成。
上記例はこうなる。

public class HogeObject : MonoBehaviour
{
    public GameObject prefab;
    public void Start()
    {
        //StartCoroutine(CreateIterator());//この1行が
        TaskScheduler.Instance.AddIterator(CreateIterator());//これになる
    }

    private IEnumrator CreateIterator()
    {
        for(int i = 0;i < 10;++i)
        {
            var go = Instantiate(prefab) as GameObject;//heavy work
            go.transform.SetParent(this.transform,false);
            yield return null;
        }   
    }
}

StartCoroutineだった箇所をTaskScheduler.Instance.AddIteratorに変える事で、TaskScheduler(Singletone)で1本だけ走っているStartCoroutineが時間を計測しつつよしなにCreateIteratorを処理するので、HogeObject が100個あっても1000個あっても処理落ちはしない(はず)。

仕組み

  • UnityのStartCoroutineIEnumeratorを渡して実行しているが、仕組みとしてはIEnumerator#MoveNextを呼ぶ事で処理を中断→継続させている。このMoveNextを呼ぶ処理を自分で作れば自前StartCoroutineみたいな事が出来る。
  • そこで、複数のIEnumratorを受け取って、順次MoveNextを呼んでいく+処理時間が一定時間を超えていたらyield return null;するIEnumratorStartCoroutineしておく(何言ってんだ)
  • すなわち、UnityのStartCoroutineの拡張をUnityのStartCoroutineを使うことで実装した感じ(何言ってんだその2)。
        public void Start()
        {
            StartCoroutine(Iterator());
        }
        private IEnumerator Iterator()
        {
            while (true)
            {
                var ts = Time.realtimeSinceStartup;
                do
                {
                    if (iteratorList.Count <= 0) break;
                    foreach (var itr in iteratorList.ToList())
                    {
                        if (itr.Value.MoveNext() == false)
                        {
                            iteratorList.Remove(itr.Key);
                        }
                        if (Time.realtimeSinceStartup - ts > targetTime) yield return null;
                    }
                } while (Time.realtimeSinceStartup - ts <= targetTime);
                yield return null;
            }
        }

補足

  • Action 渡せるものもついでに作ったので、TaskScheduler.Instance.AddAction(()=>Instantiate(hogeObj));のようにラムダ式渡したりなんかも。
  • yield return new WaitForSeconds(ms); などには未対応。 というか、処理の負荷分散目的であって、順次処理目的でのStartCoroutineは普通にStartCoroutineでやってくれれば良いのでは。
  • WWW www;で yield return www; とかは価値があるけど未対応。 while(www.isDone == false){yield return null;}にすれば使えると思う(試してない)
  • 基本は独立のStartCoroutineに横のつながりをもたせたなら、Priorityの概念を入れるのが筋だとは思うが未実装。必要に駆られたら作ります。
13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10