Unityに関連する記事です

 引き続き、Mediator パターンと抽象化によるポップアップ管理の実装例です。

 複数のポップアップを1つのクラスで管理して新しいポップアップの表示・前のポップアップの非表示の制御の自動化を行います。


<完成動画>



 前回はこちらになります。

  => Mediator パターンと抽象化によるポップアップ管理の実装例



Madiator パターンによるポップアップ管理クラス ーPopupManager クラスー


 先ほど作成した AbstractSingleton クラスを継承し、シングルトンクラスとして作成します。
型引数には PopupManager クラスを指定し、PopupManager クラスをシングルトンクラス化しています。
これにより、いずれかのクラスからでも PopupManager クラスに容易にアクセスできる状態になり、ポップアップへの制御命令が可能になります。

 クラスの継承を行い、ポップアップにアタッチしているクラスの抽象化をしていることにより、List の管理も抽象化できます。
これにより、基底クラスである PopupBase を継承している派生クラスであれば、
複数のポップアップを PopupManager がまとめて管理することが可能です。



 処理のポイントとしては、List 内にある指定されたポップアップを表示し、かつ、以前に開いていたポップアップを閉じる動作を行います。
指定されたポップアップであればどれでも開くことが可能となり、加えて、以前のポップアップも自動的に非表示になります。

 またオプションとして、Stack を利用し、以前表示されていたポップアップに戻るヒストリー機能が追加されます。

 このようにポップアップの表示制御を行うために利用しているが Mediator デザインパターンを採用している目的になります。



PopupManager.cs

 <= クリックすると開きます。



<処理のポイント>


 Mediatorパターンの一つの主要な利点は、異なるコンポーネント(この場合はポップアップ)が直接的に通信せず、
中央のMediator(PopupManager として実装されています)を介してコミュニケーションが行われることです。
これにより、各コンポーネントはお互いの詳細を知る必要がなく、より疎結合で柔軟で保守しやすいコードを実現できます。

 PopupManager の Show メソッドがMediatorとして機能し、
異なるポップアップの表示と非表示がこの1つのメソッドで制御されることで、以下の利点があります。


1.単一責任の原則 (Single Responsibility Principle)

 各ポップアップは表示自体の責任しか持っておらず、他のポップアップとの連携や表示順序について心配する必要がありません。
Show メソッドがそれらの連携を中央で制御することで、各ポップアップはシンプルで独立した形を保ちます。

 これにより、前に開いていたポップアップの非表示について、各ポップアップは心配する必要もありません。


2.柔軟性と拡張性(Flexibility and Scalability)

 変更への柔軟性は、主にサブクラスの内部実装の変更が発生した場合に、他のクラスに影響を与えずに変更できる能力を指しています。

 Mediatorパターンでは、各クラスがMediatorを介して通信し、具体的な実装の詳細を知らないようになります。
例えば、A というサブクラスを修正しても、その修正は、B というサブクラスや C というサブクラスには影響がありません。

 このため、あるクラスの内部実装が変更された場合でも、他のクラスは変更に気づかずにそのまま動作できます。
サブクラスが内部実装の変更に対して開かれ、他のコンポーネントからは隔離されていることが柔軟性をもたらします。



 変更への拡張性も同様で、新しいサブクラスが追加されたとき、それが他のクラスに影響を与えずに変更できる能力を指しています。
 
 Mediatorパターンでは、新しいポップアップが追加された場合でも、それが他のポップアップとどのように連携するかについて考える必要がありません。

 PopupManager の Show メソッドを呼び出すだけで、新しいポップアップを表示し、前のポップアップを閉じるセットの処理が行われます。
この簡潔な処理は、開発者が手動で複雑な状態管理を行う必要がなくなり、直感的で使いやすくなります。

 そのため、既存の Show メソッドを呼び出すだけで済むため、開発者が覚えやすく(理解しやすく)、コードがシンプルかつ、読みやすさも向上します。

 これは Mediator パターンに加えて、ポップアップが PopupBase クラスをベースに抽象化されて機能しているため実現できています。


