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
からprogress
、isDone
、allowSceneActivation
、priority
といったメンバーを継承しています。また、completed
イベントも使用できます。
- progress: 進捗状況を示す
float
型のプロパティで、0から1の範囲の値を持ちます。1になるとisDone
がtrue
を返します。 - 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:
isDone
がfalse
の場合、インスタンス化をキャンセルします。最終段階が完了していない場合のみ有効です。 - IsWaitingForSceneActivation:
allowSceneActivation
がfalse
で、最終段階を除いたすべてのステップが完了したときに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コードに慣れている人には、この例は奇妙に見えるかもしれません。Span
はref struct
でスタックに保存されますが、非同期メソッド内でSpan
を宣言すると、コンパイラがエラーを出します。しかし、UnityではC++エンジンを使用しているため、マネージドヒープとは別にネイティブメモリスペースが存在し、そこにデータがコピーされます。
Spansは一時的なコンテナとして機能し、データはネイティブメモリスペースにコピーされて使用されます。スタックへのアクセスはコピー後には不要なので、安心して使用することができます。
これでInstantiateAsync
メソッドに関する説明は以上です。ベータ版の機能ではありますが、非アクティブなオブジェクトを事前に生成して必要なときに有効化する場合や、オブジェクトプールを管理する際に役立つ機能です。
質問があればコメントでお知らせいただくか、直接ご連絡ください。また、新しい投稿を見逃さないためにニュースレターやRSSフィードを購読することもできます。
ディスカッション
コメント一覧
まだ、コメントがありません