【Unity】ObjectPoolをちょっとだけラクに実装する



はじめに

Unityではオブジェクトの生成・破棄は負荷が大きいため、大量に呼び出すとフレームレートの低下(最悪の場合は処理落ち)してしまいます。

そこでオブジェクトプールというオブジェクトの生成に関するパターンがあります。これはオブジェクトの数を制限し、再利用したい時に利用するパターンです。

少し制限がありますが、その分ちょっとラク?に実装する方法を紹介します。


実装する

ObjectPoolクラス

抽象クラスとして扱います。

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// ObjectPool
/// </summary>
/// <typeparam name="T">Component限定</typeparam>
public abstract class ObjectPool<T> : MonoBehaviour where T : Component
{
    /// <summary>
    /// Poolしたいオブジェクト
    /// </summary>
    [SerializeField] private T poolObject;
    
    /// <summary>
    /// PoolのためのList
    /// </summary>
    private List<T> _poolList;

    /// <summary>
    /// Poolするオブジェクトの親オブジェクト
    /// </summary>
    [SerializeField] private Transform poolParent;
    
    /// <summary>
    /// 初期化時にPoolする数
    /// </summary>
    [SerializeField] private int initializePoolCount;

    protected virtual void Awake()
    {
        _poolList = new List<T>();

        for (int i = 0; i < initializePoolCount; i++)
        {
            var pool = CreatePool(Vector3.zero, Quaternion.identity);
            pool.gameObject.SetActive(false);
        }
    }

    /// <summary>
    /// Poolを新しく生成
    /// </summary>
    /// <param name="createPosition"></param>
    /// <param name="createRotation"></param>
    /// <returns></returns>
    private T CreatePool(Vector3 createPosition, Quaternion createRotation)
    {
        var newPoolObject = Instantiate(poolObject, createPosition, createRotation);
        newPoolObject.transform.parent = poolParent;
        _poolList.Add(newPoolObject);
        return newPoolObject;
    }

    /// <summary>
    /// Poolしたオブジェクトから再利用 or 新しく生成
    /// </summary>
    /// <param name="setPosition"></param>
    /// <param name="setRotation"></param>
    /// <returns>Poolしたオブジェクト</returns>
    protected T ActivatePool(Vector3 setPosition, Quaternion setRotation)
    {
        foreach (var pool in _poolList)
        {
            if (pool == null)
            {
                continue;
            }

            if (pool.gameObject.activeSelf)
            {
                continue;
            }

            pool.transform.position = setPosition;
            pool.transform.rotation = setRotation;
            pool.gameObject.SetActive(true);
            return pool;
        }

        return CreatePool(setPosition, setRotation);
    }

    /// <summary>
    /// Poolした全オブジェクトの破棄
    /// </summary>
    protected void ClearAllPool()
    {
        foreach (var pool in _poolList)
        {
            Destroy(pool.gameObject);
        }

        _poolList.Clear();
    }
}

ObjectPoolを扱うクラス

Playerなど、Poolするオブジェクトを生成するclassです。

ObjectPool<PoolしたいオブジェクトのComponent>を継承する必要があります。

Poolする対象をComponentに限定しているため、GameObject型などは扱えません。

今回はTestBulletをPoolする対象とします。

using UnityEngine;

public class TestPlayer : ObjectPool<TestBullet>
{
    private Transform _generateTransform;
    
    protected override void Awake()
    {
        // Awakeで何か宣言したい場合↓を呼ぶ必要アリ
        // base.Awake();
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // Poolしたオブジェクトの利用
            var pos = _generateTransform.position;
            var rot = _generateTransform.rotation;
            ActivatePool(pos, rot);
        }
    }
}

Poolする対象

利用時の初めに必要な処理がある場合はOnEnable()を呼びます。

また、破棄したいタイミングではDestroy()の代わりにSetActive(false)を呼びます。

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class TestBullet : MonoBehaviour
{
    private Rigidbody _rigidbody;

    private void Awake()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void OnEnable()
    {
        _rigidbody.velocity = transform.forward;
    }

    private void OnTriggerEnter(Collider other)
    {
        gameObject.SetActive(false);
    }
}

さいごに

説明不足な部分が多々あると思いますが、ご容赦ください。

また、間違いやアドバイス等ありましたらドシドシお願いします!!!

UniRxにあるObjectPoolの方が便利です(早く覚えないと...!)