0から2Dアクションバトルゲームを作ろう!④当たり判定を実装しよう

0からアクションゲームを作ろう講座その4です。
当たり判定とそれに関わる処理を実装し、少しずつ遊べるゲームを目指していきましょう!

この章でやること

前章でプレハブの概念を学び、弾を発射する処理までを実装しました。
しかし当たり判定をとっていないので、弾は敵をただ通過するのみです。

今回は当たり判定を実装し、「オブジェクト同士の接触の検出」と「検出時の処理」を作ってみましょう!(前回の内容が不安な方はこちらから復習しておきましょう)

目標とする動作

今回目標とする仕様は以下の通りです。

  • ボスとプレイヤーは体力の概念を持つ。
  • プレイヤーが発射する弾とボスが接触した時、以下の処理を行う。
    ①弾オブジェクトは消滅する。
    ②ボスの体力を1減らす。
  • ボスは体力が0になった時消滅する。

当たり判定とは?

当たり判定とは「2つの物体が接触しているかどうかの判定」です。

アクションゲーム開発では必須の概念で、Unity以外では基本的に沢山の計算式が必要になります。
ですがUnityではコンポーネントの組み合わせだけで判定をとる部分まで行ってくれますので、プログラミングも簡単に済ませられます。

当たり判定によって難易度が全く異なったり、必要以上に判定領域が広いとユーザーの不満の原因になったりもするので、当たり判定はゲームにおいても大切な要素になります。

それでは実装の手順を見ていきましょう。

当たり判定のコンポーネント

基本的には、まず必要なコンポーネントを2つ取り付け、スクリプトで接触時の処理を書くという流れが一般的です。
なお物理演算をさせたいだけの場合はスクリプトは不要で、コンポーネント取り付けのみで完了します。

今回は物理演算なしの場合の手順をとっていきましょう。

オブジェクト側の設定

判定をとりたい2つのオブジェクト両方に必要なコンポーネントを付けていきます。
1つは剛体情報を管理する「Rigidbody 2D」です。(片側のオブジェクトに付いていればOK)
そして当たり判定の位置や形・大きさ等を設定する「〇〇 Collider 2D」です。

Collider 2Dシリーズにはいくつかの種類があり、それぞれ判定の形状が異なっています。(3Dには3D専用のColliderが存在します)
オブジェクトの見た目にあった物を選びましょう。

  • Box Collider 2Dコンポーネント(上図1番目)
    四角形の当たり判定を持ちます。
  • Circle Collider 2Dコンポーネント(上図2番目)
    円形の当たり判定を持ちます。今回のゲームではこれのみを使います。
  • Capsule Collider 2Dコンポーネント(上図3番目)
    円+四角形の当たり判定を持ちます。
  • Composite Collider 2Dコンポーネント
    複数の当たり判定を統合して1つにできます。
  • Edge Collider 2Dコンポーネント
    線による当たり判定を実装できます。
  • Polygon Collider 2Dコンポーネント
    オブジェクトの見た目に則した形で自動的に形状が作られます。
    便利ですが、動作が重たい上制約もあります。

Bossオブジェクトの設定

コンポーネント追加の方法も今までと同じです。
まずはBossオブジェクトを選択し、「Rigidbody 2D」と「Circle Collider 2D」を追加してください。

ここで設定を少し変えておきます。
まずRigidbody 2Dの設定は、このままでは物理演算が適用されてしまうので演算をオフにする必要があります。
パラメータの[Body Type]がDynamicとなっている所をKinematicに変更してください。

これで物理演算を切る事が出来ました。これをしないと重力に引っ張られて画面下へ落ちていきます…。(倒した時の演出としてはいいかもしれませんがBossとしてはかっこ悪いですね…)

 

次に、Circle Collider 2Dも設定を調整しましょう。シーンビュー上に緑色の枠が表示されていると思いますが、これが当たり判定の形を表しています。ちょっと見た目よりも大きいですね。

コンポーネントのRadiusパラメータはこの円の半径を指定しているので、数値を1くらいまで下げてみましょう。
(ボスのテクスチャを自前で用意されている方などはまた別の数値に調整する必要があります。)

