ObjectPoolとは?

Unityでは通常、銃弾やエフェクトなど、オブジェクトが必要になったときに、Instantiateしてインスタンスを生成します。

それだと効率が悪いので、事前に一定数インスタンスを用意してプールしておき、

必要に応じてそこから取り出す手法が使われてきました。

これがオブジェクトプールです。

UnityでもいくつかObjectPoolの実装がありますが、Unity2021.2以降では公式にも提供されています。

オブジェクトプールのイメージ

レストランで考えてみると以下の様になります。

従来の場合

  • 注文が入る → 食器を購入する
  • 食事が終わる → 食器を捨てる

オブジェクトプールの場合

  • 開店時にまとめて食器を購入して棚にストックしておく
  • 注文が入る → 棚から食器を取り出す
  • 食事が終わる → 食器を洗って棚に戻す

比べてみると、オブジェクトプールの方が効率がよいのがイメージできると思います。

Instantiateはある程度負荷がかかるのでゲーム中に頻繁に呼び出すと処理落ちに繋がります。

オブジェクトプールでは、事前に必要な分のInstantiateを呼んでおき、

ゲーム中は生成済みのオブジェクトは取り出して再利用することで、

パフォーマンスの低下を抑えることができます。

Unity公式のObjectPool

Unity2021.2から、UnityEngine.Pool名前空間でObjectPoolが提供されています。

基本的な使い方

弾丸オブジェクトを管理するObjectPoolは以下の様になります。

using UnityEngine;
using UnityEngine.Pool;

/// <summary>
/// 弾丸
/// </summary>
public class Bullet : MonoBehaviour
{
    
}

/// <summary>
/// 弾丸用プール
/// </summary>
public class BulletPool : MonoBehaviour
{
		// 弾丸用プレハブ
    [SerializeField] private Bullet _bulletPrefab;
    
    private ObjectPool<Bullet> _bulletPool;

    private void Awake()
    {
        _bulletPool = new ObjectPool<Bullet>
        (
            CreateBullet,
            maxSize: 30 // プールで管理できるオブジェクトの最大値
        );
    }

    private void OnDestroy()
    {
        _bulletPool?.Dispose();
        _bulletPool = null;
    }
    
    /// <summary>
    /// 弾丸をプールから取り出す
    /// </summary>
    public Bullet GetBullet() => _bulletPool.Get();

    /// <summary>
    /// 弾丸をプールへ戻す
    /// </summary>
    public void ReleaseBullet(Bullet bullet)
    {
        _bulletPool.Release(bullet);
    }

    /// <summary>
    /// 弾丸生成
    /// </summary>
    private Bullet CreateBullet()
    {
        var bullet = Instantiate(_bulletPrefab, transform);
        
        return bullet;
    }
}

ObjectPoolの初期化時には、生成用メソッドを指定します。

それ以外にも

  • プールから取り出すときのイベント
  • プールに戻すときのイベント
  • 最終的にDestroyされるときのイベント

などを指定できます。

_bulletPool = new ObjectPool<Bullet>
(
    CreateBullet,
    maxSize: 30 // プールで管理できるオブジェクトの最大値
);

オブジェクトが必要になったら以下の様にObjectPoolから取り出します。

この時管理しているオブジェクトが不足していれば自動的に生成メソッドが呼ばれます。

/// <summary>
/// 弾丸をプールから取り出す
/// </summary>
public Bullet GetBullet() => _bulletPool.Get();

不要になったら再利用するためにDestroyせずに、ObjectPoolへ戻すようにします。

/// <summary>
/// 弾丸をプールへ戻す
/// </summary>
public void ReleaseBullet(Bullet bullet)
{
    _bulletPool.Release(bullet);
}

気をつける点

不要になったら必ずプールに戻す。

Get()で取り出したオブジェクトは、用が済んだら必ずプールに戻します。

再利用できるように必ず戻しましょう。

状態のリセット

プールに戻したオブジェクトはその時の状態を維持したまま、非表示になっています。

Get()で取り出したオブジェクトは必ず初期化するようにしましょう。

適切なプールサイズ

逆にメモリを消費してしまう可能性もあります。

あまり使わないオブジェクトなのに大量にプールしないように気をつけましょう。

まとめ

今回はオブジェクトプールについて解説しました。

Unity公式で提供されているObjectPoolについて説明しましたが、

自分で使いやすいように独自に実装しても問題ありません。

ゲームを作っている場合は、弾丸やエフェクト、敵キャラなど同じものを何度も生成する状況はよくあります。

生成したオブジェクトをプールして再利用することで、メモリの消費量やパフォーマンスの低下を抑えることに繋がります。

特に弾幕シューティングゲームのようなゲームでは必須の最適化手法だと思います。

画像について

本記事内の解説図は、Google DeepMindのNano Banana Pro(AI画像生成)を使用して作成しました。

📣おしらせ!

Unity Asset Storeで

The 17th Unity Awards – Publisher Bundle

が開催中です。

Unity Awardsを受賞した限定バンドルを格安で入手するチャンスです。