173.1K Views
May 12, 22
スライド概要
Event for Diverse Game Engineers #4 にて登壇した際に利用した資料です。
https://edge.connpass.com/event/122672/
UE4のための より良いゲーム設計を理解しよう! Indie-us Games alwei
alweiの自己紹介 元々ゲーム会社でゲームプログラマーな人 Twitter : @aizen76 今はIndie-us Gamesという会社の代表取締役 最近のお仕事はテクニカルアーティスト寄り 趣味はアナログでのお絵描きとゲーム制作 自称ゲーム開発の何でも屋
株式会社Indie-us Gamesとは Unreal Engine 4をメインに活動しているゲーム会社。 現在メンバーは自分を含めて9名。 仕事はゲームだけでなく、VRや映像、建築なども 幅広くやっておりアセット販売や動画講座作成 本当に何でも手広くやってます。 https://indie-us-games.co.jp
ゲーム設計
ゲームエンジンにおけるゲーム設計 ゲームエンジンを使う場合には、そのエンジンを理解した上で しっかりとしたゲーム設計を行わないと開発効率は上がりません。 むしろ変な設計を行うことで開発効率が落ちることすらあります。 ここではUnreal Engine 4(以降UE4)に特化したお話をしますが、 考え方自体はその他のエンジンでも活きてくると思います。
設計における基本 何を重視して設計する? • 再利用性 • 使いやすさ • 汎用性の高さ • 依存関係の低さ etc…
ゲームにおける設計の前提 ゲームでは単に使いやすいだけではなく、 パフォーマンスもある程度考えて設計をします。 パフォーマンスを出せるようにしつつ、 設計としてよりよいものを作るのは難しい。 常にバランスは天秤にかける必要がある。
良い見本を探す 良い設計を覚えるためには良い見本を探すのが一番。 UE4で良い見本となるのは、 『Action RPG』と『Robo Recall』のプロジェクト。 どちらもランチャーから無料でDL可能! 困った際には非常に参考になります。
過去の資料 実は設計については過去にも 発表したことがあります。 こちらも別途合わせて ご覧いただくと理解が深まります。 『UE4におけるキャラクタークラス設計』 https://www.slideshare.net/masahikonakamura50/ue4-88216233
設計思想(アーキテクチャー)
UE4における設計思想 UE4では『Gameplay Framework』という強力な クラス群が標準で提供されており、これらを使いこなすことで より堅牢な設計手法を手にいれることができます。 まずはこのGameplay Frameworkというものを しっかり理解しておくことをお勧めします。 http://api.unrealengine.com/JPN/Gameplay/Framework/
設計における三つの考え方 オブジェクト指向 コンポーネント メッセージ
オブジェクト指向 一般的なソフトウェア設計手法のひとつ。 『カプセル化』 『継承』 『多態性』の三つの特徴を持つ。 ゲームプログラミングに限らず、広く普及している設計手法。 今でも根強い考え方であり、UE4内でもオブジェクト指向に 基づいて設計されている部分は多々あります。
コンポーネント コンポーネント指向とも呼ばれる。 オブジェクト指向と同時に使われていることも多く、 UE4内でも様々なコンポーネントが存在している。 原則としてプリミティブな機能を一つだけ持つという考え方。 そして簡単につけ外しができるようになっている必要があります。
メッセージ メッセージパッシングとも呼ばれる。 本来プロセス間通信における考え方ではありますが、 UE4では様々なオブジェクト同士のやりとりが発生するため、 メッセージを送るという考え方は非常に重要。 メッセージは依存関係を持つような作りになってはいけない。
MVCモデルとかってどうなの? Model View Controllerモデルと呼ばれる設計手法。 Model → ViewとControllerの中間のやりとりを行う View → Modelから通知を受け取り描画を出力する Controller → ユーザー入力を受け取りModelに通知する 『MVCモデルについて』 https://qiita.com/s_emoto/items/975cc38a3e0de462966a
実は既に結構MVCなUE4 UE4は既にMVCに近い設計で、自然とその恩恵を受けています。 Actor, Character → Modelに相当 Skeletal Mesh, Static Mesh → Viewに相当 Player Controller → Controllerに相当 MVCの概念を理解している人はよりUE4で堅牢な設計が出来ます。
プレイヤーキャラクターで考える コントローラー キャラクター本体 アニメーション
プレイヤーキャラクターで考える • • • • メッセージ キャラロジック更新 コンポーネント更新 メッシュに反映 HUDに通知 などなど… コントローラー キャラクター本体
プレイヤーキャラクターで考える • • • • メッセージ ステートチェンジ アニメーション更新 アニメーション通知 IK処理 などなど… キャラクター本体 アニメーション
基本的に一方向 メッセージを飛ばす際には基本的に一方向。 通知を飛ばす先には依存関係がないようにしておく。 この依存関係がなければ違うアクターやコンポーネント間でも 自由にメッセージを飛ばすことが可能となるので、 非常に再利用性が高いものとなります。 メッセージ
役割の移譲 大事なのは役割の移譲を行うということ。 それぞれのアクターやコンポーネントに明確な 役割を持たせることで必要以上に多く役割を持たせない。 役割を越えた際に移譲できれば自分で行う必要はなくなり、 分業しやすい作業体制ができる事となります。
再利用性
再利用性 作ったものは使いやすく再利用したい。 UE4は再利用しやすくするための機能が充実しています。 そして再利用するためには一定のルールを守る必要があります。 これらのルールを守ったものを『コンポーネント』として 利用していくことになります。
再利用のルール • 外の処理には依存させない(完全な疎結合) • ひとつのコンポーネントにはシンプルな機能をひとつ • 簡単に取り外しが利くようにする • 外の処理を利用する場合、メッセージを使う
UE4で再利用しやすいもの • アクターコンポーネント • ブループリント関数&マクロライブラリー • ビヘイビアツリー • ビヘイビアツリータスク • サブアニメーションブループリント • ウィジェットブループリント • ゲームプレイアビリティブループリント
再利用性 とにかく再利用可能かを検討してみよう。 ブループリントの一部切り出せないか? 切り出した場合に簡単に利用できるか? 依存する部分がないか? これらを考えられるようになれば設計が自然と上手くなります。
依存関係
依存関係 設計する上で最も悩む部分。 何かに依存するということは設計の幅を狭めてしまう。 理想は依存するものをゼロにしたい。 ただし全てにおいてそう都合よくはいかない。 そういった時にどういう対処をすればいいのか。
UE4で依存関係を確認する方法 実際にどう依存しているかを確認。 アセットを右クリック ↓ Reference Viewer
これでもまだ序の口
依存関係を断ち切る 依存関係を発生させないためには様々なことに注意が必要。 何も考えずに組んでいるとあっという間に依存が発生。 ではどこから注意するべきか?をしっかりと理解。 複雑な依存関係を断ち切りましょう!!
複雑なアセットを参照しない 基本中の基本ですが多くのアセット情報を持つ、 ブループリントを直接参照しないこと。 BP内には他のアセットの情報が多く紐付けられていることが 多いために、適当に参照を行うだけで芋づる式に参照されます。
キャストをなるべくしない とにかく手軽に対象の情報にアクセスできる 超便利機能なキャストですがこれが一番弊害が大きい。 特にお互いの持つ情報で類似部分がない場合には 一瞬で参照を増やしてしまう原因。 ゼロとまでは言わないので極力なしで!
同じボリュームの変数を作成しない 変数を持つ際に、別クラスアクターのオブジェクト変数を 作成することがありますが、これもキャスト同様危険。 特に自身と同じ情報ボリュームを持つ 変数を作るとこれまた参照が増えまくり! こういう構造の場合は設計を見直そう!
基本的な戦略 『メッセージ』を使うことを考える! メッセージというと抽象的ですが、UE4で使えるメッセージ としては『インターフェース』という機能があります。 『ブループリント インターフェースについて』 http://unrealengine.hatenablog.com/entry/2014/09/23/201458
インターフェース インターフェースを使うことで大きく依存性を下げることが可能です。 『ブループリント インターフェース』 http://api.unrealengine.com/JPN/Engine/Blueprints/UserGu ide/Types/Interface/index.html 『インターフェース(C++)』 http://api.unrealengine.com/JPN/Programming/UnrealArchitecture/Ref erence/Interfaces/index.html
カプセル化 インターフェースを使うことでオブジェクト間は抽象化されることにより カプセル化されていますので、依存関係が発生しません。 昔ながらのオブジェクト指向の考え方ではありますが、 現代においても非常に重要かつ、一番難しい部分。 カプセル化された状態でやりとりを行えば、それは自然と オブジェクト同士でメッセージを飛ばしていると言えます。
タグの利用 もうひとつ依存性がない状態で相手の情報を知る方法として、 『タグ』という機能があります。 タグは文字ベースの情報なので、参照がつくことはありませんが 相手の持つ情報を知るための気軽に利用可能な情報です。
Gameplay Tag 更に発展したタグの機能として、『Gameplay Tag』という機能もあります。 これはタグ自体を高機能化し、検索性や利便性をより強化しています。 もちろんタグの利点もそのままです。 『UE4 Gameplay Tagを使ってゲームプレイ時のタグ管理をより扱いやすくする』 http://unrealengine.hatenablog.com/entry/2017/02/21/220000
ハード参照とソフト参照 参照にはハード参照とソフト参照の2種類があります。 リファレンスビューアー上で確認が可能。 ハード参照はホワイトのラインで ハード参照 ソフト参照はマゼンタのラインで それぞれ表示される。 ソフト参照
ハード参照 通常は全てハード参照になります。 ハード参照されたオブジェクトは1つ読み込まれた時点で、 依存関係のあるものが全て一気にメモリー上へ展開されます。 できればハード参照させずに解決させたいが、 面倒な事前読み込み対応もやらなくていいので、 規模が小さいうちはハード参照のみでも十分。
ソフト参照 少し特殊な参照方法。 C++で『TSoftObjectPtr』を使うか BPでも『Primary Asset ID 』の登録でソフト参照が可能になります。 そして自分で読み込み対応処理を記述する必要があり。 普段使いはハード参照と同じように利用できます。 『アセットの参照』 http://api.unrealengine.com/JPN/Programming/Assets /ReferencingAssets /
Asset Manager このようなソフト参照されたアセットを読み込むためにUE4では 『Asset Manager』という仕組みがあります。 うまくソフト参照が紐付いていれば自分が欲しいアセットのみを ロードすることが可能となり、メモリーと時間の節約になります。 『アセット管理』 http://api.unrealengine.com/JPN/Engine/Basics/AssetsAndPackages/AssetManagement/
シングルトン or マネージャー頼り シングルトンやマネージャークラスはダメとは言いませんが、 なんでもかんでもシングルトンに頼る設計はやめましょう。 シングルトンによる依存関係は後々の設計変更があったり、 クラスやアクター単体での動作が出来なくなったりします。 『Singleton何が悪いの?』 https://qiita.com/mo12ino/items/abf2e31e34278ebea42c
シングルトン アンチパターン シングルトンによるグローバル値に頼らない設計をしましょう。 シングルトン依存による特定システムでしか動作しない設計は 後々にゲーム部分の仕様変更や単体システム動作を阻害します。 まずはそのシングルトン or マネージャー必要?と疑いましょう。 必要になった場合もシングルトンなしで動作できる仕組みを実装しましょう。
イベントドリブン
イベントドリブン イベントドリブンによる設計を意識しましょう! 何かを処理する時、それはイベントが発生するまで処理する必要はない。 その更新処理、本当に必要?
Tick依存による設計 よくありがちなのが、全てTickで処理してしまうというもの。 ループがずっと回っているので、確認がラク。 そもそも昔のゲームはずっとそうやって作ってきた。 現代のゲームはネットワーク要素も多々あり、 イベントが来てから処理するという考え方も一般的になってきた。 Tickはそもそも必要でなくなってきている。
Tickは重い Tickを回しているだけで負荷はかなりかかります。 これはBPだろうとC++だろうと同様。 アクタティック内のStart with Tick Enabledは基本的にオフ。 Tick Intervalを[0.0]以外に設定することでループインターバルを変更。 非常に単純なことですが、 アクターが多いほど効果大!
Tickなしでどうする? イベントドリブンな設計を考える。 Timer Event Event Dispatcher Timeline DelayなどのLatent処理 正直BPの方が圧倒的に作りやすいです。
Tickをオンオフする Tick自体を自動的にオンオフする仕組みを作ることもできます。 距離やカメラなどで判別する材料を作り、Tickを必要に応じて切り替え。 以下はフォートナイトでも実際に使われています。 『Significance Manager(重要性管理)を使ってTickを抑制する』 http://unrealengine.hatenablog.com/entry/2018/11/13/235636
Tickがなくてもなんとかなる 実際に作ってみてわかるのは、意外とTickがなくてもなんとかなるものです。 UE4にはTickがなくてもGameplay Frameworkが提供する 標準イベントが大量に用意されているので、 これらを駆使することでかなり楽に組むことが可能。 目指せ!脱Tick!
BPとC++
BPは重い、C++は速い 一般的なイメージはこのはず。 実際何も考えなければBPで組むと相当重くなります。 BP VS C++
C++のメリット • ランタイムパフォーマンスの向上 • 明示的なデザインが可能 • 広範なAPIアクセス • 詳細なデータ制御 • 厳密なネットワークレプリケーション • 高速な数学計算 • 差分/マージの容易性
BPのメリット • 高速なクリエイション • 高速なイテレーション • フローの視覚化 • フレキシブルな編集 • 簡単なデータの扱い • ゲームデザイナー、アーティスト、フレンドリー • NULLアクセス違反が発生しない
よくある誤解
BPって本当に遅いの?
半分正解、半分間違い 正確にはBPで作った処理をBPで何度も呼びだすと重たいです。 C++で作られた処理をBPで呼び出すのはほぼC++ネイティブ同様のコスト。 つまりエンジン側で実装されていたり、C++で実装されているノードは BPで呼び出してもほとんどコストにはならない。 BPが苦手なのは大量のループ処理。配列アクセスにも注意。
配列ループ処理を効率良くする 配列ループ処理はいくつか最適化可能です。 Pure関数をForEachLoopに利用すると非常に負荷がかかるのでこれも最適化します。 『配列要素の処理をForEachLoopなしで行う』 http://unrealengine.hatenablog.com/entry/2017/01/14/133301 『Blueprintアンチパターン その2 -Pure関数+ForEachLoop-』 http://unwitherer.blogspot.com/2017/04/bpblueprint-2-pureforeachloop.html
C++化の前に よく考えてください。 まずは本当にそのBPがネックになっているのかを検証。 プロファイラーを使いましょう!! 標準のプロファイラーは非常に簡単に使えて便利です。 『プロファイラ ツール リファレンス』 http://api.unrealengine.com/JPN/Engine/Performance/Profiler/
イベントドリブン推奨 設計をイベントドリブン化することで、BPでも十分パフォーマンスが出ます。 BPから負荷の高いBPノードの呼び出しをしないように。 複雑な汎用計算はC++化してBPから呼び出す。 BPは基本のロジックフロー制御用に使いましょう。 これだけでほとんどのケースでBPがボトルネックになることはなくなるはず。
ハードコード
ハードコード C++上に値を直接書き込むこと。 これを行うと数値の調整が一気にやりにくくなる。 全ての数値を公開すればいいというものではありませんが、 ハードコードされた値をプログラマー以外が直接触るのは非常に面倒。 必要に応じてしっかりUPROPERTYをつけてあげてください。
C++からのアセット参照に注意 C++ではコンストラクター上で、 『FObjectFinder』や『FClassFinder』を利用することで 文字列からアセットを読み込ませることが可能になりますが、 これらはプロジェクトのスタートアップ時に実行されることになり、 無駄にメモリーを消耗したり、起動までの時間が伸びたりします。 テストデータであればいいですが、可能な限りC++クラスを継承したBPを 作成してBP上でアセットをセットしてあげましょう。
クック時に問題発生 C++上で『LoadObject』関数を使うことで文字列から アセットを読み込ませることが可能となっていますが、これも問題。 これらは文字列となっており、パッケージング時にアセットがクックされずに エディター上では動作するのにゲーム上で読み込まれないことがあります。 ハード参照されない場合、ソフト参照してAsset Managerで読み込みましょう。 文字列のハードコードは基本的に追跡することが不可能です。
理想な設計
理想的な設計を目指すために より依存関係を少なく、より取り回しがしやすいように。 ほげたつさん(@HogeTatu)の以下の記事は設計に関する 非常に具体的な指針を示してくれてます。 『UE4でゲームを作る時に考えていること2選』 http://hogetatu.hatenablog.com/entry/2017/12/11/000638
アクターは単体で動作させる 外部のGame ModeやPlayer Controllerに依存しない。 つまりアクターを配置すればそれだけで動作するようにします。 理想はGame Modeを切り替えるだけで、別のゲームになるように。 Player Controllerは入力を抽象化し、操作キャラクター毎に切り替えを。 例えばポーズ状態の実装もキャラ依存ではなくPlayer Controllerで 制御することでどのキャラクターでも同じようにポーズが出来ます。
キャラクターAIの場合 キャラクターAIの場合であれば、 メインとなるキャラクタークラスに紐付けるAI Controllerクラスを 別のAI Controllerクラスに切り替えるだけでAI自体の振舞いを 切り替えることができるようにしておきましょう。 同じ見た目でも別キャラクターのような動作が可能となります。 デバッグ時もAIを切り替えるだけで色々な確認が可能です。
依存部分を外部から注入 なんだかんだでアクターは完全に動作するために、何かしらに依存します。 重要なのは依存情報をアクター本体が抱えない事です。 必要な情報は外部へリクエストし、状況に応じてアクターが自動的に 取得先を切り替えられるようにしておきます。 アクターはあくまでも単体で動作させられるように。
まとめ
まとめ • 設計の良いお手本をたくさん見る • オブジェクト指向、コンポーネント、メッセージを理解する • 再利用しやすいようにシンプルなものに分解する • 依存関係を発生させないように細かく注意する • イベントドリブンな設計をしっかりと意識する • C++化する前に、それはBPでもよくないかを検討する • アクターが単体で動作できるようにしっかりと意識する
ルールを守って楽しく ゲーム設計しましょう!
ご清聴いただき、 ありがとうございました!!