第01回 オブジェクトプーリング
オブジェクトプーリングは最適化の1つでオブジェクトを再利用する仕組みのことを指します。今回は大量に発射する弾を再利用することによって最適化を測りましょう。
この回はゲーム制作編第01回終了時点のプロジェクトをオブジェクトプーリングの向けに改変したものを使用します。
上記のUnityプロジェクトを開いたらStageシーンを開いてください。
図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のコストが高いといっても、オブジェクトプーリングを行うコストもあるわけですから場合によってはオブジェクトプーリングを行わない場合のほうが結果的に良い場合もあります。
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回 スプライトとスプライトアニメーションの作成
第02回 プレイヤーの移動
第03回 プレイヤーから弾を撃つ
第04回 敵を作成しよう
第05回 当たり判定とアニメーションイベントとレイヤー
第06回 背景を作る
第07回 Wave型の仕組み作り
第08回 音をつける
第09回 プレイヤーの移動制限と様々な修正
第10回 タイトルを付ける
第11回 エネミーのHP、弾の攻撃力、アニメーションの追加
第12回 Waveを5個にする、スコアの実装
第01回 複数の解像度に対応する(黒帯を追加する)
第02回 複数の解像度に対応する(引き伸ばす)
第03回 タッチパネル対応
第04回 バーチャルジョイスティック対応