またCircle Collider 2Dはもう1つすべき設定があります。それは[Is Trigger]というチェックボックスへのチェックです。
Is Triggerをオンにするとそのオブジェクトはトリガー状態となり、他のオブジェクトと接触しても物理演算を行わなくなります。
先ほどRigidbody 2D側で物理演算を切ったので、オフのままでも問題はありませんが誤動作の原因となりうるのでオンにしておきましょう。
また、トリガーであるか否かでスクリプトで使用するメソッドも変化します。(後述)


最終的にこのようになっていればOKでしょう。

BulletPrefabの設定

次は、弾プレハブ側の設定です。こちらも同じようにコンポーネントを付けるだけですが、プレハブを対象とした操作で行います。
プロジェクトウィンドウからBulletPrefabを選択し、Circle Collider 2Dを追加しましょう。

こちらでもColliderのRadius(半径)の設定は必要です。
画面で確認をしたい場合、一度インスタンスを生成してから調整すると良いでしょう。
インスタンス側で設定を変えた後は、インスペクターウィンドウのApplyボタンで変更した内容を元のプレハブに反映できます。
とりあえず、値を0.15程度にしてみましょう。
またIs Triggerへのチェックも忘れずにしておきましょう。


インスタンスで調整した場合、そのインスタンスはDeleteで削除しておきましょう。Rigidbody2Dは片側にだけ付けても良いので、弾プレハブでは設定を省略しました。
付けても大丈夫ですが、その場合設定をKinematicにするのを忘れないでください。

 

オブジェクトが小さすぎる場合

他の弾スプライトを使う場合は、オブジェクト自体が小さすぎてColliderのRadiusを設定するのが難しいという場合もあります。


そんな時は、ヒエラルキーウィンドウからオブジェクトをダブルクリックするとそのオブジェクトが拡大されて表示されます。ゲーム内で大きさが異なるオブジェクトを扱う際には非常に役立ちます。

 

スクリプトの準備

オブジェクト側の設定は終わりました。
この状態で既に当たり判定を取る事は出来ているのですが、取った後の処理が書かれていないのでテストプレイをしても変化はありません。
なので、「ボスと弾の接触時の処理」を書いていきましょう。

接触時の処理は弾ではなく、ボスオブジェクトの方で行っていきます。よってボスに取り付けるスクリプトをまずは用意する必要があります。
今までと同じようにスクリプトを作成します。今回は「BossController」という名前にしましょう。
そしてBossオブジェクトに取り付けます。


(コンポーネントの表示は▼をクリックすることで折りたたむ事が出来ます。)

接触時の処理を書こう

それではBossControllerスクリプトを編集していきましょう。

接触時に呼び出されるメソッド

他オブジェクトが当たり判定内に侵入した時などに自動で呼び出されるメソッドがあります。
Start()やUpdate()等と同じ、特殊なメソッドといえるものです。
そのメソッドでは引数として接触オブジェクトの情報が渡されるので、オブジェクトごとに処理の内容を変える事も可能です。

具体的なメソッド名は以下です。(引数まで正確に入力する必要があります!)

  • void OnTriggerEnter2D (Collider2D collider)
    トリガー領域内に他オブジェクトが侵入した時呼び出されます。
  • void OnTriggerStay2D (Collider2D collider)
    トリガー領域内に他オブジェクトが留まっている間呼び出され続けます。
  • void OnTriggerExit2D (Collider2D collider)
    トリガー領域内から他オブジェクトが退出した時呼び出されます。

参考までに、Is Triggerでない場合に呼び出されるメソッドも記しておきます。

  • void OnCollisionEnter2D (Collision2D collision)
    コライダーに他オブジェクトが接触した時呼び出されます。
  • void OnCollisionStay2D (Collision2D collision)
    コライダーに他オブジェクトが接触している間呼び出され続けます。
  • void OnCollisionExit2D (Collision2D collision)
    コライダーから他オブジェクトが離れた時呼び出されます。

2Dと3Dの違いでも少し名前が変わるため注意してください。
(引数名以外は1文字でも間違っていると呼び出されません。引数名のみ任意に指定可能です。)

弾が当たった時に消滅させる