3.疎結合性 (Loose Coupling)

 各ポップアップは PopupManager に対して疎結合です。
PopupManager は各ポップアップを知っている(List で管理している)が、
各ポップアップには PopupManager の変数がありませんので、PopupManager を知らない状態です。
同様に、各ポップアップ同士もポップアップへの変数を持ちませんので、それぞれのポップアップ同士も知らない状態です。

 つまり、それぞれのポップアップは他のポップアップの内部実装や機能について知る必要がありません。
各ポップアップ内に変更が発生した場合や、新しいポップアップが追加されても、
Mediatorパターンにより、既存のコードに対する影響が最小限に抑えられます。


4.管理が容易

 PopupManager が異なるポップアップを管理し、表示順序や連携を制御しているため、コード全体の可読性が向上し、ポップアップの管理が容易になります。


5.エラーの減少

 手動でポップアップの表示と非表示を管理する場合、開発者がうっかり処理の順序を間違える可能性があります。
Mediatorパターンにより、正しい順序でポップアップが処理されることが保証され、エラーの可能性が減少します。
以前開いていたポップアップのケアも不要になるため、重複してポップアップが開いてしまうケースも抑制されます。



 このような Mediatorパターンに基づいた設計は、開発者はポップアップの表示と非表示に関連する複雑な状態遷移を気にせず、
シンプルで一貫性のある処理を使用できます(毎回 Show メソッドを使うだけで済みます)。
これにより、コードの保守性が向上し、バグの発生リスクが低減します。誰にとっても、理解しやすく、安全にポップアップを管理できるようになります。
 
 加えて、抽象化によって、コードがより理解しやすく、変更に対する柔軟性が向上し、再利用性と拡張性も向上します。

 これが、Mediatorパターンと抽象化が、設計や保守性向上に寄与する主なメリットの一つです。


テスト用クラス ーPopupChangeTester クラスー


 ポップアップの切り替え制御の確認用のテストクラスを作成しておきます。
特定のボタンを押した際にポップアップが切り替わる設計です。

 ボタンは任意に設定してください。

 UpdateAsObservable メソッドを利用するため

using UniRx;
using UniRx.Triggers;

 の2つを追加しています。



PopupChangeTester.cs

 <= クリックすると開きます。




 連打防止機能は入っていません。
追加する場合には ThrottleFirst オペレーターを活用してください。


    // キーボードの P ボタンを押したら StoreGoodsPop を表示する
        this.UpdateAsObservable()
            .Where(_ => Input.GetKeyDown(KeyCode.P))
            .ThrottleFirst(System.TimeSpan.FromSeconds(1.0f))    // ← 連打防止機能用に追加します
            .Subscribe(_ => PopupManager.instance.Show<StoreGoodsPop>());


PopupManager ゲームオブジェクトを作成し、PopupManager スクリプトと PopupChangeTester スクリプトをアタッチし、設定を行う


 ヒエラルキー内の空いている場所で右クリックしてメニューを開き、Create Empty で新しいゲームオブジェクトを作成します。
名前を PopupManager に変更し、先ほど作成した PopupManager スクリプトをアタッチします。


<インスペクター画像>



<インスペクター画像>




 PopupList 変数のサイズが 0 ですので、こちらを 3 に変更します。
アサイン可能なクラスが PopupBase であることを確認しておきましょう。

 アサイン時に確認しているのは、基底クラス(PopupBase)です。
そのため、基底クラスを継承している派生クラスは、どれであってもアサイン可能な状態になっています。

 これは抽象化の恩恵の1つです。


<インスペクター画像>



 どの順番でもよいので、ヒエラルキーにある各ポップアップのゲームオブジェクトをドラッグアンドドロップしてアサインします。
アサインはゲームオブジェクトしか出来ません。(スクリプトをそのままアサインすることは出来ません。)


<インスペクター画像>



PopupManager ゲームオブジェクトに PopupChangeTester スクリプトをアタッチする


 スクリプトをアタッチするのみで、設定は不要です。


