【Unity】1つのゲームオブジェクトと複数のコライダーをアタッチしてそれぞれで検出できるようにする(2Dサンプル)

デフォルトでは、ゲームオブジェクトに複数のコライダーがアタッチされていると複数回のOnTriggerイベントなどが呼び出されます
作成したスクリプトにOnTriggerイベントを実装すると、複数回イベントが実行されるわけです
今回は、それぞれ発生するイベントを個別で処理したい場合について説明します

基本のサンプル

プレイヤーに頭と体の位置に当たり判定用のコライダーを2つ取り付け、それぞれに接触した時、別々のメソッド(イベントハンドラ)が実行されるようにしてみます

実行結果

先に実行した結果を見てみましょう
コンソール画面にそれぞれの当たり判定のメッセージが表示されるのがわかります

シーンの配置

評価のため、コライダーがアタッチされたゲームオブジェクトを配置します

頭側近くの高さに1つと、体側近くの高さに1つゲームオブジェクトを配置します

当たり判定用のコライダーがアタッチされているゲームオブジェクトをそれぞれ用意します

当たり判定検出コライダーをアタッチするため、プレイヤーに2つの子オブジェクトを作成します
子オブジェクトを作成する理由は、ローカル座標で親オブジェクト共に移動できるからです

頭当たり判定用

②の手順の詳細

コライダーの位置を適切な場所に移動しておきます

体当たり判定用

②の手順は頭の場合と同じように選択します(選択するのはOnBodyの方ですから間違わないでください)

コライダーの場所を適当な位置に移動しておきます

当たり判定用のゲームオブジェクトにアタッチするスクリプト

Unity2023バージョン以降の場合

using UnityEngine;
using UnityEngine.Events;

public class Detector : MonoBehaviour
{
    public UnityEvent<Collider2D> OnCollider;
    void OnTriggerEnter2D(Collider2D other)
    {
        // Debug.Log(gameObject.name + "当たり判定");
        OnCollider?.Invoke(other);
    }
}

Unity2022バージョンの場合

using UnityEngine;
using UnityEngine.Events;
using System;

public class Detector : MonoBehaviour
{
    public CustomEvent OnCollider;
    void OnTriggerEnter2D(Collider2D other)
    {
        // Debug.Log(gameObject.name + "当たり判定");
        OnCollider?.Invoke(other);
    }
}

[Serializable]
public class CustomEvent : UnityEvent<Collider2D>
{ }

このコードはUnityのイベントシステムを利用して、特定の条件下(この場合はオブジェクトが他のコライダー2Dに接触した時)でカスタムイベントを発火する方法を示しています。主要なポイントを以下にまとめます。

使用されているUnityのクラスとコンポーネント
  • UnityEngine: Unityの基本的な機能とAPIへのアクセスを提供します。
  • UnityEngine.Events: Unityのイベントシステムに関連するクラスを含んでいます。
  • MonoBehaviour: Unityにおけるすべてのスクリプトの基底クラスで、このクラスを継承することでゲームオブジェクトに添付して動作させることができます。
クラスの定義
  • DetectorクラスはMonoBehaviourを継承しており、Unityのゲームオブジェクトにアタッチ可能なコンポーネントとして機能します。
カスタムイベント
  • public UnityEvent<Collider2D> OnCollider;: UnityEvent<T>を使って、Collider2D型の引数を持つカスタムイベントOnColliderを定義しています。これにより、他のオブジェクトがこのオブジェクトのコライダー2Dに触れた時に特定のアクションを実行できます。
イベントの実行
  • void OnTriggerEnter2D(Collider2D other): このメソッドは、ゲームオブジェクトのCollider2Dがトリガーモードであり、他のCollider2Dオブジェクトと接触した時に自動的に呼び出されます。
  • OnCollider?.Invoke(other): カスタムイベントOnColliderを発火し、接触したCollider2Dオブジェクト(other)をイベントのリスナーに渡します。ここでの?.は、OnColliderがnullではない場合のみInvokeメソッドを呼び出すnull条件演算子です。