上記のメソッドを実際に利用して接触時の処理を書いてみましょう。
まずは体力の要素無しで、弾が1発でもボスに触れればボスが消滅する動作を実装します。

BossController.cs

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

public class BossController : MonoBehaviour {

	// 当たり判定内に他オブジェクトが侵入した際呼び出されるメソッド
	// 引数:接触オブジェクトしたオブジェクトのCollider情報
	void OnTriggerEnter2D (Collider2D collider)
	{
		// 弾とボスオブジェクトを消滅させる
		Destroy (collider.gameObject);	// 弾オブジェクト消去
		Destroy (gameObject);			// 自オブジェクト消去
	}
}

接触時呼び出しメソッドの使い方は分かりましたか?
これでテストプレイをすると、とりあえずボスを一瞬で倒せるゲームになったのを確認できます。

ボスに体力の要素を付けよう

ボスが一発で倒せてしまうようではやはり味気ないです。
ここは体力の概念を導入して、まずは5発は耐えるようにしてみましょう。

体力を実装した接触処理

publicメンバ変数で体力の管理を行い、被弾時にマイナス1、0になったら消滅…というプログラミングをします。

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

public class BossController : MonoBehaviour {

	// メンバ変数宣言
	public int health; // 体力

	// 当たり判定内に他オブジェクトが侵入した際呼び出されるメソッド
	// 引数:接触オブジェクトしたオブジェクトのCollider情報
	void OnTriggerEnter2D (Collider2D collider)
	{
		// 弾オブジェクトを消滅させる
		Destroy (collider.gameObject);

		// 自身の体力を1減らす
		health--;
		// ボス消滅処理
		if (health <= 0)
		{// ボスの体力が0以下
			Destroy (gameObject); // 自オブジェクト消去
		}
	}
}

体力をスクリプト内で設定してないので、Bossオブジェクト側でインスペクターウィンドウから値をセットしてあげます。

if文の扱いには慣れてきましたか?
今回行った条件分岐では0以下という式にしていますが、「health == 0」でも問題はありません。
ただその場合、今後の新要素の実装等何らかの事情で0を下回ると不死身になってしまう為、とりあえず0以下で条件を取るのが安心だと思います。

動作確認

テストプレイはこまめに行うと良いでしょう。
今の状態ではボスが弾を5発受けると消滅するようになっているはずです。

もしうまくいかない場合はスクリプトやオブジェクトの設定を見直す必要があります。
コンポーネント側での値の設定忘れはありませんか?

プレイヤーにも当たり判定を実装

ボスの攻撃は次章で実装しますが、ここでプレイヤーへの当たり判定も実装しておきましょう。
基本的な流れはボスと同じですが、ボスのスクリプトを含め少し変更点があります。

Playerオブジェクトの設定

コンポーネントの追加と設定は今までと同じで大丈夫です。
Rigidbody2DとCircleCollider2Dをセットし、「Kinematic化」と「Is Triggerのオン」、そして「判定の大きさ」の3つを設定します。
判定の大きさについては、プレイヤーはRadius(半径)0.35程で良いです。
シューティングを作る場合でも言えるのですが、プレイヤーキャラクターの喰らい判定は小さめにすると親切なゲームになります。

参考スクリプト

まずPlayerControllerに敵弾接触時の処理を書き込みます。体力も同時に実装します。
自弾発射の処理にも変更点があるので注意してください。

PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
	// メンバ変数宣言
	public GameObject bulletPrefab; // 弾のプレハブ
	private int health; // 体力

	// 起動時に1回だけ呼び出されるメソッド
	void Start ()
	{
		health = 3;
	}

	// 毎フレーム呼び出されるメソッド
	void Update ()
	{
		// -----移動処理-----
		// マウスカーソルの座標をVector2型変数cursorPosに取得
		Vector2 cursorPos = Input.mousePosition;
		// cursorPos内の座標データをスクリーン座標からワールド座標に変換する
		cursorPos = Camera.main.ScreenToWorldPoint (cursorPos);
		// Transformコンポーネントのpositionに計算したcursorPosを代入
		transform.position = cursorPos;

		// -----弾発射処理-----
		if (Input.GetMouseButtonDown (0))
		{ // 左クリックを押された瞬間
		  // GameObject型ローカル変数を宣言 (生成したインスタンスを格納する)
			GameObject obj;
			// 弾プレハブのインスタンスを生成し、変数objに格納
			obj = Instantiate (bulletPrefab);
			// 弾インスタンスの座標にプレイヤーの座標をセット
			obj.transform.position = transform.position;
			// インスタンスのオブジェクト名を変更(敵弾と区別するため)
			obj.name = "PlayerBullet";
		}
	}

	// 当たり判定内に他オブジェクトが侵入した際呼び出されるメソッド
	void OnTriggerEnter2D (Collider2D collider)
	{
		// ボスが発射した弾でなければ処理を終了
		if (collider.gameObject.name != "EnemyBullet")
		{ // 接触オブジェクト名がEnemyBulletで無ければ
			return; // メソッド終了
		}

		// 弾オブジェクトを消滅させる
		Destroy (collider.gameObject);

		// 自身の体力を1減らす
		health--;
		// プレイヤー消滅処理
		if (health <= 0)
		{// プレイヤーの体力が0以下
			Destroy (gameObject); // 自オブジェクト消去
		}
	}
}

接触処理や体力機能は先ほど確認した通りですが、
今回は弾発射時に「PlayerBullet」という名前を付け、被弾判定時も「EnemyBullet」で無いかという条件分岐をかけています。

(ゲームオブジェクト変数).nameはオブジェクト名を指しているので、スクリプトからオブジェクト名を変えたり参照する事が可能です。
ちなみにスクリプト中で文字列を扱う場合、文字列の前後は”(ダブルクォーテーション)で囲みます。

コメントでも説明がありますが、この処理は「プレイヤーが発射した弾」と「ボスが発射した弾」を判定処理内で区別する必要がある為に行っているものです。
この処理が無いと、プレイヤーは弾を撃った瞬間自分に命中しダメージを受けてしまいます。
今回はプレイヤーの弾に「PlayerBullet」を、ボスの弾に「EnemyBullet」という名前を付けて管理していきます。

そして次はBossControllerの編集に入ります。
こちらもオブジェクト名を使った管理に対応させましょう。

BossController内OnTriggerEnter2Dメソッド

	void OnTriggerEnter2D (Collider2D collider)
	{
		// プレイヤーが発射した弾でなければ処理を終了
		if (collider.gameObject.name != "PlayerBullet")
		{ // 接触オブジェクト名がPlayerBulletで無ければ
			return; // メソッド終了
		}

		// 弾オブジェクトを消滅させる
		Destroy (collider.gameObject);

		// 自身の体力を1減らす
		health--;
		// ボス消滅処理
		if (health <= 0)
		{// ボスの体力が0以下
			Destroy (gameObject); // 自オブジェクト消去
		}
	}

これで次章実装するボスの攻撃でボス自身がダメージを受ける事は無くなります。

ここまでのおさらい

アクションゲームでは必須となる概念「当たり判定」について学び、実装してきました。

2Dの当たり判定で必要になるのは「Rigidbody 2D」と「〇〇 Collider 2D」の2コンポーネントです。
このゲームのように物理演算が必要ない場合、Kinematic化の設定とIs Triggerの設定が必要です。
またオブジェクトの形状によってコライダーの種類や大きさは適宜調整しましょう。

スクリプト側では当たり判定検出時に自動で呼び出されるメソッドを使い、任意の処理を書き込むことができます。
今回はOnTriggerEnter2D()メソッドを使いましたが、
「呼び出しタイミング」「IsTriggerの状態」「2Dか3D」で使用するメソッドが細かく変化する事に注意してください。
また、メソッド名や引数の型名を間違えると全く呼び出されなくなる事にも注意が必要です。

次章で学ぶこと

次章ではいよいよボスの攻撃を実装します。やっとゲームらしい状態になりますよ!
ここまでの知識を幅広く使用するので、特にスクリプトの基礎が不安だという方は復習しておくと良いでしょう。

 

<前回>  <=  「③弾を発射させてみよう

<次回>  =>  「⑤ボスの攻撃を作ろう

オススメアプリ!渋谷で気軽に友達を作れるアプリが登場!