Unity 6でCatEscapeを作る② ― 当たり判定とHPゲージ、スクリプト同士の連携(PC版)
CatEscapeの2回目です。前回は、猫を矢印キーで動かし、Prefabから作った矢を1秒ごとに降らせるところまで作りました。今回は、いよいよ矢が猫に当たったときの処理を作ります。
Unity 6でCatEscapeを作る① ― 猫の移動と、Prefabで矢を降らせる仕組み(PC版)
| 回 | 内容 |
|---|---|
| ①(前回) | 猫を矢印キーで動かす/Prefabで矢を作る/矢を一定間隔で降らせる/画面の外に出た矢を消す |
| ②(今回) | 矢と猫の当たり判定/HPゲージを減らす/スクリプト同士で連絡を取り合う |
この記事で作るもの
- 矢が猫に当たったら、その矢を消す
- 画面の右上に、円形のHPゲージを表示する
- 矢が当たるたびに、HPゲージが1割ずつ減る
| スクリプト・オブジェクト | 今回やること |
|---|---|
ArrowController(矢) | 当たり判定を追加する。当たったら監督に知らせる |
GameDirector(空のオブジェクト) | 新しく作る。HPゲージを減らす役(監督) |
| hpGauge(UIのImage) | 新しく作る。円形のHPゲージ |
1. 当たり判定の考え方 ― 円と円が重なったら当たり
CatEscapeでは、矢と猫をそれぞれ円とみなして、2つの円が重なったかどうかで当たりを判定します。
- 矢を半径 r1 = 0.5 の円、猫を半径 r2 = 1.0 の円と考える
- 矢の中心と猫の中心の距離 d を求める
- d が r1 + r2(= 1.5)より小さければ、円が重なっている = 当たり
| 矢の中心 | 猫の中心 | 中心どうしの距離 d | 判定 |
|---|---|---|---|
| (3, -2.8) | (3, -3.6) | 0.8 | 0.8 < 1.5 → 当たり |
| (4.5, -3.0) | (3, -3.6) | 約1.62 | 1.62 > 1.5 → 当たらない |
| (0, 2.0) | (0, -3.6) | 5.6 | 5.6 > 1.5 → 当たらない |

距離は、2点の座標の差(ベクトル)の「長さ」で求めます。Unityでは Vector2 の引き算で差を作り、magnitude で長さを取り出せます。中学校で習う三平方の定理(√(x² + y²))を、Unityが代わりに計算してくれるイメージです。
Vector2 p1 = transform.position; // 矢の中心座標
Vector2 p2 = player.transform.position; // 猫の中心座標
Vector2 dir = p1 - p2; // 猫から矢へのベクトル
float d = dir.magnitude; // その長さ = 中心どうしの距離
2. ArrowController に当たり判定を追加する
前回作った ArrowController に、当たり判定を追加します。まずは「当たったら矢を消す」ところまで作りましょう。
using UnityEngine;
public class ArrowController : MonoBehaviour
{
GameObject player; // 猫を覚えておく変数
void Start()
{
// シーンの中から猫を探しておく
player = GameObject.Find("player_0");
}
void Update()
{
// 毎フレーム、下に0.1ずつ動かす
transform.Translate(0, -0.1f, 0);
// 画面の下の外に出たら、この矢を消す
if (transform.position.y < -5.0f)
{
Destroy(gameObject);
}
// 当たり判定(円と円)
Vector2 p1 = transform.position; // 矢の中心座標
Vector2 p2 = player.transform.position; // 猫の中心座標
Vector2 dir = p1 - p2;
float d = dir.magnitude; // 中心どうしの距離
float r1 = 0.5f; // 矢の半径
float r2 = 1.0f; // 猫の半径
if (d < r1 + r2)
{
// 当たったら矢を消す
Destroy(gameObject);
}
}
}
再生して、猫を矢の下に動かしてみてください。矢が猫に触れた瞬間に消えれば成功です。
GameObject.Find("player_0") は、シーンの中から名前で猫を探す命令です。猫の名前が player_0 になっているか、Hierarchyで確認しておきましょう。GameObject.Find については、SwipeCarの記事でも解説しています。
Unity 6でスワイプして車を走らせる ― SwipeCarで学ぶスワイプ判定・効果音・距離表示(PC版)
3. HPゲージを作る(UIのImage)
次は、画面の右上に表示する円形のHPゲージです。UIのImageを使います。
- Hierarchyで右クリックし、UI → Image を選ぶ(Canvasも自動で作られる)
- 作られたImageの名前を
hpGaugeに変える - Inspectorの Source Image に
hp_gauge.pngをドラッグ&ドロップする - Rect Transformのアンカーを右上にし、下の表のように位置と大きさを設定する
- Imageの Image Type を Filled に変え、下の表のように設定する
| 設定項目 | 値 | 意味 |
|---|---|---|
| アンカー(Anchor Presets) | 右上(top-right) | 画面の大きさが変わっても、右上からの位置を保つ |
| Pos X / Pos Y | -120 / -120 | 右上の角から、左に120・下に120の位置 |
| Width / Height | 200 / 200 | ゲージの大きさ |
| Image Type | Filled | 画像の一部だけを表示できるようにする |
| Fill Method | Radial 360 | 時計の針のように、円形に減らす |
| Fill Origin | Top | 上(12時の位置)から減り始める |
| Fill Amount | 1 | 表示する割合(1 = 満タン) |
Fill Amount は、画像をどれだけ表示するかを 0〜1 の割合で表します。Inspectorでスライダーを動かしてみると、ゲージが円形に減っていく様子を確かめられます。
| Fill Amount | ゲージの見た目 |
|---|---|
| 1 | 満タン(円がすべて表示される) |
| 0.7 | 3割減った状態 |
| 0.5 | 半分 |
| 0 | 空っぽ(何も表示されない) |

