Unity初心者の知識保存用日記

基本的にUnityに関する技術やプログラミングで得た知識を振り返って確認するためのブログです。

Unityでオブジェクトのpool化?

pool化とはなんぞや

poolとは…

ソフトウェアの分野では、プログラムの実行時に何度も繰り返し利用する資源をいちいち生成・破棄する手間を軽減するため、必要なくなった資源を回収してすぐに再利用できるように一時的に貯めておく仕組みや保管領域のことをプールという。

プールとは - IT用語辞典

きっかけ

昨日、ある知人(ミリオタ)がFPSゲーム作っていて
銃からでる弾をpool化していなかったので友人と考えながら実装しました。
今回はその実装内容を忘れないように書き残します。

そもそもpool化しない場合

まあ、銃から弾を出すわけですが
出る弾の数が決まっている訳ではないので、
「任意のタイミングで弾を生成する処理」と
「一定時間経ったら弾を削除する処理」が必要になります。

Unityでオブジェクトを生成するにはInstantiate関数を使います。
また、オブジェクトを削除するにはDestroy関数を使います。

実はこの二つの関数が問題です。

自分で検証したことはありませんが、
InstantiateDestroyは処理が重い」とよく聞きます。

そこで、pool化するわけです。

じゃあpool化したい…

pool化するとしても必要な処理は対して変わりません。

「任意のタイミングで弾が必要なら生成する処理」と
「一定時間経ったら弾を非表示にする処理」

生成は必要最低限だけにして削除せずに見えなくするってことです。
※本来は生成される最大数がわかってることが多いですが、
今回は最大数がわからないことを想定して動的に生成します。

下準備

先に発射される弾を作ります。
適当に球のオブジェクトを作って「bullet」に名前を変更します。

次に以下のコードを作成し適用します。

using System.Collections;
using UnityEngine;

public class BulletMove : MonoBehaviour {

    void Start()
    {
        StartCoroutine("ObjectSetActive");
    }

    // activeになったときもコルーチンを始める
    void OnEnable()
    {
        StartCoroutine("ObjectSetActive");
    }

    // コルーチン
    private IEnumerator ObjectSetActive()
    {
        // 2秒後にオブジェクトを非表示にする
        yield return new WaitForSeconds(2.0f);
        gameObject.SetActive(false);
        yield break;
    }

    void Update()
    {
        // 移動処理
        transform.position += Vector3.forward;
    }
}


そして、AssetsフォルダにResourcesフォルダを作成し
作ったオブジェクトをプレハブ化します。

これで下準備は完了です。

ようやく処理を書きます

また何かオブジェクトを作って、以下のコードを作成し適用します。

using System.Collections.Generic;
using UnityEngine;

public class BulletPool : MonoBehaviour {

    private GameObject BulletPrefab;

    // バイトメンバーリスト
    [SerializeField, Header ("弾のリスト")]
    private List<GameObject> Bullets = new List<GameObject>();

    void Start()
    {
        // Resourcesフォルダからbulletプレハブを取得
        // コイツがクローン体の素体
        BulletPrefab = Resources.Load("bullet") as GameObject;
    }

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Space))
        {
            // 最初はバイトメンバーが0人なので
            // 必ず人員を追加する(きっとコイツがバイトリーダー)
            if (Bullets.Count == 0)
            {
                NewIns();
            }
            // それ以降はバイトメンバーリストで働いていないやつを探す
            else
            {
                bool insRequest = true;

                for (int i = 0; i < Bullets.Count; i++)
                {
                    // 休憩中のやつを探し出してしっかり働かせる
                    if (Bullets[i].activeSelf == false)
                    {
                        ReuseBullet(Bullets[i]);
                        insRequest = false;
                        break;
                    }
                }

                // Bullesリスト内のやつらが全員働いていたので
                // 仕方なく増員
                if (insRequest == true)
                {
                    NewIns();
                }
            }
        }
    }

    // 新しく弾を生成(増員)
    private void NewIns()
    {
        GameObject GO = Instantiate(BulletPrefab,transform.position,Quaternion.identity,transform);
        Bullets.Add(GO);
    }

    // 弾を再利用(「休憩は終わりだ!」)
    private void ReuseBullet(GameObject bullet)
    {
        bullet.transform.position = transform.position;
        bullet.SetActive(true);
    }
}


見てわかる通りリストを作って生成された弾を入れておいて、
任意のタイミングでリスト内で非表示のオブジェクトがあったら使いまわしています。

コメントアウトでは弾をバイト人員に見立てて
説明を面白くしようとしている努力が見られます…

うまく動作していると、
スペースキーを押した時に非表示のオブジェクトが
あれば使いまわし
なければ生成が行われます。