オブジェクトのプーリングと再利用
UnityのUnityEngine.Pool APIを使用することで、CPUロードとガベージ コレクターのオーバーヘッドを削減できます。これにより、頻繁に使用されるオブジェクトを繰り返し作成/破棄するのではなく、それらをプールして再利用できます。
読み終わるまでの所要時間 24 分最終更新 3日前
オブジェクトプールと再利用は、頻繁に使用されるオブジェクトのインスタンスをプールに返し、そこから取得して再使用できるプログラミングパターンです。オブジェクトプールは、新しいオブジェクトインスタンスのインスタンス化と破棄を繰り返すことによって発生するオーバーヘッドを削減し、割り当てと割り当て解除の全体的な数を制限します。これは、ガベージコレクション(GC)オーバーヘッドの最小化とCPUのロード軽減に役立ちます。
GameObjects はプールできますが、配列、リスト、ディクショナリなどのコレクションタイプなど、頻繁に使用する他のタイプもプールできます。独自のプーリング ロジックを作成することもできますが、Unity は便宜上、 名前空間に、Pooling GameObjects といくつかのコレクション タイプのための実装を備えた API のセットを提供します。
UnityEngine.PoolPooling and reusing GameObjects (ゲームオブジェクトのプールと再利用)
発射物など、ゲーム内のオブジェクトの中には、シーン内に同時に数個しか存在しないものであっても、繰り返し頻繁に登場するものがあります。発射されるたびにプレハブから新しい砲弾オブジェクトをインスタンス化する代わりに、ゲームの開始時に砲弾オブジェクトのプールを作成し、必要に応じて再利用できます。
オブジェクトプールの作成
Unity の クラスは、再利用可能なオブジェクトプールパターンの実装を提供し、アプリケーションにオブジェクトプールを実装する最も簡単な方法です。 のインスタンスは、 型のオブジェクトのプールを管理します。コンストラクタを使用してを作成する場合は、プールの以下のキーを定義するためのパラメーターを指定します。
ObjectPool<T>ObjectPool<T>TObjectPool<T>ObjectPool<T>-
プールの開始容量。
-
プールを拡張できる最大容量。
-
同じオブジェクト インスタンスをプールに二重解放しないようにコレクションの安全性チェックを実行するかどうか。
-
一連の C#デリゲート。Unity がプールで以下のキーアクションを実行するときに呼び出すコールバックです。
Action<T>- プールからオブジェクトを取得するときに呼び出すコールバックバック
- オブジェクトをプールに返すときに呼び出すコールバックバック
- 新しいオブジェクトインスタンスがリクエストされ、プールが空の場合に、インスタンスの作成時に呼び出されるコールバック
- オブジェクトインスタンスを破棄するときに呼び出されるコールバック
プールからのオブジェクトの取得
ObjectPool<T>.GetObjectPoolactionOnGetプールが空のときにプールからオブジェクトをリクエストすると、新しいオブジェクトインスタンスが作成されます。作成プロセスは、コンストラクターにパラメーターとして指定したメソッドを呼び出します。
ObjectPoolcreateFuncObjectPool<T>.GetGet(out PooledObject<T> pooledObject)PooledObject詳細とコード例については、 API リファレンス を参照してください。
PooledObject<T>.Getプールへのオブジェクトの返却
終了したオブジェクトは、プールに返します。引数として返すオブジェクトとともに を呼び出します。このアクションでは、コンストラクタに パラメーターとして指定したメソッドも実行されます。このメソッドは、返されたオブジェクトの非アクティブ化とステートのリセットを可能にします。
ObjectPool<T>.ReleaseObjectPoolactionOnReleaseプールがいっぱいになったときにオブジェクトをプールに戻すと、オブジェクトは破棄されます。破棄プロセスは、コンストラクタにパラメーターとして指定したメソッドを呼び出します。
ObjectPoolactionOnDestroyコンストラクターにパラメーターとしてを渡すと、コレクションの安全性チェックが有効になります。コレクション チェックが有効になっているときに、すでにプールにあるオブジェクトを返そうとした場合、Unity は例外をスローします。有効にした場合でも、コレクションの確認はエディターでのみ可能で、プレイヤービルドでは使用できません。コレクション チェックを有効にするとオーバーヘッドが増えますが、ダブル リリース エラーがキャッチされるため、開発時に使用することをお勧めします。
collectionChecktrue使用済みオブジェクトをリセットする
プールから取得したオブジェクトは、使用中にステートを変更できます。例えば、プールされた敵ゲームオブジェクトは、シーン内でアクティブなときに、その位置、体力、アニメーションステート、物理演算ステートを変更できます。オブジェクトがプールに返されたときにこのステートをリセットすることが重要です。そうすることで、後で再度取得したときに、最初に使用したのと同じステートで開始されます。
リセットするステートに応じて、 デリゲートのプールからオブジェクトを取得したとき、または デリゲートのプールにリリースして戻すときに、オブジェクトのリセットを選択できます。プールされたオブジェクト自体にカスタムの非アクティブ化メソッドを実装し、プールの取得またはリリースアクションからそれらのメソッドをコールこともオプションます。
actionOnGetactionOnReleaseリセットが必要なステートは、プールされたオブジェクトの特定の動作とコンポーネントによって異なります。リリース時の一般的なリセットタスクには、コルーチンの停止、イベントのサブスクライブ解除、物理演算状態のリセット、アニメーションのクリア、パーティクルシステムの停止などがあります。リリース時に GameObjects を非アクティブにして、プールで非アクティブの間に呼び出しを受けないようにすることも重要です。
Update例: 砲弾のプール
砲弾用のの一般的な実装には、以下のキーステージがあります。
ObjectPool<T>- ゲームの開始時に非アクティブの砲弾オブジェクトのプールを作成します。プールされたオブジェクトの新規作成、プールからのオブジェクトの取得、プールへのオブジェクトの解放、プールが満杯になったオブジェクトの破棄時にコールバックとして呼び出すために必要なデリゲートをコンストラクターに提供します。
- 発射物が必要な場合 (例えば、ユーザーが武器を発射する場合)、を使用してプールから非アクティブの発射物を取得します。対応する
ObjectPool<T>.Getコールバックから、取得したオブジェクトをアクティブ化し、位置や向きなどの関連プロパティを初期化します。actionOnGet - 砲弾が不要になったとき (例えば、ターゲットに当たったとき、または範囲外になったとき)、とともにプールに戻します。発射体には専用のプールへのリファレンスを渡すのが一般的です。そうすることで、発射後に自身をリリースできます。対応する
ObjectPool<T>.Releaseコールバックから、返されたオブジェクトを非アクティブにし、速度やアニメーションステートなどの関連プロパティをリセットします。actionOnRelease
以下のコードは、にプールされた砲弾のスクリプトコンポーネントコードです。砲弾には、それ自体のプールへのリファレンスを与える public プロパティがあります。このプロパティは、時間遅延後に自身をプールにリリースするために使用されます。
ObjectPool<T>using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Pool; public class RevisedProjectile :MonoBehaviour { // Deactivate after delay [SerializeField] private float timeoutDelay = 3f; private IObjectPool<RevisedProjectile> objectPool; // Public property to give the projectile a reference to its ObjectPool public IObjectPool<RevisedProjectile> ObjectPool { set => objectPool = value; } public void Deactivate() { StartCoroutine(DeactivateRoutine(timeoutDelay)); } IEnumerator DeactivateRoutine(float 遅延) { yield return new WaitForSeconds(遅延); // Reset the moving Rigidbody Rigidbody rBody = GetComponent<Rigidbody>(); rBody.linearVelocity = new Vector3(0f, 0f, 0f); rBody.angularVelocity = new Vector3(0f, 0f, 0f); // Release the projectile back to the pool objectPool.Release(this); } }
以下のコードは、砲塔にアタッチされたコンポーネントのコードで、砲弾の発射と砲弾オブジェクトのプールの管理を行います。ユーザーが射撃したときに、オブジェクトプールのが呼び出され、プールされた砲弾が取得されます。
Getusing UnityEngine; using UnityEngine.Pool; using UnityEngine.Events; public class RevisedGun :MonoBehaviour { [Tooltip("撃つプレハブ ) [SerializeField] private RevisedProjectile projectilePrefab; [ツールチップ("発射力")] [SerializeField] private float muzzleVelocity = 1500f; [ツールチップ("ショットが表示される銃の終了位置")] [SerializeField] private Transform muzzlePosition; [Tooltip("射撃間隔 / 小さい = 発射率が高い")] [SerializeField] private float cooldownWindow = 0.1f; [SerializeField] private UnityEvent m_GunFired; // Stack-based ObjectPool available with Unity 2021 and above private IObjectPool<RevisedProjectile> objectPool; // Throw an exception if we try to return an existing item, already in the pool [SerializeField] private bool collectionCheck = true; // Extra options to control the pool capacity and maximum size [SerializeField] private int defaultCapacity = 20; [SerializeField] private int maxSize = 100; private float nextTimeToShoot; private void Awake() { objectPool = new ObjectPool<RevisedProjectile>(CreateProjectile, OnGetFromPool, OnReleaseToPool, OnDestroyPooledObject, collectionCheck, defaultCapacity, maxSize); } // Invoked when creating an item to populate the object pool private RevisedProjectile CreateProjectile() { RevisedProjectile projectileInstance = Instantiate(projectilePrefab); projectileInstance.ObjectPool = objectPool; return projectileInstance; } // Invoked when returning an item to the object pool private void OnReleaseToPool(RevisedProjectile pooledObject) { pooledObject.gameObject.SetActive(false); } // Invoked when retrieving the next item from the object pool private void OnGetFromPool(RevisedProjectile pooledObject) { pooledObject.gameObject.SetActive(true); } // Invoked when the maximum number of pooled items is exceeded (i.e. destroy the pooled object) private void OnDestroyPooledObject(RevisedProjectile pooledObject) { Destroy(pooledObject.gameObject); } private void FixedUpdate() { // Invokes the Shoot method if the delay time is exceeded if (Input.GetButton("Fire1") && Time.time > nextTimeToShoot && objectPool != null) { Shoot(); } } private void Shoot() { // Get a pooled object instead of instantiating RevisedProjectile bulletObject = objectPool.Get(); if (bulletObject == null) return; // Align to gun barrel/muzzle position bulletObject.transform.SetPositionAndRotation(muzzlePosition.position, muzzlePosition.rotation); // Move projectile forward bulletObject.GetComponent<Rigidbody>().AddForce(bulletObject.Transform.フォワード * muzzleVelocity, ForceMode.Acceleration); // Turn off after a few seconds bulletObject.Deactivate(); // Set cooldown delay nextTimeToShoot = Time.time + cooldownWindow; m_GunFired.Invoke(); } }
このコードを使用した完全なサンプルプロジェクトは、必要なすべてのプレハブとプールされた砲弾を発射する作業用タレットを含めて、アセットストア)から無料のパッケージとしてダウンロードできます。詳細については、『 オブジェクトプール使用による Unity での C# スクリプトのパフォーマンス向上 』を参照してください。
プールされたオブジェクトのシーンアンロードの処理
デフォルトでは、オブジェクトプールは作成されたシーンに関連付けられています。そのシーンがアンロードされると、Unity はそのシーン内のすべての GameObjects (プールとプールされたインスタンスを含む) を破棄します。
シーンのリロード後にオブジェクトプールを維持するには、 を呼び出すルートゲームオブジェクトの下にプールマネージャーを配置します。 を呼び出すと、プールルートの下にプールされた項目をリリース時に親にして、シーンで破棄しないようにすることができます。
object.DontDestroyOnLoadtransform.SetParentアンロード時にシーン内にプールされたアクティブなインスタンスがある場合は、それらがとでリリースされているか、アイテムを呼び出すか破棄するコールバックが登録されていることを確認してください。
OnDisableOnDestroySceneManager.activeSceneChanged非アクティブの場合、プールされたインスタンスへの長期参照を避けます。 を使用して、例えばレベル間で非アクティブなインスタンスをすべて破棄します。
ObjectPool<T>.Clearコレクションタイプのプーリングと再利用
また、Lists や Dictionaries など、 名前空間から収集オブジェクトをプールして再利用することもできます。
System.Collectionコレクションクラスは、コレクションの値を削除しますが、コレクションに割り当てられたメモリはリリースしない メソッドを公開します。これは複雑な計算のために一時的なヘルパーCollectionsを割り当てたい場合に便利です。
Clear以下のコード例では、フレームごとに 1 回 を割り当てて、一連のデータ ポイントを収集しています。これは非効率な実践方法です。
nearestNeighborsList// Bad C# script example. This Update method allocates a new `List` every frame.void Update() { List<float> nearestNeighbors = new List<float>(); findDistancesToNearestNeighbors(nearestNeighbors); nearestNeighbors.Sort(); // … use the sorted list somehow …}
代わりに、このをメソッドの外、包含するクラスに持ち上げ、そのたびに消去して再利用できます。これにより、コードがフレームごとに新しいを割り当てる必要がなくなります。
ListList// Good C# script example. This method re-uses the same List every frame.List<float> m_NearestNeighbors = new List<float>();void Update() { m_NearestNeighbors.Clear(); findDistancesToNearestNeighbors(NearestNeighbors); m_NearestNeighbors.Sort(); // … use the sorted list somehow …}
このサンプル コードでは、インスタンスのメモリを複数のフレームで保持して再利用しています。コードは、の拡張が必要なときのみ新しいメモリーを割り当てます。
ListListUnityEngine.PoolList<T>HashSet<T>Dictionary<TKey, TValue>ObjectPool<T>CollectionPool<T>追加リソース
- チュートリアルの学習:オブジェクトプールを使用してUnityのC#スクリプトのパフォーマンスを向上
- マネージメモリの概要
- リファレンスタイプの管理
- アレイの最適化
- 砲弾と爆発のインスタンス化。