<インスペクター画像>


 以上で完成です。


ゲームを実行して動作を確認する


 PopupManager によるポップアップの制御が行われるか、ゲームを実行して確認します。
どのような制御が行われているか、想定通りになっているかをイメージして動作確認を行ってください。
漠然とゲームを実行して、動作確認をしないようにしましょう。

 テストクラスにボタン押下時の処理が書いてありますので、
該当するいずれかのボタンを押して、ポップアップが表示されるか確認を行います。

 その後、別のボタンを押し、ポップアップが切り替わることを確認します。

 同様に、同じボタンを押した際に、重複してポップアップが開かないことも確認します。
この場合、Console ビューにログが表示されます。


<確認動画>



 すべて想定通りの挙動になっていれば制御成功です。


まとめ


 通常の実装では、「1.新しいポップアップを開く」と「2.前のポップアップを閉じる」がセットであり、
この過程を正しく管理しないと、ポップアップの表示状態が不正確になり、予測できない挙動が発生する可能性があります
そのため「2」の部分を手動でケアしたり、閉じたいポップアップの管理をしなければ正常な動作が保証されません
よって、2つの命令が常にセットで必要になります。

 今回の場合、テストクラスのソースコードを見ると分かりますが、「1.新しいポップアップを開く」動作しか書いていません

        this.UpdateAsObservable()
            .Where(_ => Input.GetKeyDown(KeyCode.O))
            .Subscribe(_ => PopupManager.instance.Show<TitlePop>());

 今回採用している実装では、この「2」の部分を気にする必要がありません。
なぜなら、PopupManager クラスの実装が「1.新しいポップアップを開く」命令だけを行えば、自動的に「2.前のポップアップを閉じる」を行うように処理が作られているので、
「2.前のポップアップを閉じる」を書き忘れしまうこともなく、安全に「2.前のポップアップを閉じた」上で「1.新しいポップアップを開く」ことが出来ています。

 通常であれば前のポップアップを閉じ忘れてしまうと、正しい動作になりませんが、閉じるために管理して(覚えて)おく必要もなければ、閉じる命令処理も必要ありません。

 以上のことから、Mediator デザインパターンを導入することで、この問題を効果的に解決できていることが分かります。



 また、Mediator デザインパターンと抽象化を活用した設計を行って実装しておくことにより、
新しいポップアップを追加した場合であっても、命令先を切り替えるだけで済みます。

PopupManager.instance.Show<TitlePop>();

PopupManager.instance.Show<新しいポップアップのクラス名>();  //  ← <型引数>の指定を変えるだけ。
そのため、ポップアップが増えたとしても、新たに PopupManager の中身を書き換える必要もありません

 これは Show メソッドがジェネリック型の型引数を受け取って実行できる仕組みになっており、
かつ、その型が PopupBase を継承しているものだけに限定していることで実現しています。

