Unityに関連する記事です

 Mediator(メディエーター) パターンを採用した場合の実装例です。

 Mediator パターンは、異なるオブジェクト間の通信を仲介するデザインパターンです。

 今回の例では、UI の制御者(UIController クラス、UIManager クラス) として、Mediator パターンを採用した例です。

 UI 表示する際、Mediator 用のクラス内の List 内にある指定された View を表示したり、
以前表示されていた View に戻ったりといった挙動が実現可能です。

 View を戻すケースにおいては、1つ目のポップアップウインドウを開き、その中でさらに別の2つ目のポップアップウインドウを開いたとします。
その場合、1つ目のポップアップウインドウを非表示にします。

 このとき、Mediator パターンを利用して実装をおこなっておくことで、2つ目のポップアップウインドウが閉じたタイミングで
最初に開いていた1つ目のポップアップウインドウを再表示するような挙動が表現出来ます。

 もちろん、UI管理以外にもさまざまな場面で有効です。
プレイヤーの特定のアクションが実行された時に、Mediator 用のクラスが他のクラス(敵)に命令を出したり、
異なるゲームオブジェクト(コンポーネント・クラス)がお互いに直接依存せず、Mediator を介して通信することで柔軟性を向上させることが可能です。



Mediator パターン


 Mediator パターンは、多数のオブジェクト間の調整や通信を中央に位置する Mediator(仲介者・調停者) クラスに委任する設計パターンです。
これにより、各オブジェクトが直接通信せず、中央の Mediator を介して相互にコミュニケーションすることが可能となります。

 UIManagerが異なるUI要素(Viewクラスなど)を制御し、調整することで、各UI要素がお互いに直接通信するのではなく、
UIManager(Mediator)を介して相互にコミュニケーションする形になります。これにより、各UI要素は相互に疎結合になり、拡張や変更がしやすくなります。

 Mediator パターンは、特に大規模で複雑なシステムや、異なる部分が疎結合であることが望ましい場合に役立ちます。
UI コントローラーが UI 要素を中央に配置しているような場面では、Mediator パターンの考え方が活用できる可能性があります。



 また、UI については Observerパターンの要素も存在する可能性があります。
たとえば、UIManagerが特定のイベント(ボタンのクリックなど)に対して通知を発行し、それに対してUI要素が反応するような場合です。
この場合、UIManagerが被観察者であり、UI要素が観察者となります。

 UIManager を Presenter とするならば、MVP パターンによる設計も可能です。

 そのため、複数のデザインパターンの要素を取り入れた設計によるアプローチが有効です。


設計


 UI 制御用の基底クラスを作成し、派生クラスをサンプルとして2つ作成します。
その後、Mediator 用のクラスを作成します。

 UniRx、UniTask を利用しています。


MicroSoft
抽象化 (抽象型およびインターフェイス)

https://learn.microsoft.com/ja-jp/dotnet/standard/...


基底クラス ーView クラスー


 UI 制御用の基底クラスです。

 Canvas コンポーネントがアタッチされているゲームオブジェクトにアタッチして利用する前提です。


using System;
using UniRx;
using UnityEngine;

public abstract class View : MonoBehaviour
{
    // 具体的なUIイベントを公開
    public virtual IObservable<Unit> OnClickObservable { get; }
 
    protected Canvas canvas;


    protected void Reset() {
        if(TryGetComponent(out canvas)) {
            Debug.Log($"{canvas} 取得しました");
        }
    }   
    
    /// <summary>
    /// Initializes the View
    /// </summary>
    public abstract void Initialize();
        
    /// <summary>
    /// Makes the View visible
    /// </summary>
    public virtual void Show()
    {
        canvas.enabled = true;
    }

    /// <summary>
    /// Hides the view
    /// </summary>
    public virtual void Hide()
    {
        canvas.enabled = false;
    }
}

 UI の場合、ゲームオブジェクトを SetActive で切り替えてしまうと、true になった際に再描画処理が走ります。

 Canvas を enabled で切り替えた場合には再描画処理は走りませんので、こちらの方が負荷のかからない表示切り替え処理になります。

 DOTween でアニメ付の表示制御する場合には、CanvasGroup を DOFade メソッドで表示制御しつつ、Blocks Raycast の切り替えを行うようにして対応します。


派生クラス  Buttonー



using System;
using UniRx;
using UnityEngine;
using UnityEngine.UI;

public class SubClassButtonView : View
{
    [SerializeField] protected Button btnUI;

    // 今回はボタンと紐づける(マッピングする)
    public override IObservable<Unit> OnClickObservable => btnUI.OnClickAsObservable();
    
    public override void Initialize() {
        
        // UniRxを使用してイベントを登録する例
        OnClickObservable
            .ThrottleFirst(TimeSpan.FromSeconds(1.0f))
            .Subscribe(_ => Debug.Log($"Button Click"))
            .AddTo(this);
    }
}


