2D Game Kitは、プログラムコードを書かなくても結構な横スクロールアクションゲームが作れるUnity公式の素敵アセットです。ゲーム開発に役立つ機能が色々と実装されているのですが、パーティクルやゲームオブジェクトをプールして管理できるVFXController とObject Pooling機能は、スマホゲームなどを作る時にとても有用そうです。公式ドキュメントのObject Poolingを読んだ時のメモ(ほぼ訳)です。目次は私が適当に付けたもので原文にはありません。
(2018/3/5 ObjectPoolとPoolObjectの説明順が直感的ではなかったので、順番を入れ替えました)
目次
Object Poolingのための2つのクラス
2D Game Kitに実装されている拡張可能なObject Pooling
システムを使うには、2つのクラスを作成する必要があります。一つはObjectPool
クラスを継承したクラス、もう一つはPoolObject
クラスを継承したクラスです。ObjectPool
を継承したクラスはプールそのもので、PoolObject
を継承したクラスはプール内の各プレハブごとのラッパーです。
ObjectPool
とPoolObject
の宣言
2つのクラスはGenericタイプで関連付けます。ObjectPool
とPoolObject
の2つのクラスを型として持ったGenericタイプで宣言する必要があります。例えば以下のような宣言になります。
public class SpaceshipPool: ObjectPool<SpaceshipPool, Spaceship> { … } public class Spaceship: PoolObject<SpaceshipPool, Spaceship> { … }
これにより、プールは自らが保持するオブジェクトのタイプを知ることができて、オブジェクトは自分がどのタイプのプールに属するかを知ることができます。
3つ目のパラメーター
これらのクラスは3つ目のGenericタイプをオプションとして定義することができます。このオプションは、PoolObject
がプールから取得される時に呼ばれるWakeUp
関数に渡したいパラメーターがある場合に使えます。例えばスペースシップを登場させる時に燃料がどれぐらいあるかをfloat
の値で指定する場合、以下のように定義します。
public class SpaceshipPool: ObjectPool<SpaceshipPool, Spaceship, float> { … } public class Spaceship: PoolObject<SpaceshipPool, Spaceship, float> { … }
ObjectPool
PoolObject全体を管理するプールです。
ObjectPool
が持つフィールド
ObjectPool
はMonoBehaviour
であり、GameObject
にアタッチすることができます。デフォルトでは以下のフィールドを持ちます。
Prefab
: プールを生成する時に複数回Instantiate
されるプレハブへの参照ですInitialPoolCount
:Start
メソッドで最初に生成しておくPoolObject
の数ですPool
:PoolObject
のリストです
ObjectPool
が持つ関数
ObjectPool
が持っている関数です。
Start
: 最初にプールが作成される時に呼び出されます。もしObjectPool
内にStart
関数を実装していた場合、ベースクラスのStart
メソッドが隠されるので、必要に応じて呼び出す必要があることを覚えておいてくださいCreateNewPoolObject
:PoolObject
が生成される時に呼び出されて、SetReferences
を呼び出したら、関数をSleep
します。この関数はvirtual
ではないのでオーバーライドはできませんが、protected
なので継承したクラスから呼び出すことができますPop
: プールからPoolObject
を取得する時に呼び出されます。デフォルトでは、プールからinPool
フラグがtrue
になっている最初のオブジェクトを見つけて、それを返します。inPool
フラグがtrue
のものが一つも見つからなかった場合は、新しいオブジェクトを生成して返します。この関数は、取り出したPoolObject
のWakeUp
関数を呼び出します。この関数はvirtual
なので、オーバーライドできますPush
: この関数は、PoolObject
をプールに戻す時に呼ばれます。デフォルトでは、inPool
フラグをtrue
にして、PoolObject
が持つSleep
を呼び出すだけです。この関数はvirtual
なのでオーバーライドできます
詳しい利用方法については、以降のBulletPoolのドキュメントとスクリプトを見てください。
PoolObject
ゲームオブジェクトと一対一で対応するオブジェクトで、これのリストがObjectPool
で管理されます。
PoolObject
が持つフィールド
デフォルトでは、PoolObject
は以下のフィールドを持っています。
- inPool:
PoolObject
がプールにあるのか、利用されているかを表すbool
値です - instance: この
PoolObject
をラップしたプレハブからInstantiate
されたゲームオブジェクトのインスタンスです - objectPool: この
PoolObject
が所属するオブジェクトプールです。このクラスと同じObjectPool
のタイプを持ちます
PoolObject
が持つvirtual
関数
PoolObject
は以下のVirtual
関数を持ちます。
SetReferences
: この関数はPoolObject
が作成された時に一度だけ呼び出されます。これは参照をキャッシュするためのもので、PoolObject
が利用状態になる度に毎回実行する必要がない1回だけ初期化すればよい処理のために利用しますWakeUp
:PoolObject
がプールから取り出されて、利用状態になるタイミングで毎回呼ばれます。PoolObject
が利用可能になるタイミングで毎回行う必要がある初期化処理のための関数です。クラス定義で3つ目のGenericパラメーターが設定されていた場合、WakeUp
関数に指定した型の引数を渡すことができますSleep
: 利用しなくなるPoolObject
をプールに戻す際に毎回呼ばれる関数です。PoolObject
の利用後に毎回行う必要がある解放処理などのために使いますReturnToPool
: デフォルトではPoolObject
をプールに返すだけですが、追加の処理をしたい場合はこの関数をオーバーライドします
BulletPoolの解説
概要
BulletPool MonoVehaviour
はBullet(=弾)のオブジェクトのプールで、弾プレハブのインスタンスを管理するものです。BulletPool
は主人公のEllenと敵の両方で利用しますが、それぞれで少し違うものを使います。Ellen用のBulletPool MonoBehaviour
は、親のゲームオブジェクトのPrefab
欄に弾のプレハブを設定することでアタッチされます。敵用のBullet Pool
はEnemyBehaviour
クラスで使われます。GetObjectPool
というstatic
な関数を使って、インスタンスの生成をせずにBulletPool
が使えます。
BulletPool
が持つフィールド
BulletPool
クラスは以下のフィールドを持ちます。
Prefab
: 利用したい弾のプレハブを設定しますInitial Pool Count
: 開始時にいくつの弾を生成しておくかを設定します。同時に撃てる最大の弾数を設定しておきます。この数より多い弾を発射しようとすると、実行時に弾が生成されますPool
: プール内のBulletObject
です。Inspectorには表示されません
BulletPool
が持つ関数
BulletPool
クラスは以下の関数を持ちます。
Pop
: プールからBulletObject
を取り出しますPush
:BulletObject
をプールに戻す時に呼びますGetObjectPool
: 指定したPrefab
が設定されているBulletPool
を見つけるstatic
関数です
BulletObject
が持つフィールド
プールから取得した弾は、BulletObject
のフォームとして渡されます。BulletObject
クラスは以下のフィールドを持ちます。
InPool
: その弾がプールにあるのか、利用されているのかを表しますInstance
: プレハブのインスタンスObjectPool
: このBulletObject
が所属するBulletPool
へのリファレンスTransform
: このインスタンスのTransform
コンポーネントへのリファレンスRigidbody2D
: このインスタンスのRigidbody2D
コンポーネントへのリファレンスSpriteRenderer
: このインスタンスのSpriteRenderer
コンポーネントへのリファレンスBullet
: このインスタンスのBullet
スクリプトへのリファレンス
BulletObject
が持つ関数
BulletObject
は以下の関数を持ちます。
WakeUp
:Pop
関数が呼ばれた時にBulletPool
から呼ばれますSleep
:Push
関数が呼ばれた時にBulletPool
から呼ばれますReturnToPool
: 弾の利用を終了する時に呼ぶことになっている関数です。この関数はBulletPool
のPush
関数を呼び出して、Sleep
関数を呼びます
まとめ
公式ドキュメントはこんな感じでした。詳しくは実際のコードを見てください、という感じです。
シーンの開始時に必要なインスタンスをまとめて生成して、その後はそのインスタンスを使い回してくれるので、メモリーの確保と解放を繰り返すことによる速度低下や、メモリーの断片化を避けることが期待できます。予定数をオーバーした場合にも対応していて、超過した時はその時点でInstantiate
してくれる柔軟性を持っています。
WebGLやスマホでは、オブジェクトの初回描画時に処理落ちすることがあります。それを避けるためには、シーンの開始時に、必要なオブジェクトを一度画面に描画するウォーミングアップ処理をするとよいようです( 【Unity】パフォーマンスチューニング - KAYAC engineers' blog )。デフォルトのままだとこの辺の対処はありませんが、Start
関数に自前でコードを追加すれば対応できそうです。
再利用するオブジェクトは、動作速度を安定させるためには把握しておきたい要素なので、予めObject Pooling
を導入しておけば、あとで膨大なプロジェクトの中から再利用しているオブジェクトを探し出す手間がなくなるので楽ができそうです。