すいかゲームのクローンを設計する
趣旨
某ゲームのクローンを作ります。
実装は以下のリポジトリにあります
Scriptsディレクトリの構成
オニオンアーキテクチャ+MVPパターンで設計しました
- 各層でAssembly Definitionを定義しています
- 下位層から上位層を参照することはできません
- DIのためにZenjectを利用しています
- MVPパターンのためにUniRxを利用しています
- 非同期処理のためにUniTaskを利用しています
Domain層
どこにも依存しない(UniRxやUniTaskへの依存は許可)
- Interfaces:Domain ServiceおよびInfrastructure層のインタフェースを定義
- Services:Domain Serviceを実装
- ValueObject:Domain Modelを実装
- Entity:Domain Modelを実装(現状は無し)
UseCase層
Domain層を参照(インタフェース経由でInfrastructure層の実装を利用可能)
- Interfaces:UseCaseのインタフェースを定義
- UseCases:Domain Serviceやリポジトリを利用してUseCaseを実装
Infrastructure層
Domain層を参照(直接参照されることはない)
- Repositories:Domain ModelやDomain Serviceを利用してリポジトリを実装
- Services:外部APIなどを利用してServiceを実装(現状は無し)
- SODefinitions:スクリプタブルオブジェクトを定義(後述)
Presentation層
Domain層およびUseCase層を参照
- Interfaces:Viewのインタフェースを定義
- Presenter:Presenterを実装(UseCase層とViewのバインド)
- View:Viewを実装(MonoBehaviourの継承許可)
- SODefinitions:スクリプタブルオブジェクトを定義(後述)
Installer層
DIのためすべての層を参照
- DI設定を記述
シーン
各シーンは以下のような要素を持っています
※DIするMonoBehaviourなスクリプトおよびPrefabは実行するまでヒエラルキーに表示されません
※DIする非MonoBehaviourなスクリプトは実行してもヒエラルキーに表示されません
各シーン共通
プロジェクトコンテクスト(レイヤー”Default”)
実行時にDIされDontDestroyOnLoadになる
- インストーラ
- オーディオBGN
- 常に再生
- オーディオSE
- ユーザインタラクションに応じて再生
- インプットイベントハンドラ
- キーボード入力を検知してストリームソースを生成
タイトルシーン
メインカメラ(レイヤー”Default”)
レイヤー”Default”のオブジェクトを映す
UIカメラ(レイヤー”Default”)
レイヤー”UI”のオブジェクトを映す
バックグラウンドページ(レイヤー”Default”)
- 背景イメージ
タイトルページ(レイヤー”UI”)
- ボタン×3
- メインシーンに遷移
- 詳細なスコアランキング表示
- 音量設定パネル表示
- 詳細スコアランキングパネル
- ボタン×1
- 前の画面に戻る
- ボタン×1
- 設定パネル
- スライダー×2
- BGM音量
- SE音量
- ボタン×1
- 前の画面に戻る
- スライダー×2
ローディングページ(レイヤー”UI”)
シーン遷移の際に表示される
- ロード画面イメージ
インストーラ(レイヤー”Default”)
- タイトルシーンインストーラ
シーンコンテクスト(レイヤー”Default”)
イベントシステム(レイヤー”Default”)
メインシーン
メインカメラ(レイヤー”Default”)
レイヤー”Default”のオブジェクトを映す
UIカメラ(レイヤー”Default”)
レイヤー”UI”のオブジェクトを映す
ステージ(レイヤー”Default”)
- 箱
- マージアイテムとの接触判定を持ち、あふれるとゲームオーバー条件を満たす
- マージアイテム
- マウスを動かしたときに移動
- クリックしたときにマージアイテムを落下、SEを再生
- 同じ種類のマージアイテムが接触するとスコア加算、SEを再生
バックグラウンドページ(レイヤー”Default”)
- 背景イメージ
メインページ(レイヤー”UI”)
- マージアイテムパネル表示(次回に選ばれるアイテム)
- スコアパネル
- スコアランキングパネル
- 左右キーでカテゴリをスクロール
- ポーズパネル(Escキーを押したとき)
- ボタン×3
- メインシーンに遷移
- リスタート
- 現在のゲームに戻る
- ボタン×3
- ゲームオーバーパネル(ゲームオーバー条件を満たしたとき)
- ゲームオーバー時のスクリーンショット表示
- スコアランキング表示
- ボタン×3
- リスタート
- 詳細なスコアランキングパネル表示
- タイトルシーンに遷移
- 詳細スコアランキングパネル
- ボタン×1
- 前の画面に戻る
- ボタン×1
ローディングページ(レイヤー”UI”)
シーン遷移の際に表示される
- ロード画面イメージ
インストーラ(レイヤー”Default”)
- メインシーンインストーラ
シーンコンテクスト(レイヤー”Default”)
イベントシステム(レイヤー”Default”)
ModelとViewのバインド
UniRxを用いたMV(R)Pパターンにより、PresenterがModelとViewをバインドします。
UseCase層に実装したクラスをModel、Presentation層のViewに実装したクラスをViewとしました。
- UseCase層
- IReadOnlyReactivePropertyを公開
- Presentation層(View)
- IObservableを公開
- Presentation層(Presenter)
- IReadOnlyReactivePropertyとIObservableを購読
スクリプタブルオブジェクトの扱い
スクリプタブルオブジェクトはどの層からもアクセスできますが、オニオンアーキテクチャに従うのであればインフラ層からアクセスするべきだと思います。
現状は、SODefinitionsというスクリプタブルオブジェクトを定義するディレクトリがインフラ層とプレゼンテーション層に存在します。後者はViewで表示したい文字情報を定義するために使用されており、インフラ層を介さずプレゼンテーション層から直接アクセスしています。インフラ層→ユースケース層→プレゼンテーション層という流れが冗長に感じたためこのような仕様になっています。
このあたりは改善の余地があります。