ターン制RPGを作って気づいた設計の話

仕事や趣味でターン制RPGを作ることがありました。そのRPGのバトル部分に用いたアーキテクチャと課題についてまとめます。ここでいうターン制RPGとはドラゴンクエストみたいなゲームのことです

仕様としては、ファミコン時代のドラクエです。ドラクエ6以降みたく敵がリッチにアニメしたり、味方が表示されてアニメしたりはしません。スキル発動時などに立ち絵がスライドインしたり、エフェクトはパーティクル使ったリッチなものです。

レイヤードアーキテクチャ

今回採用したのは、EricEvans型レイヤードアーキテクチャです。

[DDD]ドメイン駆動 + オニオンアーキテクチャ概略 - Qiita
DDD連載記事なぜDDD初心者はググリ出してすぐに心がくじけてしまうのかドメイン駆動設計の定義についてEric Evansはなんと言っているのかモデルでドメイン知識を表現するとは何かドメイン…

厳密にいうと、だいぶ崩して使っているので半端なレイヤードアーキテクチャになりました。

UI

UI層は、Viewのみ。開発期間の短さとUGUI以外に切り替えることはありえなかったので単純化しました。ViewModelも作らず、ドメイン層のEntityModelを直接渡す力技で実装しました。ただし悪い実装だと思ったので随時ViewModelを使用するようにリファクタリングしてます。

Application

Application層は、疑似階層型ステートマシンで実装しました。Web系アーキテクチャはステートレスな実装が多いです。しかしゲームのような複雑な状態遷移を持つソフトウェアでステートレスな実装をするとだいたいスパゲッティコードになりバグが噴出します。コルーチンはシンプルな一方通行の状態遷移をあつかうならまだ使えますが、バトルは循環型の状態遷移であり向いていません。

Domain

Domain層は、ターン制バトルのコアです。仕様をもとに素直に実装しました。ユニットテストもここのクラスに対し書きました。ドメインイベントは使いませんでした。UniRXとか流行ってますが、Application層でも述べた通りリアクティブスパゲッティになるのでやめました。かわりにApplication層からドメインクラスの関数を呼ぶようにしました。

Infrastructure

Infrastructure層は、API通信のクラスやアセット読み込みクラスを実装しました。ドメインクラスはバトル開始時に一括で生成し常駐させておくので、リッチなDBアクセスクラスなどはありません。

ロジックと演出の分離

関心事の分離はソフトウェア設計の基本的な考えです。ドメインイベントを使わず、Application層からの指示に依存する箇所はこのロジックと演出の分離を用いました。具体的には、ロジックとはHPの減算。演出とはエフェクトの再生、ダメージボイス再生、SE再生、ダメージアニメーション再生などです。

ロジックとはドメインクラスの関数呼び出しです。Application層からDomainServiceに処理を委譲します。演出は、Application層で演出用のデータを作成しキューにつめます。それをApplication層が処理してPresentation層に対し、エフェクト再生を命令したり、ダメージポップアップ命令したり、サウンド再生命令したりします。演出用データはコマンドパターンを使って実装しました。

ここまでの設計において、敵クラスやプレイヤクラスが存在しないことに気づいたでしょうか。FPSとかアクションゲームだったら必ず、AI制御の敵クラスや操作対象のプレイヤクラスがあります。そしてそのクラスが描画やドメインロジックを委譲したりします。

しかし今回の場合はキャラクターを制御しない仕様なので、わざわざ操作用にキャラクタークラスを作る必要がないと判断しました。敵と味方で処理が違う場合は、敵用コマンド、味方用コマンドを作ることで対応します。これなら召喚獣やボスのような新しい概念が出ても柔軟に拡張できます。

考察

Domain層に敵クラス、プレイヤクラスを作りました。しかし表示制御クラスも含めたキャラクタークラスが無いのは直感に反します。キャラクター制御をしないゲームをあまり作ったことがないため、この設計が正解なのかは自信がありません。

ドメインイベントは使いませんでしたが、メインシーケンス(FSM)に影響がない場合に限っては使った方が単純になるかなと思いました。例えばダメージポップアップはFSMとは完全に独立した演出です。コマンド内でドメインクラスに対しHP減算、ダメージポップアップ制御クラスに対しポッブアップ。とするよりもドメインクラスに対しHP減算、ドメインイベントが発行されてダメージポップアップ制御クラスが受信してポップアップ。の方が、HP減算したらダメージポップアップという処理をまとめたApplication Serviceを用意するより簡単かなと思いました。

レイヤードアーキテクチャにおけるinfrastructureとUIを明確に分離するメリットを感じませんでした。どちらもDomainとApplicationの外にあるものくらいに感じました。体感的には今回の設計は、オニオンアーキテクチャの方が近いように思いました。

まとめ

アクションゲームなどではキャラクターが主体なので、エージェントアーキテクチャ中心の設計がうまくいきます。これと違って、普通のレイヤードアーキテクチャがうまくいくのは本当におもしろいです。もちろんロジックと演出の分離という点に気づかないと巨大なFSMになって苦労します。