UniRxの中に知る人ぞ知る(?) UniRx.Toolkit というネームスペースがあり、この中に ObjectPool というクラスが定義されています。
これは 使用済みのGameObjectをDestroyせずに保存(プール)しておき、後で使い回す ことでパフォーマンスに優しいプログラムにできるというすばらしいクラスです。
UniRx/ObjectPool.cs at master · neuecc/UniRx · GitHub
独自でオブジェクトプールを実装している方も多いと思いますが、こちらはUniRxならではの機能が盛り込まれていて割といいかんじだったので使い方を紹介します。
ObjectPool使い方
Poolクラスを定義する
UniRx.Toolkit ネームスペースを usingし、ObjectPool クラスを継承したクラスを作成します。
・EnemyObjectPool.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UniRx.Toolkit; public class EnemyObjectPool : ObjectPool<Enemy> { public GameObject EnemyPrefab; // オブジェクトが空のときにInstantiateする関数 protected override Enemy CreateInstance() { return GameObject.Instantiate(EnemyPrefab).GetComponent<Enemy>(); } }
Enemy クラスはプール対象のクラスで、適宜書き換えてください。これは Unity.Component 型を継承したクラスしか指定できないですが、基本MonoBehaviourを継承したクラスを指定すると思うので問題ないでしょう。
最低限オーバーライドする関数は CreateInstance メソッドで、この中でプレハブをInstantiateします。
Poolクラスを使う
Poolクラスをnewで作成。
void Start () { // プールの作成、プレハブ渡す pool = new EnemyObjectPool(); pool.EnemyPrefab = EnemyPrefab; }
オブジェクトを取り出す
Enemy enemy = pool.Rent();
オブジェクトをプールに返す
pool.Return(enemy);
返したオブジェクトは即座にはDestroyされず、Disableにされます。
Poolを空にする
プールしているオブジェクトを全てDestory
pool.Clear();
あらかじめObjectを作成しておく
事前にオブジェクトを作成するには PreloadAsync 関数を使用。
pool.PreloadAsync(10, 1).Subscribe(_ => Debug.Log("preload finished")));
UniRx内のコルーチンで、フレーム単位で分けてInstantiateしていきます。
PreloadAsyncの第一引数は作成する数、第二引数は1フレームあたりに作成する数を指定します。
戻り値がIObservable型なので、Subscribeしないと処理が走らないので注意。
現在のプール数を一定まで減らす
Shrink を使います。
pool.Shrink(0.6f, 4);
上のコードだと、オブジェクトを現在の数の 6割に減らしますが、最低4個はそのまま持っておきます。
一定時間ごとにShrinkしたい場合、 StartShrinkTimer が使えます。
下のコードでは、2秒ごとに現在数の4割(最低3個)にShrinkします。
IDisposable disposable = pool.StartShrinkTimer(System.TimeSpan.FromSeconds(2), 0.4f, 3);
はずなんですが、バグなのかわかりませんが、StartShrinkTimerのコードを下のように書き換えないと動きません。
.TakeWhile(_ => disposedValue == false)
使いたかったら書き換えましょう。
タイマを止めたい場合は、StartShrinkTimer の戻り値からDisposeをコール。
disposable.Dispose();
オブジェクトの貸し出し、返却時に処理を差し込む
OnBeforeRent、OnBeforeReturn をオーバーライド
オブジェクト作成を非同期に行う
AsyncPoolObject クラスを継承すると、Rendメソッドの代わりに RentAsync メソッドで貸し出しになります。
この戻り値はIObservableなので、Subscribeすることで、オブジェクトが空だった場合の非同期貸し出しが可能。
注意
- GameObjectのプールを前提に作られている
基底クラスで、プール対象オブジェクトは Component クラスを継承しているものだけに制限されています。
(下のコードの where T : UnityEngine.Component の部分)
public abstract class ObjectPool<T> : IDisposable where T : UnityEngine.Component
また、virtual メソッド内でプール対象オブジェクトのgameObject変数を参照しているので、GameObjectのみ扱えると考えて使ったほうがいいでしょう。
- MonoBehaviourを継承していない
ObjectPoolクラスをGameObjectにアタッチできないので、スクリプト内で new でインスタンス化する必要があります。
そのため、ObjectPoolクラスを管理するためのMonoBehaviourクラス を作成したくなってしまうかもしれません。
回避策としては、ObjectPoolの基底クラスにMonoBehaviourを追加してしまえば、
public abstract class ObjectPool<T> : MonoBehaviour, IDisposable where T : UnityEngine.Component
GameObjectにアタッチ可能&Unityのコールバックを受け取ることができるようになります。
https://www.assetstore.unity3d.com/jp/#!/content/17276
GitHub - neuecc/UniRx: Reactive Extensions for Unity
コメントの “Bass class” は “Base class” の間違いじゃないの? 低音なの? 2箇所あるどちらも間違ってるけどこっちが正しいの? 後に引けなくなって意地はってるの?