今回はUnity公式サイトに掲載されているチュートリアルの2Dシューティングに挑戦。敵の作成と当たり判定のステップを行いました。チュートリアルを進めながら変更した箇所などをピックアップしています。
前回の記事はこちら。
Unity:自機の移動と弾の発射~公式チュートリアル 2Dシューティングに挑戦
敵を作成しよう
チュートリアルページ:敵を作成しよう
このステップの手順は次の通りです。
- スクリプトの使い回し
- 敵を表示する
- 敵専用のスクリプトを書く
Spaceship.csの作成
このステップではまず自機と敵機で共通の処理(機体の移動と弾の発射)をスクリプトSpaceship で実装し直します。チュートリアルに掲載されているスクリプトにコメントを加えました。また Rigidbody2D コンポーネントの取得は Awake 処理に移動しました。
using System.Collections; using System.Collections.Generic; using UnityEngine; // Rigidbody2Dコンポーネントを必須にする [RequireComponent(typeof(Rigidbody2D))] public class Spaceship : MonoBehaviour { // 機体の移動スピードを格納する変数 public float speed; // 弾を撃つ間隔を格納する変数 public float shotDelay; // 弾のプレハブを格納する変数 public GameObject bullet; // Rigidbody2D コンポーネントを格納する変数 private Rigidbody2D rb; // ゲーム起動時の処理 void Awake() { // Rigidbody2D コンポーネントを取得して変数 rb に格納 rb = GetComponent<Rigidbody2D>(); } // 弾を作成する処理 public void Shot(Transform origin) { // 弾を引数 origin と同じ位置/角度で作成 Instantiate(bullet, origin.position, origin.rotation); } // 機体の移動処理 public void Move(Vector2 direction) { // Rigidbody2D コンポーネントの velocity に方向と移動速度を掛けた値を渡す rb.velocity = direction * speed; } }
スクリプト Spaceship で Rigidbody2D コンポーネントの取得を Awake() 関数で処理した理由
スクリプト Enemy の Start() 関数では、スクリプト Spaceship の呼び出しと機体の移動処理 spaceship.Move を実行しています。
// ゲームのスタート時の処理 void Start() { // スクリプト Spaceship コンポーネントを取得 spaceship = GetComponent<Spaceship>(); // ローカル座標のY軸のマイナス方向(画面上から下)に移動する spaceship.Move(transform.up * -1); // 弾の発射処理(コルーチン Shot )を実行 StartCoroutine("Shot"); }
スクリプト Spaceship の Start() 関数で Rigidbody2D コンポーネントの取得を行うと、スクリプト Enemy の機体移動処理 spaceship.Move(transform.up * -1) と同時に処理されるため、Rigidbody2D コンポーネントの参照が間に合わずにエラーが発生する場合がありました。
RequireComponent
作成した Spaceship スクリプトを自機や敵機に割り当てると Loosing prefab のダイアログが表示されます。スクリプト Spaceship から強制的に Rigidbody2D コンポーネントを追加するのでプレハブ構造が解除されますが続けますか?という質問なので[Continue]ボタンをクリック。
先に作成した自機制御のスクリプト Player の修正です。こちらも Rigidbody2D コンポーネントの取得は Start 処理に移動したままで弾を発射する処理のみコルーチンにしています。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { // スクリプト Spaceship コンポーネントを格納する変数 Spaceship spaceship; // ゲームのスタート時の処理 void Start() { // スクリプト Spaceshipコンポーネントを取得 spaceship = GetComponent<Spaceship>(); // 弾の発射処理(コルーチン Shot )を実行 StartCoroutine("Shot"); } // ゲーム実行中の繰り返し処理 void Update() { // 右・左のデジタル入力値を x に渡す float x = Input.GetAxisRaw("Horizontal"); // 上・下のデジタル入力値 y に渡す float y = Input.GetAxisRaw("Vertical"); // 移動する向きを求める // x と y の入力値を正規化して direction に渡す Vector2 direction = new Vector2(x, y).normalized; // Spaceship コンポーネントの Move() 処理を実行 spaceship.Move(direction); } // 弾の発射処理(コルーチン) IEnumerator Shot() { while (true) { // 弾をプレイヤーと同じ位置/角度で作成 spaceship.Shot(transform); // shotDelay 秒待つ yield return new WaitForSeconds(spaceship.shotDelay); } } }
弾の作成
敵機の弾オブジェクトのプレハブを作成したのでプレハブはこのようになりました。
弾の発射
この手順で掲載されているスクリプト Enemy の内容もコメントを加えて弾を発射する処理は新しくコルーチン Shot に書いています。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour { // スクリプト Spaceship コンポーネントを格納する変数 Spaceship spaceship; // ゲームのスタート時の処理 void Start() { // Spaceshipコンポーネントを取得 spaceship = GetComponent<Spaceship>(); // ローカル座標のY軸のマイナス方向(画面上から下)に移動する spaceship.Move(transform.up * -1); // 弾の発射処理(コルーチン Shot )を実行 StartCoroutine("Shot"); } // 弾の発射処理(コルーチン) IEnumerator Shot() { while (true) { // 子要素を全て取得する for (int i = 0; i < transform.childCount; i++) { // Transformコンポーネント shotPosition を作成して子要素を格納 Transform shotPosition = transform.GetChild(i); // 弾をプレイヤーと同じ位置/角度で作成 spaceship.Shot(shotPosition); } // shotDelay 秒待つ yield return new WaitForSeconds(spaceship.shotDelay); } } }
当たり判定とアニメーションイベントとレイヤー
チュートリアルページ:当たり判定とアニメーションイベントとレイヤー
このステップの手順は次の通りです。
- プレイヤーに当たり判定を付ける
- エネミーに当たり判定を付ける
- 弾に当たり判定を付ける
- スクリプトから当たり判定を検出する
- 爆発の制御
- 爆発した後のゲームオブジェクト削除
- 弾とエネミーが削除されるエリアを作る
- PlayerBulletゲームオブジェクトの削除
プレイヤーに当たり判定を付ける
チュートリアルでは当たり判定に Box Collider 2D を使用してかなり小さい当たり判定にしていましたが、敵機と同じく Polygon Collider 2D を使用して設定しました。
弾に当たり判定を付ける
チュートリアルでは弾のあたり判定の設定を「Circle Collider 2DのRudiusを0.14にして」と書いてありましたが、プロジェクトフォルダにある完成見本のプレハブ EnemyBullet を確認すると Rudius 0.07 になっていたので、そちらに合わせました。
アニメーションイベント
チュートリアルではプレハブ Explosion シーンに配置してから[Animation]ウィンドウを編集していましたが、別の方法も試してみました。
プレハブ Explosion シーンに配置せず、[Project]ウィンドウ > Animations > Explosion > Explode を選択。
[Animation]ウィンドウを開いて Event を作成。
[Inspector]ウィンドウ > Function フィールドに OnAnimationFinish と入力する方法でも大丈夫でした。
弾とエネミーが削除されるエリアを作る
こちらの手順で作成するスクリプト DestroyArea にコメントを追加しました。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DestroyArea : MonoBehaviour { // トリガーから出た時の処理 // 衝突した相手の Collider2D コンポーネントを引数 c に格納 void OnTriggerExit2D(Collider2D c) { // 引数 c に格納されたコンポーネント(オブジェクト)を削除 Destroy(c.gameObject); } }
スクリプトでレイヤー制御
この手順で修正したスクリプト Player にコメントを追加しました。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { // スクリプト Spaceship コンポーネントを格納する変数 Spaceship spaceship; // ゲームのスタート時の処理 void Start() { // Spaceshipコンポーネントを取得 spaceship = GetComponent(); // 弾の発射処理(コルーチン Shot )を実行 StartCoroutine("Shot"); } // ゲーム実行中の繰り返し処理 void Update() { // 右・左のデジタル入力値を x に渡す float x = Input.GetAxisRaw("Horizontal"); // 上・下のデジタル入力値 y に渡す float y = Input.GetAxisRaw("Vertical"); // 移動する向きを求める // x と y の入力値を正規化して direction に渡す Vector2 direction = new Vector2(x, y).normalized; // Spaceship コンポーネントの Move() 処理を実行 spaceship.Move(direction); } // トリガーに入った時の処理 // 衝突した相手の Collider2D コンポーネントを引数 c に格納 void OnTriggerEnter2D(Collider2D c) { // レイヤー名を取得して layerName に格納 string layerName = LayerMask.LayerToName(c.gameObject.layer); // レイヤー名が Bullet (Enemy) の場合 if (layerName == "Bullet (Enemy)") { // 弾の削除 Destroy(c.gameObject); } // レイヤー名が Bullet (Enemy) または Enemy の場合 if (layerName == "Bullet (Enemy)" || layerName == "Enemy") { // 爆発処理 // スクリプト Spaceship の関数 Explosion() を実行 spaceship.Explosion(); // プレイヤーを削除 Destroy(gameObject); } } // 弾の発射処理(コルーチン) IEnumerator Shot() { while (true) { // 弾をプレイヤーと同じ位置/角度で作成 spaceship.Shot(transform); // shotDelay 秒待つ yield return new WaitForSeconds(spaceship.shotDelay); } } }
エネミーの当たり判定
こちらの手順で修正したスクリプト Player にコメントを追加しました。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour { // スクリプト Spaceship コンポーネントを格納する変数 Spaceship spaceship; // ゲームのスタート時の処理 void Start() { // Spaceshipコンポーネントを取得 spaceship = GetComponent<Spaceship>(); // ローカル座標のY軸のマイナス方向(画面上から下)に移動する spaceship.Move(transform.up * -1); // 弾の発射処理(コルーチン Shot )を実行 StartCoroutine("Shot"); } // トリガーに入った時の処理 // 衝突した相手の Collider2D コンポーネントを引数 c に格納 void OnTriggerEnter2D(Collider2D c) { // レイヤー名を取得して layerName に格納 string layerName = LayerMask.LayerToName(c.gameObject.layer); // レイヤー名がBullet (Player)以外の時は何も行わない if (layerName != "Bullet (Player)") return; // 弾の削除 Destroy(c.gameObject); // 爆発処理 // スクリプト Spaceship の関数 Explosion() を実行 spaceship.Explosion(); // エネミーの削除 Destroy(gameObject); } // 弾の発射処理(コルーチン) IEnumerator Shot() { // canShotがfalseの場合、ここでコルーチンを終了させる if (spaceship.canShot == false) { yield break; } while (true) { // 子要素を全て取得する for (int i = 0; i < transform.childCount; i++) { // Transformコンポーネント shotPosition を作成して子要素を格納 Transform shotPosition = transform.GetChild(i); // 弾をプレイヤーと同じ位置/角度で作成 spaceship.Shot(shotPosition); } // shotDelay 秒待つ yield return new WaitForSeconds(spaceship.shotDelay); } } }
WebGL
ここまでの内容を動作確認用にWebGLでビルドしてみました。ゲーム画面の縦横比と オブジェクトが削除されるエリア DestroyArea はWebページ埋め込み用に16:9に変更。敵機を複数配置して移動速度と弾の発射タイミングを調整しました。
画像クリックで再生(ファイルサイズ:約4MB)