それぞれのコライダーでの判定を処理するためのスクリプト

PlayerControllerに追加で2つのメソッド(イベントハンドラ)を実装します
それぞれ、頭の当たり判定の処理用と体の当たり判定の処理用です

using UnityEngine;
using UnityEngine.SceneManagement;  // LoadSceneを使うために必要!!

public class PlayerController : MonoBehaviour
{
    Rigidbody2D rigid2D;
    Animator animator;
    float jumpForce = 680.0f;
    float walkForce = 30.0f;
    float maxWalkSpeed = 2.0f;

    void Start()
    {
        Application.targetFrameRate = 60;
        this.rigid2D = GetComponent<Rigidbody2D>();
        this.animator = GetComponent<Animator>();
    }

    void Update()
    {
        // ジャンプする
        if (Input.GetKeyDown(KeyCode.Space) && this.rigid2D.velocity.y == 0)
        {
            this.animator.SetTrigger("JumpTrigger");
            this.rigid2D.AddForce(transform.up * this.jumpForce);
        }

        // 左右移動
        int key = 0;
        if (Input.GetKey(KeyCode.RightArrow)) key = 1;
        if (Input.GetKey(KeyCode.LeftArrow)) key = -1;

        // プレイヤの速度
        float speedx = Mathf.Abs(this.rigid2D.velocity.x);

        // スピード制限
        if (speedx < this.maxWalkSpeed)
        {
            this.rigid2D.AddForce(transform.right * key * this.walkForce);
        }

        // 動く方向に応じて反転
        if (key != 0)
        {
            transform.localScale = new Vector3(key, 1, 1);
        }

        // プレイヤの速度に応じてアニメーション速度を変える
        if (this.rigid2D.velocity.y == 0)
        {
            this.animator.speed = speedx / 2.0f;
        }
        else
        {
            this.animator.speed = 1.0f;
        }

        // 画面外に出た場合は最初から
        if (transform.position.y < -10)
        {
            SceneManager.LoadScene("GameScene");
        }
    }
    public void OnHeadTriggerEnter(Collider2D collider2D)
    {
        Debug.Log("頭当たり判定 " + collider2D.name + "との接触");
    }

    public void OnBodyTriggerEnter(Collider2D collider2D)
    {
        Debug.Log("体当たり判定 " + collider2D.name + "との接触");
    }

    // // ゴールに到達
    // void OnTriggerEnter2D(Collider2D collision)
    // {
    //     Debug.Log("ゴール");
    //     SceneManager.LoadScene("ClearScene");
    // }
}

UnityEventの重要性

UnityEventシステムを使用することで、スクリプト間の疎結合を実現できます。これは、特定のイベントが発生した時に特定のアクションを実行する必要があるが、それらが互いに強く依存しないような場合に特に有効です。例えば、特定のオブジェクトが特定の領域に入った時に音を鳴らす、UIを更新する、他のオブジェクトを動かすなどの処理を、直接的な参照を使わずに実行することができます。

UnityEventはインスペクターから直接リスナーを追加したり、引数を設定したりすることができるため、非プログラマーでもゲームのロジックに介入しやすくなるなど、開発の柔軟性と拡張性を高める利点があります。

イベントハンドラに2つの引数を渡したい場合のサンプル

using UnityEngine;
using UnityEngine.Events;

public class Detector : MonoBehaviour
{
    public UnityEvent<Collider2D, GameObject> OnCollider;
    void OnTriggerEnter2D(Collider2D other)
    {
        // Debug.Log(gameObject.name + "当たり判定");
        OnCollider?.Invoke(other, gameObject);
    }
}

このコードはUnityのゲーム開発で使われるもので、特にUnityEventを利用してイベント駆動のプログラミングを行っています。ここでは、2Dゲーム内でオブジェクトが他のオブジェクトに接触した際にイベントを発火させる方法を示しています。

コードの解説

  • UnityEngineUnityEngine.Eventsの名前空間を使用しています。これにより、Unityの基本的な機能とイベントシステムにアクセスできます。
  • DetectorクラスはMonoBehaviourから派生しています。これはUnityでのスクリプトがゲームオブジェクトにアタッチされて動作するための基本クラスです。
  • public UnityEvent<Collider2D, GameObject> OnCollider;行でカスタムのUnityEventを宣言しています。このイベントはCollider2DGameObjectの2つのパラメータを持ちます。これにより、イベントのリスナー(購読者)はこれら2つのパラメータを使って具体的な処理を行うことができます。
  • OnTriggerEnter2D(Collider2D other)メソッドは、このスクリプトがアタッチされたオブジェクトのCollider2Dが他のCollider2Dと接触した際に自動的に呼び出されます。このメソッド内で、カスタムイベントOnColliderを呼び出しています。
  • OnCollider?.Invoke(other, gameObject);行で、OnColliderイベントがnullでない場合(つまり、何らかのリスナーがこのイベントに登録されている場合)、イベントを発火させます。イベントにはother(接触したCollider2D)とgameObject(スクリプトがアタッチされたオブジェクト自身)が引数として渡されます。
using UnityEngine;
using UnityEngine.SceneManagement;  // LoadSceneを使うために必要!!

public class PlayerController : MonoBehaviour
{
    Rigidbody2D rigid2D;
    Animator animator;
    float jumpForce = 680.0f;
    float walkForce = 30.0f;
    float maxWalkSpeed = 2.0f;

    void Start()
    {
        Application.targetFrameRate = 60;
        this.rigid2D = GetComponent<Rigidbody2D>();
        this.animator = GetComponent<Animator>();
    }

    void Update()
    {
        // ジャンプする
        if (Input.GetKeyDown(KeyCode.Space) && this.rigid2D.velocity.y == 0)
        {
            this.animator.SetTrigger("JumpTrigger");
            this.rigid2D.AddForce(transform.up * this.jumpForce);
        }

        // 左右移動
        int key = 0;
        if (Input.GetKey(KeyCode.RightArrow)) key = 1;
        if (Input.GetKey(KeyCode.LeftArrow)) key = -1;

        // プレイヤの速度
        float speedx = Mathf.Abs(this.rigid2D.velocity.x);

        // スピード制限
        if (speedx < this.maxWalkSpeed)
        {
            this.rigid2D.AddForce(transform.right * key * this.walkForce);
        }

        // 動く方向に応じて反転
        if (key != 0)
        {
            transform.localScale = new Vector3(key, 1, 1);
        }

        // プレイヤの速度に応じてアニメーション速度を変える
        if (this.rigid2D.velocity.y == 0)
        {
            this.animator.speed = speedx / 2.0f;
        }
        else
        {
            this.animator.speed = 1.0f;
        }

        // 画面外に出た場合は最初から
        if (transform.position.y < -10)
        {
            SceneManager.LoadScene("GameScene");
        }
    }
    public void OnHeadTriggerEnter(Collider2D collider2D, GameObject sender)
    {
        Debug.Log("頭当たり判定 " + sender + "から " + collider2D.name + "との接触");
    }

    public void OnBodyTriggerEnter(Collider2D collider2D, GameObject sender)
    {
        Debug.Log("頭当たり判定 " + sender + "から " + collider2D.name + "との接触");
    }

    // // ゴールに到達
    // void OnTriggerEnter2D(Collider2D collision)
    // {
    //     Debug.Log("ゴール");
    //     SceneManager.LoadScene("ClearScene");
    // }
}

WinFormsアプリのイベントのような引数を渡す場合のサンプル

object sender, ColliderEventArgs argsの2つの引数を渡してみましょう

カスタムイベント引数クラス

Unityでカスタムイベントを作成し管理する方法は、コードの再利用性と整理を助け、またイベント駆動型のプログラムを容易にします。上記のコードは、Unityでカスタムイベント引数を持つイベントを定義する方法の一例を示しています。これは、特定のゲームオブジェクトが他のオブジェクトと衝突した際に使用されるデータをカプセル化します

using System;
using UnityEngine;

// カスタムイベント引数クラス
public class ColliderEventArgs : EventArgs
{
    public Collider2D Collider { get; private set; }
    public GameObject GameObject { get; private set; }

    public ColliderEventArgs(Collider2D collider, GameObject gameObject)
    {
        Collider = collider;
        GameObject = gameObject;
    }
}
  • public class ColliderEventArgs : EventArgs: ここでColliderEventArgsクラスを定義しており、EventArgsから継承しています。これにより、カスタムイベント引数を作成することができます。
  • public Collider2D Collider { get; private set; }public GameObject GameObject { get; private set; }: これらのプロパティは、衝突が発生した時に参照されるCollider2DオブジェクトとGameObjectを保持します。private set;修飾子は、これらのプロパティがクラスの外部から変更されないようにします。
  • コンストラクタpublic ColliderEventArgs(Collider2D collider, GameObject gameObject): このコンストラクタは、ColliderEventArgsオブジェクトを生成する際に、衝突したCollider2DGameObjectのインスタンスを受け取り、それらをプロパティに設定します。

このクラスは、カスタムイベントを通じて、衝突に関連するデータ(衝突したオブジェクトと衝突対象のコライダー)をイベントハンドラーに渡すために使用されます。これにより、特定の衝突が発生した時に必要な情報を持ったイベントを発生させることができ、ゲームのロジックに応じた適切な反応をプログラムすることが可能になります。

カスタムイベントの定義

このコードは、Unityにおけるカスタムイベントの作成と発火の方法を示しています。特に、ゲームオブジェクトが何かに衝突した瞬間を検出し、その情報をイベントとして発行するために使用されます。このアプローチは、Unityのイベント駆動型プログラミングを活用することで、コンポーネント間の結合を緩やかにし、再利用可能なコードを作成するのに役立ちます。

using System;
using UnityEngine;
using UnityEngine.Events;

// カスタムUnityEventを定義
[Serializable]
public class CollisionEvent : UnityEvent<object, ColliderEventArgs> { }

public class Detector : MonoBehaviour
{
    // カスタムイベントを公開
    public CollisionEvent OnCollide = new CollisionEvent();

    private void OnTriggerEnter2D(Collider2D collision)
    {
        // カスタムイベント引数を作成
        ColliderEventArgs args = new ColliderEventArgs(collision, gameObject);

        // イベントを発火させ、自身を送信元として、カスタムイベント引数を渡す
        OnCollide.Invoke(this, args);
    }
}
  • [Serializable]: この属性を使用して、CollisionEventクラスがシリアライズ可能であることをUnityエディタに通知します。これにより、エディタ内で視覚的にイベントを設定することが可能になります。
  • public class CollisionEvent : UnityEvent<object, ColliderEventArgs>: ここでCollisionEventクラスを定義し、ジェネリック型のUnityEventから継承しています。このカスタムイベントは、イベント発生時にobject(イベントの発生源)とColliderEventArgs(イベントに関連する追加情報を含むカスタムイベント引数)をリスナーに渡します。
  • public CollisionEvent OnCollide = new CollisionEvent();: DetectorクラスにOnCollideという名前の公開フィールドを追加し、これを新しいCollisionEventインスタンスで初期化します。このフィールドは、Unityエディタからアクセス可能で、イベントリスナーを登録するために使用されます。
  • private void OnTriggerEnter2D(Collider2D collision): このメソッドは、2Dコライダーがトリガーと接触した際にUnityによって自動的に呼び出されます。
  • ColliderEventArgs args = new ColliderEventArgs(collision, gameObject);: 衝突に関連する情報を含む新しいColliderEventArgsインスタンスを作成します。
  • OnCollide.Invoke(this, args);: OnCollideイベントを発火させ、イベントの送信元(this、つまりDetectorコンポーネント自体)とカスタムイベント引数(args)をイベントに登録されたすべてのリスナーに渡します。

