【Unity】2Dシューティングゲームで軽い処理で弾を撃つ方法
シューティングゲームの弾のように大量に同じオブジェクトを使う必要がある場合、「Instantiateで生成してDestroyで破棄」という処理を都度、行なっていると処理がとても重くなる場合があります。
ここでは「オブジェクトプーリング」と呼ばれる方法を使ってオブジェクトを使い回し、処理を軽くする方法を説明しています。
この手の記事は「オブジェクトプール unity」とかで検索すると大量に出てくるのですが、スクリプトがペタッと貼ってあるだけの記事が多くて実際に使ってみる時に少し迷ったので、実装手順も含めた記事にしてまとめておきます。
なお、この記事は以下の記事で紹介されているスクリプトを参考にさせていただいています。
【Unity】オブジェクトプーリングでオブジェクト節約術環境
- Unity 2017.3.1f1
このページの構成
ポイント
- オブジェクトプーリングとは、オブジェクトをプールして(=溜めて)使い回すことで、生成(Instantiate)と破棄(Destroy)のコストを削減する方法
- 手順としては以下の通り
- ① 最初にある程度の数を生成しておき、画面外に溜めておく
- ② Rigidbodyのsimulatedをfalseにしておく(物理演算の停止&未使用かどうかの判定に使用)
- ③ 必要になったらそこからオブジェクトを持ってきて画面に表示し、Rigidbodyのsimulatedをtrueにする
- ④ 足りなくなっていたら新しく生成する
- ⑤ 使い終わったらまた画面外に戻してRigidbodyのsimulatedをfalseにしておく
事前準備
プレイヤーと弾の表示
まず始めに、プレイヤーと弾を用意します。素材がない場合は以下の画像を使用してください。


なお、プレイヤー画像は以下のサイトのものを使用させていただいています。ありがとうございます。
- Sprite design(Player) – MillionthVector
素材が用意できたら、シーンビューにプレイヤーと弾のsprite画像をドラッグ&ドロップして表示させておいてください。位置は適当で大丈夫です。

弾の移動
弾を移動させる処理を追加します。
先ほどシーンビューに表示させた弾を選択して、「Rigidbody2D」と「CapsuleCollider2D」コンポーネントを追加してください。
また、重力は不要なので「Rigidbody2D」の「Gravity Scale」は0 にしておいてください。

