【Unity】Unity6から追加された「InstantiateAsync」でオブジェクトを非同期生成する

Unity6から追加された「InstantiateAsync」でオブジェクトを非同期生成する方法についてまとめました。

Unity6000.0.25f1

InstantiateAsyncとは?

Unity6より前のバージョン(正確にはUnity2022LTSの途中から入ってますが)では、Prefabなどを動的に生成する場合には以下のようにInstantiateメソッドにより同期的に生成する必要がありました。

using UnityEngine;

public sealed class Example : MonoBehaviour
{
    [SerializeField] private GameObject prefab;

    private void Start()
    {
        for (var i = 0; i < 1000; i++)
            Instantiate(prefab);
    }
}

この方法には、メインフレームで同期的に処理を行うため、一度に多くのオブジェクトを生成する際にスパイクが発生しやすいという課題がありました。

そこでUnity6では非同期的にオブジェクトを生成できる InstantiateAsync が追加されました。
これを使うと、内部的にUnityのジョブシステムが使用され、マルチスレッドでいい感じに処理を複数スレッド・複数フレームに分散してくれます。

重いオブジェクトを多数生成する時に特に有効な機能であると言えます。

使い方

InstantiateAsyncの使い方は以下の通りです。

using Cysharp.Threading.Tasks;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    [SerializeField] private GameObject prefab;

    private async void Start()
    {
        // タスクを10個に分けて生成
        var tasks = new UniTask[10];
        for (var i = 0; i < 10; i++)
        {
            // 1つのタスクにつき100個ずつ生成(合計1000個)
            var task = InstantiateAsync(prefab, 100).ToUniTask();
            tasks[i] = task;
        }

        await UniTask.WhenAll(tasks);
    }
}

従来の Instantiate とは異なり、第二引数に「一度にまとめて生成する数」を入れることができる点に注意してください。

これを実行して Profiler を見ると、ワーカースレッドで生成処理されていることがよく分かります。

ワーカースレッドで生成処理

Awakeの時間を調整する

さて、Unityのスクリプトがアタッチされた GameObject の生成処理の直後には、Awake メソッドが呼ばれます。
InstantiateAsync を使って生成を行なった場合、生成処理自体は並列で行われますが、Awakeメソッドは生成後にまとめてメインスレッドで呼ばれます。

Awakeはメインスレッドで呼ばれる

非同期でいい感じに多数のオブジェクトを生成できても、このAwakeの処理に時間がかかってスパイクしたら意味がありません。

これを防ぐには、AsyncInstantiateOperation.SetIntegrationTimeMSを使用します。
これに値(ミリ秒)を設定すると、その秒数を超過したAwake処理は次のフレームの処理に回されます。
ただし、Awake処理は InstantiateAsync の第二引数に与えた個数分だけひとまとめにして行われるため、その数が多いといくらIntegrationTimeMSの値を小さくしても意味がないことに注意してください。

参考

www.youtube.com

docs.unity3d.com

docs.unity3d.com