派生クラス◆ InputFieldー



using System;
using UniRx;
using UnityEngine;
using UnityEngine.UI;

public class SubClassInputFieldView : View
{
    [SerializeField] protected InputField inputFieldUI;

    // InputField と紐づける(マッピングする)
    public IObservable<string> OnValueChangedObservable => inputFieldUI.OnValueChangedAsObservable();


    public override void Initialize()
    {
        // UniRxを使用してInputFieldのイベントを登録する例
        OnValueChangedObservable
            .ThrottleFirst(TimeSpan.FromSeconds(1.0f))
            .Subscribe(value => Debug.Log($"InputField Value Changed: {value}"))
            .AddTo(this);
    }
}


シングルトンクラス用抽象クラス



using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 派生クラスに対するシングルトンの基本機能を提供する抽象クラス
/// </summary>
/// <typeparam name="T">シングルトンのインスタンスの型</typeparam>
public abstract class AbstractSingleton<T> : MonoBehaviour where T : Component
{
  static T Instance;

    /// <summary>
    /// static Singleton instance
    /// </summary>
    public static T Instance
    {
        get
        {
            if (Instance == null)
            {
                Instance = FindObjectOfType<T>();
                if (Instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    Instance = obj.AddComponent<T>();
                }
            }

            return Instance;
        }
    }

    protected virtual void Awake()
    {
        if (Instance == null)
        {
            Instance = this as T;
        }
        else
        {
            Destroy(gameObject);
        }
    }
}



View 管理クラス ーMadiatorー


 基底クラス View を継承している派生クラスを List で管理します。
これにより、複数の View を Mediator が管理する形を取ります。

 List 内にある指定された View を表示したり、
以前表示されていた View に戻ったりといった、UI 表示を制御したりすることが今回の Mediator の目的になります。


using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class UIManager : AbstractSingleton<UIManager>
{
    [SerializeField] Canvas m_Canvas;
    [SerializeField] RectTransform m_Root;
    [SerializeField] RectTransform m_BackgroundLayer;
    [SerializeField] RectTransform m_ViewLayer;

    List<View> m_Views;
    View m_CurrentView;

    readonly Stack<View> m_History = new();


    void Start() {
        m_Views = m_Root.GetComponentsInChildren<View>(true).ToList();
        Init();

    // TODO Canvas のリサイズなど

    }

    void Init() {
        // foreach (var view in m_Views)
        //     view.Hide();
        
        // 上記を LINQ で書いた場合
        m_Views.ForEach(view => view.Hide());
        
        m_History.Clear();
    }

    /// <summary>
    /// 指定された型の最初に登録された UI View を検索します
    /// </summary>
    /// <typeparam name="T">検索対象の View クラス</typeparam>
    /// <returns>指定された型の View インスタンス。見つからない場合は null </returns>
    public T GetView<T>() where T : View {
        // foreach (var view in m_Views) {
        //     if (view is T tView) {
        //         return tView;
        //     }
        // }
        //
        // return null;
        
        // 上記を LINQ で書いた場合
        return m_Views.OfType<T>().FirstOrDefault();
        
        // 実行命令例
        // m_Hud = UIManager.Instance.GetView<Hud>();
    }

    /// <summary>
    /// 指定された型の View を検索して表示する
    /// </summary>
    /// <param name="keepInHistory">戻る場合に現在の View を履歴スタックに追加するかどうか</param>
    /// <typeparam name="T">検索対象の View クラス</typeparam>
    public void Show<T>(bool keepInHistory = true) where T : View {
        // foreach (var view in m_Views) {
        //     if (view is T) {
        //         Show(view, keepInHistory);
        //         break;
        //     }
        // }
        
        // 上記を LINQ で書いた場合
        var targetView = m_Views.OfType<T>().SingleOrDefault();  // FirstOrDefault でもよい

        if (targetView != null)
        {
            Show(targetView, keepInHistory);
        }
        
        // 実行命令例
        // UIManager.Instance.Show<SettingsMenu>();
        // UIManager.Instance.Show<ShopView>();
    }

    /// <summary>
    /// View を表示し、他の View を非表示にする
    /// </summary>
    /// <param name="view">表示する View</param>
    /// <param name="keepInHistory">戻る場合に現在の View を履歴スタックに追加するかどうか</param>
    public void Show(View view, bool keepInHistory = true) {
        if (m_CurrentView != null) {
            if (keepInHistory) {
                m_History.Push(m_CurrentView);
            }

            m_CurrentView.Hide();
        }

        view.Show();
        m_CurrentView = view;
        
        // 実行命令例
        // Show(view, keepInHistory);     // Showメソッド内
        // Show(m_History.Pop(), false);  // GoBack メソッド内
    }

    /// <summary>
    /// 以前に表示されていたページに戻る
    /// </summary>
    public void GoBack() {
        if (m_History.Count != 0) {
            Show(m_History.Pop(), false);
        }
    }
}