この方法を使用することで、ゲームオブジェクトの衝突検出を柔軟に管理できます。例えば、異なる種類のオブジェクトが衝突した際に異なる反応をするようにプログラムすることが可能です。また、UnityEventを使用することで、イベントのリスナーをエディタから直接割り当てることができ、コードの変更を伴わずにイベントの応答を簡単に調整できます。

テスト用コード

PlayerControllerスクリプトとは別にテスト用のコードを作成します
PlayerControllerスクリプトと同様にプレイヤーゲームオブジェクトにアタッチできます

イベントハンドラの登録をコードで実装するパターン

インスペクターでアウトレット接続と同様な手順をコード(AddListenerメソッド)で実装してみましょう
(もちろん、これまで通りインスペクターでの登録も可能です)

using UnityEngine;

public class CollisionListener : MonoBehaviour
{
    // Detectorコンポーネントへの参照を設定
    public Detector headDetector;
    public Detector bodyDetector;

    private void Start()
    {
        // 安全なチェック
        if (headDetector != null)
        {
            // OnCollideイベントにハンドラー(メソッド)を登録
            headDetector.OnCollide.AddListener(HandleCollision);
            bodyDetector.OnCollide.AddListener(HandleCollision);
        }
    }

    // イベントが発火したときに呼ばれるメソッド
    private void HandleCollision(object sender, ColliderEventArgs args)
    {
        Debug.Log($"Collision detected between {args.GameObject.name} and {args.Collider.name}");
    }

    private void OnDestroy()
    {
        // オブジェクトが破棄される時にイベントリスナーを解除
        if (headDetector != null)
        {
            headDetector.OnCollide.RemoveListener(HandleCollision);
        }
    }
}

CollisionListenerスクリプトのアタッチ

コードの詳細な解説

パブリック変数の定義

public Detector headDetector;
public Detector bodyDetector;

Detector型のheadDetectorbodyDetectorという二つのパブリック変数を定義しています。これらは、Unityエディターから直接設定できるように公開されており、特定のゲームオブジェクト(例えば、キャラクターの頭部や身体部分)の衝突を検知するために使用されます。

Startメソッド

if (headDetector != null)
{
    headDetector.OnCollide.AddListener(HandleCollision);
    bodyDetector.OnCollide.AddListener(HandleCollision);
}

Unityのゲームオブジェクトが初めてアクティブになったときに実行されるStartメソッド内で、headDetectorbodyDetectorOnCollideイベントにHandleCollisionメソッドをリスナーとして登録しています。これにより、これらの検知器が衝突を検知したときに、自動的にHandleCollisionメソッドが呼び出されます。

HandleCollisionメソッド

private void HandleCollision(object sender, ColliderEventArgs args)
{
    Debug.Log($"Collision detected between {args.GameObject.name} and {args.Collider.name}");
}
  • 衝突が検知されたときに呼ばれるメソッドです。ColliderEventArgs型の引数argsを通じて、衝突が発生したオブジェクトの情報にアクセスし、その名前をログに出力しています。

OnDestroyメソッド

if (headDetector != null)
{
    headDetector.OnCollide.RemoveListener(HandleCollision);
}
  • ゲームオブジェクトが破棄される際に実行されるOnDestroyメソッド内で、イベントリスナー(HandleCollisionメソッド)をイベントから削除しています。これは、不要になったリスナーを適切にクリーンアップするための一般的なプラクティスです。

このコード例から、Unityでのイベント駆動プログラミングの基本的なアプローチが見て取れます。UnityEventを使用することで、イベントの発生を監視し、イベントに対応して特定のアクションを実行する方法を簡単に実装できます。これは、ゲーム開発において非常に強力で柔軟な手法です。

Unity

Posted by hidepon