1. はじめに
こんにちは、azarashin です。 ゲームを開発しようと興味を持った人の多くは2Dシューティングを作成してみたいと考えています(断言?) …と言いたくなるくらい、2Dシューティングというのはオーソドックスなゲームの代表例の一つだと言えると考えています(^-^; この2Dシューティングゲームを開発するにあたって必ず必要になるのが当たり判定処理です。 本記事ではUnity のCollider+RigidBody 2Dを利用した当たり判定の検出について紹介していきたいと思います。
2. 準備
2.1. 今回使用するアセット
2Dシューティングゲームを作るにあたり、自機や敵・弾などのグラフィックを準備する必要があります。 今回はUnity の無償アセットのうち下記を使用して2Dシューティングゲームの一部を作成していきます。 (以下、「無償アセット」と表記していきます。)
2.2. キャラを作る
まずは敵キャラを作成します。今回は当たり判定を検出することを目的としていますので、敵キャラは大きなものを一つ準備し、弾幕を張らせるような環境を構築していきます。 Hierarchy 上で右クリックし、2D Object → Sprites → Square を選択してください。
これでScene 欄及びHierarchy 欄にスプライトオブジェクトが生成されますが、初期状態では絵が割り当てられていないので真っ白な四角のままです。
そこで、無償アセットから適当なスプライト画像を選択し、先ほど作成したスプライトオブジェクトのSprite Renderer → Sprite の欄にドラッグ&ドロップします。
これで敵の画像がScene 欄に表示されるようになります。
以上の作業をプレイヤーキャラ(自機)及び敵の弾も実施して、 同様にスプライトオブジェクトとして追加してください。
2.3. 敵の弾をprefab化する
先ほど追加したスプライトオブジェクトのうち、敵の弾はゲーム進行中にて大量に生成することを想定しておく必要があります。 そこで敵の弾として生成したスプライトオブジェクト(Bullet)をPrefab 化します。 Hierarchy 欄にあるBullet をProject 欄の適当な場所にドラッグ&ドロップし、Prefab化してください。
2.4. プレイヤーを動かす
続いてプレイヤーを動かす処理を記述します。 本格的なゲームであればキーボードやゲームパッドなど複数のデバイスに柔軟に対応させるためにInput System を使うことが多いかと思いますが、今回は簡易的にプログラムを記述するため、旧式のInput Manager を使ってコードを記述していきます。
using UnityEngine; public class ShootingPlayer : MonoBehaviour { [SerializeField] float _speed = 0.1f; void Update() { UpdateMove(); } private void UpdateMove() { if (Input.GetKey(KeyCode.A)) { // 左へ移動 transform.position += -Vector3.right * _speed; } if (Input.GetKey(KeyCode.D)) { // 右へ移動 transform.position += Vector3.right * _speed; } if (Input.GetKey(KeyCode.W)) { // 上へ移動 transform.position += Vector3.up * _speed; } if (Input.GetKey(KeyCode.S)) { // 下へ移動 transform.position += -Vector3.up * _speed; } } }
上記のように記述することで、ShootingPlayer コンポーネントがアタッチされたGameObject を キーボード入力(W, S, A, D)により上下左右に動かすことができるようになります。 上記のShootingPlayer コンポーネントを自機用のスプライトオブジェクトに付与しましょう。
これでキーボード入力により自機を動かす環境が出来上がりました。
2.5. 敵に弾を発射させる
続いて敵に弾を発射させるプログラムを記述します。 下記のようなShootingEnemy クラスを追加してください。
using UnityEngine; public class ShootingEnemy : MonoBehaviour { [SerializeField] GameObject _prefabBullet; [SerializeField] float _bulletVelocity = 2.0f; [SerializeField] float _interval = 0.125f; // 弾を発射する時間間隔 [SerializeField] float _bulletLife = 10.0f; // 弾の生存時間(秒) float _remainingTime = 0.0f; // 次に弾を発射するまでの残り時間 void Update() { _remainingTime -= Time.deltaTime; if(_remainingTime < 0.0f) { _remainingTime = _interval; GameObject bullet = Instantiate(_prefabBullet); bullet.transform.position = transform.position; // 弾の座標を敵の座標に合わせる // bullet.Setup(NewBulletSpeed()); bullet.GetComponent<Rigidbody2D>().velocity = NewBulletSpeed(); Destroy(bullet, _bulletLife); // _bulletLife 秒後に弾を消滅させる } } Vector3 NewBulletSpeed() { float vx = Random.value * 2.0f - 1.0f; // -1.0f ~ 1.0f float vy = -Random.value; // -1.0f ~ 0.0f if(vx == 0.0f && vy == 0.0f) { vy = 1.0f; // vx, vy 両方とも0だと弾が動かないので強制的に値を変更する } Vector3 velocity = new Vector3(vx, vy, 0.0f); velocity.Normalize(); // ベクトルの長さが1になるように正規化する velocity *= _bulletVelocity; return velocity; } }
敵の処理で特徴的なポイントは下記のとおりです。
- 弾を発射する=生成する元になる弾のprefab を保持させ、Instantiate メソッドで弾を複製する
- 残り時間(remainingTime )を最初に決めておく(初期値はinterval)
- 都度残り時間を減らしていき、残り時間が0以下になったら弾を生成して残り時間を初期値(_interval)に戻す
- 弾が永久に残留することを防ぐため、Destroy メソッドを使って一定時間後(_bulletLife)に弾を消滅させる
これをコードで記述すると下記のようになります。
_remainingTime -= Time.deltaTime; if(_remainingTime < 0.0f) { _remainingTime = _interval; GameObject bullet = Instantiate(_prefabBullet); bullet.transform.position = transform.position; // 弾の座標を敵の座標に合わせる // bullet.Setup(NewBulletSpeed()); bullet.GetComponent<Rigidbody2D>().velocity = NewBulletSpeed(); Destroy(bullet, _bulletLife); // _bulletLife 秒後に弾を消滅させる }
弾の速度はランダムにし、下左右(上方向には飛ばさない)ようにします。
Vector3 NewBulletSpeed() { float vx = Random.value * 2.0f - 1.0f; // -1.0f ~ 1.0f float vy = -Random.value; // -1.0f ~ 0.0f if(vx == 0.0f && vy == 0.0f) { vy = 1.0f; // vx, vy 両方とも0だと弾が動かないので強制的に値を変更する } Vector3 velocity = new Vector3(vx, vy, 0.0f); velocity.Normalize(); // ベクトルの長さが1になるように正規化する velocity *= _bulletVelocity; return velocity; }
最後に、先ほど作成した弾のprefab をShootingEnemy コンポーネントの_prefabBullet にドラッグ&ドロップで割り当てておきます。
以上の作業を行うことで、以下のように敵が弾を乱射するようになります。
3. 弾と自機との当たり判定を検出する
さて、前置きが非常に長くなりました(今までのは前置きです!) これから弾と自機との当たり判定を検出する方法について説明していきます。
3.1. BoxCollider2D を割り当てる
まず自機と弾のスプライトオブジェクトにBoxCollider2D コンポーネントを付与します この時、それぞれのBoxCollider2D コンポーネントのIs Trigger にチェックを入れておきます。 これで弾が自機に衝突したときに弾が自機にめり込むようになります。 逆に言うと、これにチェックを入れておかないと弾が自機に衝突したときに、互いにめり込まないようそれぞれの位置が補正されてしまいます。
BoxCollider2D コンポーネントを付与した時点でスプライトオブジェクトの画像の範囲に合わせて当たり判定(緑の四角領域)が設定されますが、 もしこの範囲を変更したい場合はBoxCollider2D コンポーネントのEdit Collider 横のアイコンをクリックして範囲を変更することができます。 シューティングゲームの場合は画像の見た目と当たり判定とが一致しないことが多いですので、活用していきましょう。
3.2. RigidBody2D を割り当てる
続いて自機と弾のスプライトオブジェクトにRigidBody2D を割り当てます。 今回は重力の影響を無視しますので、Gravity Scale の欄を0 にしておきます。
3.3. タグを割り当てる
一般的に様々なオブジェクトに対して当たり判定処理が実施されます。 そのため、自機が当たり判定を検出したときに相手が弾かどうかを判断する必要があります。 ここでは相手が何かを判定するため、弾のスプライトオブジェクトにBullet という名のタグを割り当てるようにします。
3.4. 当たり判定を検出するためのプログラム
当たり判定を検出するにはOnTriggerEnter2D というメソッドを使用します。 これを当たり判定を求めたいスプライトオブジェクト(正確にはGameObject)にて定義すると、 当たり判定が発生したときにこのメソッドが呼び出されるようになります。 今回は当たり判定が発生したかどうかを判断することが目的ですので、 当たり判定を検出したら自機を消滅させるようにします。
using UnityEngine; public class ShootingPlayer : MonoBehaviour { // ... private void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("Bullet")) // 弾と衝突したらプレイヤーを消滅させる { gameObject.SetActive(false); } } }
以上の作業を行うと、下記のような動きをするようになります。
4. 終わりに
冒頭にも記述しましたが、2Dシューティングゲームを開発するにあたって当たり判定処理は非常に重要な位置づけとなります。 実際にはシューティングだけでなくアクションゲーム系においても同様によく使われる機能です。 もしシューティング系やアクション系のゲーム開発に興味があるようでしたら本記事を参考にしながら 最小構成のゲームから作成し、徐々にオリジナリティの高いゲームへ進化させてみてはいかがでしょうか?