マップを作る(2)(Unityメモ)
クラス構造の見直し。
シーン間の情報伝達
ゲーム内のイベント演出では、「マップから戦闘シーンに移行」「ホームに戻る」というような、シーン間の遷移を行うものがあります。特に戦闘シーンへの遷移では、敵パーティやマップ固有のパラメータといった戦闘情報をマップシーンから戦闘シーンへ伝達する必要があります。前回の記事ではその部分が未設計でした。
クラス構造の見直し
シーン間遷移を設計するにあたって、前回の設計のままシーン間の情報伝達を組むと処理が煩雑になったので、実行フローとクラス構造を修正しました。
前回時点の実行フローの問題点
前回時点の実行フローでは、演出オブジェクトをViewヤード(表示システム側)で管理します。表示対象を表示システム自身が管理するという点では依存関係が表示システム内で閉じるのですが、他のヤードに情報伝達する際に呼び出し関係が混乱してしまいました。
・呼び出し階層が深く、かつ依存性逆転の回数が多い
・外部ライブラリ側のViewヤードに属する演出オブジェクトが、内部側のContentヤードに属するマスタデータを持つため、参照の方向が設計方針と食い違う
2点目について追記すると、例えば敵パーティはマップのスポットごとに設定します。そのため敵パーティの情報はマップのマスタデータとして扱うのが自然です。一方、戦闘シーンの演出表示は表示システム側で保守するのが自然ですが、その際には敵パーティの情報が必要となります。この時に参照の方向と設計方針との食い違いが生じます。
演出実行処理をHubヤードで保守
ヤード間の依存関係を整理するため、演出オブジェクトの実行処理をViewではなくHubヤードに所属させることにしました。演出オブジェクトはマスタデータを扱うContentヤードへの所属となります。これで設計が少しすっきりしました。
なお演出の内容として画面表示などViewヤードが保守する処理があり、それらは演出オブジェクトから呼び出します。中間層のContentヤードから外部寄りのViewヤードを参照するのは設計方針どおりです。
変更後のマップシーンのクラス構造
実行フローの変更に伴ってマップシーンのクラス構造も見直しました。大きな変更点としては、マップ情報のプレハブを丸ごとViewからContentヤードに移動しました。
前述の通り、演出オブジェクトはContentヤードの所属とします。「演出がViewの要素であり、Domainであるゲームルールとは直接関係しない」という考えはそのままです。ただし演出の内容として資源入手などゲームルールに関わる処理はあり得ます。その場合はその都度Domainヤードの処理を参照します。この点でも、Contentヤードの所属だとDomainとViewを両方参照できるため好都合です。
なお状態機械はBehaviourに持たせたままとしました。演出オブジェクトから状態遷移の呼び出しを受けるのはServiceですが、ロード待機など実行時に状態の管理を必要とするのはBehaviourであるためです。
シーン遷移
シーン遷移のフローは前回とおおむね同じです。違いはシーン間の情報伝達を体系化したことと、Contentから最上位のSceneSwitcherまでの呼び出し階層が浅くなったくらい。
ホームからマップシーンへの遷移
シーン遷移では色々な処理があります。マップから戦闘シーンへの遷移の設計も含めて、シーン遷移のフローを整理しました。特に考慮したのは以下の点です。
・シーン間の情報伝達
・シーンの表示切替え演出(フェードアウト・イン)
・アセットのロードと待機
これを踏まえて、ホームからマップシーンへの遷移処理を考えました。
現状では状態遷移用のクラスを使って待機処理を実装していますが、ここまでフローを分割していると、awaitで待機処理を書けそうな気もします。
シーン間の情報伝達に関して、基本設計では1回のフローで情報伝達していますが、詳細設計では状態監視をはさんでフローを分割しています。これにより依存性逆転をメニュー(下位)・SceneService(上位)間の逆転とSceneService(下位)・SceneSwitcher(上位)間の逆転に分割し、依存関係を簡潔にしています。
マップシーンから戦闘シーンへの遷移
マップシーンから戦闘シーンへの遷移処理も整理できました。ホームからマップへの遷移と同様に扱えます。プレイヤーパーティはホームからマップへの遷移時に、マップシーンがホームから受け取った情報です。戦闘時はこれもマップシーンから戦闘シーンに渡されます。
戦闘シーンでは戦闘ログを計算し、その再生を行います。戦闘ログはゲームルールに依存するのでDomainヤードに属します。一方で戦闘ログの再生はViewヤードに属します。戦闘ログの再生、つまり戦闘エフェクト等の演出はUnity等のゲームエンジンに依存するためです。そのため、Domainヤードの戦闘ログを、その再生システムが受理できるViewヤード用のものに変換する必要があります。
演出オブジェクトの実行
基本設計
演出オブジェクトの実行フローは前回とおおむね同じです。違いは以下のとおり。
・演出オブジェクトからSceneSwitcherまでの呼び出し階層が減った
・上記に伴って依存性逆転の回数が減った
・演出オブジェクトがDomainを参照できるようになったので、ヤード変換が不要になった
詳細設計:サブサービスに分割
クラス構造を変更してシーン間の情報伝達を行いやすくなりましたが、サービスに機能が集まり過ぎています。そこで機能をサブサービスに分割します。
・Viewに属するBehaviourの制御→MapSceneService(今まで通り)
・演出オブジェクトの実行位置の制御→DirectionServiceに分割
詳細設計:実行フロー
演出オブジェクトの関係先がDirectionServiceに替わった他は、前回と同様です。
Envメンバの取得に関して、コールバックが不要になりました。DirectionServiceはHubヤードに属するため、依存関係に逆らわずにDomainヤードを参照できます。
演出オブジェクトの実行位置の制御方法については要検討のままです。現状は「演出ポインタ」(スポットリストの要素と、そのスポット内の演出リストの要素のインデックス)を定義して実行位置を制御しています。
メッセージ表示演出の処理も大枠は同じです。表示先レイヤの取得時にMapSceneBehaviourへの照会が必要なので、DirectionServiceにMapSceneBehaviourへの参照を持たせます。
戦闘シーンへ遷移する演出については、演出オブジェクトとSceneSwitcherとで処理を分担します。
・演出オブジェクト:「Serviceを状態遷移」まで
・SceneSwitcher:「状態変化を検出」以降
動作の実例
演出オブジェクト側で敵パーティを指定して戦闘シーンに遷移できるようになりました。戦闘ログの生成システムと戦闘シーンとはまだ結合していませんが、その枠組みはできました。
(参考)サービスを演出オブジェクトに渡す
シーン間の情報伝達に関して、現状ではサービスのコールバックを使って演出オブジェクトからサービスへ情報オブジェクトを渡しています。この方法の場合、情報オブジェクトの種類が増えるとコールバックの種類も増えて保守が煩雑になります。
そこで演出オブジェクトに情報の送信先となるサービスのインスタンスを渡し、演出オブジェクト側でサービスを直接呼び出してもらう方法を検討してみました。
演出オブジェクトに送信先のサービスのインスタンスを渡す場合、まず演出オブジェクトの親がそのインスタンスを知っている必要があります。しかし多くの場合サービス同士は互いに独立しています。特にインスタンス生成時に他のサービスのインスタンスを取得できません。そこでインスタンス生成とは別のタイミングで送信先サービスのインスタンスを取得することになります。結果として、サービスロケータのような仕組みが必要になります。
サービスロケータは便利ですが乱用は避けたいです。また(ゲームエンジンの方の)UnityのGetComponent()がサービスロケータのような機能を持ちますが、特定のゲームエンジンに依存したくないHubヤードでGetComponent()に頼るのも考え物です。
他にはPub/Subアーキテクチャを使ってイベント駆動型にして、サービス間の依存性を分離することもできます。しかしライブラリを使っても実装が大掛かりになりがちです。