本記事は「Unreal Engine (UE)のカレンダー | Advent Calendar 2023」 13日目の記事です。
はじめに
Uneral Fest 2023 におけるホグワーツレガシーの開発者による講演「Collision Data in UE5: Practical Tips for Managing Collision Settings & Queries(Unreal Engine 5での衝突データ: 衝突設定とクエリの管理に関する実用的なアドバイス)」が素晴らしい内容だったのでメモとして記事にしました。
所々エディタ操作を交えた解説があるので、本記事を軽く読んだ後に講演動画を見ると理解が深まると思います!(公式の日本語字幕こないかなぁ…|д゚)チラッ)
www.youtube.com
関連記事
- Collision PresetにおけるCustomを無効化する方法について
- Collision Presetの選択画面にて検索可能にする方法について
- Collision Presetの設定状況をReference Viewerから確認する方法について
- Collision Presetのリスト情報を取得する方法について
- アウトライナーの目玉アイコン(Visibility)をEditorUtilityからONOFFする方法について
ざっくりまとめ
- ホグワーツレガシーの開発では、Collision設定に関するルールが整備されていなかったため、プロジェクトの規模・コンテンツ量が増えるにつれて不具合や対応コストが膨大になった
- そのため、初期段階からCollision設定に関するルールを作成し、各自がそれを守る・注意することが大事。また、それを継続するための仕組み・ツールづくりも重要
- Collision設定をAsset・Componentごとにカスタマイズすると不具合の元になるし調査もしづらいので、個々のカスタムは禁止して必ずCollision Presetを指定する形が良い。必要に応じてプリセットを追加していく
- Preset名はCollisionの処理内容ではなく、使用用途がわかる名前にする
- Presetの数を増やしてもパフォーマンスに影響はないので、沢山追加してOK
- 微小なエンジン改造やツールを用意することで、ヒューマンエラーの防止や不具合発生時の調査の効率を上げることができる
- LineTraceをはじめとした様々なCollision Query関数に関しても不具合のもとになることが多く、正しく理解した上で活用・使い分けをしていく必要がある
- Collision設定とQuery処理を正しく活用することで、不具合が少ない堅牢な実装を実現できるだけでなく、ボトルネックになりがちなCollision処理を最適化することができる
Recap the Basics(0:00~6:30)
まずはCollisionに関する基本的な機能・用語についてのおさらいから
- Collision Channels にはObjectとTraceの2種類があり、前者はオブジェクトに対して設定できるが後者はできない(後述のRayCastなどで使用)という違いがある
- Collision Profile ではCollision自身のChannelと、各Collision Channnelへの反応(Ignore, Overlap, Block)を設定する
- これらのフィルタリングを適切に設定することで、意図したオブジェクト同士の当たり判定・制御を実現できる
- Collision Queries は レイ などを飛ばして周辺オブジェクトの情報をチェック・取得する仕組み。 Collision Queries も Collision Profile を元に処理を行う
- Collision Preset は Collision Profile のプリセット。エンジン標準だけでなく、プロジェクト設定から追加可能。プリセットを活用することで、すべての設定を手動で指定する必要がなくなる
Managing Collision Settings(6:30~18:35)
Collision設定の管理における注意点とオススメの方法について
まずはCollision設定の管理が難しい理由について
- オブジェクトの数が増えるほど、Collision間の相互作用の組み合わせが大幅に増える
- Collision Queriesで実現するシステムも多いため、更に考慮すべき事項・組み合わせが増える
- Collisionデータを使う処理がプログラマが書くが、元となるCollision設定がゲームデザイナーやアーティストが定義する。その結果、Collision設定を考慮・理解していないメンバーが少なからず出てくる
- 更にコンテンツの量・プロジェクトの規模が増えるほどに管理の難易度が上がるため、各自がCollision設定を正しく理解する必要性が高くなる
- ホグワーツレガシーではCollision Channelのルールが定まっていなかったため、様々な混乱と不具合が発生した。さらにその問題を解決するために無数のChannelが追加され、最終的にはCollision Channelの上限数に達してしまった。その結果、多数のアセット・コードの修正に追われた。
- Collision Channelの役割・動作を明確にすることが大事。そのために、そのChannelがどのように使われ、データ側ではどう設定されるかを定義する必要がある
- エンジン標準のChannelには注意。プロジェクトにおける役割・動作が明確になっていない状態なので、もし使用するならそれらを決める必要がある。
- Collision Channelの追加は常に慎重になるべき。追加されるたびに全てのCollisionデータに影響するため。Channelの追加は限られた人のみができるようにするのがオススメ
- 各Channel の説明と各反応(ignore, overlap, block)の意味をドキュメントにまとめることで、どのように使用されるべきかを明確化・共有することが大事。コード・アセットの実装時だけでなく、不具合調査時にも非常に役に立つ
- ホグワーツレガシーでは、各アセットでカスタムされたCollision Profileが使われ、Collision Presetが使われていなかった。その結果、同じようなアセットでもCollision判定の結果が異なる問題などが起き、多くの不具合の元になっていた(上図はその例)。更に、Collision設定や使用方法に変更が必要になった際に大量の変更・チェック作業が発生していた
- こういった問題を避けるには、Collision Presetを使用し、アセットでのカスタムを避けるのがオススメ。実装・設定が明確になるし、不具合が起きた際もアセット毎に修正・チェックする必要がない。更にPresetを更新したら一括で適応されるため、対応コストが大幅に下がる
- Collision Presetの数に制限はなく、数が増えることで処理負荷が増えることもない。そのため、好きなだけ追加できることを前提知識として持っておくと良い
- Collision Presetの名前は、それがどんな反応・処理を行うかではなく、それがどのように使われるかを期待して決めるべき。そうすることでデータ側の作業者が深く考える必要なく、適切なPresetを設定することができる。その目的を達成するためなら、同じ設定であっても別名のPresetが複数あっても良い
- エンジンが標準で用意しているCollision Presetの一部には要注意。例えば、Overlap Allはトリガーボックス用によく使用されているが、すべてを検出する必要があるケースは見たことがないし、パフォーマンス面で問題が発生する可能性がある。
- Collision Presetの使用を徹底するため、エンジンコードを数行変更することで、Custom Collision Profileを使用不可能にした
- 更に禁止していることを明確にするためにグレーアウトしたり、多くのCollision Presetを扱うために検索可能にした
※ 具体的な改造方法については説明がなかったので…自分で調べて記事にしました!
pafuhana1213.hatenablog.com
pafuhana1213.hatenablog.com
- Collision PresetをReference Viewerで確認できるようにすることで、各箇所における設定状況のチェックとルールの維持を実現した。
- Custom設定を禁止する上で、どの箇所でCustom設定になってしまっているのかを把握する上でも助かった
- エンジンに数十行のコードを追加することで実現できる
※ 具体的な改造方法については説明がなかったので…自分で調べて記事にしました!
pafuhana1213.hatenablog.com
- EditorUtilityWidgetを用いてCollision設定に基づいてエディタ上での表示ONOFFを行うことで、視覚的にCollisionの設定状況・設定ミスを確認できるようにした
- Custom設定のオブジェクトのみを表示
- 敵の視線を遮るオブジェクトのみを表示
- 移動可能オブジェクトのみを表示し、パフォーマンスに影響する不必要な設定を可視化
※ 雑に作ってみたという記事を書きました
- Collision Presetのリスト情報を取得する方法について
- アウトライナーの目玉アイコン(Visibility)をEditorUtilityからONOFFする方法について
Collision設定の管理に関するまとめ
- 適切に管理するためには、事前の計画と継続的な努力が必要。問題が静かに積み重ならないように、常に意識することが大事
- Collision Channelの目的と、そのChannelへの各Responseが何を意味するのかの明確な定義が必要
- 設定ミスを減らすため、CollisionPresetを様々な箇所で使用するべき
Using Queries Effectively(18:35~32:52)
Collision設定から、CollisionQueryの話へ
- Collision設定とCollisionQueryは強く紐づいており、設定が適切に行われているとQuery処理は簡単かつ効率的に実行できる。逆に、設定が不十分・複雑だと適切なQuery処理を行うことが困難になる
- CollisionQueryを正しく理解しないことで様々な間違いが発生していた
- 不必要に新しいChannelを追加された結果、その上限に達したことで多くの追加作業が発生してしまった
- データをフィルタリングするために必要以上に多くのQuery処理を行うことでパフォーマンスに影響が出ていた。また、不具合や結果が不安定になるなどの問題も発生した
- CollisionフィルタリングやCollisionQueryについて正しく理解することで上記のような問題を回避することができる
- CollisionQueryの複雑さの要因の一つとして、標準で用意された関数の多さがある。それらについての要約と違いについて説明していく
基本的なレイキャストである LineTraceSingleByChannel について
- Collision Channelによるフィルタリングを行い、そのChannelに対してBlock反応を持つオブジェクトと衝突したら停止する(IgnoreやOverlap設定の場合は通過する)
- Query関数にはFCollisionResponseParamsが引数としてあり、フィルタリングに用いられる。これは後述で詳細を解説する
- FHitResultはヒットした場所、Normal(法線)、そしてヒットしたコンポーネントを含む情報を格納している
LineTraceSingleByProfileについて
- LineTraceSingleByChannel と挙動はほぼ同じだが、ChannelとTraceの反応を指定するのではなく、既存のCollision Presetを使うところが異なる
- エンジンコードを見ると、たしかに実際に動いてるのはLineTraceSingleByChannel
bool UWorld::LineTraceSingleByProfile(struct FHitResult& OutHit, const FVector& Start, const FVector& End, FName ProfileName, const struct FCollisionQueryParams& Params) const { ECollisionChannel TraceChannel; FCollisionResponseParams ResponseParam; GetCollisionProfileChannelAndResponseParams(ProfileName, TraceChannel, ResponseParam); return LineTraceSingleByChannel(OutHit, Start, End, TraceChannel, Params, ResponseParam); }
LineTraceSinglebyObjectTypeについて
- ByChannelやByProfileと異なり、各Channelによるフィルタリングは行わずに、ObjectTypeのみで判定を行う
- 上記の違いを混乱の原因になることが多く、2つの違いを常に念頭に置くことが大事
- ByObjectType利用時にHit情報に対してフィルタリング処理を行っている場合や、1つのQuery処理で複数のObjectTypeに対してトレースする必要がある場合はByChannelを使った方がいい可能性がある
LineTraceMultiByChannelについて
- LineTraceSingleByChannelとは異なり、途中で検出されたすべてのOverlapの情報も取得できる。そのため、1つのQuery処理で複数のオブジェクトを検出可能
LineTraceTest~について
- テストバージョンではHitしたか否かの情報を返すだけで、Hitしたオブジェクトに関する情報は返さない。他に比べるとパフォーマンスが少し改善するため、Hitの有無だけで問題ない場合はこのバージョンを使うのが良い
Sweep~(SweepSingleByChannelなど)について
既に少し触れた FCollisionResponseParams について
- これまで説明してきたLineTrace, Sweep処理は指定のChannelに対してBlock反応をするオブジェクトを検出していたが、このルールは変更することが可能
- 例① IK用にTraceする際にキャラクタを無視
- 例② 既存のChannelとLineTraceMultiを使う場合
- Weapon Channelが既にあり、各敵はWeapon Channel (Object Type)に対してBlock反応をもっている状況
- 複数の敵に対して攻撃できる武器を追加する際、Weapon ChannelはOverlap反応されないため、そのままだと複数の敵を検出することができない
- 各敵に対してOverlap反応を持つ新しいCollision Channelを追加することでも解決できるが、Channelが増えてしまう懸念が出てくる
- Traceに対してPawn Channel (Object Type)ならOverlap反応するように変更することで、Channelを増やすことなく問題を解決することができる
- TickでCollision Queryを実行し、結果をデバッグ描画するツール(Actor)を用意することで、Query処理を実装する前のテスト、Collision設定の理解、不具合のデバッグに役に立った
- Trace処理の種類や各パラメータを詳細パネルから変更することが可能
- Sweepに対してのデバッグ描画のヘルパーを追加することで、Sweepを使用している処理の可視化やデバッグの高速化を実現した
- BPで使えるQuery用ノードはC++版に比べて機能が制限されていることに注意
- 上述のFCollisionResponseParamsの設定を行うことができない
- UKismetSystemLibrary::LineTraceSingleのように、BP向けに用意された関数をC++で使うのは避けた方がいい。FCollisionResponseParamsの制限に加えて、通常のQuery用関数とはパラメータが異なるので混乱を招く可能性がある
Case Studies(32:52~)
Query処理は様々なオプションを利用することができ、それらを活用することで効果的に実装することができる。ここからは、Collision設定とCollsiionQueryを活用した例を紹介する
例①:プレイヤーだけが衝突し、敵やプロジェクトが衝突しない見えない壁などに使う、PlayerOnly Collision
- Playerキャラに特殊なObjectTypeを設定することができるが、別処理にて他キャラクタ(Object Type:Pawn)と同じ反応を期待している処理が既にある場合は難しい
- プレイヤーのObject Typeを変更するのではなく、Collsiionフィルタリングで実現する
- 新しいObject Channel「PlayerOnly」を追加し、デフォルトの反応をIgnoreに設定
- プレイヤー用のCollision Preset「Player Capsule」にて、「PlayerOnly」に対してBlock反応をするように変更
- 新しいCollision Preset「BlockOnly Player」を追加し、ObjectTypeをPlayer Onlyにし、PawnにのみBlock反応するように設定
「PlayerOnly」の活用例について
- プレイヤーが入るときだけ処理を行うトリガーボックスの実装において、全てのオブジェクトとOverlapし、それに対してCastなどで判定するのは非常に効率が悪い
- ObjectType「PlayerOnly」 かつ Pawn:Overlap反応 に設定したPreset「OverlapOnlyPlayer」を使ってフィルタリングすることで、プレイヤー以外を無視しパフォーマンスを改善することができる
カメラ用コリジョンについて
- モデルの内部が見えてしまったり、カメラとプレイヤーの間にオブジェクトが入ることを避けるため、プレイヤーからカメラ位置に対してSweepし、なにかに衝突したらカメラを引き寄せる実装にしていた
- Sweepで使用するChannel「Camera」に対してオブジェクトがBlock反応することが前提になっている
- 背の高い細い柱がカメラとプレイヤーの間に入ると、カメラが頻繁に大きく動いて負荷に感じさせる問題が発生した
※ 右に伸びる影で少し見づらいですが、左端が柱のモデルです
- 柱を無視すれば問題は解決するが、カメラ内部が見えることを防ぐ必要があったため、Channel「Camera」を無視することはできなかった
- 柱に対して、カメラの埋まりを避けつつカメラとプレイヤーの間に入ることを許容するため、Channel「Camera」に対してOverlap反応するようにした
- Block設定のオブジェクトに対してのSweepは続けつつ、Overlapテストにより柱への埋まりを回避した
Collisionに関するその他の領域について
- パフォーマンス
- 移動、Query、物理処理は負荷が高い上に、Collisionのストリーミング時間とメモリ消費量は課題だった
- これらをプロファイリングし、最適化するための方法を調査するのに非常に多くの時間を費やした。更に、多くのコンテンツ変更やキャラクタの動きの調整を行った
- キャラクターの動きの質に関して
- 動きの質を向上させるには、Collisonについて十分に考慮した上でコード・コンテンツの両方を適切に実装していく必要がある
- Collison設定を管理する方法について積極的に考えるべき
- チームがCollisionに関する知識を理解することが大事
- これら2つを支援するためのベストプラクティスやツールは多くある
- これらの全てを行うことで、長期的には多くの時間と作業コストを節約できる
さいごに
Collisionの管理についてここまでまとめている講演・記事は(あまり)なかったので、めちゃめちゃ良い内容でした!!!
(やはり失敗からどう改善していったのかの情報はとても参考になる…ありがてぇ…ありがてぇ…)
講演内で何度も触れていた通り、途中から管理していくのはコストが非常に高い上に放置するとバグが増えていく一方という辛い状況になるので、プロジェクトの序盤からしっかりシステム・ツールを整えたり、チーム内で話し合っていくのが大事だと思います!
おしまい
Unreal Engine (UE) Advent Calendar 2023、既にたくさん記事が公開されてるので是非見てみてください!
明日以降の記事もすごく楽しみですー!
qiita.com