チュートリアル:UnityEvent を使った衝突&トリガーイベント処理

1. チュートリアルの概要

本チュートリアルでは、以下の内容を実践的に学びます。

  • Unity プロジェクトの作成とシーンの基本設定
  • UnityEvent を用いた衝突(Collision)イベントとトリガー(Trigger)イベントの実装
  • カスタムイベントの定義と、なぜカスタムイベントにするのか、そのメリット
  • 拡張例として、イベント発生時のログ出力や条件チェック、発生回数のカウントなどの実装方法
  • Example スクリプトで、両イベントのハンドラを登録し、実際にイベント発生時に処理を実行する方法

2. プロジェクトとシーンの準備

2.1 Unity プロジェクトの作成

  1. Unity Hub を起動し、「New Project」をクリック。
  2. テンプレートは「3D」を選び、プロジェクト名(例:UnityEventTutorial)を設定してプロジェクトを作成します。

2.2 シーンの設定

  1. シーンに Plane(床オブジェクト)を配置し、オブジェクトの落下先とします。
  2. 衝突検知用に Cube を配置します。
    • Cube には Box Collider が自動で付いているので、Rigidbody コンポーネントも追加して物理演算を有効にします。
  3. トリガー検知用に、別の Cube を配置します。
    • この Cube の Box ColliderIs Trigger オプションにチェックを入れます。

3. スクリプトの作成

ここでは、UnityEvent を用いた衝突イベントとトリガーイベントの実装例を示します。
さらに、カスタムイベントとして定義する理由(拡張性や可読性の向上)と、拡張例も紹介します。

3.1 衝突イベント用スクリプト (CollisionTriggerHandler.cs)

まず、衝突イベント用のカスタム UnityEvent を定義します。
シンプルな実装と、拡張例(発生回数のカウント、ログ出力)を以下に示します。

基本版

using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class CollisionEvent : UnityEvent<Collision> { }

public class CollisionTriggerHandler : MonoBehaviour
{
    // インスペクター上でハンドラ登録可能な UnityEvent
    public CollisionEvent OnCollisionEnterEvent;

    private void OnCollisionEnter(Collision collision)
    {
        if (OnCollisionEnterEvent != null)
            OnCollisionEnterEvent.Invoke(collision);
    }
}
using UnityEngine;
using UnityEngine.Events;

// CollisionEvent クラスは、Collision 型のパラメータを受け取る UnityEvent の派生クラスです。
// [System.Serializable] 属性により、インスペクター上でシリアライズされ、設定が可能になります。
[System.Serializable]
public class CollisionEvent : UnityEvent<Collision> { }

// CollisionTriggerHandler クラスは、オブジェクトに衝突が発生した際に、登録されたイベントハンドラを呼び出すためのクラスです。
// MonoBehaviour を継承することで、Unity のライフサイクルに参加します。
public class CollisionTriggerHandler : MonoBehaviour
{
    // インスペクター上でハンドラを登録可能な UnityEvent を宣言しています。
    // このフィールドにより、ユーザーは Unity エディターのインスペクターで、衝突時に実行する処理(関数)を割り当てることができます。
    public CollisionEvent OnCollisionEnterEvent;

    // Unity の物理エンジンがオブジェクト同士の衝突を検出した際に呼び出されるコールバックメソッドです。
    // このメソッドは、引数 collision に衝突に関する情報を含んでいます。
    private void OnCollisionEnter(Collision collision)
    {
        // インスペクターでイベントが設定されている場合のみ、イベントを発火します。
        // null チェックにより、イベントハンドラが登録されていない場合のエラーを防ぎます。
        if (OnCollisionEnterEvent != null)
            OnCollisionEnterEvent.Invoke(collision);
    }
}

拡張例:発生回数のカウントや自動ログ出力を追加

using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class CollisionEvent : UnityEvent<Collision>
{
    public int invocationCount = 0;

    // UnityEvent の Invoke をオーバーライドして拡張
    public new void Invoke(Collision collision)
    {
        invocationCount++;
        Debug.Log("CollisionEvent invoked " + invocationCount + " times");
        base.Invoke(collision);
    }
}

public class CollisionTriggerHandler : MonoBehaviour
{
    public CollisionEvent OnCollisionEnterEvent;

    private void OnCollisionEnter(Collision collision)
    {
        if (OnCollisionEnterEvent != null)
            OnCollisionEnterEvent.Invoke(collision);
    }
}
using UnityEngine;
using UnityEngine.Events;

// CollisionEvent クラスは、UnityEvent<Collision> を継承し、衝突イベントに対して発火回数をカウントする機能を追加しています。
// [System.Serializable] 属性により、インスペクター上でシリアライズされ、設定が可能になります。
[System.Serializable]
public class CollisionEvent : UnityEvent<Collision>
{
    // このフィールドは、イベントが発火された回数を記録します。
    public int invocationCount = 0;

