関連ブログ
- [UE4][UE5]開発環境の容量を少しでも減らす 2024.08.14UE
- [UE5]マテリアルでメッシュをスケールする方法 2024.01.17UE
- [UE5] SafeZoneを使用してUIがノッチに隠れるのを対策 2023.07.05UE
CATEGORY
2024.05.29UE4UE5UE/ C++UE/ Network
執筆バージョン: Unreal Engine 5.3.2
|
こんにちは。エンタープライズエンジニアの方井です。
本日はPushModel型のReplicationを試してみようと思います。
UE5 Deep Dive 2023の「次世代ネットワーク通信実装Irisについて」にて、Irisではなくとも現存のシステムでも使えることが紹介されていました。ですので、今回は現存システムでの試行です。講演の内容については、以下をご覧ください。
元来、ReplicationとはEngine側が同期したいプロパティに対して定期的に変更の有無を確認し、もし変更があればそれを同期することを努める機能を持っています。この変更の確認間隔ですが、NetUpdateFrequencyやNetDormancyである程度調整ができるものの、Engine側がわざわざ御用聞きすることには変わりません。詳しくは以下の仕様にまとまっております。
UE4でマルチプレイヤーゲームを作ろう【CEDEC 2019】
こちらの資料でもご指摘の通り、この定期確認は長い間変更が無くても何度も確認が走ってしまうため、無駄な処理と捉えることもできます。もし、Engine側が聞きに来るのではなくGame側が変更したことを伝えられるようになれば、Engine側の負担も減るのではないでしょうか?まさにそれがPushModel型のReplicationです。
1. 新規プロジェクト作成
リスポーン地点のような安心感。新規プロジェクト作成画面です。実験の分かりやすさを優先してThirdPersonTemplateを選びます。また、Replicationの処理を記述するため、C++で作成します。
2. ReplicationするためのActorComponentを作成する
手法は問いません。C++でActorComponentを作成します。そして、以下のヘッダーファイルのようにReplicationするint32値を持つクラスにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "HistoriaBlogActorComponent.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE(FHistoriaBlogUpdateDelegate); UCLASS(ClassGroup = (Custom), BlueprintType, Blueprintable, meta = (BlueprintSpawnableComponent)) class PUSHMODELREPLICATION_API UHistoriaBlogActorComponent : public UActorComponent { GENERATED_BODY() public: // Sets default values for this component's properties UHistoriaBlogActorComponent(); virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; // Rep対象を設定 public: UFUNCTION(BlueprintCallable) void SetNewValue(const int32 newValue); UFUNCTION(BlueprintCallable) void NotifyNewValue(); UFUNCTION(BlueprintPure) int32 GetNewValue()const; protected: UPROPERTY(ReplicatedUsing = OnRep_MyValue) int32 MyValue = 0; UFUNCTION() void OnRep_MyValue(); UPROPERTY(BlueprintAssignable) FHistoriaBlogUpdateDelegate HistoriaBlogUpdateDelegate; }; |
ソースファイルは以下のような形です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#include "HistoriaBlogActorComponent.h" #include "Net/UnrealNetwork.h" // もしPushModel型Replicationが有効ならば...(デフォで有効) #if WITH_PUSH_MODEL #include "Net/Core/PushModel/PushModel.h" #endif // Sets default values for this component's properties UHistoriaBlogActorComponent::UHistoriaBlogActorComponent() { // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; // ... // Replicate準備 SetNetAddressable(); // このActorComponentがSpawnしたとしても、名前解決できるようにする SetIsReplicatedByDefault(true); // bReplicatesをtrueにするための関数 } void UHistoriaBlogActorComponent::OnRep_MyValue() { // Replicateされたことをログ出力で示す UE_LOG(LogTemp, Log, TEXT("***************** MyValue is Replicated: %d *****************"), MyValue); HistoriaBlogUpdateDelegate.Broadcast(); } void UHistoriaBlogActorComponent::SetNewValue(const int32 newValue) { MyValue = newValue; // 値を更新したことをログ出力で示す UE_LOG(LogTemp, Log, TEXT("***************** MyValue is updated: %d *****************"), MyValue); } void UHistoriaBlogActorComponent::NotifyNewValue() { #if WITH_PUSH_MODEL // Game側から値が変更されたことを伝える MARK_PROPERTY_DIRTY_FROM_NAME(UHistoriaBlogActorComponent, MyValue, this); #endif } int32 UHistoriaBlogActorComponent::GetNewValue() const { return MyValue; } void UHistoriaBlogActorComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); // PushModel型Replicationが有効ならばそれを使い、無効ならばいつも通りのReplicationを行う #if WITH_PUSH_MODEL FDoRepLifetimeParams params; params.bIsPushBased = true; DOREPLIFETIME_WITH_PARAMS_FAST(UHistoriaBlogActorComponent, MyValue, params); #else DOREPLIFETIME(UHistoriaBlogActorComponent, MyValue); #endif } |
通常と異なる部分は、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を構築していきます。 BoxCollisionを2つ用意し、それぞれ「Add(赤)」「Notify(緑)」と名付けます。そして、位置をズラします。分かりやすさ向上のため、線の色・太さを変えたり、HiddenInGameをFalseにすると良いでしょう。
続いて、Add側にTextRenderをぶら下げます。文字が見られればなんでも良いです。適度に移動したり、大きくしたり、Alignmentを変更したりします。
最後に、先ほどC++で定義したActorComponentを追加します。これでComponent構造は準備OKです。
グラフ構造は上記画像のように構築します。 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型を取り止めることができます)。
6. PushModel型を試す
それではPIEで動作を確認してみましょう。
想定通り、Add側に触れたタイミングでDedicatedServerのログ出力では値の更新が確認できるものの、Player1のログ出力ではReplicateを確認できていません。そして、Notify側に触れたタイミングでログ出力にReplicateが確認でき、双方のPlayer画面で値が更新されていることが確認できます。
見事、PushModel型のReplicationが行えました!!変更を逐一チェックしないこともそうですが見ての通り、変更を逐一送信しないこともできます。DedicatedServer側でバッチ処理的に、ある程度データを貯めてからフラグを立てて送信することもできるので、さらなる最適化の選択として候補になるかと思います。
以上で、デモの説明は終了です。
制限
1. Packagingする場合
Packagingする場合は、もうひと手間必要です。Server側のTarget.csに以下の1行を加えてください。これが無ければPushModel型の設定が未完了となり、従来のReplicationが行われてしまいます。
1 |
bWithPushModel = true; |
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生活を!!