public void Show<T>(bool keepInHistory = true) where T : PopupBase {  // ← where 句により、T 型で実行可能な型引数の型を PopupBase か、それを継承しているクラスに限定している


 処理の抽象化をすることにより、柔軟かつ、汎用的な処理を構築することが可能になります。

 このような設計でポップアップの制御のサイクル化を行うことにより、
新しいポップアップのゲームオブジェクトを作成した場合には、PopupBase クラスを継承した派生クラスを作成すれば
その新しいポップアップも PopupManager による表示の管理が可能になります。


設計デザインのポイント


 複数のプログラム上の考え方(概念)を上手く組み合わせることで、今回のポップアップの管理機能を構築しています。


1.シングルトン (Singleton)デザインパターン


 PopupManager クラスが AbstractSingleton<T> クラスを継承しており、これによりシングルトンパターンが実装されています。
シングルトンは、特定のクラスのインスタンスがプログラム内で唯一であることを保証し、グローバルにアクセス可能な管理者クラスのようなものを提供します。
これにより、いずれかのクラスでも簡単にポップアップの制御命令を出すことが可能な状態になります。


2.抽象化(Abstraction)とポリモーフィズム(多態性)


 抽象化は、プログラム内での共通のパターンや概念を抽出し、それに対する共通のインターフェースや基底クラスを定義するプロセスです。
今回は PopupBaseクラスや AbstractSingleton クラスがその一例です。
PopupBase においては、異なるポップアップが共通の基底クラスを共有することで、PopupManagerは異なるポップアップを統一的に扱うことができます。

 また、共通の操作や挙動に関するインターフェースを定義することも抽象化の一環です。
PopupBaseクラスがボタンのクリックや表示・非表示などの共通の操作を定義しており、これが抽象的なインターフェースとなります。

 抽象化により、新しい種類のポップアップを追加する際にも、PopupBaseクラスを継承するだけで、既存の基本的な挙動を利用できます。
これが変更への柔軟性と拡張性を提供します。

 そして同じ基底クラスを継承することで、共通の挙動やプロパティを再利用できます。
これにより、コードの冗長性(同じ内容を色々な場所に書く)が減少し、保守性が向上します。

 こういった部分が抽象化の恩恵になります。



 ポリモーフィズムは、オブジェクト指向型プログラミングにおける、3つある概念の1つです。

 抽象化により、異なるポップアップが PopupBase クラスを共通の基底クラスとして継承することで、ポリモーフィズムが実現されます。

 PopupManagerはPopupBase型を扱うことで、異なるポップアップの実体を気にせずに、共通の基底クラスで統一的に操作・管理できます。
これにより、PopupManager は異なるポップアップの派生クラスを List に入れて、一元管理できる状態になります。


3.オブザーバー(Observer)デザインパターン


 UniRxを使用して PopupBase クラス の btnClose のクリックイベントを観察し、クリックが検出されると指定の処理が実行されます。
これはObserverデザインパターンの一形態で、ボタンのクリックといったイベントに対して柔軟で反応性の高い処理を提供します。


4.メディエーター(Mediator)デザインパターン


 PopupManager が異なるポップアップ間のコミュニケーションを調整しています。
Show メソッドを通じて、異なるポップアップが相互に通信し、表示や非表示の制御を行っています。
これはMediatorデザインパターンに近いアプローチです。


5.スタックデータ構造の利用


 履歴スタック (history) を使用して、ポップアップの表示履歴を保持しています。
これにより、ユーザーが前に表示されていたポップアップに戻ることができます。


6.LINQの利用


 元となる処理も一緒に記述されていますが、LINQを使用してコードを簡潔にしています。
特に OfType メソッドを使用して指定された型のポップアップを抽出する際に効果的に利用されています。


7.ジェネリック(T)型と型引数の利用


 PopupManager の Show メソッドに型引数を設定し、where 句で PopupBase のみを受け取り可能にすることにより、
PopupBase を継承している派生クラスの型のみを受け取り、ポップアップを安全に表示させることが出来ます。

 AbstractSingleton では型引数に where 句で Component を指定しているため、Unity の Component 型に制限しています。
Component 型はすべてのゲームオブジェクトにアタッチされるベースとなるクラスです。
自作した任意のクラスや Unity のコンポーネント名を指定することで、シングルトンクラスとして実装することが出来ます。



 総じて、この設計デザインはポップアップの一元管理、状態管理、イベント処理を効果的に行うためのパターンを組み合わせています。
これにより、柔軟性があり、新しいポップアップを容易に追加でき、異なるポップアップ間の連携がしやすくなっています。



 以上になります。


<参考サイト>


ソフトライム様
【Unity】Unityでメディエイターデザインパターンを当てはめてみる
https://soft-rime.com/post-12107/
Qiita @Cova8bitdot in Graffity株式会社 様【Unity】Unityで学ぶデザインパターン17: Mediator パターン【デザパタ】
https://qiita.com/Cova8bitdot/items/be33b97f35c84e...

コメントをかく


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

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

Menu



技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

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

3D脱出ゲーム(抜粋)

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

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

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

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

VideoPlayer イベント連動の実装例

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

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

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

private



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

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