ObjectPoolを使って、メモリスパイクを回避せよ!

ゲーム開発をしていると、大量のオブジェクトを作ったり消したりするようなシチュエーションが発生します。そんなとき気になるのがパフォーマンス!UnityではInstantiateやDestroyを多用するとメモリスパイクを起こしたりして、カクカクしてしまいます。それを改善するのがObjectPooling!今回はその導入方法をご紹介します!

目次

Object Poolはどんなことができる?

オブジェクトプールについて簡単にどんなものか理解を深めましょう。

メダルゲームやシューティングゲームなんかで大量のオブジェクトを作ったり消したり

Unityでゲームを作っていると、敵キャラやアイテムなどを生成・削除するたびに「一瞬カクつく」現象に悩まされることがあります。特にメダルゲームのように、プレイヤーが大量のメダルを一気に落としたり、消えたりする場面では、メモリの確保と解放が頻発し、メモリスパイクが原因でフレームレートが乱れることも少なくありません。
こうした問題を解決するための定番手法が Object Pool(オブジェクトプール) です。あらかじめ必要な数のオブジェクトを用意し、使い回すことでメモリ負荷を抑え、快適なゲーム体験を実現できます。

プロジェクトにもすぐに入れやすい

またObjectPoolの良いところは、すでに進行中のプロジェクトにも比較的簡単に実装することが可能なのもポイントが高いところです。いろんなプロジェクトでも使いまわしができるように設計しています。

  • 生成するオブジェクトを管理する側へのプログラム更新
    • メダルゲームの場合はメダルを作る側のプログラム
  • 生成オブジェクト側の継承クラス
    • メダルゲームの場合はメダル。
    • 作成するクラスを継承することで簡単に実装ができる

プロジェクトに実装する前準備

今回は2つのファイルを用意してから実際にオブジェクトプールの実装を行います。ここで作成するクラスをもとに、プロジェクトで使いやすいカスタマイズを施すことも可能ですよ!

大量に作成するオブジェクトが継承するクラス

まずは主役である生成される側のスクリプトを作成します。といってもここで作成するのはプールするオブジェクトを継承させるクラスを作成しましょう。ObjectPoolを再利用する場合きっと役に立つクラスとなるでしょう。

using UnityEngine;
using UnityEngine.Pool;

public interface IPooledObject<T> where T : class
{
    void OnCreate(IObjectPool<T> objectPool);
    void Activate();
    void Deactivate();
}

public abstract class PooledObject<T> : MonoBehaviour, IPooledObject<T> where T : class
{
    public IObjectPool<T> ObjectPool { get; set; }

    public void OnCreate(IObjectPool<T> objectPool)
    {
        ObjectPool = objectPool;
    }

    public void Activate()
    {
        ActivatePooledObject();
    }

    protected virtual void ActivatePooledObject()
    {
        // Initialize object state
    }

    public void Deactivate()
    {
        DeactivatePooledObject();
        ObjectPool.Release(this as T);
    }
    protected virtual void DeactivatePooledObject()
    {
        // Reset object state
    }
}

ファイル内のポイントをいくつか補足

  • プールオブジェクト用のインターフェースを用意
    • 作成時:OnCreate
    • 起動時:Activate
    • 停止時:Deactivate
  • 実際に継承して利用するPooledObjectクラス
    • 基本的には何も実装しないでそのまま利用できる
    • PooledObject付きのメソッドをオーバーライドしてカスタマイズ可能

オブジェクトを生成する側の継承するクラス

PooledObjectを管理するクラスも抽象的なクラスとして用意します。これらの2つのクラスを利用することで、別のオブジェクトや他のプロジェクトでも再利用可能になります。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public abstract class PoolManager<T> : MonoBehaviour where T : PooledObject<T>
{
    [SerializeField] private T pooledPrefab;
    [SerializeField] private int maxSize = 100;

    protected IObjectPool<T> objectPool;
    public IObjectPool<T> ObjectPool
    {
        get
        {
            if (objectPool == null)
            {
                objectPool = new ObjectPool<T>(
                    Create,
                    OnGet,
                    OnRelease,
                    OnDestroyObject,
                    false, 32, maxSize);
            }
            return objectPool;
        }
    }

    protected virtual T Create()
    {
        T instance = Instantiate(pooledPrefab, transform.position, Quaternion.identity, transform);
        instance.OnCreate(objectPool);
        return instance;
    }
    protected virtual void OnGet(T pooledObject)
    {
        pooledObject.gameObject.SetActive(true);
    }
    protected virtual void OnRelease(T pooledObject)
    {
        pooledObject.gameObject.SetActive(false);
    }
    protected virtual void OnDestroyObject(T pooledObject)
    {
        pooledObject.Deactivate();
        Destroy(pooledObject.gameObject);
    }
}

実装編

今回はメダルゲームを題材にObjectPoolを試したいと思います。要件としてはシンプルなもの。

  • クリックしたらメダルが出てくる
  • メダルはy座標-5以下に移動すると消える

メダルのスクリプト

メダルはy座標が一定以下になったら消える処理を実行。ただしDestroyではなくObjectPool的な削除としてDeactivateを使います。

public class Medal : PooledObject<Medal>
{
    void Update()
    {
        if (transform.position.y < -5f)
        {
            // メダルが画面下に落ちたらプールに戻す
            Deactivate();
        }
    }
}

メダル自体はぶっちゃけやることはないですね。

メダルを作るマネージャー側のスクリプト

メダルを作る側のスクリプト。タップした場所に出したりするとそれっぽいですが、今回はスクリプトを貼り付けるTransformの場所から発生させます。

using UnityEngine;

public class MedalManager : PoolManager<Medal>
{
    public void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Medal medal = ObjectPool.Get();
            medal.transform.position = transform.position + new Vector3(0, 1, 0);

            Rigidbody medalRigidbody = medal.GetComponent<Rigidbody>();
            medalRigidbody.isKinematic = false;

            // メダルを画面手前にややランダムに回転させながら弾く
            medalRigidbody.linearVelocity = new Vector3(Random.Range(-1f, 1f), 3, -1.5f);
            medalRigidbody.angularVelocity = new Vector3(Random.Range(-1, 1), Random.Range(-1, 1), Random.Range(-1, 1)) * 5f;
        }
    }
}
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次