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.80.8 < 1.5 → 当たり
(4.5, -3.0)(3, -3.6)約1.621.62 > 1.5 → 当たらない
(0, 2.0)(0, -3.6)5.65.6 > 1.5 → 当たらない
円と円の当たり判定:中心どうしの距離dが半径の和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を使います。

  1. Hierarchyで右クリックし、UI → Image を選ぶ(Canvasも自動で作られる)
  2. 作られたImageの名前を hpGauge に変える
  3. Inspectorの Source Image に hp_gauge.png をドラッグ&ドロップする
  4. Rect Transformのアンカーを右上にし、下の表のように位置と大きさを設定する
  5. Imageの Image Type を Filled に変え、下の表のように設定する
設定項目値意味
アンカー(Anchor Presets)右上(top-right)画面の大きさが変わっても、右上からの位置を保つ
Pos X / Pos Y-120 / -120右上の角から、左に120・下に120の位置
Width / Height200 / 200ゲージの大きさ
Image TypeFilled画像の一部だけを表示できるようにする
Fill MethodRadial 360時計の針のように、円形に減らす
Fill OriginTop上(12時の位置)から減り始める
Fill Amount1表示する割合(1 = 満タン)

Fill Amount は、画像をどれだけ表示するかを 0〜1 の割合で表します。Inspectorでスライダーを動かしてみると、ゲージが円形に減っていく様子を確かめられます。

Fill Amountゲージの見た目
1満タン(円がすべて表示される)
0.73割減った状態
0.5半分
0空っぽ(何も表示されない)
HPゲージはImageのImage TypeをFilled、Fill MethodをRadial 360にし、fillAmountを0.1ずつ減らす

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を減らしてもらう
矢からGameObject.Find、GetComponent、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のGameDirectorCatEscapeの矢
[SerializeField] でシーンの物を設定できるできない
相手の見つけ方Inspectorで設定 または FindStart で 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の全体の構造は、次の記事でも解析しています。

猫が逃げるゲームの解析例(学習目的)

訪問数 2 回, 今日の訪問数 2回

広告