【Unity】オブジェクトプールって何?
こんにちは(ؓؒؒؑؑؖؔؓؒؐؐ⁼̴̀ωؘؙؖؕؔؓؒؑؐؕ⁼̴̀ )
ーーーーーーーーーーーー余談ーーーーーーーーーーーーーーー
最近、MOSHIMOというバンドの猫かぶるをヘビロテしている私です。
ボーカルの声がとても独特ですぐはまりました。。
2週間後にライブもあるのでわっくわっくしております₍₍ (ง ˙ω˙)ว ⁾⁾
ーーーーーーーーーーここまで余談ーーーーーーーーーーーーー
さて、今回も引き続いてTrocco制作から学んだ知識をまとめていこうと思います。
今回は「オブジェクトプールとはなんぞ?」がテーマです。
オブジェクトプールとは、「オブジェクトをプール(備蓄)しておく」ことで、生成と破壊のコストを削減する方法です。
たとえば、シューティングゲームで弾を打つたびにプレハブを「Instantiate」し、弾が当たるたびに「Destroy」していたのでは、かなりコストがかかってしまいます。
なので予め弾丸を用意しておいて、それを「SetActive(true)または(false)」して使い回し、コストを下げよう、という方法です。
最初に断っておくと、今回紹介する方法は効率が悪いです!(えぇー・・・)
効率の良い方法は後々学んでいくとして、今回はとりあえずオブジェクトプールがどんなものか体験してもらおうと思います。
1、準備
まずは「Cube」を用意し、これをPlayerとします。
そしてこれに「PlayerController」というスクリプトをアタッチします。
次に「Sphere」を用意し、これをBulletとします。
これには「BulletScript」をアタッチします。
最後に「空のゲームオブジェクト」を用意し、これをBulletGeneratorとします。
これには「BulletGenerator」をアタッチします。
「Bullet」はプレハブにして消しておきます。
2、実装
まず「BulletScript」に次のように書いておきます。
using UnityEngine; public class BulletScript : MonoBehaviour { Transform transf_Bullet; const float BULLET_LIFE_TIME = 3; float bulletLifeTimer; float moveSpeed = 10; public void Init(Vector3 startPos) { // Transformをキャッシュしておく transf_Bullet = GetComponent<Transform>(); // 弾の位置を発射場所に設定 transf_Bullet.position = startPos; // 弾が飛んでいられる時間を設定 bulletLifeTimer = BULLET_LIFE_TIME; // 弾をアクティブにする gameObject.SetActive(true); } // Update is called once per frame void Update () { // 弾の移動 transf_Bullet.Translate(transf_Bullet.forward * Time.deltaTime * moveSpeed); // 弾の持続時間をカウント bulletLifeTimer -= Time.deltaTime; // 一定時間たったら弾は非アクティブにする if(bulletLifeTimer < 0) this.gameObject.SetActive(false); } }
次は「BulletGenerator」に次のように書いておきます。
using UnityEngine; using System.Collections.Generic; // .Genericを忘れずに! public class BulletGenerator : MonoBehaviour { // エディタから弾として使うプレハブを設定 public GameObject pf_Bullet; // 弾を備蓄しておくList List<BulletScript> list_Bullets = new List<BulletScript>(); // 備蓄しておく弾の数 const int MAX_BULLETS = 10; void Start () { BulletScript bullet; // 最初に一定数の弾を備蓄しておく for(int i=0; i<MAX_BULLETS; i++) { // 弾の生成 bullet = ((GameObject) Instantiate(pf_Bullet)).GetComponent<BulletScript>(); // 弾を、この「BulletGenerator」オブジェクトの子にしておく bullet.transform.parent = this.transform; // 発射前は非アクティブにしておく bullet.gameObject.SetActive(false); // Listに追加 list_Bullets.Add(bullet); } } public void FireBullet(Vector3 startPos) { // Listの中身を最初から確認していき、非アクティブのオブジェクトを探す for(int i=0; i<list_Bullets.Count; i++) { if(list_Bullets[i].gameObject.activeSelf == false) { // 非アクティブの弾を発射させる list_Bullets[i].Init(startPos); return; } } // LINQを知っていればこれでもおk。using System.Linq;を忘れずに。 // BulletScript bullet = list_Bullets.FirstOrDefault(b => !b.gameObject.activeSelf); // if(bullet) bullet.Init(startPos); } }
それから「PlayerController」に弾を発射する処理を書いておきます。
using UnityEngine; public class PlayerController : MonoBehaviour { // エディタから「BulletGenerator」スクリプトを設定 public BulletGenerator sc_BulletGenerator; // Update is called once per frame void Update () { // スペースキーを押した時、「Player」の位置から弾を発射する if(Input.GetKeyDown(KeyCode.Space)) { sc_BulletGenerator.FireBullet(transform.position); } } }
最後に、忘れずにエディタから「弾のプレハブ」と「BelletGenerator」スクリプトを登録しておきましょう。
3、実際に試してみる
それではプレイボタンを押して、スペースキーを押してみましょう。
「Player」オブジェクトから、丸い「Bullet」オブジェクトが発射されるはずです。
内部の処理をわかりやすくすると、
スペースキーを押す
↓
PlayerController「Update()」から、BulletGenerator「FireBullet()」が呼ばれる
↓
「FireBullet()」内で、「list_Bullets」の中から今使用されていないBulletを探す
↓
BulletGenerator「FireBullet()」から、非アクティブのBulletScript「Init()」が呼ばれる
↓
弾(Bulletオブジェクト)を発射
といった感じです。
さてここまでオブジェクトプールを説明してきましたが、今紹介した方法そのままでは、スペースキーを早く連打して弾を10発撃ったら、次の弾を撃つまでに少し時間が空いてしまいます。
それなら最初に用意する弾の数を「10」ではなくて「20」に増やせばOK、という対処も出来ますが、後々弾の持続時間を延ばした時にまた修正が必要になります。
注意しなくてはいけないのが、オブジェクトプールにもコストはかかるので、単純に用意しておく弾の数を増やしておくというのは、逆に負荷が増えることになります。
もうちょっと効率の良い方法を知りたい、という方は、Unityの公式チュートリアルでオブジェクトプールが解説されているので、読んでみることをお勧めします。
・第01回 オブジェクトプーリング · unity3d-jp-tutorials/2d-shooting-game Wiki · GitHub
またいろんなUnity使いの方(響きがかっこいい)のブログでも紹介されているので、もっと知りたいという方はどうぞ。
・簡易オブジェクトプール - かずおの開発ブログ(主にRuby)
・新・オブジェクトプール - テラシュールブログ
・ゲームは初心者にやさしく: Unity オブジェクトプールの作成 ~その1
それではまたぬ〜ん\\\└('ω')┘////