Upgrade to Pro — share decks privately, control downloads, hide ads and more …

大規模Unityゲーム開発の設計事例 〜ドメイン駆動設計とDIコンテナを導入した一年を振り返る〜 / cedec2021-ddd

大規模Unityゲーム開発の設計事例 〜ドメイン駆動設計とDIコンテナを導入した一年を振り返る〜 / cedec2021-ddd

CEDEC2021 「大規模Unityゲーム開発の設計事例」の資料です。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
セッションの内容
現在開発中タイトルにおける、プログラム設計に関しての知識・経験を共有します。

ソーシャルゲーム開発では、作品を素早く完成させてリリースする事も大切ですが、同時にその後の継続的な運用開発についても考える必要があります。その際に重要になってくるのが設計です。

属人化を避けたり、メンテナンス性、制作スピードを維持するためにはメンバー間で統一された設計思想が必要だと考えました。

そのために私達は「ドメイン駆動設計(DDD)」と「DIコンテナ」という2つの手法を導入しました。

導入から一年が経ち、それらを振り返りつつ事例を紹介します。

https://cedec.cesa.or.jp/2021/session/detail/s60644099db9d6

CyberAgent SGE Engineer

November 17, 2021
Tweet

More Decks by CyberAgent SGE Engineer

Other Decks in Programming