    // UnityEvent の Invoke メソッドをオーバーライドして拡張します。
    // このメソッドは、イベントが呼び出されるたびに invocationCount をインクリメントし、
    // デバッグログに発生回数を出力した後、基底クラスの Invoke を呼び出して登録されたリスナーに通知します。
    public new void Invoke(Collision collision)
    {
        invocationCount++; // 発火回数をカウントアップ
        Debug.Log("CollisionEvent invoked " + invocationCount + " times"); // 現在の発火回数をログ出力
        base.Invoke(collision); // 基底クラスの Invoke を呼び出して、登録されたイベントハンドラに通知
    }
}

// CollisionTriggerHandler クラスは、物理的な衝突が発生した際に、登録された CollisionEvent を呼び出すためのクラスです。
// MonoBehaviour を継承することで、Unity のライフサイクルに参加し、物理イベントを受け取ることができます。
public class CollisionTriggerHandler : MonoBehaviour
{
    // インスペクター上で設定可能な CollisionEvent を宣言しています。
    // このフィールドにより、ユーザーは Unity エディターのインスペクターで衝突時に実行する処理を割り当てることができます。
    public CollisionEvent OnCollisionEnterEvent;

    // Unity の物理エンジンがオブジェクト間の衝突を検出した際に自動的に呼び出されるコールバックメソッドです。
    // 引数 collision は衝突に関する詳細な情報を含んでいます。
    private void OnCollisionEnter(Collision collision)
    {
        // インスペクター上でイベントが設定されている場合のみ、イベントを発火します。
        // null チェックにより、イベントハンドラが未登録の場合のエラーを防止します。
        if (OnCollisionEnterEvent != null)
            OnCollisionEnterEvent.Invoke(collision);
    }
}

ポイント:
カスタムイベントとして CollisionEvent を定義することで、単に public UnityEvent<Collision> OnCollisionEnterEvent; とする場合に比べ、イベントの意図や拡張(例:発生回数カウント、ログ出力)が容易になります。


3.2 トリガーイベント用スクリプト (TriggerHandler.cs)

衝突イベントと同様に、トリガーイベントもカスタムイベントとして定義します。

基本版

using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class TriggerEvent : UnityEvent<Collider> { }

public class TriggerHandler : MonoBehaviour
{
    public TriggerEvent OnTriggerEnterEvent;

    private void OnTriggerEnter(Collider other)
    {
        if (OnTriggerEnterEvent != null)
            OnTriggerEnterEvent.Invoke(other);
    }
}

カスタムイベントにする理由

  • 可読性:
    TriggerEvent として定義することで、このイベントが何を扱うのかが明示され、コードの理解が容易になります。
  • 拡張性:
    将来的に、イベント発生前に条件チェック(例:特定のタグのみ対象)を入れる、あるいは発生回数をカウントするなど、機能を追加しやすくなります。

例えば、タグチェックを追加する拡張例:

using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class TriggerEvent : UnityEvent<Collider>
{
    public string requiredTag = "Player"; // 指定タグ以外は無視

    public new void Invoke(Collider other)
    {
        if (!other.CompareTag(requiredTag))
        {
            Debug.Log("Tag 不一致のためイベント発生しません: " + other.gameObject.name);
            return;
        }
        base.Invoke(other);
    }
}

public class TriggerHandler : MonoBehaviour
{
    public TriggerEvent OnTriggerEnterEvent;

    private void OnTriggerEnter(Collider other)
    {
        if (OnTriggerEnterEvent != null)
            OnTriggerEnterEvent.Invoke(other);
    }
}

3.3 両方のイベントを扱う Example スクリプト (Example.cs)

衝突イベントとトリガーイベントの両方を 1 つのスクリプトで扱い、イベントハンドラを登録する例です。
UnityEvent はインスペクター上での設定も可能ですが、ここではコード側で登録する方法を示します。

using UnityEngine;

public class Example : MonoBehaviour
{
    // Hierarchy 上で設定するための参照
    public CollisionTriggerHandler collisionHandler;
    public TriggerHandler triggerHandler;

    private void Start()
    {
        // コード側でイベントにハンドラを登録(AddListener で追加)
        collisionHandler.OnCollisionEnterEvent.AddListener(HandleCollisionEnter);
        triggerHandler.OnTriggerEnterEvent.AddListener(HandleTriggerEnter);
    }

    private void HandleCollisionEnter(Collision collision)
    {
        Debug.Log("衝突発生: " + collision.gameObject.name);
    }

    private void HandleTriggerEnter(Collider other)
    {
        Debug.Log("トリガー発生: " + other.gameObject.name);
    }
}

3.4 トリガーイベントの拡張(オプション): OnTriggerStay & OnTriggerExit

基本の OnTriggerEnter に加えて、トリガー内に留まる状態や退出した状態も検知する拡張版の例です。

using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class AdvancedTriggerEvent : UnityEvent<Collider> { }

