Skip to content

第01回 オブジェクトプーリング

kyusyukeigo edited this page Feb 26, 2015 · 1 revision

オブジェクトプーリングは最適化の1つでオブジェクトを再利用する仕組みのことを指します。今回は大量に発射する弾を再利用することによって最適化を測りましょう。

使用するプロジェクト

この回はゲーム制作編第01回終了時点のプロジェクトをオブジェクトプーリングの向けに改変したものを使用します。

プロジェクトファイルをダウンロード

上記のUnityプロジェクトを開いたらStageシーンを開いてください。

ひたすら弾を打ち続け、DestroyAreaで削除される
図1.1: ひたすら弾を打ち続け、DestroyAreaで削除される

スクリプトの確認

Bullet.cs

using UnityEngine;

public class Bullet : MonoBehaviour
{
    // 弾のスピード
    public int speed = 10;

    void Start ()
    {
        // 弾の移動
        rigidbody2D.velocity = transform.up.normalized * speed;
    }

    // 弾が何らかのトリガーに当たった時に呼び出される
    void OnTriggerExit2D (Collider2D other)
    {
        // 弾の削除
        Destroy (gameObject);
    }
}

Spaceship.cs

using UnityEngine;
using System.Collections;

public class Spaceship : MonoBehaviour
{

    // 弾のプレハブ
    public GameObject bulletPrefab;

    // 弾を撃つ間隔
    public float shotDelay;

    void Start ()
    {
        // 弾をうつ(コルーチン)
        StartCoroutine (Shoot ());
    }

    IEnumerator Shoot ()
    {
        while (true) {

            // shotDelay秒待つ
            yield return new WaitForSeconds (shotDelay);

            // 子要素を全て取得する
            foreach (Transform child in transform) {

                long start = System.DateTime.Now.Ticks;
                // ShotPositionの位置/角度で弾を撃つ
                Instantiate (bulletPrefab, child.transform.position, child.transform.rotation);

                // 処理時間でInstantiateとObjectPoolを比較してみる
                Debug.Log (System.DateTime.Now.Ticks - start);
            }
        }
    }


}

InstantiateとDestroyは呼び出しコストが高い

ここで「コストが高い」とあるのはオブジェクトプーリングを行わない場合より高いということで、今回の弾ように「同じゲームオブジェクトを繰り返し大量に生成する」ゲームであれば考慮しなければなりませんが、その他のゲームでは考慮する必要はありません。

InstantiateやDestroyのコストが高いといっても、オブジェクトプーリングを行うコストもあるわけですから場合によってはオブジェクトプーリングを行わない場合のほうが結果的に良い場合もあります。

ObjectPool.csの作成

ObjectPool.csを作成しましょう。

ObjectPool.csでは、使用/未使用をゲームオブジェクトのアクティブ状態で判断しています。

ObjectPool.cs

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

public class ObjectPool : MonoBehaviour
{
    private static ObjectPool _instance;

    // シングルトン
    public static ObjectPool instance {
        get {
            if (_instance == null) {

                // シーン上から取得する
                _instance = FindObjectOfType<ObjectPool> ();

                if (_instance == null) {

                    // ゲームオブジェクトを作成しObjectPoolコンポーネントを追加する
                    _instance = new GameObject ("ObjectPool").AddComponent<ObjectPool> ();
                }
            }
            return _instance;
        }
    }

    // ゲームオブジェクトのDictionary
    private Dictionary<int, List<GameObject>> pooledGameObjects = new Dictionary<int, List<GameObject>> ();

    // ゲームオブジェクトをpooledGameObjectsから取得する。必要であれば新たに生成する
    public GameObject GetGameObject (GameObject prefab, Vector2 position, Quaternion rotation)
    {
        // プレハブのインスタンスIDをkeyとする
        int key = prefab.GetInstanceID ();

        // Dictionaryにkeyが存在しなければ作成する
        if (pooledGameObjects.ContainsKey (key) == false) {

            pooledGameObjects.Add (key, new List<GameObject> ());
        }

        List<GameObject> gameObjects = pooledGameObjects [key];

        GameObject go = null;

        for (int i = 0; i < gameObjects.Count; i++) {

            go = gameObjects [i];

            // 現在非アクティブ(未使用)であれば
            if (go.activeInHierarchy == false) {

                // 位置を設定する
                go.transform.position = position;

                // 角度を設定する
                go.transform.rotation = rotation;

                // これから使用するのでアクティブにする
                go.SetActive (true);

                return go;
            }
        }

        // 使用できるものがないので新たに生成する
        go = (GameObject)Instantiate (prefab, position, rotation);

        // ObjectPoolゲームオブジェクトの子要素にする
        go.transform.parent = transform;

        // リストに追加
        gameObjects.Add (go);

        return go;
    }

    // ゲームオブジェクトを非アクティブにする。こうすることで再利用可能状態にする
    public void ReleaseGameObject (GameObject go)
    {
        // 非アクティブにする
        go.SetActive (false);
    }
}

Bullet.csにあるDestroyをObjectPool.instance.ReleaseGameObjectに、SpaceshipにあるInstantiateをObjectPool.instance.GetGameObjectに変更します。

Bullet.cs

using UnityEngine;

public class Bullet : MonoBehaviour
{
    // 弾のスピード
    public int speed = 10;

    // 弾が表示された時に呼び出される
    void OnEnable ()
    {
        // 弾の移動
        rigidbody2D.velocity = transform.up.normalized * speed;
    }

    // 弾が何らかのトリガーに当たった時に呼び出される
    void OnTriggerExit2D (Collider2D other)
    {
        // 弾の削除。実際には非アクティブにする
        ObjectPool.instance.ReleaseGameObject (gameObject);
    }
}

Spaceship.cs

using UnityEngine;
using System.Collections;

public class Spaceship : MonoBehaviour
{

    // 弾のプレハブ
    public GameObject bulletPrefab;

    // 弾を撃つ間隔
    public float shotDelay;

    void Start ()
    {
        // 弾をうつ(コルーチン)
        StartCoroutine (Shoot ());
    }

    IEnumerator Shoot ()
    {
        while (true) {

            // shotDelay秒待つ
            yield return new WaitForSeconds (shotDelay);

            // 子要素を全て取得する
            foreach (Transform child in transform) {

                long start = System.DateTime.Now.Ticks;
                // ShotPositionの位置/角度で弾を撃つ
                ObjectPool.instance.GetGameObject (bulletPrefab, child.transform.position, child.transform.rotation);

                // 処理時間でInstantiateとObjectPoolを比較してみる
                Debug.Log (System.DateTime.Now.Ticks - start);
            }
        }
    }
}

これでゲームを再生した時に見た目は代わりませんが、オブジェクトプーリングの実装が出来ました。

第01回終わり

今回はここで終了です。つまずいてしまった方はプロジェクトファイルをダウンロードして新たな気持ちで次の回へ進みましょう。

今回のプロジェクトファイルをダウンロード

Clone this wiki locally