4. HPを減らす監督を作る(GameDirector)
HPゲージを減らす処理は、矢でも猫でもない監督役のスクリプトに任せます。Hierarchyで Create Empty を選んで空のオブジェクトを作り、名前を GameDirector にして、次のスクリプトを付けます。
using UnityEngine;
using UnityEngine.UI; // Image を使うために必要
public class GameDirector : MonoBehaviour
{
GameObject hpGauge; // HPゲージを覚えておく変数
void Start()
{
// シーンの中からHPゲージを探しておく
hpGauge = GameObject.Find("hpGauge");
}
// HPを1割減らす(矢から呼ばれる)
public void DecreaseHp()
{
hpGauge.GetComponent<Image>().fillAmount -= 0.1f;
}
}
DecreaseHp は、ほかのスクリプトから呼び出してもらうためのメソッドです。先頭に public を付けるのがポイントです。public がないと、このクラスの外(ArrowController)からは呼び出せません。
fillAmount -= 0.1f で、ゲージは1回につき1割ずつ減ります。10回当たると0になります。Unityの fillAmount は0〜1の範囲におさまるようになっているので、0より小さくはなりません。
5. 矢から監督に「当たったよ」と知らせる
最後に、矢が猫に当たったときに、GameDirectorの DecreaseHp を呼び出します。ArrowControllerの当たり判定の部分を、次のように書き換えます。これで完成です。
using UnityEngine;
public class ArrowController : MonoBehaviour
{
GameObject player;
void Start()
{
player = GameObject.Find("player_0");
}
void Update()
{
transform.Translate(0, -0.1f, 0);
if (transform.position.y < -5.0f)
{
Destroy(gameObject);
}
// 当たり判定(円と円)
Vector2 p1 = transform.position;
Vector2 p2 = player.transform.position;
Vector2 dir = p1 - p2;
float d = dir.magnitude;
float r1 = 0.5f;
float r2 = 1.0f;
if (d < r1 + r2)
{
// 監督に「猫に当たった」と知らせる
GameObject director = GameObject.Find("GameDirector");
director.GetComponent<GameDirector>().DecreaseHp();
// 当たった矢を消す
Destroy(gameObject);
}
}
}
追加した2行は、次の3つのステップになっています。
| コード | やっていること |
|---|---|
GameObject.Find("GameDirector") | シーンの中から、監督のオブジェクトを探す |
.GetComponent<GameDirector>() | そのオブジェクトに付いている GameDirector スクリプトを取り出す |
.DecreaseHp() | 監督の DecreaseHp メソッドを呼んで、HPを減らしてもらう |