public class AdvancedTriggerHandler : MonoBehaviour
{
    public AdvancedTriggerEvent OnTriggerEnterEvent;
    public AdvancedTriggerEvent OnTriggerStayEvent;
    public AdvancedTriggerEvent OnTriggerExitEvent;

    private void OnTriggerEnter(Collider other)
    {
        if (OnTriggerEnterEvent != null)
            OnTriggerEnterEvent.Invoke(other);
    }

    private void OnTriggerStay(Collider other)
    {
        if (OnTriggerStayEvent != null)
            OnTriggerStayEvent.Invoke(other);
    }

    private void OnTriggerExit(Collider other)
    {
        if (OnTriggerExitEvent != null)
            OnTriggerExitEvent.Invoke(other);
    }
}

そして、これらを扱う Example スクリプトの変更例:

using UnityEngine;

public class AdvancedExample : MonoBehaviour
{
    public CollisionTriggerHandler collisionHandler;
    public TriggerHandler triggerHandler;
    public AdvancedTriggerHandler advancedTriggerHandler;

    private void Start()
    {
        collisionHandler.OnCollisionEnterEvent.AddListener(HandleCollisionEnter);
        triggerHandler.OnTriggerEnterEvent.AddListener(HandleBasicTriggerEnter);
        advancedTriggerHandler.OnTriggerEnterEvent.AddListener(HandleAdvancedTriggerEnter);
        advancedTriggerHandler.OnTriggerStayEvent.AddListener(HandleAdvancedTriggerStay);
        advancedTriggerHandler.OnTriggerExitEvent.AddListener(HandleAdvancedTriggerExit);
    }

    private void HandleCollisionEnter(Collision collision)
    {
        Debug.Log("衝突発生: " + collision.gameObject.name);
    }

    private void HandleBasicTriggerEnter(Collider other)
    {
        Debug.Log("基本トリガー発生: " + other.gameObject.name);
    }

    private void HandleAdvancedTriggerEnter(Collider other)
    {
        Debug.Log("拡張トリガー進入: " + other.gameObject.name);
    }

    private void HandleAdvancedTriggerStay(Collider other)
    {
        Debug.Log("拡張トリガー滞在中: " + other.gameObject.name);
    }

    private void HandleAdvancedTriggerExit(Collider other)
    {
        Debug.Log("拡張トリガー退出: " + other.gameObject.name);
    }
}

4. Unityエディタ上での設定と実行

4.1 スクリプトのアタッチ

  1. CollisionTriggerHandler.cs を衝突検知用の Cube にアタッチします。
    • Cube には Rigidbody も追加してください。
  2. TriggerHandler.cs をトリガー検知用の Cube にアタッチします。
    • この Cube の Collider の Is Trigger オプションをオンにします。
  3. AdvancedTriggerHandler.cs(拡張版)を、トリガー内の留まりや退出を検知したいオブジェクトにアタッチします。
  4. Example.cs または AdvancedExample.cs を、両方のイベントを管理するための GameObject(空のオブジェクトなど)にアタッチします。

4.2 インスペクターでの参照設定

  • Example(または AdvancedExample)スクリプトのインスペクターで、対応するフィールド(collisionHandler、triggerHandler、advancedTriggerHandler)に対象のオブジェクトをドラッグ&ドロップして設定します。
  • また、各 Handler コンポーネントのインスペクター上でも、必要に応じて UnityEvent のハンドラ(関数)を設定できます。

4.3 チュートリアルの実行

  1. Unity エディタ上部の再生ボタンをクリックしてプレイモードに入ります。
  2. Rigidbody を持つ Cube が Plane に衝突すると、コンソールに「衝突発生: [オブジェクト名]」と表示されます。
  3. トリガー用 Cube に他のオブジェクトが進入すると、コンソールに「トリガー発生: [オブジェクト名]」と表示されます。
  4. AdvancedTriggerHandler を利用している場合は、トリガー内に留まったり退出したタイミングでもそれぞれログが出力されます。

5. まとめ

  • UnityEvent の利用メリット:
    UnityEvent を使うことで、インスペクターから直接イベントハンドラの登録や差し替えができ、コードを変更せずに動作を調整できるため、柔軟性が向上します。
  • カスタムイベントの利点:
    [System.Serializable] 属性を使いカスタムイベント型(例:TriggerEvent や CollisionEvent)を定義することで、
    • イベントの意味が明確になり、可読性が向上
    • 将来的な拡張(発生回数カウント、条件uniチェック、追加プロパティの保持など)が容易になる
      というメリットがあります。
  • 複数のイベント処理:
    衝突イベント、基本トリガーイベント、拡張トリガーイベント(OnTriggerStay、OnTriggerExit)をそれぞれ専用のスクリプトで実装し、Example スクリプトで一元管理することで、シーン上の複数のインタラクションを網羅的に検知できます。

このチュートリアルを参考に、UnityEvent を用いたイベント処理をプロジェクトに合わせて柔軟に拡張し、より堅牢で管理しやすいコード設計を実現してください。

Unity,イベント

Posted by hidepon