【Unity】楽にオブジェクトプールを実装できる PoolableMonoBehaviour の紹介

# ✒背景
楽にオブジェクトプールを実装できる PoolableMonoBehaviour を作ったので紹介します。

楽なポイント

* プールは全て ObjectPool 、プールにセットするものは全て PoolableMonoBehaviour なので、宣言が楽
* オブジェクト単体でプールに戻れるので、管理が楽

# 📄コード
PoolableMonoBehaviour.cs は抽象クラスです。継承したクラスを繰り返し使うオブジェクトに追加して使用します。

``` PoolableMonoBehaviour.cs
using UnityEngine;

public abstract class PoolableMonoBehaviour : MonoBehaviour
{
    public ObjectPool Pool { get; set; }
    public abstract void Init();
    public abstract void Sleep();
}
```
ObjectPool.cs は PoolableMonoBehaviour.cs を生成したりプールしたりできるクラスです。SetOriginal, Create, Release だけでも動きます。

``` ObjectPool.cs
using UnityEngine;
using System.Collections.Generic;
using System.Threading.Tasks;

public class ObjectPool : MonoBehaviour
{
    private PoolableMonoBehaviour original;
    private Stack<PoolableMonoBehaviour> pool = new Stack<PoolableMonoBehaviour>();

    /// <summary>
    /// オブジェクトプールするものをセット
    /// </summary>
    /// <param name="original">オブジェクトプールするもの</param>
    public void SetOriginal(PoolableMonoBehaviour original)
    {
        this.original = original;
    }

    /// <summary>
    /// プールから一つ取り出す
    /// </summary>
    /// <returns></returns>
    public Component Create()
    {
        PoolableMonoBehaviour obj;
        if (pool.Count > 0)
        {
            obj = pool.Pop();
        }
        else
        {
            obj = Instantiate(original).GetComponent<PoolableMonoBehaviour>();
            obj.Pool = this;
        }
        obj.Init();
        return obj;
    }

    /// <summary>
    /// プールに戻す
    /// </summary>
    /// <param name="obj">オブジェクト</param>
    public void Release(PoolableMonoBehaviour obj)
    {
        obj.Sleep();
        pool.Push(obj);
    }

    /// <summary>
    /// プールの要素数を取得
    /// </summary>
    /// <returns>要素数</returns>
    public int GetPoolCount()
    {
        return pool.Count;
    }

    /// <summary>
    /// プールの要素を破壊
    /// </summary>
    /// <param name="count">破壊数</param>
    public void Destroy(int count)
    {
        for (int i = Mathf.Min(pool.Count, count) - 1; i >= 0; i--)
        {
            Destroy(pool.Pop().gameObject);
        }
    }
    /// <summary>
    /// 全てのプールの要素を破壊
    /// </summary>
    public void DestroyAll()
    {
        foreach (PoolableMonoBehaviour item in pool)
        {
            if (item == null) continue;
            Destroy(item.gameObject);
        }
        pool.Clear();
    }

    /// <summary>
    /// あらかじめ生成
    /// </summary>
    /// <param name="count">生成数</param>
    /// <param name="duration">生成時間</param>
    public async void Preload(int count, float duration)
    {
        if (!Mathf.Approximately(duration, 0f))
        {
            int interval = (int)(duration / count * 1000);
            PreloadOnce();

            for (int i = count - 2; i >= 0; i--)
            {
                await Task.Delay(interval);
                PreloadOnce();
            }
        }
        else
        {
            for (int i = count - 1; i >= 0; i--) PreloadOnce();
        }

        void PreloadOnce()
        {
            PoolableMonoBehaviour obj = Instantiate(original).GetComponent<PoolableMonoBehaviour>();
            obj.Pool = this;
            Release(obj);
        }
    }
}
```

# 📖関数の説明
|関数名|説明|
|--|--|
|void SetOriginal(PoolableMonoBehaviour original)|オブジェクトプールするものをセットします。<br>original : オブジェクトプールするもの|
|Component Create()|プールから一つ取り出します。<br>戻り値 : PoolableMonoBehaviourを継承したクラス|
|void Release(PoolableMonoBehaviour obj)|プールに戻します。<br>obj : 戻すオブジェクト|
|int GetPoolCount()|プールの要素数を取得します。<br>戻り値 : 要素数|
|void Destroy(int count)|プールの要素を破棄します。<br>count : 破棄数|
|void DestroyAll()|全てのプールの要素を破棄します。|
|void Preload(int count, float duration)|プールするオブジェクトをあらかじめ生成します。<br>count : 要素数<br>duration : 生成時間|

# 💡使い方の例

弾を発射する例を作りました。
![ShootAnimation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307985/fcb9be74-805b-efb4-8ba4-fbb843bdf8aa.gif)
1. Shooterコンポーネントをシーン上のオブジェクトに追加します
2. 弾にしたいオブジェクトをプレハブにして、Bulletコンポーネントを追加します
3. Shooterコンポーネントにプレハブを渡します
4. シーンを再生してスペースキーを押すと弾が発射されます


``` Shooter.cs
using UnityEngine;

public class Shooter : MonoBehaviour
{
    [SerializeField] PoolableMonoBehaviour original;
    ObjectPool pool = new ObjectPool();

    void Start()
    {
        pool.SetOriginal(original);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            var bullet = pool.Create() as Bullet;
            bullet.transform.position = transform.position;
            bullet.transform.rotation = transform.rotation;
        }
    }
}
```

``` Bullet.cs
using UnityEngine;

public class Bullet : PoolableMonoBehaviour
{
    float aliveCount;

    public override void Init()
    {
        aliveCount = 1f;
        gameObject.SetActive(true);
    }

    public override void Sleep()
    {
        gameObject.SetActive(false);
    }

    void Update()
    {
        transform.position += transform.forward * 10f * Time.deltaTime;

        aliveCount -= Time.deltaTime;
        if (aliveCount < 0f) Pool.Release(this);
    }
}
```