概要
UE5マルチプレイ時のプロパティのレプリケーションについてのメモ書きです。
更新履歴
| 日付 | 内容 |
|---|---|
| 2026/02/19 | 初版 |
参考
以下を参考にさせて頂きました、ありがとうございます。
UE4でマルチプレイヤーゲームを作ろう【CEDEC 2019】
UE公式:プロパティのレプリケーション
UE公式:Replicated Subobject
UE公式:RPC について
UE4 MultiPlayer Online Deep Dive: 実践編1
[UE4] マルチプレイでの所有権とRPC
[UE4]ネットワーク対応をしたオブジェクトを作成
UE5 同期処理(レプリケーション)のC++実装
Understanding replication atomicity in Unreal Engine
環境
Windows11
Visual Studio 2022
UnrealEngine 5.6, 5.7
関連ソース
"Engine\Source\Runtime\CoreUObject\Public\UObject\Object.h"
"Engine\Source\Runtime\Engine\Classes\GameFramework\Actor.h"
レプリケーションについて
定義方法
基本的な使い方として、UPROPERTYメタ情報にReplicated を付ける。OnRep関数を使う場合はReplicatedUsing=OnRepFuncName とすると OnRepFuncName()を定義でき、プロパティが変更された場合に呼びだされます。
OnRep関数
レプリケーション対象の変数が変化したときにクライアントに対して実行されます、サーバー側で実行したい場合はサーバー側が明示的にOnRep関数を呼び出す必要があります。
RPC(Remote Procedure Calls)
UFUNCTIONメタ情報に Server Client NetMulticast のいずれかをつける。信頼性ありなしはReliable UnReliable でそれぞれ指定できる
各クラスでの実装方法について
ACharacterクラス(APawnクラス)でレプリケートする
実際のキャラクタークラスに処理を書く場合は以下のような感じになります。レプリケートとサーバーRPCの実行、OnRep関数も使う場合のテストコード。
UCLASS()
class SAMPLE_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
..省略..
public:
// ネットワークレプリケーションに使用されるプロパティを返す
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override;
// レプリケートする値
UPROPERTY(ReplicatedUsing=OnRep_TestValue)
int32 TestValue = 0;
// OnRep関数
UFUNCTION()
void OnRep_TestValue(const int32 _OldValue);
// アクセサ
int32 GetReplicated_TestValue() const { return(TestValue); }
void SetReplicated_TestValue(int32 _InValue);
// サーバーRPC
UFUNCTION(Server, Reliable)
void ServerRPC_TestValue(int32 _InValue);
};
#include "Net/UnrealNetwork.h"
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, TestValue);
}
// OnRep関数
void AMyCharacter::OnRep_TestValue(const int32 _OldValue)
{
}
// Setter
void AMyCharacter::SetReplicated_TestValue(int32 _InValue)
{
// Authorityならそのまま設定、AutonomousProxyの場合はServerRPCを呼ぶ
if( HasAuthority() ){
const auto _Old = TestValue;
TestValue = _InValue;
OnRep_TestValue(_Old);
return;
}
check(GetLocalRole() == ROLE_AutonomousProxy); // SimulatedやNoneの場合サーバRPCは送信不可
ServerRPC_TestValue(_InValue); // ServerRPC
}
// サーバーRPC
void AMyCharacter::ServerRPC_TestValue_Implementation(int32 _InValue)
{
SetReplicated_TestValue(_InValue);
}
APlayerControllerクラスでレプリケートする
サーバー側は全員分のコントローラーを持っていますが、クライアント側は自分のコントローラーしか持っていないため他プレイヤーのレプリケートされた値を取得することができません。
一方、クライアント側のネットワークロールは AutonomousProxy となるため(APawnと一致する)サーバーRPCを実行することはできます。
APlayerStateクラスで レプリケートする
サーバー側もクライアント側も各プレイヤーに対応した APlayerStateクラスを持っているため他プレイヤーのレプリケートされた値を見ることができます。
一方、クライアント側のネットワークロールは SimulatedProxyのため(APawnと一致しない)サーバーRPCを実行することができません。
オーナーを APlayerControllerにすればサーバーRPCが使えるようになりそうですが試していません。
APlayerStateクラスとAPlayerControllerクラスの併用
レプリケートしたい値を APlayerStateに置きつつ、サーバーRPCを APlayerControllerに置くこともできます。以下コード例。
UCLASS()
class SAMPLE00_API AMyPlayerState : public APlayerState
{
GENERATED_BODY()
..省略..
public:
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override;
// レプリケートする値
UPROPERTY(ReplicatedUsing=OnRep_TestValue)
int32 TestValue = 0.0f;
// OnRep関数
UFUNCTION()
void OnRep_TestValue(const int32 _Old);
int32 GetReplicated_TestValue(){ return(TestValue); }
void SetReplicated_TestValue(int32 _TestValue);
};
レプリケートしたい値のセッターにサーバーRPCを入れます。
ただし APlayerState では使えないため APlayerControllerにサーバーRPCを用意しそれを呼び出します。
void AMyPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, TestValue);
}
void AMyPlayerState::OnRep_TestValue(const int32 _Old)
{
UE_LOG(LogTemp, Log, TEXT("OnRep>> %d -> %d"), _Old, TestValue);
}
void AMyPlayerState::SetReplicated_TestValue(int32 _TestValue)
{
// Authorityならそのまま設定、AutonomousProxyの場合はServerRPCを呼ぶ
if( HasAuthority() ){
const auto _Old = TestValue;
TestValue = _TestValue;
OnRep_TestValue(_Old);
return;
}
if (auto _Pawn = GetPawn()) {
if ( auto _PC = Cast<AMyPlayerController>(_Pawn->GetController()) ) {
check(_PC->GetLocalRole() == ROLE_AutonomousProxy);
_PC->ServerRPC_TestValue(_TestValue); // ServerRPC
}
}
}
サーバーRPC用のメソッドを書きます。
UCLASS()
class SAMPLE_API AMyPlayerController : public APlayerController
{
GENERATED_BODY()
..省略..
public:
// サーバーRPC
UFUNCTION(Server, Reliable)
void ServerRPC_TestValue(int32 _InValue);
};
APlayerStateにあるセッターを呼び出します。
#include "Net/UnrealNetwork.h"
// サーバーRPC ##サーバープロセス上でのみ実行される
void AMyPlayerController::ServerRPC_TestValue_Implementation(int32 _TestValue)
{
if( AMyPlayerState* _PS = GetPlayerState<AMyPlayerState>() ){
_PS->SetReplicated_TestValue(_TestValue);
}
}
これで APlayerState にある値をレプリケートもできてクライアント側からの変更もできて他プレイヤーからの参照もできます。
構造体でのレプリケーションについて
構造体で関連した値をまとめてレプリケートすることもできます。
構造体のレプリケート実装例
int型2つを持つ構造体を定義してこれをレプリケートするサンプルコードです。
// 構造体のレプリケーションテスト用
USTRUCT(BlueprintType)
struct FTestStruct
{
GENERATED_BODY()
FTestStruct() {}
// 変数A
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 PropertyA = 0;
// 変数B
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 PropertyB = 0;
// 比較関数
bool operator==(const FTestStruct& _Other) const
{
return((PropertyA == _Other.PropertyA) && (PropertyB == _Other.PropertyB));
}
bool operator!=(const FTestStruct& _Other) const
{
return !(*this == _Other);
}
};
プロパティを持つキャラクタークラスは通常と変わりません。
UCLASS()
class AMyCharacter : ACharacter
{
..省略..
public:
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override;
// レプリケートする構造体
UPROPERTY(ReplicatedUsing = OnRep_TestStruct)
FTestStruct TestStruct;
// OnRep
UFUNCTION()
void OnRep_TestStruct();
};
void AMyCharacterBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, TestStruct);
}
// OnRep
void AMyCharacterBase::OnRep_TestStruct()
{
UE_LOG(LogTemp, Warning, TEXT("A:%d, B:%d"), TestStruct.PropertyA, TestStruct.PropertyB);
}
Componentでのレプリケートについて
コンポーネントでのレプリケートは ACharacter とほとんど変わりませんが、Ownerのアクターがレプリケートされているのが必須で、他には OwnerによってRPCが使えるかどうかが影響します。
ActorComponentを使う
以下はFloatとInt変数をレプリケートするコンポーネントのサンプルコード。
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Net/UnrealNetwork.h"
#include "ReplicatedStateComponent.generated.h"
UCLASS()
class SAMPLE00_API UReplicatedStateComponent : public UActorComponent
{
GENERATED_BODY()
public:
UReplicatedStateComponent();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
// レプリケート対象1
UPROPERTY(ReplicatedUsing=OnRep_ValFloat)
float ValFloat = 0.0f;
// レプリケート対象2
UPROPERTY(Replicated)
int32 ValInt = 0;
// OnRep
UFUNCTION()
void OnRep_ValFloat();
public:
// アクセサ
float GetValFloat() const { return(ValFloat); }
int32 GetValInt() const { return(ValInt); }
// サーバーRPC
UFUNCTION(Server, Reliable)
void ServerRPC_ValFloat(float _InValue);
// サーバーRPC
UFUNCTION(Server, Reliable)
void ServerRPC_ValInt(int32 _InValue);
};
#include "ReplicatedStateComponent.h"
UReplicatedStateComponent::UReplicatedStateComponent()
{
// レプリケーション設定
SetIsReplicatedByDefault(true);
}
void UReplicatedStateComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, ValFloat);
DOREPLIFETIME(ThisClass, ValInt);
}
// OnRep
void UReplicatedStateComponent::OnRep_ValFloat()
{
UE_LOG(LogTemp, Log, TEXT("%s : %f"), *FString(__FUNCTION__), ValFloat);
}
// サーバーRPC
void UReplicatedStateComponent::ServerRPC_ValFloat_Implementation(float _InValue)
{
ValFloat = _InValue;
}
// サーバーRPC
void UReplicatedStateComponent::ServerRPC_ValInt_Implementation(int32 _InValue)
{
ValInt = _InValue;
}
実装側キャラクタークラスにてコンポーネントを作成するだけでOKです。
// ヘッダファイルに以下のプロパティ定義をする
//UPROPERTY()
//UReplicatedStateComponent* StateComponent;
AMyCharacter::AMyCharacter()
{
// レプリケート管理コンポーネントを作成
StateComponent = CreateDefaultSubobject<UReplicatedStateComponent>(TEXT("StateComponent0"));
}
UObjectのレプリケートについて
AddReplicatedSubObject を使う
UE5.1 から追加されたらしい AddReplicatedSubObject を使ってみます。
UObject を継承したレプリケートする変数を持つオブジェクトを定義します。
RPCを使う場合は int32 GetFunctionCallspace() と bool CallRemoteFunction() が必要になります。
以下サンプルコード。
#pragma once
#include "CoreMinimal.h"
#include "Logging/LogMacros.h"
#include "ReplicatedSubObject.generated.h"
UCLASS()
class UMySubObject : public UObject
{
GENERATED_BODY()
public:
virtual bool IsSupportedForNetworking() const override { return true; }
// UObject RPC を Outer Actor にルーティング ##RPCを使用しないなら不要
virtual int32 GetFunctionCallspace(UFunction* Function, FFrame* Stack) override;
// NetDriverのProcessRemoteFunctionを呼び出し ##RPCを使用しないなら不要
virtual bool CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack) override;
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const override;
public:
UPROPERTY(ReplicatedUsing = OnRep_Value)
int32 Value = 0;
void SetRelicated_Value(int32 _InValue);
UFUNCTION()
void OnRep_Value()
{
UE_LOG(LogTemp, Log, TEXT("OnRep_Value: %d"), Value);
}
UFUNCTION(Server, Reliable)
void ServerRPC_Value(int32 _InValue);
};
#include "./ReplicatedSubObject.h"
#include "Net/UnrealNetwork.h"
int32 UMySubObject::GetFunctionCallspace(UFunction* Function, FFrame* Stack)
{
if( AActor* _Owner = GetTypedOuter<AActor>() ){
return _Owner->GetFunctionCallspace(Function, Stack);
}
return(Super::GetFunctionCallspace(Function, Stack));
}
bool UMySubObject::CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack)
{
AActor* _Owner = GetTypedOuter<AActor>();
if (!_Owner) { return(false); }
UNetDriver* _NetDriver = _Owner->GetNetDriver();
if (!_NetDriver) { return(false); }
_NetDriver->ProcessRemoteFunction(_Owner, Function, Parms, OutParms, Stack, this);
return(true);
}
void UMySubObject::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, Value);
}
void UMySubObject::SetRelicated_Value(int32 _InValue)
{
const AActor* _OwnerActor = GetTypedOuter<AActor>();
if (_OwnerActor->HasAuthority()) {
Value = _InValue;
return;
}
check(_OwnerActor->GetLocalRole() == ROLE_AutonomousProxy);
ServerRPC_Value(_InValue);
}
void UMySubObject::ServerRPC_Value_Implementation(int32 _InValue)
{
SetRelicated_Value(_InValue);
}
実装側キャラクタークラスは以下のようになります。
レプリケートサブオブジェクトもAddReplicatedSubObject()だけではなくレプリケートする設定にしないとなりません。
UCLASS()
class SAMPLE00_API AMyCharacter: public ACharacter
{
GENERATED_BODY()
..省略..
public:
// ネットワークレプリケーションに使用されるプロパティを返す
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override;
UPROPERTY(Replicated)
TObjectPtr<UMySubObject> SubObject;
};
AMyCharacter::AMyCharacter()
{
// ReplicatedSubObjectを使う場合に必要な設定
bReplicateUsingRegisteredSubObjectList = true;
}
void AMyCharacter::BeginPlay()
{
// レプリケートサブオブジェクトを生成する
if (HasAuthority()) {
SubObject = NewObject<UMySubObject>(this);
AddReplicatedSubObject(SubObject);
}
}
void AMyCharacter::EndPlay()
{
// レプリケートサブオブジェクトを始末
if (HasAuthority()) {
RemoveReplicatedSubObject(SubObject);
SubObject = nullptr;
}
}
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, SubObject);
}
まとめ
UEのレプリケート処理の実装についてまとめました。Roleをかなり意識しないと意図した動作になりません。