C#のActionを使用したイベント処理の実装方法

C#のイベント処理とは

イベント処理とは、ゲーム内でボタンを押すと何かが起きるなど、特定のトリガーに応じて処理を実行する仕組みです。
ゲームではこのような処理が多く、衝突判定やUIなど、さまざまな場面で活用できます。
Unity独自のイベント処理であるUnityEventもありますが、今回は純粋なC#のイベント処理を用いた方法を紹介します。

イベント処理のメリット

イベント処理を使うメリットの一つは、クラス間の依存関係を減らせることです。私は最近、中規模程度のゲームを作っていると、設計をあまり考えていない場合に依存関係が密になり、相互参照が発生することが多々ありました。そういった場合にイベント処理が役立ちます。例えば、Aでイベントが発生した際にBを実行する場合、BがAのことを知らなくても実行できるようにすることで、依存関係を減らすことができます。

C#のイベント処理の実装

C#のイベント処理には、ActionやFuncなどがありますが、今回はActionを使用します。Action<T>のように引数ありのものがありますが、今回は単純にイベントが発火した際に何も引数を渡さないもので実装していきます。

以下は、スイッチにプレイヤーが触れると足場が起動して動き出す処理を再現したものです。アクションゲームではよくある気がするので参考にしてみるといいかもしれません。

イベントを発火させる側の処理(スイッチのオブジェクト)

using UnityEngine;
using System;

public class Switch : MonoBehaviour
{
    //イベントを宣言
    public static event Action playerHit;

    void OnCollisionEnter2D(Collision2D collisionInfo)
    {
        if (collisionInfo.gameObject.tag == "Player")
        {
            //イベントを発火させる
            playerHit?.Invoke();
        }
    }
} 

staticを用いることで外部のクラスからアクセスできるため、このクラスを外部から購読することができます。
.Invoke()メソッドを用いて、宣言したイベントを発火させます。購読している処理が外部にある場合、このタイミングで実行されます。

イベントを購読する側の処理(動く床のオブジェクト)

using UnityEngine;

public class MovingPlatform : MonoBehaviour
{
    [SerializeField] private float moveDuration = 2f;
    [SerializeField] private float moveRange = 3f;

    //switchを押して動き出すかどうか
    private bool isMoving = false;

    private Vector2 startPos;
    private Vector3 targetPos;

    //経過時間
    private float elapsedTime = 0f;
    //上昇中か下降中か
    private bool goingUp = true;

    void Awake()
    {
        //イベントを購読
        Switch.playerHit += StartMoving;

        //初期位置と目標位置を設定
        startPos = transform.position;
        targetPos = startPos + Vector2.up * moveRange;
    }

    void OnDestory()
    {
        //イベントを購読解除
        Switch.playerHit -= StartMoving;
    }

    //動きだす
    private void StartMoving()
    {
        isMoving = true;
    }

    private void FixedUpdate()
    {
        if (!isMoving) return;
        MovePlatform();
    }


    private void MovePlatform()
    {
        //移動の時間
        elapsedTime += Time.deltaTime;

        //移動距離に対してどれだけ時間がかかったのか計算
        //0~1の範囲で値を返す
        float t = elapsedTime / moveDuration;

        //補完された位置を計算
        //Lerpはtの値に応じてstartPosとtargetPosの間を補完した位置を返す
        //tが0ならstartPosで、1ならtargetPosになる
        transform.position = Vector2.Lerp(startPos, targetPos, t);

        //目的地に達したら往復
        if (t >= 1f)
        {
            elapsedTime = 0f;
            goingUp = !goingUp;

            //スタートとターゲット位置を入れ替える
            var temp = startPos;
            startPos = targetPos;
            targetPos = temp;
        }
    }
}

床のオブジェクトの動きに関する詳細な処理は、次回の記事で触れようと思います。
AwakeメソッドでSwitchクラスのplayerHitというイベントに、床のオブジェクトが動き出すためのStartMovingメソッドを登録しておくことで、Switchクラスでイベントが発行された場合にStartMovingメソッドが実行されます。

実行結果

各種オブジェクトにRigidbody、Collider、スクリプトをアタッチします。
今回は、赤色のスイッチにプレイヤーのオブジェクトが衝突すると足場が動き出すようにしました。

まとめ

以上が簡単にイベントの処理について書いてみました。設計を意識していないと、インスペクター上で参照する項目が増えるなど、依存関係が強くなり、修正時にコードが読みにくくなるため、このようなイベント処理を使うと、対策ができて便利ですね。

最新情報をチェックしよう!
>
CTR IMG