Unityでの非同期オブジェクト生成 – InstantiateAsyncメソッドの使い方

以下は、UnityのInstantiateAsyncメソッドに関する記事の日本語翻訳です。


著者: Giannis Akritidis
投稿日: 2024年1月29日


はじめに

Unity 2023.3(執筆時点ではまだベータ版)から、ゲームオブジェクトを非同期で生成するためのInstantiateAsyncメソッドが利用可能になりました。これにより、パフォーマンスの向上が期待できます。非同期でオブジェクトを生成することで、Unityが必要な重い処理をメインスレッドとは別のスレッドで実行し、最後の段階でメインスレッドに処理を任せることができるようになります。

InstantiateAsyncメソッドはAsyncInstantiateOperationを返します。このオブジェクトは、生成処理の進捗状況を提供し、制御することが可能です。処理は、オブジェクトの統合段階(Awakeメソッドの呼び出しを含む)を除いて非同期で行われます。

シンプルな使い方

まずは、従来のInstantiateメソッドとInstantiateAsyncメソッドの比較を見てみましょう。

public class ToSpawn : MonoBehaviour
{
    private void Awake() => Debug.Log("ToSpawnスクリプトのAwakeフレーム: " + Time.frameCount);
}

以下のように、通常のInstantiateメソッドを使用するスクリプトがあります。

private void Start()
{
    Instantiate(objectToSpawn);
    Debug.Log("Instantiateの呼び出しの後");
}

ゲームを実行すると、次のようにコンソールに出力されます。

ToSpawnスクリプトのAwakeフレーム: 1
Instantiateの呼び出しの後

この処理は同期的に実行され、オブジェクトが生成された後にDebug.Logメソッドが呼び出されます。

次に、InstantiateメソッドをInstantiateAsyncメソッドに置き換えてみます。

private void Start()
{
    InstantiateAsync(objectToSpawn);
    Debug.Log("InstantiateAsyncの呼び出しの後");
}

ゲームを実行すると、コンソールには次のように出力されます。

InstantiateAsyncの呼び出しの後
ToSpawnスクリプトのAwakeフレーム: 2

InstantiateAsyncを呼び出した後にすぐDebug.Logメソッドが実行されていることがわかります。処理は非同期で実行されており、Unityはメインスレッドをブロックせずに進行し、オブジェクトが準備できたときに最後の処理をメインスレッドで行います。

InstantiateAsyncメソッドのオーバーロード

InstantiateAsyncメソッドには、Instantiateメソッドと同じ引数を持つ4つのオーバーロードがあります。

public static AsyncInstantiateOperation<T> InstantiateAsync(T original);

public static AsyncInstantiateOperation<T> InstantiateAsync(T original, Transform parent);

public static AsyncInstantiateOperation<T> InstantiateAsync(T original, Vector3 position, Quaternion rotation);

public static AsyncInstantiateOperation<T> InstantiateAsync(T original, Transform parent, Vector3 position, Quaternion rotation);

違いは戻り値の型にあります。InstantiateAsyncメソッドはAsyncInstantiateOperation<T>を返します。

AsyncInstantiateOperation型

AsyncInstantiateOperation型は、非同期操作に関する情報を取得したり、制御するためのプロパティやメソッドを提供します。その中で最も重要なのはResultプロパティです。

AsyncInstantiateOperation<T>はジェネリック型で、ResultプロパティはT型の配列を返します。従来のInstantiateメソッドが1つのオブジェクトを返すのに対し、InstantiateAsyncメソッドでは、1回の呼び出しで複数のオブジェクトを生成できるため、配列で返されます。例えば、countパラメーターに数値を指定すれば、複数のオブジェクトを一度に生成できます。

次のコードは、その使用例でResultプロパティも利用しています。

[SerializeField] private ToSpawn objectToSpawn;
private ToSpawn[] _spawnedObjects;

private void Start()
{
    _spawnedObjects = InstantiateAsync(objectToSpawn, 10).Result;
}

ここで、_spawnedObjectsは新しく生成された10個のオブジェクトを保持する配列です。

AsyncOperationから継承されたメンバー

AsyncInstantiateOperationは、AsyncOperationからprogressisDoneallowSceneActivationpriorityといったメンバーを継承しています。また、completedイベントも使用できます。

  • progress: 進捗状況を示すfloat型のプロパティで、0から1の範囲の値を持ちます。1になるとisDonetrueを返します。
  • allowSceneActivation: falseに設定すると、進捗が0.9まで達した時点で統合の最終段階が停止し、Awakeメソッドの実行が保留されます。これにより、ゲームの遅延が気にならないタイミングまでオブジェクト生成を遅らせることが可能です。

