2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FEHで学ぶDDD 後編 -ルートエンティティとトランザクション-

Last updated at Posted at 2018-12-21

##部隊を編成しよう ルートエンティティ

FEHでは基本的に4人で1部隊を作り、戦闘に臨みます。この部隊は保存可能です。
image.png

エンティティが他のエンティティを含む場合、それはルートエンティティとなります。
つまり組織に人間が所属するように部隊に人間が所属する「部隊」がルートエンティティ…と思いきやそうではありません。

リアルでは正しいかもしれないがFEHでは間違った例
リアルでは正しいかもしれないがFEHでは間違った例

同じ種類のドメインオブジェクトにおいて複数のルートエンティティが同じエンティティを含むことはできません。例えば複数の部隊が同時に別々の戦場に出るとき、同じエンティティを含んでいる場合は問題になってしまいます。同じ人間が複数存在するわけがないですからね。エンティティへの参照・コピーなら含むことができますが。
ですがFEHでは別部隊にも同じラインハルトを含むことができます。戦闘は一度に一つしか発生しないからです。
FEHでは部隊というのは名簿でしかないというわけですね。

ではFEHにはエンティティを含むルートエンティティはないのでしょうか?

ルートエンティティとなりえるものは有ります。戦闘/戦場そのものです。

正しい例
正しい例

戦闘を開始した場合、クリアするか明示的に終了するまで戦闘は終わらず、中断した場合は同じ状況から再開します。つまり1戦闘=エンティティとしての1トランザクションということですね。中断の際に状況を保存する際にもこのルートエンティティを丸ごと保存すると保存する単位が分かりやすいですね。

##操作はルートエンティティへ行う

DDDでは個別のエンティティへの操作はルートエンティティを通して行うことになっています。この例でいえば、各ユニットをそれぞれ操作するというよりは戦場に対してどのユニットをどこへ動かすかを操作するという事ですね。

image.png

各ユニットをエンティティとして操作したくなりますが、そうすると同じ場所に2ユニット重なってしまったりとか整合性の取れない位置関係になったりします。操作を戦場への操作に限定し、位置関係の管理を戦場の責務とすることで整合性を保つことができます。このユニットの動きをクローンしようとした際に(LibGDXはユニットへのタッチを検出するため)うっかりユニットを操作する仕組みで作ろうとしてバグりまくってあきらめたのは秘密です

##ルートエンティティに含まれるエンティティは具体的には何か?

さてこのルートエンティティに含まれるユニットとしてのエンティティですが、これはラインハルトの「全て」を含むべきでしょうか?実のところFEHにおいてはその必要はありません。
ラインハルトのスキル構成は戦闘中には変更できませんし、能力値は実はレベルと成長率と得意不得意で計算されるのでレベル以外はガチャで出たときから同じです。
戦闘中に経験値が増えたりその結果レベルが上がったりHPが減ったりその結果なくなったら除去されたりバフやデバフがかかったりしますが、戦闘中にしか意味がないため戦闘が終わればなくなります。

つまり、同じエンティティにおいて、ユニットの編成上での状態とユニットの戦闘上での状態との二つがあるわけですね。ここでエンティティを二つのオブジェクトに分けて、戦闘中のオブジェクトから編成上のオブジェクトを参照する形にすることで、状態の寿命を管理することができます。
image.png

##最終形

戦場をルートエンティティとし、戦場のユニットたちを子とします。戦場のユニットたちは戦場でのみ存在する状態を持ち、元になる編成されたユニットへの参照を持ちます。編成されたユニットはその元となる英雄の能力を参照します。この元となる英雄は運営が頑張って追加してくれていて、基本的には不変です。余談ですが不変なので英雄の能力値はステータス(状態)ではありません。

image.png

戦闘が始まるときに戦場のユニットを初期化し、戦闘中はこのオブジェクトとその位置のみが状態を変えます。元になるユニットは更新されないので直接の参照か値オブジェクトで十分です。もしトランザクション中に更新される可能性があるならば(本にあったように)トランザクション開始時に値をコピーしておくべきでしょう。
なお位置はユニット自身に持たせたくなりますが、他のユニットとの位置関係が重要になる一方、自分で自分の位置を参照することに意味はありません。よって戦場がまとめて管理するのが良いでしょう。

プレイヤーは戦場に操作を指示し、戦場はそれを該当する戦場のユニットに伝えます。戦場のユニットは自分の状態とユニット本来の性能から移動や戦闘やアシストを行い状態を変えます。
全員が行動を終えたら戦場が対戦相手に操作の権利を渡します。
そしてこれらの行動と状態を必要に応じてDBなどに保存します。これによって、戦場の状態だけを連続して保存することができ、不意の中断時にも状況を再開できます。
戦闘が終わったら、取得した経験値、SP、英雄の羽をもとになるユニットへ書き戻します。
これでトランザクション終了です。

寿命の短いほうから長いほうへ参照するのがポイントですね。逆に寿命の長いオブジェクトに寿命の短いオブジェクトを状態として持たせるほうが実装は楽なのですが、保存する際の単位が分かりにくくなります。

結局のところこれはDBで言うところの古き良きマスタ/トランザクションの仕組みなわけですが、マスタとトランザクションが具体的にどう違うのか、あるデータがあったとしてそれがどちらであるかは皆さん後輩とかにきちんと理由をつけて説明できていましたか?少なくとも私は「頻繁に更新されるのがトランザクション」くらいしか聞いた覚えがありません。でもこれでトランザクションであるかその境界はどこにあるかって具体的に説明/モデル化できますね。

編成する、つまりもとになるユニットを変更する際にはそのユニットそのものがルートエンティティとして独立しているので自分自身をDBに保存する際には整合性の問題は発生しません。経験値取得は少々問題になりますが、今回に限れば増加分だけ管理すればいいでしょう。

ユニットのデータ構造については去年の記事をご覧ください。

##トランザクション=境界付けられたコンテキストか?

前回書いたように、複数のゲームモードがありそれぞれ別のルールが適用されている=境界付けられたコンテキストです。
そしてこのトランザクションはゲームモードに入るところから始まり出るところで終わるのでトランザクション=境界付けられたコンテキストです。
なお、このコンテキスト内に「将棋盤のように駒を動かし位置が変化する」サブドメインと「戦闘を行いステータスが変化する」サブドメインの二つがあります。「将棋盤のように駒を動かし位置が変化する」サブドメインで別のSLGも作れますからね。

##マイクロサービスの単位=サブドメインか?
マイクロサービスにはあまり詳しくないのですが「マイクロサービスの単位」が1アクセスだとするとサブドメインを複数含むこの構造はマイクロサービスの単位≠サブドメインとなってしまいます。
ただし実装上はこの戦闘トランザクションはローカル環境で完結し、戦闘結果としてのユニットの成長のみサーバに送ることになり位置関係は送りません。つまり、英雄(編成)サブドメインで完結します。結果論としてはマイクロサービスの単位=サブドメインとなります。

余談ですが戦闘には乱数が絡まないので初期状態と手筋を保存すればいつでも再現できます。
最近、天空城という自分でマップを作る/他のユーザの作ったマップに挑戦するモードが追加されました。攻められた時の手筋はリプレイできるようになっていて結構面白いです。攻める分にはコピペマップばかりで面白くは無いですが。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?