はじめに
UENightはUnrealEngineのちょっとニッチな技術や機能について語り合ったり討論したりするイベントです。月に一度程度の間隔で開催しています。この記事は10/12に開催されたUE5 Logic Nightのログです。
開催場所のDiscordの招待リンクはこちら
初心者からガチプロまで誰でも参加可能です。
過去に開催されたお題
・GAS
・Rig
・最適化&デバッグ
・Logic(クラス設計やコーディング)
※こちらのイベント/レポートの整合性、動作などの一切内容の保障は致しかねます
※筆者のスキルが追い付いていないため、一部自分の中で要約している部分がありますが、もし間違いなどあればお気軽にTwitter(@Gisu_Love)、Discord(Tukigase)、Qiitaの編集リクエストなどでご連絡ください
本編
pause中にSet Timer by Eventが動かなくなるが、Tickで代用する以外にいい方法はあるか?
Set Timer 系の関数は FTimerManager というクラスが時間を管理している。
通常、この TimerManager は World の Tick 処理で更新が行われるが、 Pause が入ると World Tick
から Timer Manager を更新しなくなる。すると、 Pause 中にはタイマーが動かなくなる。(Delayも同じ扱い)
Set Timer を Pause 中も動かしたければ、 C++ でフラグを上書きするオプションを追加するなどの改造するをしかない。もしくは、 Timer Manager 互換の仕組みを停止しない Tick をソースとして追加すると実現できる。
BP オンリーなら Tick でやるので十分なケースがほとんど。
また、Set Global Time Dilation,Set Custom Time Dilationなども時間系の関数に影響することがある。
Timer的にTickを使うのって…?
悪ではない。Tick に繋がる処理の内容による。
何もしていないのに、有効な空Tickが残っているほうが悪
(詳しくはこちらの記事を参照)
ゴースト状態に戻す方法
Actor の Self を選択した状態で詳細のアクタティックで「Start With Tick Enable」を Flase にすると Tick をオフにできる。が、上のような見た目ゴースト状態には戻らない模様…
他にも
- Tick Interval からTick が発行される間隔(0.0で毎フレーム)
- Allow Tick Begore BeginPlay(BeginPlay前にもTickを有効にする)
Tick とは異なる頻度で繰り返されるイベントを利用したい
Set Timer 系関数のループか、Tick で1秒たった…などカウントする関数を作ってカウントする。
Actor とコンポーネントでは別の Tick Interval を設定できるので、コンポーネントごとに異なる頻度の Tick を利用することもできる。
プレイヤーが作成した Actor 同士での継承
公式のサンプルLyraでは継承が多用されている(この記事参考)。
ただ、近年の流行りとしては、継承を多用するよりも、コンポーネント指向や、状態を持たない純粋な関数の組み合わせによって処理を構成することが、設計やパフォーマンスの面から推奨されることもある。UE ではどうなのか?
『クラスの「継承」より「合成」がよい理由とは?ゲーム開発におけるコードのフレキシビリティと可読性の向上
』
設計次第だが、UE でも実装をコンポーネントに逃がして、過剰な継承を避けることには意義はある。
チュートリアルなどでは説明の単純化のため、コンポーネントに処理を分けずに Actor に直接実装しているケースも多い。
UEの場合、BP でコンポーネントにしてもパフォーマンスが上がるか?と言われるとそうでもない。
コンポーネントが増えれば増えるほどロードも増える。ただ、規模が大きくなれば、コンポーネント指向で組むことの設計上のメリットもしたがって大きくなる。
UE ではアセットがバイナリで、同時に編集しにくいため、担当者分割のために分ける意義はあり。
GamePlay Ability System(GAS)で Ability の付与によってふるまいをわけることも可能。
複数で参照しているオブジェクトで、特定の Actor の情報にアクセスしたいとき
AI Task や GAS、AnimNotify などから Charactor にアクセスしたいときの手段として、
- Blueprintの基底クラスに Cast
- C++の基底クラスに Cast
- コンポーネントに情報を持たせておいてそこにアクセス
- Interface 経由
などがあるがどれが良いのか。
まずは Cast でよい。より汎用性を持たせたい場合などに Interface を検討したい。
Cast 先のクラスには注意が必要。特に、テクスチャやメッシュなどへのアセット参照を持っている BP クラスにキャストしたりすると、ただのロジックのつもりなのに利用時にアセットがロードされてしまったりする。
処理しか書いてない BP の親クラスか、理想的には C++ の基底クラスにキャストしたい。
UobjectへのCastはBPでもC++でも変わらない?
ちょっとBPのほうが(BP 関数の呼び出しなどの)コストが高いが気にするほどではない。どちらにせよ、毎 Tick 呼ぶとかでなければ気にしなくてよく、適切にキャッシュすることなどを気にした方が良い。
C++ の純粋なキャスト (static_cast など) とは UObject の Cast は違うので、その点の特性の違いは抑えておけると良い
C++を使う場合、Interface、基底クラス、Enum などは C++ で定義しないと後々めんどくさい
この手のデータ定義は、BP 側で作ると C++ で利用できないという罠に嵌る。
C++ が使える人は、たとえ BP オンリーのつもりではじめたプロジェクトだとしても、後のためにこの辺だけでもC++ で書いたほうが安全かも?
どこに書けばいいの?
構造体や列挙子はUEの場合クラスやネームスペースでなく構造体名.hやCharactorDefine.hみたいなのを作って書くのが通例?らしい。データの定義とロジックの宣言は別になっていると嬉しい気持ちがある。
BPのコンポーネントを取得したい
コンポーネント階層の親から子のコンポーネントを全て取得したい場合は、"GetChildrelComponens"で取得することができる。
(余談)コンポーネントではなく、アタッチされたActorを取得する場合は"GetAttachedActors"ノードを利用する。
イベントディスパッチャーを使うときの方針
イベントディスパッチャーでどこがどれを参照しているかわからなくなりがち
運用方針などあるか?
方針という意味で言えば、UIのリストビューのような用途で言えば
親(ビュー)から子(要素)の場合→子を制限したいのでInterfaceで、子(要素)から親(ビュー)にMouseClickなどを通知する→イベントディスパッチャーを使い分ける。
なお、C++ではDelegateが同じような機能となる。
Delegateなら関数の引数として渡すことができるので、非同期処理等において簡潔にロジックを記述することが可能。
言い換えればBPの関数でDelegateを使いたくなった時に、その代わりとして使うのがイベントディスパッチャーとなる。(個人的な意見として「BPの引数にイベント型が渡せれば処理がスマートになるのに」と常々思っている)
そうでなければインターフェースで用途は満たせるはず。
Delegate参考記事
大学生の子がSNSで「今月生活費やばいよ~」とつぶやいてるのを見て、親が仕送りを勝手に送ってくれる(自分のイベントを発火しただけで親が実行してくれる)
という例えがありました(?)
widget上に反映する必要のある数値などはどこに書くべきか、どういう経路で反映するべきか
PlayerControllerで反映を行う。
Add to ViewportやAdd to ScreenもPlayerControllerでやったほうが良い。
PlayerControllerはレベルを移動するまでなくならないため
マルチ対応のためにもそのほうが良い
敵などは別(PlayerControllerに送られても困る)
ロックオンなどはPlayerControllerでよい
WidgetComponentを敵に持たせる
制限時間や敵の数などの流動的な数はGameStateに書く。→Server/Client上両方でとれるため。
Game StateはCastで参照すればよい。
※GameModeはホストしかアクセスできないので注意
基本的にネットワークに乗せれるよう設計を組むのが良い。
Enhanced Inputで一時的に違うMapping Contextを使用したい場合。
プライオリティで上からかぶせる。
UIなどでその時だけの設定を入れたい場合はUIを開いたときにAddして消したときにRemoveするなど。
Enhanced Inputを書く場所
EpicのではCharactorに書いていることがあるがどこに書くのがいいのか。
Charactorに書く。(Charactorによって操作方法が変わるなどがあるので)
BeginPlayでなくPossesed(ポーンにPlayerControllerが接続されたとき)に呼ばれるようにする。
PlayerControllerを変えることはよっぽどないので逆に書かないほうがいい。
プレイヤーが設定したキーコンフィグを保存するには
Player Mapping Input Configurationというものがある。
最近Enhanced Input User Settingsというセーブゲームを継承したシステムができた。(こんな記事ありました「【UE5.3】Enhanced Input User Settingsという新しい仕組み」)
Enhanced Player Input(プレイヤーごとに持っているマッピング設定)に対して変更があった時のイベントや追加のマッピングや削除をおこなうことができる。
ゲーム中にCharactorを変更する処理はどこに書く?
ケースによる。
Collisionに当たったときなどならそのコリジョンのActorに書いてもよいし、専用の管理Actorに書いてもよい。PlayerControllerに書いてもよい。
※専用のActorを作った場合、キーボードからの入力を直接受け取ることができない(Enterとかで検索すると出てくる赤いイベントノードで受け取ることができない)ので経由する必要がある。
Widget 上のボタンをキー入力で押すときのキーを変えたい
Widget のボタンをキー入力で押すとき、以下の条件が満たされれば押された判定になる。
- フォーカスが合っている
- 入力されたキーが Accept Key アクションにマッピングされている
キーとアクションのマッピングは、Navigation Config という Slate 関連のクラスが保持している。他にも、「戻る」アクションや、「上」「下」「左」「右」ナビゲーションなど、どのキーが何を表すかが設定される。
こちらは BP では書き換えできないが、 Slate 上で保持されている設定のルールを変えれば変更できる。
実装例: https://github.com/strvert/NavigationKeyConfig
何をGASで書くべきか?
GameplayAbilityという名前から「プレイヤーの能力」のように「技が使えるか」だけでしか利用できないように感じてしまうが、実際はもっと広域的な使い方ができる。
そうなると、GameplayAbilityって色々な所で使えそうなので、どんな風に使うのか迷子になってしまっている。
AbilitySystemComponentをどのクラスが持っているかによる。
例えば、PlayerControllerに付与したAbilitySystemComponentであれば、ワールドに入ってからいつでも使える機能の制御に使えるし、Charactorに付与したAbilitySystemComponentであればPossesしている間に使える機能の制御に使える。
例えばLyraではGameStateにAbilitySystemComponentがあり、ゲーム全体のルールを管理している。
Abilityという名前に左右されずに使ったほうがいい(剣が振れるとかに限らない)
Tagという単位で管理できるのでいろんなところで使える。
マルチプレイだとアビリティの付与にサーバーとクライアントで異なる権限があるので注意。