Transcript

  1. 大規模Unityゲーム開発の設計事例
    〜ドメイン駆動設計とDIコンテナを導入した一年を振り返る〜
    Applibot 畑山政道

    View Slide

  2. 2
    自己紹介
    【経歴】
    広告系Flashコンテンツ開発 → 2011年 アメーバピグ Flash開発で(株)サイバーエージェント入社 → スマホブ
    ラウザゲーム開発 → スマホUnityゲーム開発 → (株)アプリボット所属
    【主な仕事】
    Unity全般・設計・3D描画関係・昔はFlashでアニメーション作ったり演出・UXも考えたりしていました
    畑山 政道
    Unityエンジニア
    2011年 中途入社

    View Slide

  3. はじめに・ドメイン駆動設計を導入した経緯
    1
    2
    3
    4
    目次
    ドメイン駆動設計とUnity適用までの具体的な解説
    DIコンテナとUnity適用までの具体的な解説
    その他の設計手法の紹介
    5 実装の詳細
    6 まとめ

    View Slide

  4. 1. はじめに・ドメイン駆動設計を導入した経緯

    View Slide

  5. 5
    スマートフォン向けのソーシャルゲームを開発中
    2つの大事な要素
    1. 素早く開発してアプリをリリースする
    2. そのあとの運用・新機能追加をスムーズに行う
    つまり、開発スピードとメンテンス性の両立が必要
    アプリ開発に求められる事

    View Slide

  6. 6
    設計が重要
    設計の綺麗さは金銭的な価値も生む
    • イテレーションが素早く回せる事で試行錯誤の回数が増え、品質が上がる
    • リリース後、運用に入っても機能追加がすぐに実装できる
    • バグが出にくい
    • バグが出ても即座に対応できる
    といった事はビジネス上では重要

    View Slide

  7. 7
    設計が重要
    逆に設計を疎かにすると・・
    機能実装に時間がかかり、バグが多い場合、ユーザー離れ・機会損失に繋がる

    View Slide

  8. 8
    多人数での開発だと
    • 自己流の設計が至るところに散らばってしまう
    • 自己流がぶつかり合うと、コードレビューが難しくなる
    • 諦め (わからない/面倒だから良いや)
    • 対立 (自己流 vs 自己流)
    設計が重要

    View Slide

  9. 9
    チーム独自の方法だと
    • 本を参照したり・ネットで検索出来ない
    • チームの外に出た時に通用しない・覚え直し
    チーム内で思想を統一したい・実績のある体系化された手法が欲しい
    設計が重要

    View Slide

  10. 10
    EasyとSimpleの違い
    https://twitter.com/t_wada/status/1377147203077111814 和田 卓人さんのtweetから引用
    設計が重要 - easyとsimple

    View Slide

  11. 11
    大規模な開発にはsimpleが向いていると判断。
    Easyはモック開発であったり、小規模な開発で
    は向いている。
    EasyではなくSimpleな方針を取る。
    その代わり手数は増える(コード量は増える)が
    分かりやすい構造を目指す。
    Simpleの実現にDDDが向いていると判断
    EasyとSimpleの違い
    設計が重要 - easyとsimple
    https://twitter.com/t_wada/status/1377147203077111814 和田 卓人さんのtweetから引用

    View Slide

  12. 12
    ドメイン駆動設計とは
    2003年、エリック・エヴァンスの書籍が発売
    される。
    Domain Driven Designの頭文字を取って
    DDDとも呼ばれる。
    上手くシステム開発を進めるためのパターン・
    ランゲージ。
    良くある問題と、それに対するベストプラク
    ティスについて集めたパターン集。

    View Slide

  13. 13
    ゲーム開発ではあまり聞かない
    ゲームに適用できるのか?
    可能。ジャンルはあまり関係ない。
    ドメインに焦点をあてて開発する、というだけ
    ドメイン駆動設計とは

    View Slide

  14. 14
    ドメインって何だ?
    「ソフトウェアが解決しようとしている問題の対象領域」
    会計システムであれば、会計に必要な金銭・帳票といった概念。
    物流システムなら、物流の倉庫・貨物・輸送手段といった概念。
    一意に決まるものではなく、ソフトウェアによって違う。
    ゲーム制作で言うなら、そのゲーム独自の概念・ルール・仕様
    ドメイン駆動設計とは

    View Slide

  15. 15
    ゲーム開発において、わりと当たり前の事を言っている
    • ディレクター・プランナーとよく対話し、ゲームが必要としている独自の機
    能(ドメイン)をよく理解する
    • 対話して明らかになった知識(ドメイン)を元に、共通のモデルを作りあげる
    • モデルをプログラムに純度高く反映させる
    どうやったらこれらを実現できるのか? というベストプラクティスが説明さ
    れている。
    ドメイン駆動設計とは

    View Slide

  16. 16
    DDDスコアカード
    https://www.informit.com/articles/article.aspx?p=1944876&seqNum=2 より引用
    ドメイン駆動設計とは
    向かないジャンル
    「完全にデータ中心である」
    「小規模・シンプルな場合」

    View Slide

  17. 17
    反対に向いているのは、継続的に機能が変更され、複
    雑性をもったアプリケーション。ゲーム開発はこれに
    当てはまる。
    向いているジャンル
    ドメイン駆動設計とは

    View Slide

  18. 18
    ドメイン駆動設計 == ドメインに焦点をあてた開発
    ドメインモデルをコードに正確に落とし込む事が大事。
    ベースとなる技術
    オブジェクト指向
    ドメイン駆動設計とは

    View Slide

  19. 19
    ここで言うオブジェクト指向とは?
    ・データと関数を個別に扱わずに、双方を一体化したオブジェクトを基礎要素にする
    ・オブジェクト間の相互作用を重視して、プログラムを構築する
    ・現実世界の”物”や”概念”をオブジェクトに模して表現する
    ドメイン駆動設計 - オブジェクト指向
    +

    View Slide

  20. 20
    逆にオブジェクト指向では無いものを考える
    手続き型
    データと振る舞い(関数)の記述が分かれている。オブジェクトは単なるデータ
    構造としてみる。
    ドメイン駆動設計 - オブジェクト指向

    View Slide

  21. 21
    オブジェクト指向を踏まえて
    ドメイン駆動設計に向かないジャンル
    「高速化が最重要である場合」
    現在Experimental版のECS(Entity Component System)はデータ指向であり、オ
    ブジェクト指向では書けないため、ドメイン駆動設計が適用しずらい
    ドメイン駆動設計 - オブジェクト指向

    View Slide

  22. 22
    「高速化が最重要である場合」
    ゲームのドメイン部分はDDDで開発し、
    高速化が必要になる部分はECS、のような住み分けが必要になる
    (おそらくECS化されるような部分は
    グラフィック描画、物理演算、サウンド処理、AIなど?)
    ドメイン駆動設計 - オブジェクト指向

    View Slide

  23. 2. ドメイン駆動設計とUnity適用までの具体的な解説

    View Slide

  24. 24
    ドメイン駆動設計の原典。
    しかし、かなり難しい。
    ドメイン駆動やるぞ! と意気込んで読み始め
    ると、まず挫折しがち。
    参考書籍 エヴァンス本
    原典: エヴァンス本

    View Slide

  25. 25
    PDFで配布されている。
    こちらもドメイン駆動設計をコンパクトに要約
    したもの。日本語版もある。
    ドメインモデルを作っていく過程などの例がと
    ても分かりく、オススメ
    https://www.infoq.com/jp/minibooks/domain-driven-design-quickly/
    参考書籍 DDD Quickly

    View Slide

  26. 26
    帯や「はじめに」にあるように、エヴァンス本にあるプロ
    グラミングに適用するパターンをメインに紹介している。
    ・チーム内で読書会を実施
    ・その後も継続的に勉強会実施
    ドメインについても多く言及されている。
    その名の通り、入門に最適。
    https://www.amazon.co.jp/dp/B082WXZVPC
    参考書籍 ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

    View Slide

  27. 27
    ドメインモデリングをはじめとして、DDD全般的に解説
    されている。
    ドメインモデリングについて書かれている書籍は少ないの
    で、大変参考になる。
    https://booth.pm/ja/items/1835632
    参考書籍 ドメイン駆動設計 モデリング/実装ガイド

    View Slide

  28. 28
    PDFで配布されている。
    全容を把握するのに非常に役に立つ。
    https://www.domainlanguage.com/ddd/
    reference/
    日本語に訳している方も
    https://zenn.dev/takahashim/books/fb4cdc32f8e95c
    参考書籍 DDD Reference

    View Slide

  29. 29
    改めてゲーム制作のドメインとは何か考える
    「ソフトウェアが解決しようとしている問題の対象領域」
    「ゲームが表現しようとしている対象領域」
    ・自分たちが作っているゲームで表現しようとしているもの
    ・ゲームが提供する「遊び」
    参考: ドメイン駆動設計 モデリング/実装ガイド - little-hands - BOOTH
    https://booth.pm/ja/items/1835632

    View Slide

  30. 30
    改めてゲーム制作のドメインとは何か考える
    「ゲームが表現しようとしている対象領域」
    データベース
    ファイルI/O
    物理エンジン
    UI
    サウンド処理
    描画エンジン
    ユーザー入力
    世界観
    キャラのスキル
    成長ロジック
    ダメージ計算
    キャラ性能
    通信
    行動エリア
    地形
    キャラ属性

    View Slide

  31. 31
    データベース
    ファイルI/O
    物理エンジン
    UI
    サウンド処理
    描画エンジン
    ユーザー入力
    世界観
    キャラのスキル
    成長ロジック
    ダメージ計算
    キャラ性能
    通信
    行動エリア
    地形
    システムを構成する要素から、汎用的な部分を抜いて残る部分
    キャラ属性
    改めてゲーム制作のドメインとは何か考える
    「ゲームが表現しようとしている対象領域」

    View Slide

  32. 32
    • 自キャラの成長ロジックなど
    • どうやってレベルアップする?
    • 武器/防具を装備する?
    • スキルの存在
    • インゲーム
    • ゲームのルール
    • いつスキルが発動するのか?
    • ダメージの計算方法は?
    改めてゲーム制作のドメインとは何か考える
    「ゲームが表現しようとしている対象領域」→ ゲーム独自の概念・ルール・仕様

    View Slide

  33. 33
    自分たちが作っているゲームで実現しようとしているもの
    • アクションゲームだと
    • ステージ情報
    • 何がどこに配置される?
    • 壊せる・壊せない・移動する etc..
    • 自キャラ・敵キャラの配置
    • 自分・敵キャラのステータス
    • HP、MP、攻撃方法 etc..
    • ジャンプする・敵を踏みつける事で敵を撃退する、のようなゲーム固有のルール
    改めてゲーム制作のドメインとは何か考える

    View Slide

  34. 34
    将棋ゲームのドメイン知識 の一例
    • 登場キャラクター
    • 40枚の駒
    • 駒の性能は8種類。それぞれ動ける範囲が違う
    • 成長ロジック
    • 成駒。動ける範囲が増える
    • mapの広さ・位置
    • 9x9の81マスで戦う
    • 相手の駒を取り、保持する/使う
    改めてゲーム制作のドメインとは何か考える

    View Slide

  35. 35
    • 9人 vs 9人
    • 選手には打率・防御率・スタミナ、etc…などの能力パラメーターがある
    • ストライク3つでアウト
    • バッターはボールを打ち返す
    • 攻撃側・防御側に分かれ、交互に入れ替わる
    • これらにゲーム特有のルールも加わる
    • 選手の成長要素、必殺技、アイテムetc..
    野球ゲームのドメイン知識 の一例
    改めてゲーム制作のドメインとは何か考える

    View Slide

  36. 36
    これらのドメイン知識をゲーム中で扱えるように、モデルを作りあげる。
    オリジナルゲームの場合、最初から明確な事は多くなかったりする。
    対話・モデリングを繰り返し、ぼんやりしたモデルの輪郭をハッキリさせていく。
    改めてゲーム制作のドメインとは何か考える

    View Slide

  37. 37
    ドメインとそれ以外で分離させる事が大事。ドメインロジックをUIコンポーネントに
    書いたりしない。
    Viewとドメインを分離する。ここらへんはMVC的な発想と同じ。
    ドメインの分離

    View Slide

  38. 38
    どういう時に、どういう条件で点滅するのか? をViewには記述しない。
    瀕死状態の定義や、瀕死状態かどうかの判断 を書くのはドメインロジック。
    お題
    「瀕死のキャラクターが点滅する」
    ドメインの分離

    View Slide

  39. 代表的な概念・パターンの紹介

    View Slide

  40. エヴァンス本で紹介されているパターン一覧
    DDD難民に捧げる Domain-Driven Designのエッセンス 第1回 ドメイン駆動設計とは
    https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap1.html より引用
    重要 + 実例が示しやすい赤枠部分を中心に紹介します

    View Slide

  41. 41
    アプリが対象とする領域の専門家のこと
    銀行の業務支援アプリを作るなら、
    銀行の業務を熟知する銀行員がドメインエキスパート
    ゲームにおけるドメインエキスパートは?
    現プロジェクトでは、ディレクター・プランナーといったゲームの
    ルール・世界・仕様を考える人たち、と捉えた。
    ドメイン エキスパート

    View Slide

  42. 42
    ドメインエキスパートと絶えず対話し、ドメインをうまくモデリングするのが大切。
    仕様書をそのまま実装する、というのはNG。
    仕様書に記載された機能の裏にある目的などを理解する必要がある。
    ドメイン エキスパート

    View Slide

  43. 43
    プロジェクト内で使う共通の言葉を定義する。
    開発者とドメインエキスパートが同じ単語を別の意味で用いないようにする。
    ユビキタス言語

    View Slide

  44. 44
    例) キャラクターが持つ特殊能力について・・
    使用する用語をプロジェクト全体で統一する。
    バラバラだと変換コストが発生するし、勘違いも生まれる。
    アビリティ スキル
    スキル
    ユビキタス言語

    View Slide

  45. 45
    プログラム中に使われる関数・変数もユビキタス言語を採用する。
    しかし日本語だった場合は難しい。
    そのためにも、現プロジェクトでは英語との対応表を作って管理している。
    サーバー・クライアントでプログラム内で使われる単語を合わせるのにも役立つ。
    ユビキタス言語

    View Slide

  46. プログラムに適用出来るパターンを一部紹介
    値オブジェクト
    1
    エンティティ
    2
    3
    リポジトリ
    4
    サービス
    5
    集約
    6 境界づけられたコンテキスト
    7 レイヤードアーキテクチャ

    View Slide

  47. 47
    • C#の値型とは無関係
    • 一度生成されたら中身が変化しない。不変である。
    • 中身が同じなら等価とみなす
    1. 値オブジェクト

    View Slide

  48. 48
    実装に面倒な箇所があるが、
    不変性によって実装は大幅に単純化され、安全に共有や参照渡しができるようになる
    という大きなメリットがある
    DDDに関係無く有用
    1. 値オブジェクト

    View Slide

  49. 49
    値オブジェクトを
    疑似コードで表現すると
    1. 値オブジェクト

    View Slide

  50. 50
    現プロジェクトでは、
    全てのフィールドをreadonlyにして、コンスタントラクタで値を設定している
    1. 値オブジェクト
    値オブジェクトの実装について - 不変の実装

    View Slide

  51. 51
    厳密には
    • 全てのフィールド変数が同一であるか比較するEquals()を用意
    • 全てのフィールドを考慮したGetHashCode()関数を用意する必要がある
    1. 値オブジェクト
    値オブジェクトの実装について - 等価の実装

    View Slide

  52. 52
    Equals()、GetHashCode()があると・・
    1. 値オブジェクト
    値オブジェクトの実装について - 等価の実装
    中身で比較される

    View Slide

  53. 53
    1. 値オブジェクト
    Dictionaryのkeyとして使う時、中身ベースで使用される
    これはstructのVector2の例だが、これと同じ事が出来るようになる。
    値オブジェクトの実装について - 等価の実装
    Equals()、GetHashCode()があると・・

    View Slide

  54. 54
    • データベースのエンティティとは無関係
    • 生成された後、中身がどんどん変化していくもの
    2. エンティティ
    同一性比較のため、目印となるidを持つ。idが同じであれば、中身が違っても等価
    とみなす
    同一性によって区別される
    中身が変化しても同一のものとして扱う

    View Slide

  55. 55
    現プロジェクトでは
    • 変化していくユーザー情報
    • 成長していくキャラクター情報など
    • 同一のキャラクターが、level、hp、mpなどのパラメーター変化していく事を表現す

    2. エンティティ

    View Slide

  56. 56
    エンティティを
    疑似コードで表現すると
    2. エンティティ

    View Slide

  57. 57
    「物」としてモデリングできないものもある。そうしたものは値オブジェクトやエ
    ンティティを取り扱う「サービス」として実装する
    3. サービス
    気を抜くと手続き型となる。多用しないように注意。

    View Slide

  58. 58
    関連するオブジェクトの集まり。データを変更するための単位として扱われる
    4. 集約
    OrderItem
    Create
    Destroy
    Price
    VatRate
    Value
    to_i
    to_f
    Customer
    Order
    add_item
    remove_item
    total_price
    Entity
    Entity
    Entity Value object
    窓口 = 集約Root
    集約内部
    この中はカプセル化されている
    Aggregate
    窓口を通さないアクセスは認めない
    引用 Facade Pattern as the way to implement Aggregate http://rubyblog.pro/2017/05/facade-pattern

    View Slide

  59. 59
    ひとつの塊として扱う
    塊の中にアクセスするための窓口を用意しており、窓口を飛び越して中身にアクセス
    出来ない
    Facadeパターンによく似ている
    4. 集約

    View Slide

  60. 60
    Facadeパターン
    Facade適用後
    Subsystem内に、
    外から自由にアクセスしている
    窓口を通してしかアクセスできない
    引用 Facade Pattern as the way to implement Aggregate http://rubyblog.pro/2017/05/facade-pattern
    窓口を設ける事で、複雑な構造をカプセル化する
    4. 集約

    View Slide

  61. 61
    データ(集約)の置き場所
    データストアを操作する処理をカプセル化する。集約の保存・取得はここを通す。
    他のクラスはデータストアを直接操作しない。
    DBがある事を意識せず、メモリ内コレクションのように振る舞う。
    5. リポジトリ
    依頼者




    write
    read
    保存
    問い合わせ
    返却
    リポジトリ

    View Slide

  62. 62
    複数チームで同時開発をしていると、
    ある概念に対して複数のモデルが出来る事がある。
    これに対し、無理に統一された大きなモデルを作る必要はない。
    6. 境界づけられたコンテキスト
    メインロジックが扱う
    キャラクターのモデル
    Viewが扱う
    キャラクターのモデル

    View Slide

  63. 63
    それぞれのモデルが適用される境界を明確にし、
    境界内だけで適用されるモデルを作る。
    境界を明示的にする事で、境界内を独立して開発可能になり、
    他の箇所との結合度が減る。
    6. 境界づけられたコンテキスト
    ?
    コンテキストが異なる
    キャラクター
    キャラクター
    キャラクター

    View Slide

  64. 64
    7. レイヤードアーキテクチャ
    ・ユーザーインターフェース (プレゼンテーション)
    ・アプリケーション
    ・ドメイン
    ・インフラストラクチャー
    の4層に分ける
    「エリック・エヴァンスのドメイン駆動設計」より引用

    View Slide

  65. 65
    インフラストラクチャの位置を変更したバージョンを採用。
    7. レイヤードアーキテクチャ
    ヴォーン・ヴァーノン 著
    「実践 ドメイン駆動設計」より引用

    View Slide

  66. 66
    実践ドメイン駆動設計で紹介されているバージョン。
    ドメインオブジェクトを管理するインフラストラクチャ層
    が、ドメイン層を参照できた方が都合が良かったため。
    Assembly Definitionsを使って管理
    7. レイヤードアーキテクチャ

    View Slide

  67. 67
    各パターンがどの層にあてはまるか
    インフラストラクチャー層
    • 基盤的な機能の実装
    • 通信機能、DBアクセス、ローカルセーブデータなど
    の実装
    • リポジトリの実装
    7. レイヤードアーキテクチャ

    View Slide

  68. 68
    ユーザーインターフェース層 (プレゼンテーション層)
    • UI表示
    • アウトゲームのMVC/MVPで構成された各画面
    現プロジェクトでは、
    MonoBehaviour継承クラスだからUI層、とはしていない。
    そのクラスが提供している機能で判断している。
    各パターンがどの層にあてはまるか
    7. レイヤードアーキテクチャ

    View Slide

  69. 69
    アプリケーション層
    • ドメインオブジェクト・基盤機能を使って、アプリ
    ケーション全体の調整・進行役を担う
    • ドメインとUI層の架け橋
    各パターンがどの層にあてはまるか
    現プロジェクトでは、ユーザー情報、キャラクター情報の管理、取
    得、変更などを担う機能実装がある。
    機能毎にユースケース・クラスを作成し、そこに処理を記述してい
    る。
    キャラクターをレベルUPさせる・装備を変更する・etc..
    7. レイヤードアーキテクチャ
    ゲームが持つ機能の実装

    View Slide

  70. 70
    ドメイン層
    • ドメインロジックの実装
    • 値オブジェクト
    • エンティティ
    • ドメインサービス
    各パターンがどの層にあてはまるか
    システムの心臓部
    7. レイヤードアーキテクチャ

    View Slide

  71. 71
    ドメイン層が他のレイヤーと隔離されてい
    る事が重要。
    他にもパターンは
    ・オニオンアーキテクチャ
    ・ヘキサゴナルアーキテクチャなどがあ
    る。
    参考 : [DDD]ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か
    https://qiita.com/little_hand_s/items/ebb4284afeea0e8cc752
    7. レイヤードアーキテクチャ

    View Slide

  72. 72
    Assembly Definitionsで定義
    メリット
    相互参照を無くして依存関係が単純になる
    レイヤードアーキテクチャの実装

    View Slide

  73. 73
    レイヤードアーキテクチャを厳密に運用するのは結構難しい。
    特にAssembly Definitionsを使って厳密に依存関係をコントロールする場合。
    上から下へ、単一の方向にしかアクセス出来なくなる。
    とはいえ逆方向にあるレイヤーの機能を使いたい事はある。
    現プロジェクトでの解決方法を紹介します。
    レイヤードアーキテクチャの実装

    View Slide

  74. 74
    依存関係逆転の原則
    レイヤードアーキテクチャの実装
    具象 ではなく、抽象 だけを参照するようにする
    具象 → 実装クラス
    抽象 → interfaceやabstractなどの抽象宣言
    <参考>
    Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計

    View Slide

  75. 75
    依存関係逆転の原則
    レイヤードアーキテクチャの実装
    Presenter
    Application
    上層から下層への依存関係しか存在しなくなる
    ソースコードの依存方向
    (※ 処理の流れではない)
    Assembly Definitionsを設定
    UIレイヤー (プレゼンテーション層)
    アプリケーション層

    View Slide

  76. 76
    レイヤードアーキテクチャの実装
    下層から上層のクラスを参照する事はできない。
    しかし、上層に対して処理を実行したい。
    Presenter
    Application
    依存関係逆転の原則

    View Slide

  77. 77
    レイヤードアーキテクチャの実装
    アクセス可能な階層のinterfaceに依存 するようにする。
    依存関係逆転の原則
    Presenter
    Application IOutput
    OK
    OK

    View Slide

  78. 78
    実際の処理の流れ と、ソースコードの参照方向が逆になる
    レイヤードアーキテクチャの実装
    Presenter
    Application
    Presenter
    Application IOutput
    処理の流れ ソースコードの参照方向
    依存関係逆転の原則

    View Slide

  79. 79
    しかし、結局は上層のインスタンスを取得する必要がある。
    では、どう取得するのか?
    Abstract Factory、Service Locatorなどの解決方法があるが、
    現プロジェクトでは後述のDIコンテナを使用した。
    レイヤードアーキテクチャの実装
    依存関係逆転の原則
    Presenter
    Application IOutput
    実体であるviewは、
    どうやって取得するのか?

    View Slide

  80. 3. DIコンテナとUnity適用までの具体的な解説

    View Slide

  81. 81
    多用されがちだったsingletonを使いたくない。singletonだと・・
    • global変数
    • 依存関係が見えづらい
    • 具象クラスに依存するため依存関係逆転の原則が使えない
    • Interfaceを使った実装の入れ替えが難しい
    経緯

    View Slide

  82. 82
    Interfaceを使った実装の入れ替え とは
    状況に応じて、実装A と 実装B を 切り替える
    経緯
    Interface
    実装 A 実装 B

    View Slide

  83. 83
    外部から必要とするインスタンスを渡すのがDI (Dependency Injection)
    (「DIコンテナ」ではなく、 ただの「DI」)
    DIコンテナとは
    これを自動でやってくれるのが 「DIコンテナ」

    View Slide

  84. 84
    Zenject(exenject) https://github.com/modesttree/Zenject
    リポジトリにあるサンプルコードを引用して説明します。
    DIコンテナとは

    View Slide

  85. 85
    DIコンテナとは
    階層構造の一番下

    View Slide

  86. 86
    DIコンテナとは
    上から渡してあげる

    View Slide

  87. 87
    DIコンテナとは
    渡すものを作る必要がある

    View Slide

  88. 88
    この繰り返しが、ずっと上の階層まで続いてしまう
    DIコンテナとは
    上から渡してあげる

    View Slide

  89. 89
    最終的に、上記のような依存関係の解決だけを担ってくれるものが必要になる。
    上記のような処理を自動で解決してくれるのがDIコンテナ。
    (誰が何を必要としているのか・生成の順序などを管理。生成知識を一手に担う黒子のような存在)
    DIコンテナとは
    48. GoFデザインパターンとDI (前編) w/ twada
    https://fukabori.fm/episode/48

    View Slide

  90. 90
    Unityでの実際の使用イメージ
    ISomeServiceとして、SomeServiceを登録

    View Slide

  91. 91
    Unityでの実際の使用イメージ

    View Slide

  92. 92
    • 各種サービスの連携
    • レイヤーを跨いだ参照の受け渡し
    • 逆方向のレイヤーにある実装を使用する場合は、
    interfaceを置く場所で制御
    プロジェクトでの使い方
    依存関係逆転の原則を活用
    Interface
    実装
    ここにinterfaceを置く
    この層の実装を使いたい

    View Slide

  93. 93
    Interfaceに依存する事で、実装の差し替えを行う事ができる。
    実装の差し替え
    インターフェース IOutput
    通常UI UI非表示 log出力のみ
    例「デバッグ用に、UI非表示でlog出力だけ行う高速周回モードの実装」
    通常時 デバッグ時
    プロジェクトでの使い方

    View Slide

  94. 94
    DIコンテナを導入するとどう変わるのか
    クラス間の依存関係把握が簡潔になる・アクセスコントロールがやりやすい
    あるクラスの機能を使うためにはコンスタントラクタで宣言する必要があるため、
    依存関係が見えやすい。
    不要なクラスの利用がなくなり、役割分担も明確になる。
    Singletonはどこからアクセスされるのか把握が難しい。

    View Slide

  95. 95
    落とし穴
    ”DIコンテナ上で管理されているオブジェクト同士を、気の向くまま思うままに関連付けします。DIコン
    テナ上に管理されていないければ有り得ないような依存性であっても気にせず依存させるため、図8の
    ように依存性が網の目のようになります。”
    網の目依存
    DIコンテナの本当の使いどころ
    https://www.ulsystems.co.jp/topics/025 より引用

    View Slide

  96. 96
    落とし穴
    網の目依存はやりがちなのでは。
    あらゆるクラスをDIコンテナに登録、自由に参照を取得する
    (全てがsingleton / 全てstaticクラスな事と同じ)

    View Slide

  97. 97
    落とし穴
    DIコンテナに登録するクラスは良く考慮する
    現プロジェクトではサービスやリポジトリのRootインスタンスのみがDIコンテナに
    登録可能。
    Root要素から下は参照関係の解決にDIコンテナは使わない。
    参照の受け渡し(ただのDI)で解決している
    Easyのためではない。simpleにするために使う

    View Slide

  98. 98
    ライブラリ
    Zenject (extenject) を採用
    Zenjectの固有機能はあまり使わず、他のDIコンテナライブラリに簡単に乗り換えら
    れるようにしている

    View Slide

  99. 99
    ライブラリ
    •プロジェクト起動時、シーン読み込み直後の初期化の際にDIコンテナに詰める・取
    得するだけ
    •シーン初期化以降、動的に生成されるinstance/prefabにはinjectionしていない
    •シーンはタイトル画面・アウトゲーム・インゲームのような粒度で分けている
    •基本はコンストラクタ インジェクション
    •mono behaviorに対してはメソッド インジェクション
    マイルドな使い方

    View Slide

  100. 4. その他の設計手法の紹介

    View Slide

  101. 101
    • ロジックが書かれた集約・Entity・値オブジェクトなどのユニットテスト
    • ドメインモデルとして纏める事でテストしやすくなる
    • ライブラリ的な機能のテスト
    これらはUnityのTest Runnerを使い、jenkinsで定期実行
    テスト

    View Slide

  102. 102
    関数の呼び出し元を顧客、呼ばれる関数を供給者と見立てる。
    顧客と供給者の間に契約が結ばれている、というメタファー。
    「顧客が契約を守るなら、供給者は仕事を保証する」
    具体的には?
    契約による設計
    オブジェクト指向入門 第2版 原則・コンセプト
    バートランド・メイヤー (著), 酒匂 寛 (翻訳)

    View Slide

  103. 103
    下記の3つの条件を満たすようにコードを記述する
    契約による設計
    事前条件
    関数の開始時に、関数を呼ぶ側で保証すべき条件
    事後条件
    関数が終了時に保証すべき条件
    不変条件
    そのオブジェクトが常に満たすべき条件
    コードにすると?
    参考URL
    https://developer.hatenastaff.com/entry/2016/09/01/163542

    View Slide

  104. 104
    契約による設計
    参考URL : 契約による設計、例外、表明の関係について個人的なまとめ
    https://qiita.com/hiko1129/items/f312212070716f672ff6
    こういった四角形を表すクラスがあるとする

    View Slide

  105. 105
    契約による設計
    参考URL : 契約による設計、例外、表明の関係について個人的なまとめ
    https://qiita.com/hiko1129/items/f312212070716f672ff6
    事前条件
    ・処理の最初に引数のチェック
    事前条件

    View Slide

  106. 106
    契約による設計
    参考URL : 契約による設計、例外、表明の関係について個人的なまとめ
    https://qiita.com/hiko1129/items/f312212070716f672ff6
    事後条件
    ・関数の最後で結果・returnする値のチェック
    事後条件
    ・処理の最後にreturnする値をチェック
    事後条件

    View Slide

  107. 107
    契約による設計
    参考URL : 契約による設計、例外、表明の関係について個人的なまとめ
    https://qiita.com/hiko1129/items/f312212070716f672ff6
    不変条件
    ・このobjectが守るべき条件をチェック
    不変条件

    View Slide

  108. 108
    ドメイン駆動設計でも「表明(Assertion)」としてパターンが紹介されている。
    現プロジェクトでは、これから充実させていくところ。主にドメインロジック
    をチェック。
    契約による設計

    View Slide

  109. 109
    小さい単位で独立してテスト可能なように作る
    単体で動くように作る
    とても重要。最初期からこれを念頭に作る。
    ゲームを最初から立ち上げずに、最小ではprefab単位で挙動のチェックが出来るように
    結合度が低く、使い回しが効くパーツが作られる

    View Slide

  110. 110
    アウトゲームの各画面・ポップアップなどに関して
    単体で動くように作る
    とても重要。最初期からこれを念頭に作る。
    ゲームを最初から立ち上げずに、最小ではprefab単位で挙動のチェックが出来るように
    MVPのView部分だけ切り出してテスト可能
    View部分を単独で作り込める
    現プロジェクトの一例

    View Slide

  111. 111
    インゲームのviewテスト用シーン
    テスト項目をinspecterに表示。
    ボタン押下でviewの挙動をチェックできる。
    単体で動くように作る

    View Slide

  112. 112
    単体で動くように作る
    Inspecterにボタンを表示するのはアセット「Odin」を使用
    インゲームのviewテスト用シーン

    View Slide

  113. 113
    prefabの構造規約
    ドメイン駆動設計における集約の考え方を適用する
    • Prefabパーツへのアクセスはroot要素のコンポーネントを経由する
    • inspecter、Find()、GetComponentInChildren()等でprefabのroot要素を飛び越
    えて中身の参照を取得しない
    Subsystem内に、
    外から自由にアクセスしている
    窓口を通してしかアクセスできない

    View Slide

  114. 114
    prefabの構造規約
    UnityにはEasyにするための仕組みが沢山ある。
    しかし敢えて使わない。
    デバッグ機能やモックシーンは使っても良い (EasyでOK)
    Subsystem内に、
    外から自由にアクセスしている
    窓口を通してしかアクセスできない

    View Slide

  115. 5. 実装の詳細
    例外・つまづいた点・悩みごと

    View Slide

  116. 実装においての例外

    View Slide

  117. 117
    DIコンテナだと困るもの
    UIのボタンクラスなど、UnityのHierarchyツリー上で下層に置かれるようなUIパー
    ツなど。
    末端オブジェクトからDIコンテナに入っているオブジェクトにアクセスしたい場合。
    DIコンテナでの例外パターン
    SoundManager.Instance.PlaySe();
    こういった機能は割り切ってsingleonにしている。
    例: ボタンを押したら音を鳴る

    View Slide

  118. 118
    Observerパターン ・ イベントの発行
    ゲーム固有のイベントを発行・管理するものもsingletonにしている
    DIコンテナでの例外パターン
    サウンド再生もイベントで表現できるが、単独で使用される事が多いため敢えて分けている
    Observer.Instance.Notify("XxxEvent");
    どんなイベントが発行されるか?
    たとえばps4のトロフィー獲得表示。何かを達成したタイミングで表示されるもの

    View Slide

  119. つまづいた点

    View Slide

  120. 120
    値オブジェクト等のImmutableなクラスに変更。
    ただ情報を他のレイヤーに運ぶだけのオブジェクトであれば、
    DTO(Data Transfer Object)として表現。
    現プロジェクトではDTOもImmutableとして作っている。
    とりあえず色々Entityにされがち
    ドメイン駆動設計 - つまづいた点
    生成後に変化しない物もEntityとして作ってしまった。
    対処

    View Slide

  121. 121
    Entity・値オブジェクトにドメインロジックが書かれず、サービスに手続き型で書
    かれてしまう
    ドメインモデル貧血症
    ドメイン駆動設計 - つまづいた点
    OOPを意識して書く。
    チームでモデリングの技量を上げる必要がある。継続的に勉強会を実施。
    対処

    View Slide

  122. 122
    例えばUser情報管理オブジェクトなど。
    複数のEntityや値オブジェクトから出来る集約は、直接中身のエンティティ等を触り
    たくなる。
    集約の境界が曖昧で、集約内部に直接アクセスを許してしまう
    ドメイン駆動設計 - つまづいた点

    View Slide

  123. 123
    面倒だが、ここは必要経費と捉えてRootを通したアクセスのみ認めるように変更。
    情報を伝えるためのバケツリレーのようなメソッドがたくさん出来てしまうが、ここ
    は我慢。
    (ここもeasy vs simpleで、simpleを採用する話)
    集約の境界が曖昧で、集約内部に直接アクセスを許してしまう
    ドメイン駆動設計 - つまづいた点
    対処

    View Slide

  124. 124
    集約の境界が曖昧で、集約内部に直接アクセスを許してしまう
    ドメイン駆動設計 - つまづいた点
    理想的には全てのアクセスで集約Rootを通すようにしたい。
    ただ、それだと実装が煩雑になる部分も出てくるので、部分的に許可している。
    内部の値を一時的に外部から参照するだけならOK
    境界内のオブジェクトは互いに参照を保持し合ってOK
    OK
    集約内部に持つインスタンスのメソッドを、外部から実行するのはNG
    NG

    View Slide

  125. 125
    集約の境界が曖昧で、集約内部に直接アクセスを許してしまう
    ドメイン駆動設計 - つまづいた点
    基本的にはデメテルの法則を適用する。
    クラスCのメソッドfは、次のオブジェクトのメソッドのみを呼び出すべき
    • Cそのもの
    • fで生成されたオブジェクト
    • fの引数で渡されたオブジェクト
    • Cのインスタンス変数に保持されたオブジェクト
    fは、上記の許されたメソッドから返されたオブジェクトのメソッドを呼び出し
    てはいけません。つまり友達とのみ会話し、知らない人とは会話してはいけない
    のです。
    Robert C.Martin,花井 志生. Clean Code アジャイルソフトウェア達人の技 (Japanese Edition)
    (Kindle の位置No.2626-2631). Kindle 版.

    View Slide

  126. 126
    集約の境界が曖昧で、集約内部に直接アクセスを許してしまう
    ドメイン駆動設計 - つまづいた点
    ただし、デメテルの法則を適用するのは振る舞いを持つオブジェクトに対してのみ。
    振る舞いを持たないデータ構造は内部を公開しても構わない。
    データ構造に過ぎず、何ら振る舞いを持たないのであれば、これらは内部構造をごく自然に公開しており、デメテルの法則は
    適用されません。
    Robert C.Martin,花井 志生. Clean Code アジャイルソフトウェア達人の技 (Japanese Edition) (Kindle の位置No.2654-2655). Kindle 版.

    View Slide

  127. 127
    ドメイン駆動設計 - つまづいた点
    集約のルールを徹底するだけでスパゲティコードになる可能性はグッと下がる。
    とても重要。
    Subsystem内に、
    外から自由にアクセスしている
    窓口を通してしかアクセスできない

    View Slide

  128. 128
    本来、リポジトリは集約の単位で作成される。
    しかし集約内部にあるEntity単位でもリポジトリがある状態だった。
    集約の内側パーツをリポジトリが持つことになってしまう。
    リポジトリの粒度が集約単位ではない
    ドメイン駆動設計 - つまづいた点
    モデリングが上手くいっておらず、集約の範囲が曖昧だったのが原因。集約の境界
    をしっかり定義し、その単位で整理しなおす
    対処

    View Slide

  129. 悩んでいる点

    View Slide

  130. 130
    値オブジェクト - Equals()、GetHashCode()の実装について
    メンバ変数が少ない場合は大丈夫だが、多い場合は実装が大変。
    ドメイン駆動設計 - 悩み

    View Slide

  131. 131
    値オブジェクトの実装 | Microsoft Docs
    https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects
    値オブジェクト基底クラスを作って運用する方法がMSドキュメントにて紹介され
    ているが、パフォーマンス面で不安は残る。
    値オブジェクト - Equals()、GetHashCode()の実装について
    ドメイン駆動設計 - 悩み

    View Slide

  132. 132
    C#9のinitキーワード、record型が来れば楽になりそう。
    Unityでも早く使えますように
    現状、数が少なければ手動で記述。
    数が多い場合はIDEの自動生成を使うのが楽だが、メンバ変数が増えた際、忘れず
    に生成しなおす必要がある。
    そのため、タプルを使った実装も検討中。
    値オブジェクト - Equals()、GetHashCode()の実装について
    ドメイン駆動設計 - 悩み
    対処

    View Slide

  133. 133
    Equals()が自動的に実装されるが、継承が出来ない。
    パフォーマンスの懸念も。
    値オブジェクトの実装について - structを使うとどうなるのか?
    このような理由で、現プロジェクトではクラスを使っている
    ドメイン駆動設計 - 悩み

    View Slide

  134. 134
    大きな集約をどこまで許容するか
    • 分割した場合、整合性・不変条件をどう保っていくか
    ある概念が情報を大量に保持しており、どれも密接に関係していて切り離すのが難
    しいケースがよくある。
    ドメイン駆動設計 - 悩み

    View Slide

  135. 135
    大きな集約をどこまで許容するか
    • 分割した場合、整合性・不変条件をどう保っていくか
    参考
    DDDで複数集約間の整合性を確保する方法(サンプルコードあり)[ドメイン駆動設計]
    https://little-hands.hatenablog.com/entry/2021/03/08/aggregation
    トランザクション単位で分割する手法があるが、
    UnityのクライアントコードではDBが絡むトランザクションが関係ない事も多い。
    その場合は大きいままでも良い?
    モデリングが上手く行っていないのかも?
    まだ明確な答えは出ておらず、いったん大きいまま扱っている。
    ドメイン駆動設計 - 悩み

    View Slide

  136. 136
    ドメイン駆動設計の導入直後、
    ゲームに必要な汎用機能の実装をどのレイヤーに置くのか悩んだ。
    特にUnityの機能を使った、Viewと関連する部分。
    Unityでの実装がどのレイヤーになるのか

    ドメイン駆動設計 - 悩み

    View Slide

  137. 137
    ユースケースの実現(アプリケーションが提供する機能)と捉え、現状アプリケーション層で
    実装されている。
    しかしドメインと関係の無い基盤機能と捉える方が適切なため、実装はインフラストラク
    チャ層、その機能を使う処理がアプリケーション層にある、という形の方が適切だった
    Unityでの実装がどのレイヤーになるのか
    ドメイン駆動設計 - 悩み
    汎用機能
    ・アセットバンドルを読み込む
    ・ポップアップを表示
    ・シーンの読み込み、画面遷移させる、など
    ・音を出す

    View Slide

  138. 138
    SoundManager.Instance.PlayBgm();
    実装クラス指定アクセスになる。
    interfaceを使った依存関係逆転の原則も使えない。
    ServiceLocatorを使ってsingletonを纏める手法を検討中。
    しかし一部singletonになっているものは、気軽にレイヤーを変更できない
    ドメイン駆動設計 - 悩み

    View Slide

  139. 139
    適切なレイヤーはどこなのか
    未だに悩む。
    とはいえドメインとそれ以外を分離する事が最も大切。
    そこ以外は厳密に考えなくても良いのかも?
    ドメイン駆動設計 - 悩み

    View Slide

  140. 6. まとめ

    View Slide

  141. 141
    ドメイン駆動設計
    良いところ
    • DDDを導入する事でチーム内で統一された設計思想を共有できるようになった
    • 悩んだ時 / 説明する際に参照できるリソースが豊富
    • データと振る舞いがセットで記述される事でコードの保守性・可読性が向上する
    • ドメインへの理解が推奨される
    • 一度習得すれば所属プロジェクトが変わっても様々な面で力になる
    • UnityでなくてもOK。特定の技術にあまり左右されない

    View Slide

  142. 142
    ドメイン駆動設計
    • 導入のハードルはなかなか高い
    • 継続的な学習は必要
    • チーム内に浸透するまで時間がかかる
    • まずはプログラムの実装パターン適用からでも効果はある
    • それでも各実装パターンへの正確な理解は必要
    • ドメイン駆動の名の通り、ドメインへの理解を深める事が本質
    • しかし形から入る事で分かる事も多い
    大変なところ・注意したいところ

    View Slide

  143. 143
    DIコンテナ
    良いところ
    • singletonの代わりとなる
    • 多態性を使ったモック作成がやりやすい
    • 依存関係を管理しやすい
    大変なところ
    • 最初の学習コスト
    • 結局やってる事は単純ながら、概念を掴むのが難しい

    View Slide

  144. 144
    ドメイン駆動設計 と DIコンテナ
    2つは非常に相性が良い。
    レイヤードアーキテクチャを実践するうえで役に立つ。
    まとめ
    双方とも、アプリの種類や使用している技術に左右されずらく、
    汎用的な知識・手法として役に立つ。
    設計するうえで、非常にオススメです。

    View Slide

  145. View Slide

  146. 146
    エリック・エヴァンスのドメイン駆動設計
    https://www.amazon.co.jp/dp/B00GRKD6XU
    ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本
    https://www.amazon.co.jp/dp/B082WXZVPC
    実践ドメイン駆動設計
    https://www.amazon.co.jp/dp/B00UX9VJGW
    ドメイン駆動設計 モデリング/実装ガイド
    https://booth.pm/ja/items/1835632
    Clean Code アジャイルソフトウェア達人の技
    https://www.amazon.co.jp/dp/B078HYWY5X
    Clean Architecture 達人に学ぶソフトウェアの構造と設計
    https://www.amazon.co.jp/dp/B07FSBHS2V
    オブジェクト指向入門 第2版 原則・コンセプト
    https://www.amazon.co.jp/dp/4798111112
    参考書籍

    View Slide

  147. 147
    DDDスコアカード
    https://www.informit.com/articles/article.aspx?p=1944876&seqNum=2
    Domain Driven Design(ドメイン駆動設計) Quickly 日本語版
    https://www.infoq.com/jp/minibooks/domain-driven-design-quickly/
    DDD Reference - Domain Language
    https://www.domainlanguage.com/ddd/reference/
    DDDリファレンス 定義とパターン概要 (鋭意修正中, CC-BY)
    https://zenn.dev/takahashim/books/fb4cdc32f8e95c
    ドメイン駆動設計をゲーム開発に活かす
    https://www.slideshare.net/masuda220/ss-111011089
    ゲームで学ぶ「役に立つ」ドメインモデルの考え方 - Qiita
    https://qiita.com/MinoDriven/items/7b4609d0bc6717769060
    DDD難民に捧げる Domain-Driven Designのエッセンス 第1回 ドメイン駆動設計とは
    https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap1.html より引用
    Facade Pattern as the way to implement Aggregate
    http://rubyblog.pro/2017/05/facade-pattern
    参考資料URL

    View Slide

  148. 148
    [DDD]ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か
    https://qiita.com/little_hand_s/items/ebb4284afeea0e8cc752
    DIコンテナの本当の使いどころ
    https://www.ulsystems.co.jp/topics/025
    48. GoFデザインパターンとDI (前編) w/ twada
    https://fukabori.fm/episode/48
    契約による設計の紹介 - Hatena Developer Blog
    https://developer.hatenastaff.com/entry/2016/09/01/163542
    契約による設計、例外、表明の関係について個人的なまとめ
    https://qiita.com/hiko1129/items/f312212070716f672ff6
    値オブジェクトの実装 | Microsoft Docs
    https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects
    DDDで複数集約間の整合性を確保する方法(サンプルコードあり)[ドメイン駆動設計]
    https://little-hands.hatenablog.com/entry/2021/03/08/aggregation
    参考資料URL

    View Slide