MVP パターンにおける Observer パターンと Madiator パターンの相違点


 MVP(Model-View-Presenter)パターンとObserverパターンは、
異なる観点でシステムの構造を整理するものであり、それぞれの役割や目的が異なります。

 以下に、MVPパターンとObserverパターンの主な違いを示します。


MVP(Model-View-Presenter)パターン

目的:

  MVPは、アプリケーションの機能をモデル、ビュー、プレゼンターの3つの主要なコンポーネントに分離し、それぞれの責務を明確にします。
 主にUIロジックを疎結合にし、テスト容易性を向上させることを目的とします。
コミュニケーション:

  モデル(データとビジネスロジック)とビュー(ユーザーインターフェース)の間の通信は、プレゼンターを介して行われます。
 プレゼンターはモデルの変更を検知し、それに基づいてビューを更新します。
データフロー:

 データフローは一方向です。モデルからビューへの通知と、ビューからモデルへのユーザーアクションの伝達があります。


Observerパターン

目的:

  Observerパターンは、オブジェクトの状態変化を観察し、その変化を依存関係にあるオブジェクトに通知することを目的とします。主にオブジェクト間の疎結合を提供します。
コミュニケーション:

  観察者(Observer)と被観察者(Subject)の2つの主要な役割があります。被観察者が変更があると、それに応じて登録された観察者に通知します。
データフロー:

  データフローは通常、被観察者から観察者に向かう一方向です。観察者は変更通知を受け取り、その変更に対して必要な処理を実行します。


MVP(Model-View-Presenter)パターンとObserverパターンの違い

対象の範囲:

  MVPは主にUIコンポーネントとそれらの間のロジックに焦点を当てています。
Observerは一般的なオブジェクトの状態変更を監視するため、特定のアーキテクチャに依存せず、広範な使用が可能です。
通信方向:

  MVPは通常、モデルからビューへの一方向の通信を強調します。
 Observerは一般的には被観察者から観察者への一方向通知ですが、両方向が可能です。
目的:

  MVPはUIコンポーネントの構造化とテスト容易性を重視しています。
 Observerはオブジェクト間の疎結合と通知メカニズムを提供し、拡張性を向上させます。

  簡潔に言えば、MVPは主にUIコンポーネントの構造とロジックの分離に焦点を当てていますが、
 Observerは一般的なオブジェクトの状態変更を監視し、通知メカニズムを提供しています。



 Mediator(調停者)パターンとObserver(観察者)パターンは、いくつかの重要な点で異なります。以下は、これらのパターンの主な違いです。

Mediator パターン

目的:

  Mediator パターンは、多数のオブジェクト間の相互作用を中央の Mediator クラスに集約することで、オブジェクト間の結合度を低く保ちます。
 主な目的は、オブジェクト同士の直接の相互作用を減らし、中央の調停者を介してコミュニケーションを行います。
通信:

  オブジェクトは Mediator と通信し、Mediator は他のオブジェクトとの相互作用を制御します。
 各オブジェクトは通常、Mediator インターフェースを通じて Mediator とやり取りします。

一般的な使用:

  特に複雑なシステムやユーザーインタフェースにおいて使用され、各コンポーネントが互いに依存せず、中央の調停者に依存することが特徴です。


Mediator(調停者)パターンとObserver(観察者)パターンの違い

役割:

  Mediator パターンでは、中央の調停者がオブジェクト間の相互作用を制御します。
 Observer パターンでは、被観察者と観察者が直接通信します。
結合度:

  Mediator パターンはオブジェクト同士の結合度を低く保つことが主な目標です。
 Observer パターンはオブジェクト同士を疎結合にし、変更に対する柔軟な反応を提供します。
通信の方向:

 Mediator パターンは通常、中央の Mediator に対してメッセージを送り、Mediator が他のオブジェクトと通信します。
Observer パターンでは通常、被観察者が観察者に変更通知を送ります。

 簡潔に言えば、Mediator パターンはオブジェクト同士の中央制御に焦点を当て、Observer パターンはオブジェクトの状態変更に対する通知と反応に焦点を当てています。



 以上になります。

<参考サイト>


ソフトライム様
【Unity】Unityでメディエイターデザインパターンを当てはめてみる
https://soft-rime.com/post-12107/
Qiita @Cova8bitdot in Graffity株式会社 様【Unity】Unityで学ぶデザインパターン17: Mediator パターン【デザパタ】
https://qiita.com/Cova8bitdot/items/be33b97f35c84e...
はなちるのマイノート様
【Unity】FacadeパターンとMediatorパターンの違いと使い分けについて
https://www.hanachiru-blog.com/entry/2020/11/16/12...

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます