解決したいこと
Unity2021.1から、Unity公式がオブジェクトプールを実装してくれています。
とても便利なのですが、
使い終わったオブジェクトをプールに戻す時、以下のように書く必要があります。
pool.Release(obj);
このメソッドを呼び出すためには、Release呼び出すクラスが
- プールのインスタンス
- 返却対象にインスタンス
の両方を参照する必要があります。
そのため、機能が分離できず、返却ロジックも書きにくくて、とても不便です!!
こちらは、Unity標準機能を利用することで、この問題に対処する方法の記事になります!
IDisposableを使う方法
実はPoolからオブジェクトを借りてくるときに、返却機能を一緒に取り出すメソッドが用意されています。
Get()
メソッドの返り値を使わずに、Get(out T obj)
メソッドを使って、out 修飾子のついた引数でオブジェクトを取り出してください。
- Bullet obj = _pool.Get();
+ IDisposable disposable = _pool.Get(out Bullet obj);
このとき返り値となっている型はIDisposable
を実装しており、IDisposable.Dispose()
でこのオブジェクトをプールに返却する機能を持っています。
実際にこのとき返り値となっているのはPooledObject<T>
という型です。
PooledObjectのコードを見たい方はこちら
public struct PooledObject<T> : IDisposable where T : class
{
private readonly T m_ToReturn;
private readonly IObjectPool<T> m_Pool;
internal PooledObject(T value, IObjectPool<T> pool)
{
this.m_ToReturn = value;
this.m_Pool = pool;
}
void IDisposable.Dispose() => this.m_Pool.Release(this.m_ToReturn);
}
今回必要なのはIDisposable
の機能だけなので、変数宣言でIDisposable型
を指定して変換しています。
あとは、このIDisposableをObjの返却処理を担当するクラスに渡してあげれば完成です!
例えばBulletクラスを都度借りてきて返却するコード例は以下のようになります!!
public class Bullet : MonoBehaviour
{
private IDisposable _disposable;
public void SetReturner(IDisposable disposable)
{
//外部からこのメソッドを叩くことで、返却機能を登録できるようにする
_disposable = disposable;
}
private void Update()
{
if ( /*返却の判定処理*/)
{
// disposeを呼ぶだけで返却される
_disposable.Dispose();
}
}
}
public class BulletPool : MonoBehaviour
{
private ObjectPool<Bullet> _bulletPool;
private void Awake()
{
/* _bulletPoolをnewしておく */
}
public Bullet GetBullet()
{
//out修飾子でbulletを取り出し、返り値をIDisposableとして取得する
IDisposable disposable = _bulletPool.Get(out var bullet);
//取得したbulletのインスタンスに、プールへの返却機能を渡す
bullet.SetReturner(disposable);
//呼び出し側にBulletのインスタンスを返す
return bullet;
}
}
PoolからはIDisposable
しか渡さず、
インスタンス側から返却ロジックを叩くことができるようになりました!
生成機能と返却機能を簡単に分離できるようになるので大変便利です!!
おわりに
以上でこの記事はおわりです!
また、今回省略したPoolの生成処理の中では、
オブジェクト返却時のコールバックを登録することができます。
gameObject.SetActive(false)
など、
返却時に他の処理が必要な場合はそちらで登録するのをオススメします!
ぜひ素敵なObjectPoolingライフを送ってください!!!
この利用方法は、struct
であるPooledObject
をIDisposable
にキャストしているのでボックス化が起ります。
おそらく、本来この機能は、using
を利用して、短い期間で寿命管理ことを想定された機能です。
その機能をハック的に利用しているため、ボックス化が起きてしまいます。
パフォーマンスが重要な場面では、PooledObject
と同じ働きをするclassを自作する方針も検討してみてください。
(検証、ご指摘くださった@junerさま、ありがとうございます!)
※2024/02/25追記>2024/03/04編集