Menu
CoddyTech

UnityのObjectPool

CoddyのUnityジャーニー「Unityにおけるゲームアーキテクチャ」セクションの一部。レッスン 39/50。

Unityにはプールが用意されています。UnityEngine.PoolにあるObjectPool<T>です。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がないためです。煙はプールをIObjectPool<Puff>として保持します。これはUnityのすべてのプールが実装するインターフェースで、Launchは使うたびに経過時間をリセットします。

collectionCheckを有効にすると、すでにプールにある煙を返却した場合、InvalidOperationExceptionがスローされます。Trying to release an object that has already been released to the pool.このチェックが実行されるのはEditor内だけです。Releaseのたびに予備を検索するためです。ビルドでは煙がプールに2つある状態になり、2回のGetで同じオブジェクトが渡されることになります。よくある原因は、解放後も動き続けるオブジェクトが再び自分自身を解放することです。そのため、actionOnReleaseでオブジェクトを無効にします。

challenge icon

チャレンジ

中級

花火。Spaceキーを押すたびにランチャーから8個の火花が飛び出し、それぞれのSparkは、寿命の1秒が終わると(Sparkの準備ができると)、ランチャーのReturn(this)を呼び出します。今日はランチャーがすべての火花をインスタンス化して破棄しています。Unityのプールを使いましょう。

  • Launcher.csで、Awake内にObjectPool<Spark>を作成します。sparkPrefabをインスタンス化して火花を作り、取り出されたときに火花をオンにし、戻されたときにオフにして、プールから解放するときに火花のオブジェクトを破棄します。
  • プールが保持する予備の火花は最大5個です。そのため、8個を発射すると、5個は待機し、3個は破棄されます。
  • Spaceキーを押すと、火花をインスタンス化する代わりにプールから8個取り出し、Returnは火花を破棄する代わりにプールへ戻します。

Playを押して、何度か発射してからCheckを押してください。

自分で試してみよう

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;

public class Launcher : MonoBehaviour
{
    public Spark sparkPrefab;
    public int sparks = 8;

    void Awake()
    {
        // プールを作成する:生成、取得、返却、破棄を設定し、予備の spark は最大 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);
            }
        }
    }

    // 寿命が尽きた spark
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // launcher の周囲に均等に広がるよう、外向きかつ上向きに
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Unityにおけるゲームアーキテクチャのすべてのレッスン