はじめに
筆者が個人でUnity開発を行う上で、どのような規模のプロダクトにおいても重要視しているのが「設計」です。
初学者のころは、レイヤー分けやクラス設計など考慮せずに MonoBehaviour
ベタ書きで作っていました。
設計に重きを置かないため、成果物が動作するまでのスピード感は良い点でしたが、進むにつれて整合性が取れなくなったり、機能の拡張がしにくいなどの悪い点が目立ちました。
結果、思い描いた品質を担保できなかったり、開発のモチベーションが低下するなど、苦い思いをすることが多々ありました。
これらの経験を踏まえて、開発体験と成果物の品質をより良いものにしたい!というモチベーションで設計工程に注力するようになりました。
本記事では、私が取り入れている設計手法とディレクトリ構成、メリット・デメリットをまとめます。
これからUnityプロダクトに設計を取り入れたい方や、同じ思いをして悩んだことのある方に届けば幸いです。
設計手法
「UCDD (UseCase Driven Design: ユースケース駆動設計) 」と「軽量DDD (Domain Driven Design: ドメイン駆動設計)」の手法を採用しています。
はじめに、UCDDにおける「ICONIXプロセス」を用い、ユースケースをもとに要件を整理します。
その後、軽量DDDの方針で、ドメインをもとに設計を行っていきます。
流れは以下の通りです。
1. UCDD
- ドメインモデリング
- 要求整理
- 用語整理
- ドメインモデル図の作成
- ユースケースモデリング
- ユースケース図の作成
- ユースケース記述の作成
- ロバストネス分析
- ロバストネス図の作成
- ドメインモデルの更新
2. DDD
- 集約設計
各項目の詳細はこちらの記事が非常に参考になります。
レイヤー構造
以下の4層から成る「オニオンアーキテクチャ」を採用しています。
- ドメイン層 (Domain Layer)
- アプリケーション層 (Application Layer)
- プレゼンテーション層 (Presentation Layer)
- インフラストラクチャ層 (Infrastructure Layer)
使用ライブラリ
前提条件として、以下のライブラリを使用します。
- R3
- クラス/レイヤー間のイベント駆動処理実装
- VContainer
- クラスの依存性注入 (DI: Dependency Injection)の実装
それぞれの導入方法については、以下の記事を参照ください。
ディレクトリ構成
ディレクトリは軽量DDDを参考に以下のレイヤー分けとします。
Assets/
└── Scripts/
├── Application/ # アプリケーション層
│ ├── Common/ # 定数・共通処理
│ ├── DTOs/ # 非ドメイン知識のデータクラス
│ ├── Services/ # 外部機能サービスのインタフェース
│ └── UseCases/ # ユースケースクラス
│
├── Core/ # レイヤー外の共通部品
│ ├── DI/ # DI
│ │ ├── XxxSceneLifetimeScope.cs # XXX画面のDIコンテナ
│ │ └── RootLifetimeScope.cs # 共通DIコンテナ
│ ├── Extension/ # 拡張
│ └── Utils/ # 共通ユーティリティ
│
├── Domain/ # ドメイン層
│ ├── Common/ # 定数・共通処理・基底クラス
│ ├── Factories/ # ファクトリ
│ ├── Models/ # ドメインモデル(集約単位)
│ │ └── ...
│ ├── Repositories/ # リポジトリのインタフェース
│ └── Services/ # ドメインサービス
│
├── Infrastructure/ # インフラストラクチャ層
│ ├── Repositories/ # リポジトリ
│ │ └── ...
│ └── Services/ # 外部機能サービス
│ ├── Audio/ # 音響(BGM・SE)
│ ├── Input/ # 入力処理
│ ├── Scene/ # シーン遷移
│ └── Time/ # 時間管理
│
└── Presentation/ # プレゼンテーション層
├── Common/ # 定数・共通処理・基底クラス
├── Objects/ # ゲームオブジェクト
│ └── ... # 配下にView/Presenter
├── Scenes/ # シーン管理
└── UIs/ # UI
└── ... # 配下にView/Presenter
各レイヤーが持つオブジェクトとその関係を以下の図に示します。
classDiagram direction TB Factories <-- Repositories Models <-- Factories Models --* Repositories Models <-- Services Services <-- UseCases Repositories --o UseCases DTOs <-- Services_ Repositories <|.. Repositories_ Services_ <|.. Services__ Services_ <-- Objects Services_ <-- UIs UseCases <-- Objects UseCases <-- UIs Objects --* Scenes UIs --* Scenes namespace Domain { class Common["Common"] class Factories class Models class Repositories class Services["Services"] } namespace Application { class Common_["Common"] class DTOs class Services_["Services"] class UseCases } namespace Infrastrucutre { class Repositories_["Repositories"] class Services__["Services"] } namespace Presentation { class Common__["Common"] class Objects class Scenes class UIs }
ドメイン層 (Domain Layer)
- アプリケーションのドメイン(エンティティ、値オブジェクトなど)を実装する。
アプリケーション層 (Application Layer)
- アプリケーションが持つ機能(ビジネスロジック)を実装する。
プレゼンテーション層 (Presentation Layer)
- UIやゲーム内のオブジェクトの表示・イベントハンドリングを実装する。
インフラストラクチャ層 (Infrastructure Layer)
- Unityエンジンの機能や外部データソースとの連携、データの永続化機能を実装する。
メリット
- クラスをレイヤー分けすることで、クラスの責務やクラス間の関係を綺麗にできる。
- クラスが疎結合になるため、機能を追加する際、既存機能のロジックへの影響を最小限にできる。
- 作業期間が空いても、上記のようなルールに則っていればキャッチアップまで時間がかからない。
デメリット
- 学習コスト、設計工数がかかる。
- 実装を開始しても、動作確認まで時間がかかる。
- 抽象化(抽象クラス、インタフェース)を多用するため、ファイル数が肥大化する。
まとめ
筆者がUnity開発を行うときに用いる設計手法とディレクトリ構成、またそのメリット・デメリットをまとめました。
取り入れるまでのコストはある程度かかるものの、コストに見合うかそれ以上の恩恵を得ることができると感じています。
ディレクトリ構成だけなど部分的にでも参考になれば幸いです。
今回は概要的なまとめでしたが、今後具体例も交えて紹介できればと思います。
ご覧いただきありがとうございました。