次に以下のスクリプトを作成し、弾のコンポーネントに追加してください。ここでは「BurretController」という名前にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class BurretController : MonoBehaviour { private const int SPEED = 10; //弾の速さ private float _screenTop; // 画面の一番上のy座標。画面外かどうかの判定に使用 private Rigidbody2D _rb; private Transform _tf; void Awake() { _rb = GetComponent<Rigidbody2D>(); _tf = this.transform; // 画面の一番上のy座標を取得 _screenTop = Camera.main.ViewportToWorldPoint(new Vector2(0, 1)).y; // 弾を上に移動させる _rb.velocity = _tf.up.normalized * SPEED; } private void Update() { // Rigidbody2Dのsimulatedがfalse(弾が使われていない状態)であれば何もしない if(_rb.simulated == false) return; // ここからはRigidbody2Dのsimulatedがtrueの場合(=弾が動いている場合) // 画面外に弾が出ていたらRigidbody2Dのsimulatedをfalseにして物理演算を止める(弾をストップする) // +1しているのは余裕を持っているだけです。 if(_tf.position.y > _screenTop + 1) { _rb.simulated = false; } } } |
ゲームを再生してみてください。
弾が画面外で止まり、「Rigidbody2D」の「simulated」のチェックが外れていればOKです。
ここまで出来ていれば弾の準備は完了です。
弾をprefab化してシーンビューからは削除しておいてください。

弾の発射
次にプレイヤーから弾を発射する処理を追加します。
以下のスクリプトを作成し、プレイヤーのコンポーネントに追加してください。ここでは「PlayerController」という名前にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { [SerializeField] private GameObject _burret; //弾のプレファブ。inspectorで指定する private float _shotDelay = 0.1f; //弾を打つ間隔 private Transform _tf; void Start () { _tf = this.transform; // 弾を打つコルーチンを呼び出す StartCoroutine(ShotBurret()); } IEnumerator ShotBurret() { while (true) { // 弾を撃つ処理を呼んで Shot(); // shotDelay秒待つ yield return new WaitForSeconds (_shotDelay); } } private void Shot () { // 弾をプレイヤーと同じ位置/角度で作成 Instantiate (_burret, _tf.position, _tf.rotation); } } |
また、inspectorで先ほど作成した弾のプレファブをセットしておいてください。

この状態でゲームを再生してみてください。
弾が連続で発射され、画面外に大量に溜まるようになっていれば現時点ではOKです。
このままだと延々と弾が増えてしまうので、次のセクションで画面外に使われていない弾があればそこから弾を持ってくるように修正します。
オブジェクトプール
プールの作成
オブジェクトの使い回しを管理するスクリプトを作成します。
先ほどは”PlayerController”で弾の生成を行なっていましたが、弾の生成から使い回しまで管理は全てこちらのスクリプトに移して、”PlayerController”は弾を使うだけにしていきます。
まず空のゲームオブジェクトを作成し、以下のスクリプトをアタッチしてください。ここではオブジェクト名を”pool”、スクリプト名を”BurretPool”という名前にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class BurretPool : MonoBehaviour { [SerializeField] private GameObject _poolObj; // プールするオブジェクト。ここでは弾 private List<GameObject> _poolObjList; // 生成した弾用のリスト。このリストの中から未使用のものを探したりする private const int MAXCOUNT = 10; // 最初に生成する弾の数 void Awake() { CreatePool(); } // 最初にある程度の数、オブジェクトを作成してプールしておく処理 private void CreatePool() { _poolObjList = new List<GameObject>(); for (int i = 0; i < MAXCOUNT; i++) { var newObj = CreateNewBurret(); // 弾を生成して newObj.GetComponent<Rigidbody2D>().simulated = false; // 物理演算を切って(=未使用にして) _poolObjList.Add(newObj); // リストに保存しておく } } // 未使用の弾を探して返す処理 // 未使用のものがなければ新しく作って返す public GameObject GetBurret() { // 使用中でないものを探して返す foreach (var obj in _poolObjList) { var objrb = obj.GetComponent<Rigidbody2D>(); if (objrb.simulated == false) { objrb.simulated = true; return obj; } } // 全て使用中だったら新しく作り、リストに追加してから返す var newObj = CreateNewBurret(); _poolObjList.Add(newObj); newObj.GetComponent<Rigidbody2D>().simulated = true; return newObj; } // 新しく弾を作成する処理 private GameObject CreateNewBurret(){ var pos = new Vector2(100,100); // 画面外であればどこでもOK var newObj = Instantiate(_poolObj, pos, Quaternion.identity); // 弾を生成しておいて newObj.name = _poolObj.name + (_poolObjList.Count + 1); // 名前を連番でつけてから return newObj; // 返す } } |
弾の管理方法の変更
弾の管理を全て”BurretPool”で行うように変更していきます。
まず”BurretPool”で弾を生成することができるようにします。
poolのinspectorに弾のプレファブをセットしてください。

次にプレイヤー側の処理を変更していきます。
プレイヤー側では弾の生成は行わず、”BurretPool”から未使用の弾をもらって、プレイヤーの位置に移動させるように”PlayerController”を変更します。
変更後のスクリプトは下記の通りです。修正点はコメントでも書いています(ここ追加①〜③とここ削除①〜②)ので、”PlayerController”を修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { //[SerializeField] //ここ削除① //private GameObject _burret; //弾のプレファブ。inspectorで指定する private float _shotDelay = 0.1f; //弾を打つ間隔 private Transform _tf; private BurretPool _pool; //ここ追加① void Start () { _tf = this.transform; // ここ追加②。プールへの参照を保存しておく _pool = GameObject.Find("pool").GetComponent<BurretPool>(); // 弾を打つコルーチンを呼び出す StartCoroutine(ShotBurret()); } IEnumerator ShotBurret() { while (true) { // 弾をプレイヤーと同じ位置/角度で作成 Shot(); // shotDelay秒待つ yield return new WaitForSeconds (_shotDelay); } } private void Shot () { // ここ追加③。プールから弾をもらう var burret = _pool.GetBurret(); burret.transform.localPosition = _tf.position; //ここ削除②。弾をプレイヤーと同じ位置/角度で作成 //Instantiate (_burret, _tf.position, _tf.rotation); } } |
以上で基本的な処理は完成です!ゲームを再生してみてください。
弾のTransform情報を見ると画面外と画面内を行ったり来たりして使い回されていることが確認できると思います。

弾を撃つ間隔を短くしたりして最初に生成した10個の弾では足りなくなると、新しく生成されることも確認できると思います。
最初にどれくらい生成しておくかはゲームにもよると思うので、適宜変えてください。