【Unity】メディエータ(Mediator Pattern)の簡単なサンプル

Mediator Patternは、オブジェクト間の直接的なコミュニケーションを避け、代わりにメディエータオブジェクトを通じて相互作用することで、システムの結合度を下げるデザインパターンです。UnityでのMediator Patternの使用例を、プレイヤーと敵との間のインタラクション管理に適用する形で紹介します。

シナリオ

ゲームにはプレイヤーと複数の敵が存在し、プレイヤーは敵にダメージを与えることができます。しかし、プレイヤーと敵は直接通信せず、GameController(メディエータ)を介して相互作用します。

サンプル

Step 1: コンポーネントインターフェースの定義

まず、ゲーム内の異なるオブジェクトがメディエータを通じてコミュニケーションを行うためのインターフェースを定義します。

public interface IGameComponent
{
    void SetMediator(GameMediator mediator);
}

Step 2: メディエータインターフェースの定義

次に、メディエータのインターフェースを定義します。これは、ゲームの異なるコンポーネント間での通信を仲介します。

public interface GameMediator
{
    void Notify(object sender, string ev);
}

Step 3: 具体的なメディエータの実装

具体的なメディエータGameControllerを実装します。このメディエータは、プレイヤーからの攻撃命令を受け取り、対応する敵にダメージを与える役割を持ちます。

using UnityEngine;

public class GameController : MonoBehaviour, GameMediator
{
    void Start()
    {
        // シーン内のすべてのMonoBehaviourを検索
        MonoBehaviour[] components = FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None);

        foreach (MonoBehaviour component in components)
        {
            // コンポーネントがIGameComponentインターフェイスを実装しているかチェック
            if (component is IGameComponent gameComponent)
            {
                // メディエータを設定
                gameComponent.SetMediator(this);
            }
        }
    }

    public void Notify(object sender, string ev)
    {
        if (ev == "Attack")
        {
            Debug.Log("プレイヤーはGameControllerを通して敵を攻撃した");
            // ここで敵にダメージを与える処理を実装
        }
    }
}

Step 4: コンポーネントの実装

プレイヤーと敵のコンポーネントを実装し、それぞれがメディエータを介して通信するようにします。

using UnityEngine;

public class Player : MonoBehaviour, IGameComponent
{
    private GameMediator mediator;

    public void SetMediator(GameMediator mediator)
    {
        this.mediator = mediator;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) // スペースキーで攻撃
        {
            Attack();
        }
    }

    void Attack()
    {
        mediator.Notify(this, "Attack");
    }
}

使用方法

  • GameControllerスクリプトをシーン内の空のGameObjectにアタッチし、メディエータとして機能させます。
  • Playerスクリプトをプレイヤーオブジェクトにアタッチし、スクリプト内でSetMediatorメソッドを使用してGameControllerを設定します。この設定は、スクリプトやエディタのInspectorから行うことができます。

このサンプルでは、GameControllerがプレイヤーと敵との間での通信を仲介し、直接の依存関係を避けることで、コンポーネント間の結合度を低く保ちながら柔軟な設計を実現しています。このパターンは、特に複数の異なるタイプのオブジェクトが相互に影響を与え合う複雑なゲームシステムにおいて、コードの整理とメンテナンスを容易にするのに役立ちます。

DI(依存性注入)

GameControllerがゲームの開始時にIGameComponentを実装する各コンポーネントに自身(メディエータ)を設定するプロセスは、依存性注入(Dependency Injection、DI)の一形態と見なすことができます。依存性注入は、コンポーネント間の依存関係を外部から注入する設計パターンで、コンポーネントの結合度を低下させ、テストしやすく、管理しやすいコードを実現するのに役立ちます。

このコンテキストでは、GameController(メディエータ)はPlayerや他のゲームコンポーネント(IGameComponentを実装するもの)に対して、「必要な依存オブジェクト(この場合はメディエータ自身)」を提供します。これにより、Playerなどのコンポーネントは、メディエータを通じて他のコンポーネントと通信することができるようになりますが、それらのコンポーネント間に直接的な依存関係は発生しません。

DIの特徴とメリット

  • 結合度の低下: コンポーネント間の依存関係を外部から注入することで、結合度を低く保ちます。これにより、コンポーネントの再利用性と交換可能性が向上します。
  • コードのテスト容易性の向上: 依存性を外部から注入することで、モックやスタブを使用したテストが容易になります。
  • 設定と実装の分離: 依存関係の設定をコードの実装から分離することができるため、設計がよりクリーンになり、変更に対して柔軟に対応できます。

このケースでのDIの使用

このケースでは、GameControllerStartメソッド内でIGameComponentインターフェイスを実装する全てのコンポーネントを検索し、それらに自身をメディエータとして設定することで、DIの原則を適用しています。この手法により、ゲーム内のコンポーネント間で発生する通信がメディエータを介して行われるため、コンポーネントの結合度を効果的に低下させることができます。ただし、Unityのエコシステム内でDIを完全に実装するには、DIフレームワーク(例えば、Zenject, VContainerなど)の使用を検討することもあります。

Unity

Posted by hidepon