UnityのObjectPool
CoddyのUnityジャーニー「Unityのゲームアーキテクチャ」セクションの一部。レッスン 48/64。
Unity にはプール ObjectPool<T> が UnityEngine.Pool に用意されています。4つのラムダ式で各段階の動作を指定すると、プールが予備のオブジェクトと数を管理します。この煙突は0.2秒ごとに煙を吐き出し、それぞれの煙は1秒後にプールへ戻ります。
using UnityEngine;
using UnityEngine.Pool;
public class Chimney : MonoBehaviour
{
public Puff puffPrefab;
private ObjectPool<Puff> pool;
private float timer;
void Awake()
{
pool = new ObjectPool<Puff>(
createFunc: () => Instantiate(puffPrefab),
actionOnGet: puff => puff.gameObject.SetActive(true),
actionOnRelease: puff => puff.gameObject.SetActive(false),
actionOnDestroy: puff => Destroy(puff.gameObject),
collectionCheck: true,
defaultCapacity: 10,
maxSize: 20);
}
void Update()
{
timer += Time.deltaTime;
if (timer >= 0.2f)
{
timer = 0f;
Puff puff = pool.Get();
puff.transform.position = transform.position + Vector3.up;
puff.Launch(pool);
}
}
}// Puff.cs
private IObjectPool<Puff> pool;
private float age;
public void Launch(IObjectPool<Puff> owner)
{
pool = owner;
age = 0f;
}
void Update()
{
transform.Translate(0f, Time.deltaTime, 0f);
age += Time.deltaTime;
if (age >= 1f) pool.Release(this);
}createFuncはGetでプールが空だったときに実行され、actionOnGetはGetのたびに、actionOnReleaseはReleaseのたびに実行されます。maxSize個の予備がすでに待機している状態で煙がプールに戻されると、プールはその煙を保持せず、actionOnDestroyで破棄します。defaultCapacityはプールのリストの初期サイズにすぎないため、最初にオブジェクトが作られることはありません。CountAllはプールが作成し、現在も所有している煙の数、CountActiveはゲーム内に出ている煙の数、CountInactiveは予備の数を数えます。Getは煙突が配置する前に煙を有効化しますが、この場合は問題ありません。Puffには位置を読み取るOnEnableがないからです。煙はプールを、すべてのUnityプールが実装しているインターフェースであるIObjectPool<Puff>として保持し、Launchは使用するたびに経過時間をリセットします。
collectionCheckを有効にすると、すでにプールにある煙を戻したときにInvalidOperationExceptionがスローされます。Trying to release an object that has already been released to the pool. このチェックはEditorでのみ実行されます。Releaseのたびに予備を検索するためです。ビルドでは煙がプールに2つある状態になり、2回のGetで同じオブジェクトが渡されてしまいます。よくある原因は、解放後も動き続けてもう一度自身を解放するオブジェクトです。そのため、actionOnReleaseでオブジェクトを無効にしています。
チャレンジ
中級Unityのプールから花火を打ち上げましょう。
LauncherのAwakeでObjectPool<Spark>を作成します。Instantiate(sparkPrefab)で生成し、取り出したときに有効化し、返却時に無効化し、解放時にオブジェクトを破棄します。maxSize: 5に設定します。Updateでは、Instantiateの代わりにpool.Get()で各Sparkを取り出します。Returnでは、Destroyの代わりにpool.Release(spark)を呼び出します。
「プレイ」を押して、何度か花火を打ち上げます。その後、「確認」を押します。
自分で試してみよう
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;
public class Launcher : MonoBehaviour
{
public Spark sparkPrefab;
public int sparks = 8;
void Awake()
{
// プールを作成する:生成、取得、返却、破棄を設定し、予備のスパークは最大 5 個にする
}
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
for (int i = 0; i < sparks; i++)
{
Spark spark = Instantiate(sparkPrefab);
spark.transform.position = transform.position + Vector3.up;
spark.Launch(Direction(i), this);
}
}
}
// 寿命を迎えたスパーク
public void Return(Spark spark)
{
Destroy(spark.gameObject);
}
// ランチャーの周囲に均等に広がるよう、外側かつ上向きに
Vector3 Direction(int i)
{
return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
}
}
Unityのゲームアーキテクチャのすべてのレッスン
自分で練習してみよう: Unityプレイグラウンド