藍と淡々

物作り冒険譚


【Unity】オブジェクトプールって何?

こんにちは(ؓؒؒؑؑؖؔؓؒؐؐ⁼̴̀ωؘؙؖؕؔؓؒؑؐؕ⁼̴̀ )

ーーーーーーーーーーーー余談ーーーーーーーーーーーーーーー

最近、MOSHIMOというバンドの猫かぶるをヘビロテしている私です。

ボーカルの声がとても独特ですぐはまりました。。

2週間後にライブもあるのでわっくわっくしております₍₍ (ง ˙ω˙)ว ⁾⁾


MOSHIMO「猫かぶる」MV(Short ver.)

ーーーーーーーーーーここまで余談ーーーーーーーーーーーーー


さて、今回も引き続いてTrocco制作から学んだ知識をまとめていこうと思います。

今回は「オブジェクトプールとはなんぞ?」がテーマです。

オブジェクトプールとは、「オブジェクトをプール(備蓄)しておく」ことで、生成と破壊のコストを削減する方法です。

たとえば、シューティングゲームで弾を打つたびにプレハブを「Instantiate」し、弾が当たるたびに「Destroy」していたのでは、かなりコストがかかってしまいます。

なので予め弾丸を用意しておいて、それを「SetActive(true)または(false)」して使い回し、コストを下げよう、という方法です。


最初に断っておくと、今回紹介する方法は効率が悪いです!(えぇー・・・)

効率の良い方法は後々学んでいくとして、今回はとりあえずオブジェクトプールがどんなものか体験してもらおうと思います。



1、準備

まずは「Cube」を用意し、これをPlayerとします。

そしてこれに「PlayerController」というスクリプトをアタッチします。

f:id:MegumiSoft:20160625115729p:plain:w500


次に「Sphere」を用意し、これをBulletとします。

これには「BulletScript」をアタッチします。

f:id:MegumiSoft:20160625120156p:plain:w500


最後に「空のゲームオブジェクト」を用意し、これをBulletGeneratorとします。

これには「BulletGenerator」をアタッチします。

f:id:MegumiSoft:20160625120411p:plain:w500


「Bullet」はプレハブにして消しておきます。

f:id:MegumiSoft:20160625120732p:plain:w500



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」スクリプトを登録しておきましょう。

f:id:MegumiSoft:20160625124813p:plain:w500

f:id:MegumiSoft:20160625124646p:plain:w500



3、実際に試してみる

それではプレイボタンを押して、スペースキーを押してみましょう。

「Player」オブジェクトから、丸い「Bullet」オブジェクトが発射されるはずです。

f:id:MegumiSoft:20160625125121p:plain:w500


内部の処理をわかりやすくすると、

スペースキーを押す
    ↓
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



それではまたぬ〜ん\\\└('ω')┘////