次は、それらがどのように動作するかを示す例です。

[SerializeField] private ToSpawn objectToSpawn;
private ToSpawn[] _spawnedObjects;
private AsyncInstantiateOperation<ToSpawn> result;

private void Start() => Spawn();

private void Update()
{
    if (!result.isDone)
        Debug.Log("スペースキーを押してオブジェクトを生成できます。現在の進捗: " + result.progress);

    if (Keyboard.current.spaceKey.wasPressedThisFrame)
        result.allowSceneActivation = true;
}

private async void Spawn()
{
    _spawnedObjects = await AsyncInstantiation();
    Debug.Log("Spawnメソッドが終了しました!");
}

private AsyncInstantiateOperation<ToSpawn> AsyncInstantiation()
{
    result = InstantiateAsync(objectToSpawn, 10000);
    result.allowSceneActivation = false;
    result.completed += Message;
    return result;
}

private void Message(AsyncOperation _) => Debug.Log("すべてのオブジェクトが生成されました!");

AsyncInstantiationメソッドは、Unityがオブジェクトを非同期で生成するようにし、最終段階だけは遅らせます。result.allowSceneActivation = falseがそれを実現しています。その後、completedイベントにMessageメソッドを登録します。

プライオリティプロパティ

priorityプロパティは、非同期操作の実行順序を定義します。Unityの非同期操作はメインスレッドとは非同期に実行されますが、他の非同期操作とは順番に処理されます。これらの操作はキューに追加され、1つが完了するまで次は実行されません。このプロパティで、これらの操作の順序を調整できます。

AsyncInstantiateOperationのその他のメソッド

  • Cancel: isDonefalseの場合、インスタンス化をキャンセルします。最終段階が完了していない場合のみ有効です。
  • IsWaitingForSceneActivation: allowSceneActivationfalseで、最終段階を除いたすべてのステップが完了したときにtrueになります。
  • WaitForCompletion: インスタンス化が完了するまで現在のスレッドをブロックします。

InstantiateAsyncメソッドのSpanオーバーロード

InstantiateAsyncメソッドには、位置や回転を指定するためのSpanオーバーロードもあります。

public static AsyncInstantiateOperation<T> InstantiateAsync(T original, int count, ReadOnlySpan<Vector3> positions, ReadOnlySpan<Quaternion> rotations);

public static AsyncInstantiateOperation<T> InstantiateAsync(T original, int count, Transform parent, ReadOnlySpan<Vector3> positions, ReadOnlySpan<Quaternion> rotations);

複数のゲームオブジェクトを一度に生成する際に、これらのオーバーロードを使用することで、各オブジェクトの位置や回転を指定できます。

private AsyncInstantiateOperation<ToSpawn> AsyncInstantiation()
{
    Span<Vector3> positions = stackalloc Vector3[3];
    Span<Quaternion> rotations = stackalloc Quaternion[3];

    positions[0] = new Vector2(1, 1);
    positions[1] = new Vector2(2, 1);
    positions[2] = new Vector2(1, 3);

    rotations.Fill

(Quaternion.identity);

    result = InstantiateAsync(objectToSpawn, 3, positions, rotations);
    result.allowSceneActivation = false;
    result.completed += Message;
    return result;
}

Spanのパラドックス

.NETコードに慣れている人には、この例は奇妙に見えるかもしれません。Spanref structでスタックに保存されますが、非同期メソッド内でSpanを宣言すると、コンパイラがエラーを出します。しかし、UnityではC++エンジンを使用しているため、マネージドヒープとは別にネイティブメモリスペースが存在し、そこにデータがコピーされます。

Spansは一時的なコンテナとして機能し、データはネイティブメモリスペースにコピーされて使用されます。スタックへのアクセスはコピー後には不要なので、安心して使用することができます。


これでInstantiateAsyncメソッドに関する説明は以上です。ベータ版の機能ではありますが、非アクティブなオブジェクトを事前に生成して必要なときに有効化する場合や、オブジェクトプールを管理する際に役立つ機能です。

質問があればコメントでお知らせいただくか、直接ご連絡ください。また、新しい投稿を見逃さないためにニュースレターやRSSフィードを購読することもできます。

Unity

Posted by hidepon