【Unity】Unity標準のObjectPoolを汎用的に使うクラスの作成

## はじめに
### 背景
以前私が執筆した「[[Unity] UniRxのObjectPoolを使ってParticleSystemを管理する](https://qiita.com/KeichiMizutani/items/fc22a6037447d840adc2)」では、UniRxを用いてオブジェクトプールのデザインパターンを実装しました。
UniRxの後継ライブラリであるR3では、`UniRx.ToolKit.ObjectPool`に相当するものがなく、R3にはオブジェクトプールの機構がありません。また、UniRxは後継のR3が登場したこともあり、現在はアーカイブされ、今後更新されないようです。

そこで今回は`UniRx.ToolKit.ObjectPool`の代替としてUnity公式のオブジェクトプールを使用することにしました。

https://github.com/Unity-Technologies/game-programming-patterns-demo

しかし、Unity標準のオブジェクトプールは、各ゲームオブジェクトごとにオブジェクトプールの生成と、プールされるゲームオブジェクトの生成、オブジェクトプールへの返却、オブジェクトプールからの取得、破棄を全て記述する必要がありとても手間がかかります。
そこで共通化できるところは共通化して継承して使ってしまおうと考えて実装したのがこの記事の内容になります。


### 検証環境
Unity : 2023.2.16f1
MacOS Ventura

## オブジェクトプールとは
オブジェクトプールとは、オブジェクトを使いまわすための機構のことです。

Unityではオブジェクトを生成するときにInstantiate、削除するときにDestroyを使いますが、InstantiateとDestroyのどちらもそれなりに重い処理であるため、多用するとゲームの処理が重くなる恐れがあります。
特にゲームのエフェクトは大量に生成されることが多く、毎回Instantiateをしてエフェクトを再生し、再生が終了したらDestroyをしていてはフレームレートを著しく下げてしまいます。

今回はこの問題を解決するためのデザインパターンのオブジェクトを用いて、生成したゲームオブジェクトやエフェクトを削除せずに非表示にして、必要になったら再度表示してエフェクトを再生する仕組みを実例として実装していきます。

## 実装
### プールされるオブジェクトのインターフェースの作成
ここでは、プールされるオブジェクトのインターフェースを作成します。
```IPooledObject.cs
using UnityEngine.Pool;

public interface IPooledObject<T> where T : class
{
    public IObjectPool<T> ObjectPool { set; }
    public void Initialize();
    public void Deactivate();
}
```
インターフェースでは、ObjectPoolと生成されたタイミングで呼ばれるメソッドと破棄されるタイミングで呼ばれるメソッドを用意します。

### オブジェクトプールを構築する抽象クラスの作成
ここでは、オブジェクトプールを構築する抽象クラスを作成します。
```PoolManager.cs
using UnityEngine;
using UnityEngine.Pool;

public abstract class PoolManager<T> : MonoBehaviour where T : MonoBehaviour, IPooledObject<T>
{
    [SerializeField] private T pooledPrefab;
    protected IObjectPool<T> _objectPool;
    
    [SerializeField] private bool _collectionCheck = true;
    
    [SerializeField] private int _defaultCapacity = 32;
    [SerializeField] private int _maxSize = 100;

    public virtual  void Initialize()
    {
        _objectPool = new ObjectPool<T>(Create, OnGetFromPool, OnReleaseToPool, OnDestroyPooledObject,
            _collectionCheck, _defaultCapacity, _maxSize);
    }
    
    protected virtual T Create()
    {
        T instance = Instantiate(pooledPrefab, transform.position, Quaternion.identity, transform);
        instance.ObjectPool = _objectPool;
        return instance;
    }
    
    protected virtual void OnReleaseToPool(T pooledObject)
    {
        pooledObject.gameObject.SetActive(false);
    }
    
    protected virtual void OnGetFromPool(T pooledObject)
    {
        pooledObject.gameObject.SetActive(true);
    }
    
    protected virtual void OnDestroyPooledObject(T pooledObject)
    {
        pooledObject.Deactivate();
        Destroy(pooledObject.gameObject);
    }
}

```
`PoolManager<T>`は、`MonoBehaviour`を継承する抽象クラスです。ジェネリック型パラメータ`T`には、`MonoBehaviour`を継承し、かつ`IPooledObject<T>`インターフェイスを実装する型を指定する必要があります。

この`IPooledObject<T>`インターフェースと`PoolManager<T>`抽象クラスを用いてオブジェクトプールの機構を作成していきます。

## 実例
### ゲームオブジェクトのオブジェクトプール
まずはプールされるゲームオブジェクトのクラスを作成します。
オブジェクトプールに返却するまでの遅延でコルーチンを使用しています。
```BulletObject.cs
using System.Collections;
using UnityEngine;
using UnityEngine.Pool;

public class BulletObject : MonoBehaviour, IPooledObject<BulletObject>
{
    private IObjectPool<BulletObject> _objectPool;
    public IObjectPool<BulletObject> ObjectPool
    {
        set => _objectPool = value;
    }

    public void Initialize()
    {
        StartCoroutine(DelayDeactivation(3f));
    }

    private IEnumerator DelayDeactivation(float seconds)
    {
        yield return new WaitForSeconds(seconds);
        Deactivate();
    }

    public void Deactivate()
    {
        var rigidbody = GetComponent<Rigidbody>();
        rigidbody.velocity = Vector3.zero;
        rigidbody.angularVelocity = Vector3.zero;
        _objectPool.Release(this);
    }
}
```
`IPooledObject<T>`のインタフェースを実装し、
BulletObjectを管理するBulletPoolManagerクラスの作成をします
```BulletPoolManager.cs
using UnityEngine;

public class BulletPoolManager : PoolManager<BulletObject>
{
    private void Awake()
    {
        Initialize();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (_objectPool == null)
            {
                return;
            }
            
            var bulletObject = _objectPool.Get();

            if (bulletObject == null)
            {
                return;
            }
            
            bulletObject.transform.position = transform.position;
            bulletObject.Initialize();
        }
    }
}
```
`PoolManager<T>`を継承し、ジェネリック型パラメータ`T`には先ほど作成した`BulletObject`クラスを指定します。

### ParticleSystemのオブジェクトプール
まずはプールされるParticleSystemにアタッチするクラスを作成します。
こちらもオブジェクトプールに返却するまでの遅延で`UniTask.Delay`を使用しています。
```ParticleObject.cs
using System.Collections;
using UnityEngine;
using UnityEngine.Pool;

public class ParticleObject : MonoBehaviour, IPooledObject<ParticleObject>
{
    private IObjectPool<ParticleObject> _objectPool;
    public IObjectPool<ParticleObject> ObjectPool
    {
        set => _objectPool = value;
    }
    public void Initialize()
    {
        var particle = GetComponent<ParticleSystem>();
        particle.Play();
        var lifetime = particle.main.startLifetime.constantMax;
        StartCoroutine(DelayDeactivation(lifetime));
    }
    
    private IEnumerator DelayDeactivation(float seconds)
    {
        yield return new WaitForSeconds(seconds);
        Deactivate();
    }

    public void Deactivate()
    {
        _objectPool.Release(this);
    }
}
```

`IPooledObject<T>`のインタフェースを実装し、
ParticleObjectを管理するBulletPoolManagerクラスの作成をします

```ParticlePoolManager.cs
using UnityEngine;

public class ParticlePoolManager : PoolManager<ParticleObject>
{
    private void Awake()
    {
        Initialize();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (_objectPool == null)
            {
                return;
            }
            
            var particle = _objectPool.Get();

            if (particle == null)
            {
                return;
            }
            
            particle.transform.position = transform.position;
            particle.Initialize();
        }
    }
}

```
`PoolManager<T>`を継承し、ジェネリック型パラメータ`T`には先ほど作成した`ParticleObject`クラスを指定します。

### 動作確認
![qiita_objectpool.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/b2bbaaa8-c13a-7611-8ddc-85605ca54dd4.gif)
オブジェクトプールに貯めてある個数以上のオブジェクトが必要になった場合はその都度オブジェクトを生成し、使用可能なオブジェクトがある場合は使いまわしているのが確認できます。
みなさんもぜひ大量生成大量破棄をするものを作る際はオブジェクトプールの機構を作ってみましょう！！