Unityの非同期処理

はじめに

こんにちは、ソーシャルゲーム事業部のUnityエンジニアの清水です。
この記事はカヤックUnityアドベントカレンダー2016の8日目の記事になります。

ゲームにおいて、重い処理を実行した時に、処理が終わるまで画面が止まるというのは避けたいものです。
今日の記事では、時間がかる処理を行なったときにFPSが低下したり、数秒間まるまる画面が固まってしまうといった問題を避ける為の、非同期処理について扱いたいと思います。

コルーチン

Unityにおける非同期処理で一般的なものは、4日目の記事でも触れたように、コルーチンを使うというものです。
コルーチンを使うと、Unityが提供している非同期処理が終わるのを待ち受けることができますが、時間のかかる一連の処理を複数フレームに分割することにも使えます。
数秒間かかる処理も、1フレームに10ミリ秒ずつ複数フレームかけて処理すれば画面が固まることはありません。
以下のコードは複数のprefabのインスタンス化を複数フレームに分割して実行する例です。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class CoroutineSample : MonoBehaviour
{
    public delegate void InstantiateObjectsCallback(List<GameObject> gameObjects);

    public void InstantiateObjects(GameObject[] prefabs, InstantiateObjectsCallback callback)
    {
        StartCoroutine(InstantiateObjectsInternal(prefabs, callback));
    }

    private IEnumerator InstantiateObjectsInternal(GameObject[] prefabs, InstantiateObjectsCallback callback)
    {
        var result = new List<GameObject>();

        float goNextFrameTime = Time.realtimeSinceStartup + 0.01f;
        foreach (GameObject prefab in prefabs)
        {
            // 10msec以上経過したら次フレームへ
            if (Time.realtimeSinceStartup >= goNextFrameTime)
            {
                yield return null;
                goNextFrameTime = Time.realtimeSinceStartup + 0.01f;
            }

            // 巨大なGameObjectの複製には時間がかかる
            result.Add((GameObject)Instantiate(prefab));
        }

        if (callback != null)
        {
            // コールバックで結果を返す
            callback(result);
        }
    }
}

スレッド

コルーチンはメインスレッドで処理を複数フレームに分けて実行するアプローチですが、重い処理を別スレッドで実行するアプローチもあります。
別スレッドで実行すればマルチコアのCPUの端末なら他のコアに処理を分散できるので、コルーチンを使うより早く処理が終わります。
またコルーチンを使った方法では、分割する単一の処理が1フレームの時間内で終わらない場合にはFPSの低下を引き起こしますが、別スレッドで実行すればそのような制限はありません。
ただし、Unityはシングルスレッドが前提で作られている為、別スレッドではUnityEngineの機能を使うことができません。
そのため、シリアライズ系の処理やIO、圧縮や展開、純粋な数値計算など、Unityに依存しない処理で使います。
UnityEngineの処理が重い場合は前項のコルーチンを使いましょう。
スレッドを使う例は以下になります。

using UnityEngine;
using System.Threading;

public class ThreadSample : MonoBehaviour
{
    private Thread _thread;

    private void Start()
    {
        _thread = new Thread(DoHeavyProcess);
        _thread.Start();
    }

    private void DoHeavyProcess()
    {
        // 別スレッドで実行する処理
        // UnityのAPIは使えない
    }

    private void OnDestroy()
    {
        if (_thread != null)
        {
            _thread.Abort();
            _thread = null;
        }
    }
}

おわりに

この記事ではUnityにおける非同期処理について紹介しました。
明日はUnityにおいてよく使われるデザインパターンについての記事をお送りします。