なぜ矢が直接HPゲージを減らさないのか
矢のスクリプトから直接HPゲージを操作することもできます。それでも監督を通すのは、役割を分けておくためです。
| オブジェクト | 担当すること |
|---|---|
| 矢(ArrowController) | 落ちる・消える・当たったら知らせる |
| 監督(GameDirector) | HPを管理して、ゲージの表示を変える |
| HPゲージ(hpGauge) | 言われた割合を表示するだけ |
こうしておくと、「HPが0になったらゲームオーバー」「当たったら効果音を鳴らす」などの機能を追加するときも、直すのは監督だけで済みます。矢のスクリプトは「当たった」と伝えるだけなので、変更の影響が広がりません。
6. もう一歩:GameObject.Find は Start で1回だけ
完成したコードでは、矢が当たるたびに GameObject.Find("GameDirector") を呼んでいます。GameObject.Find はシーン全体を探す命令なので、呼ぶ回数は少ないほうが効率的です。猫と同じように、Start で1回だけ探して変数に覚えておく書き方もできます。
GameObject player;
GameDirector director; // 監督のスクリプトを覚えておく
void Start()
{
player = GameObject.Find("player_0");
director = GameObject.Find("GameDirector").GetComponent<GameDirector>();
}
// 当たったとき
// director.DecreaseHp();
Prefabからは、Inspectorでシーンのオブジェクトを設定できない
SwipeCarの記事では、GameObject.Find の代わりに [SerializeField] でInspectorから設定する方法を紹介しました。しかし矢の場合、この方法は使えません。
矢はPrefabです。Prefabはシーンの外(Projectウィンドウ)にある「型」なので、シーンの中の猫や監督をInspectorでドラッグ&ドロップして設定することができないのです。そのため、Prefabから作られたオブジェクトは、生まれたあとに GameObject.Find などで相手を探す必要があります。
| シーンに最初からあるオブジェクト | Prefabから作るオブジェクト | |
|---|---|---|
| 例 | SwipeCarのGameDirector | CatEscapeの矢 |
| [SerializeField] でシーンの物を設定 | できる | できない |
| 相手の見つけ方 | Inspectorで設定 または Find | Start で Find など |
7. Unityの当たり判定機能(Collider2D)との違い
今回は距離を自分で計算して当たり判定を作りましたが、Unityには当たり判定の機能も用意されています。オブジェクトに CircleCollider2D などのコライダーと Rigidbody2D を付けると、当たった瞬間に OnTriggerEnter2D などのメソッドが自動で呼ばれます。
| 今回の方法(距離を計算) | Collider2D を使う方法 | |
|---|---|---|
| 当たりの形 | 円だけ(自分で計算) | 円・四角・多角形などを選べる |
| 書くコード | 距離と半径の計算 | 当たったときの処理だけ |
| 仕組みの分かりやすさ | 中身が全部見える | Unityにおまかせ |
| 向いている場面 | 仕組みを理解する学習 | 実際のゲーム制作 |
まずは今回の方法で「当たり判定は、距離と大きさの比較でできている」ことを理解しておくと、コライダーを使うときにも何が起きているのかイメージしやすくなります。
8. よくあるつまずき
矢が落ちてきた瞬間に NullReferenceException が出る
NullReferenceException: Object reference not set to an instance of an object
ArrowController.Update ()
GameObject.Find("player_0") で猫が見つかっていません。Hierarchyの猫の名前が player_0 と完全に一致しているか確認しましょう。当たった瞬間に出る場合は、GameDirector の名前を確認します。
NullReferenceExceptionを感覚で直さない
Image が見つからないというエラーが出る
error CS0246: The type or namespace name 'Image' could not be found (are you missing a using directive or an assembly reference?)
GameDirectorの先頭に using UnityEngine.UI; がありません。
DecreaseHp が呼べないというエラーが出る
error CS0122: 'GameDirector.DecreaseHp()' is inaccessible due to its protection level
GameDirectorの DecreaseHp に public が付いていません。public void DecreaseHp() と書きます。
その他のチェックポイント
- 矢が当たってもゲージが減らない → Image Type が Filled になっているか確認する(Simpleのままだと fillAmount を変えても見た目が変わらない)
- ゲージが画面の変な位置にある → アンカーが右上になっているか確認する
- 猫に当たる前に矢が消える/なかなか当たらない → 半径 r1・r2 の値を確認する
9. もう一歩:HPが0になったらゲームオーバー
監督の DecreaseHp に、HPが0になったかどうかのチェックを足してみましょう。
public void DecreaseHp()
{
Image gauge = hpGauge.GetComponent<Image>();
gauge.fillAmount -= 0.1f;
// HPがなくなったら
if (gauge.fillAmount <= 0)
{
Debug.Log("GAME OVER");
}
}
Consoleに「GAME OVER」と表示されれば成功です。ここから、ゲームオーバー画面に切り替えたり、矢の生成を止めたりと、ゲームを発展させていくことができます。こうした追加が監督の中だけで済むのが、役割を分けておいたメリットです。
まとめ
- 当たり判定は「中心どうしの距離 < 半径の和」で判定できる(円と円の当たり判定)
(p1 - p2).magnitudeで2点間の距離を求められる- HPゲージは UIのImage の Image Type を Filled にして、
fillAmount(0〜1)で表示量を変える - ほかのスクリプトから呼ぶメソッドには
publicを付ける GameObject.Find→GetComponent→ メソッド呼び出し、の流れで別のスクリプトに知らせる- 役割を監督(GameDirector)に集めておくと、機能の追加や変更が楽になる
- Prefabからは、シーンのオブジェクトをInspectorで設定できない
CatEscapeで、「オブジェクトを作る・動かす・消す・当たりを調べる・スクリプト同士で連絡する」という、ゲーム作りの基本がひととおりそろいました。CatEscapeの全体の構造は、次の記事でも解析しています。











ディスカッション
コメント一覧
まだ、コメントがありません