BLOGブログ

2024.05.29UE4UE5UE/ C++UE/ Network

[UE5] PushModel型のReplicationを使い、ネットワーク最適化を図る

執筆バージョン: Unreal Engine 5.3.2

 

こんにちは。エンタープライズエンジニアの方井です。

本日はPushModel型のReplicationを試してみようと思います。

UE5 Deep Dive 2023の「次世代ネットワーク通信実装Irisについて」にて、Irisではなくとも現存のシステムでも使えることが紹介されていました。ですので、今回は現存システムでの試行です。講演の内容については、以下をご覧ください。

次世代ネットワーク通信実装Irisについて

PushModel型のReplicationとは?

元来、ReplicationとはEngine側が同期したいプロパティに対して定期的に変更の有無を確認し、もし変更があればそれを同期することを努める機能を持っています。この変更の確認間隔ですが、NetUpdateFrequencyNetDormancyである程度調整ができるものの、Engine側がわざわざ御用聞きすることには変わりません。詳しくは以下の仕様にまとまっております。

UE4でマルチプレイヤーゲームを作ろう【CEDEC 2019】

こちらの資料でもご指摘の通り、この定期確認は長い間変更が無くても何度も確認が走ってしまうため、無駄な処理と捉えることもできます。もし、Engine側が聞きに来るのではなくGame側が変更したことを伝えられるようになれば、Engine側の負担も減るのではないでしょうか?まさにそれがPushModel型のReplicationです。

無駄な変更チェックを図解する画像

資料内P.97のこれメッチャ好き


 

1. 新規プロジェクト作成

UE5.3の新規プロジェクト作成画面

リスポーン地点のような安心感。新規プロジェクト作成画面です。実験の分かりやすさを優先してThirdPersonTemplateを選びます。また、Replicationの処理を記述するため、C++で作成します。

 

2. ReplicationするためのActorComponentを作成する

手法は問いません。C++でActorComponentを作成します。そして、以下のヘッダーファイルのようにReplicationするint32値を持つクラスにします。

ソースファイルは以下のような形です。

通常と異なる部分は、NotifyNewValue関数にあるようなMARK_PROPERTY_DIRTY_FROM_NAMEマクロやGetLifetimeReplicatedProps関数内のDOREPLIFETIME_WITH_PARAMS_FASTマクロでしょうか。 MARK_PROPERTY_DIRTY_FROM_NAMEマクロは、まさにEngine側へ変更したことを伝える部分です。そして、DOREPLIFETIME_WITH_PARAMS_FASTマクロは、その変数がPushModel型で動くことを明示する部分です。

 

3. ReplicationするBlueprintを作成する

使用するBlueprintのComponents構造、位置関係が記されている

上記画像のようにBlueprintを構築していきます。 BoxCollisionを2つ用意し、それぞれ「Add(赤)」「Notify(緑)」と名付けます。そして、位置をズラします。分かりやすさ向上のため、線の色・太さを変えたり、HiddenInGameをFalseにすると良いでしょう。

続いて、Add側にTextRenderをぶら下げます。文字が見られればなんでも良いです。適度に移動したり、大きくしたり、Alignmentを変更したりします。

最後に、先ほどC++で定義したActorComponentを追加します。これでComponent構造は準備OKです。

Blueprintのグラフ構成の画像

グラフ構造は上記画像のように構築します。 BeginPlay時にDelegateをバインドし、そのイベントでTextRenderのテキストを更新します。 Add側のBoxCollisionではOverlap時に、所有者側で値の更新(+1)を行います。 Notify側ではOverlap時に、Engine側に更新があったことを伝える関数を呼び出します。これでグラフ構造は準備OKです。

最後にこのBlueprintが同期できるようにReplicatesフラグをTrueにして完了です。

 

4. PushModel型を試す(Fallbackパターン)

それではPIEで動作を確認してみましょう。以下の動画ではPlayer1,2とPlayer1のログ、DedicatedServerのログを表示しています。

Add側のCollisionに触れた瞬間にReplicationされてしまいました…おっとそうだ、設定を一個やり忘れていました。それでも大丈夫、もしPushModel型への移行ができていなくても、従来のReplicationが働きます。

 

5. 設定を変更する

DedicatedServerに対して設定の変更を行います。今回は可用性を上げるために、サーバー起動パラメータで変えます。Editor設定→LevelEditor→PlayのAdditional Server Launch Parameters-ini:Engine:[SystemSettings]:net.IsPushModelEnabled=1を入力します(もちろん、値を0にするとPushModel型を取り止めることができます)。

AdditionalServerLaunchParametersに設定を入力した画像

 

6. PushModel型を試す

それではPIEで動作を確認してみましょう。

想定通り、Add側に触れたタイミングでDedicatedServerのログ出力では値の更新が確認できるものの、Player1のログ出力ではReplicateを確認できていません。そして、Notify側に触れたタイミングでログ出力にReplicateが確認でき、双方のPlayer画面で値が更新されていることが確認できます。

見事、PushModel型のReplicationが行えました!!変更を逐一チェックしないこともそうですが見ての通り、変更を逐一送信しないこともできます。DedicatedServer側でバッチ処理的に、ある程度データを貯めてからフラグを立てて送信することもできるので、さらなる最適化の選択として候補になるかと思います。

 

以上で、デモの説明は終了です。


 

制限

1. Packagingする場合

Packagingする場合は、もうひと手間必要です。Server側のTarget.csに以下の1行を加えてください。これが無ければPushModel型の設定が未完了となり、従来のReplicationが行われてしまいます。

Push Model Networking ← こちらに記載がありました。

 

2. PushModel.h

UnrealEngine/Engine/Source/Runtime/Net/Core/Public/Net/Core/PushModel/PushModel.h
(GithubのUnreal Engineソースコードへのリンク)

PushModel型のReplicationに関するドキュメントは無いのですが、実はヘッダーファイルにコメントとして書かれています。そもそもPushModel型とはなんぞやというところから、マクロの使い方、やりがちな危険な手法まで書かれています。

危険な手法ですが、参照返しが例に挙げられています。変数を参照返しして、クラスの外で変更が行われてしまった場合、それに対してDirtyフラグを外で立てることができないため、Engine側が知ることができません。ですので、参照返ししながらフラグを立てることで、実際に変更が無くても変更したことにすることで回避することを推奨しています。

いろんなパターンの変数や関数を持つExample Classが記載されていますので、使用の前には一読することをオススメします!

 

それでは、良いReplication生活を!!