今回はアセットの紹介をしていきます。
紹介するのはPoolManager。
大量のオブジェクトを効率よく運用するユーティリティアセットです。
お得なバンドル情報!
現在アセットストアでは、10周年を記念したメガバンドルが発売中です! 終了しました
今回紹介するPoolManagerも含め全20アセットが超お得に手に入るチャンス!
下のバナーリンクからどうぞ。
どんなアセット?
大量のオブジェクトをインスタンスする際に活躍する、オブジェクトプールを簡単に実装できるアセットです。
Instantiateしたゲームオブジェクトをプール、再利用することでガベージコレクションを回避し、
パフォーマンス向上が期待できます。
PoolManagerはUnity向けのオリジナルでベストなインスタンスプーリングソリューションで、インスタンスをより効率的に管理してパフォーマンスを向上、シーンヒエラルキーを整理、実装はシンプルです。信じられないようでしたら、わたしたちのウェブサイトやこちらに書かれているレビューをご覧ください。
インスタンスが何度も何度も必要になるのになぜ破棄するのでしょうか?再利用しましょう!PoolManagerは、プリローディングインスタンスなど多くの追加機能で、ゲームプレイとShurikenの自動消去中にインスタンスを作成されないよう、開発を支援してゲームの実行スピードアップを実現します。詳細については以下のリンクをご覧ください。
公式ドキュメントも用意されています。
docs.poolmanager.path-o-logical.com
レビューの評価が非常に高く、
2011年の初リリースから現在までアップデートされ続けているため、とても信頼性の高いアセットです。
オブジェクトプールとは?
オブジェクトをインスタンス(生成)、プール(溜める)し、
再利用をすることでガベージコレクションを防ぐデザインパターンです。
シューティングゲームで例えると
インスタンスと破棄の場合
弾を撃つたびにInstantiateし、撃ち終わった弾をDestroyすると
メモリの確保と開放が頻繁に起こるため負荷が高くなります。
オブジェクトプールの場合
オブジェクトプールなら最初にまとめてInstantiateした後、
それらのActiveを切り替え再利用することでメモリ負荷を軽減できます。
オブジェクトプールは複雑な作りではないため自作されている方も多いかと思います。
開発環境
Unity 2020.1.8
PoolManager ver7.0.1
実際に試してみる
オブジェクトプールを使わない場合
まずはオブジェクトプールを使わずに、InstantiateとDestroyで銃を撃つ仕組みを作ってみます。
Bullet
弾はこんな感じ、SphereにRigidbodyとBullet.csをアタッチします。これはプレハブ化しておきます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Bullet : MonoBehaviour { [SerializeField] Rigidbody Rb; [SerializeField] float m_Speed; [SerializeField] float m_AutoDisposeTime = 3; //初期化処理 public void Init(Transform _trn, float _speed) { transform.position = _trn.position; transform.rotation = _trn.rotation; m_Speed = _speed; Rb.AddForce(transform.forward * _speed); StartCoroutine("AutoDispose"); } //終了処理 public void Dispose() { Destroy(gameObject); } //何にも当たらない場合時間経過で消す IEnumerator AutoDispose() { yield return new WaitForSeconds(m_AutoDisposeTime); Dispose(); } private void OnTriggerEnter(Collider other) { StopCoroutine("AutoDispose"); Dispose(); } }
銃
銃にはGun.csをアタッチします。銃口となるTransformと先ほど作ったBulletプレハブを紐づけておきます。
見た目はBoxを組み合わせてそれっぽくしました。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Gun : MonoBehaviour { [SerializeField] Transform GunPoint; [SerializeField] GameObject Bullet; [SerializeField] float BulletSpeed = 1000; //マウスクリックしたら撃つ void Update() { if(Input.GetMouseButtonDown(0)) { var obj = Instantiate(Bullet); obj.GetComponent<Bullet>().Init(GunPoint, BulletSpeed); } } }
この状態でプレイしてみるとマウスの左クリックで弾が出ます。
Bulletプレハブが生成され、その後破棄されています。
PoolManagerに置き換える
先ほど作った銃と弾をPoolManagerを使ってオブジェクトプールに置き換えてみます。
SpawnPoolの作成
空のゲームオブジェクトを作成して、SpawnPoolコンポーネントをアタッチします。
このオブジェクトの名前をPoolとしておきます。
各パラメータは以下の通りです。
Pool Name : プールへのアクセスに使用する名前
Match Pool Scale : 生成されるオブジェクトをPoolオブジェクトに合わせてスケーリングする
生成するオブジェクトがUIの場合にチェックを入れるとよい
Match Pool Layer : 生成させるオブジェクトのレイヤーを、Poolオブジェクトのレイヤーと合わせる
Don't Reparent : 生成したオブジェクトをPoolオブジェクトの子にしない
Don't Destroy On Load : 生成したオブジェクトのDontDestroyOnLoad(シーン切り替えなどでDestroyされない)を有効にする
Log Messages : PoolManagerが何をしているかログを表示させる
Per-Prefab Pool Options : プールしておくオブジェクトの設定
SpawnPoolにBulletの登録
SpawnPoolコンポーネントにBulletを登録して生成させます。
Per-Prefab Pool Optionsを開いて、右の+ボタンを押すとオプション画面が表示されます。
prefab : プール対象のプレハブです。今回はBulletを登録します。
preload Amout : 事前にいくつ生成しプールしておくか。
preload Time : 何フレームかけて生成するかと、ディレイの設定。
シーン開始時のスパイクを回避するために、少し遅らせて生成することができます。
limit Instances : 生成する数に制限をつけるかどうか
プールしているインスタンス数がこれを超えた場合、SpawnされてもNullを返します。
FIFOを有効にしている場合は、一番古いインスタンスを終了させ即座に使いまわします。
cull Despawned : 生成したが使われていないインスタンスを削除するかどうか
下の例の場合、使われていないインスタンスが50以上ある場合、60秒ごとに5づつ削除する。
メモリの問題が発生していない場合は基本的に使わない方がいいらしい。
log Messages : ログメッセージを出すかどうか
今回はこのような設定にしました。
実行するとBulletが15個生成されプールされているのが確認できます。
GunからPoolにアクセスし弾を発射する
スクリプトはBullet.csとGun.csのInstantiateとDestroyの部分を差し替えるだけです。
//終了処理 public void Dispose() { PoolManager.Pools["Pool"].Despawn(transform); }
//マウスクリックしたら撃つ void Update() { if(Input.GetMouseButtonDown(0)) { var obj = PoolManager.Pools["Pool"].Spawn(Bullet); obj.transform.GetComponent<Bullet>().Init(GunPoint, BulletSpeed); } }
SpawnPoolで生成されたBulletが使われているのが確認できました。
その他有効な使用パターン
今回は一番簡単な使い方をしましたが、公式で様々なパターンが紹介されているのでこちらもどうぞ
docs.poolmanager.path-o-logical.com
まとめ
・PoolManagerを使うことで、簡単にオブジェクトプールを作成することができる
・オブジェクトプールは、ガベージコレクションを避けメモリ負荷を抑えることができる
・スクリプトは、InstantiateとDestroyをSpawnとDespawnに書き換えるだけ
参考資料
他のアセットの紹介記事はこちら↓
※本記事にはAssetStoreアフィリエイトリンクが含まれています。
他、間違っている箇所、わかりにくい所がありましたらコメントにお願いします。