참고 링크
Actor
Replicated
- Actor 내에서 Replicate 할 Property에 UPROPERTY() 선언
- UPROPERTY() 내에 'Replicated' Specifier를 입력
#pragma once
#include "DerivedActor.generated.h"
UCLASS()
class ADerivedActor : public AActor
{
GENERATED_BODY()
public:
// Property to replicate
UPROPERTY(Replicated)
uint32 Health;
// Derived Actor constructor
ADerivedActor(const class FPostConstructInitializeProperties & PCIP);
// Override Replicate Properties function
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
- 해당 Actor의 bReplicates 옵션을 활성화
- 생성자나 BP 옵션에서 제어
- GetLifetimeReplicatedProps() 함수를 override하여 DOREPLIFETIME 매크로를 선언
#include "DerivedActor.h"
#include "Net/UnrealNetwork.h"
ADerivedActor::ADerivedActor(const class FPostConstructInitializeProperties & PCIP) : Super(PCIP)
{
bReplicates = true;
}
void ADerivedActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
// Call the Super
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Add properties to replicated for the derived class
DOREPLIFETIME(ADerivedActor, Health);
}
- 위와 같은 방식으로 Object 또한 Replicate를 할 수 있다.
- Network를 거쳐서 참조되는 Object는 반드시 Network 기능이 지원되어야 한다.
- 이를 확인하려면 UObject::IsSupportedForNetworking() 함수를 이용한다.
Network Replicated Reference
- Replicated Actor
- Replicated Component
- Stably-named 하지만 Replicated 하지 않은 Actor나 Component
- Package로부터 Load 된 UObject(Actor, Component도 아닌)
Stably-named Object
- Server와 Client 양쪽에 같은 이름으로 존재하는 Object를 지칭한다.
- Actor가 Gameplay 중 Spawn되지 않고 Package로부터 직접 Load 되면 Stably-named하다.
- Actor Component는 다음 케이스일 경우 Stably-named 하다.
- Pacakge로부터 직접 load 된 경우
- 간단한 생성자 호출로 추가 된 경우
- UActorComponent::SetNetAddressable로 마킹 된 경우
- Component의 이름을 Server와 Client가 모두 명확하게 알고 있는 경우
- AActor C++ 생성자에서 추가되는 Component들이 그 대표적인 예시이다.
ReplicatedUsing
- "OnRep_"으로 시작하는 함수를 지정해 Replicate 될 때마다 특정 행동을 부여할 수 있는 Specifier
#pragma once
#include "DerivedActor.generated.h"
UCLASS()
class ADerivedActor : public AActor
{
GENERATED_BODY()
public:
// Replicated Property using OnRep_Value
UPROPERTY(ReplicatedUsing=OnRep_Value)
int32 HealthValue1;
// Replicated Property using OnRep_ConstRef
UPROPERTY(ReplicatedUsing=OnRep_ConstRef)
int32 HealthValue2;
// Replicated Property using OnRep_NoParam
UPROPERTY(ReplicatedUsing=OnRep_NoParam)
int32 HealthValue3;
// Signature to pass copy of the last value
UFUNCTION()
void OnRep_Value(int32 LastHealthValue);
// Signature to pass const reference
UFUNCTION()
void OnRep_ConstRef(const int32& LastHealthValue);
// Signature to pass no parameter
UFUNCTION()
void OnRep_NoParam();
// Derived Actor constructor
ADerivedActor(const class FPostConstructInitializeProperties & PCIP);
};
- ReplicatedUsing에 연결 될 함수들은 UFUNCTION()으로 선언되어 있어야 한다.
- 또한 경우에 따라서 Parameter를 받을 수도 있다.
- Parameter에 전달되는 값은 Replicated Property가 변경되기 이전의 값이다.
- 변경된 이후의 값은 Property가 직접 들고 있다.
#include "DerivedActor.h"
#include "Net/UnrealNetwork.h"
ADerivedActor::ADerivedActor(const class FPostConstructInitializeProperties & PCIP) : Super(PCIP)
{
bReplicates = true;
}
void ADerivedActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
// Call the Super
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Add properties to replicated for the derived class
DOREPLIFETIME(ADerivedActor, HealthValue1);
DOREPLIFETIME(ADerivedActor, HealthValue2);
DOREPLIFETIME(ADerivedActor, HealthValue3);
}
void ADerivedActor::OnRep_Value(int32 LastHealthValue)
{
UE_LOG(LogTemp, Log, TEXT("OnRep_Value with value. Last value: %d"), LastHealthValue)
// Add custom OnRep logic
}
void ADerivedActor::OnRep_ConstRef(const int32& LastHealthValue)
{
UE_LOG(LogTemp, Log, TEXT("OnRep_ConstRef with const ref. Last value: %d"), *LastHealthValue)
// Add custom OnRep logic
}
void ADerivedActor::OnRep_NoParam()
{
UE_LOG(LogTemp, Log, TEXT("OnRep_NoParam with no parameter."))
// Add custom OnRep logic
}
- RepNotify는 C++과 BP에서 조금 다르게 동작한다.
- BP로 RepNotify가 선언되어 있다면, Replicated Property에 대해 Set 함수가 호출될 때 RepNotify도 호출된다.
- 하지만 기본적으로 Replicated Property의 Reference를 갖는 Function, Macro에서는
값이 변경되더라도 RepNotify가 호출되지 않는다.
NotReplicated
- Replicated 되는 Actor나 Struct 내에서 특정 Property를 Replicate 되지 않도록 한다.
#pragma once
#include "DerivedActor.generated.h"
USTRUCT()
struct FMyStruct
{
GENERATED_BODY()
UPROPERTY()
int32 ReplicatedProperty;
// Not Replicated even though encompassing struct is Replicated
UPROPERTY(NotReplicated)
int32 NotReplicatedProperty;
};
UCLASS()
class ADerivedActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(Replicated)
FMyStruct ReplicatedStruct;
// Derived Actor constructor
ADerivedActor(const class FPostConstructInitializeProperties & PCIP);
};
- 5.4버전 엔진 코드 기준으로는 NotReplicated Property는 GetLifetimeReplicatedProps() 내에서 DISABLE_REPLICATED_PROPERTY 선언을 해주어야 한다.
- 다만 이는 Warning이 발생할 뿐이고 Error가 발생하지는 않는다.
#include "DerivedActor.h"
#include "Net/UnrealNetwork.h"
ADerivedActor::ADerivedActor(const class FPostConstructInitializeProperties & PCIP) : Super(PCIP)
{
bReplicates = true;
}
void ADerivedActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ADerivedActor, ReplicatedStruct);
}
Conditional Replication
- Replicated Property는 한번 등록되면 Lifetime 동안 해제할 수 없다.
- 기본적으로 Replicated Property는 값이 바뀔 때에 Replicate 된다.
- 그 즉슨, 값이 바뀌지 않으면 Replicate 되지 않고 Bandwidth를 점유하지 않는다.
- 때문에 Replicate 조건을 부여하여 불필요한 Replicate를 줄이면 이는 성능 향상으로 이어진다.
Replication Condition
- GetLifetimeReplicatedProps() 함수에서 DOREPLIFETIME() 매크로 대신
DOREPTIME_CONDITION() 매크로를 사용한다.
#define DOREPLIFETIME_CONDITION(c,v,cond) \
{ \
static_assert(cond != COND_NetGroup, "COND_NetGroup cannot be used on replicated properties. Only when registering subobjects"); \
FDoRepLifetimeParams LocalDoRepParams; \
LocalDoRepParams.Condition = cond; \
DOREPLIFETIME_WITH_PARAMS(c,v,LocalDoRepParams); \
}
- 이는 Replicated 뿐 아니라 ReplicatedUsing Specifier도 적용된다.
#include "DerivedActor.h"
#include "Net/UnrealNetwork.h"
void ADerivedActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
// Call the Super
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Add property replication with a condition
DOREPLIFETIME_CONDITION(ADerivedActor, Health, COND_OwnerOnly);
}
RepNotify Condition
- Replication System은 Replicate 뿐 아니라 RepNotify 호출에 대해서도 조건을 부여할 수 있다.
- 이를 위해서는 DOREPLIFETIME_CONDITION_NOTIFY가 필요하다.
/** Allows gamecode to specify RepNotify condition: REPNOTIFY_OnChanged (default) or REPNOTIFY_Always for when repnotify function is called */
#define DOREPLIFETIME_CONDITION_NOTIFY(c,v,cond,rncond) \
{ \
static_assert(cond != COND_NetGroup, "COND_NetGroup cannot be used on replicated properties. Only when registering subobjects"); \
FDoRepLifetimeParams LocalDoRepParams; \
LocalDoRepParams.Condition = cond; \
LocalDoRepParams.RepNotifyCondition = rncond; \
DOREPLIFETIME_WITH_PARAMS(c,v,LocalDoRepParams); \
}
- 코드를 보면 알 수 있듯이,
DOREPLIFETIME_CONDITION_NOTIFY에서는 RepNotify 조건 외에 Replicate 조건도 지정할 수 있다.
#include "DerivedActor.h"
#include "Net/UnrealNetwork.h"
void ADerivedActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
// Call the Super
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Add property replication with a condition
/** Use this to always execute RepNotify
* Associated OnRep called on client every time property replicates
*/
DOREPLIFETIME_CONDITION_NOTIFY(ADerivedActor, Health, COND_OwnerOnly, REPNOTIFY_Always);
/** Use this to only execute RepNotify when property changes
* Associated OnRep called on client only when property changes
*/
DOREPLIFETIME_CONDITION_NOTIFY(ADerivedActor, Health, COND_OwnerOnly, REPNOTIFY_OnChanged);
}
Custom Condition
- 다음 작업을 통해 엔진에서 제공하는 조건 외의 조건으로 Replicate를 조절할 수 있다.
- Replication Condition을 COND_Custom으로 지정
- bool 타입 혹은 이를 반환하는 함수를 PreReplication에 등록
#pragma once
#include "DerivedActor.generated.h"
UCLASS()
class ADerivedActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(Replicated)
int32 Health;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// Derived Actor constructor
ADerivedActor(const class FPostConstructInitializeProperties & PCIP);
virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override;
// Custom Replication Condition override function
bool IsInvincible();
};
- PreReplication에 Custom Condition을 지정할 때에는 DOREPLIFETIME_ACTIVE_OVERRIDE 매크로를 사용한다.
// Built on Compile time and Disable to work with Array
#define DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(c,v,active) \
{ \
static_assert(ValidateReplicatedClassInheritance<c, ThisClass>(), #c "." #v " is not accessible from this class."); \
UE::Net::Private::FNetPropertyConditionManager::SetPropertyActiveOverride(ChangedPropertyTracker, this, (int32)c::ENetFields_Private::v, active); \
}
// Built on Compile time and Enable to Work with Array
#define DOREPLIFETIME_ACTIVE_OVERRIDE_FAST_STATIC_ARRAY(c,v,active) \
{ \
static_assert(ValidateReplicatedClassInheritance<c, ThisClass>(), #c "." #v " is not accessible from this class."); \
for (int32 i = 0; i < (int32)c::EArrayDims_Private::v; ++i) \
{ \
UE::Net::Private::FNetPropertyConditionManager::SetPropertyActiveOverride(ChangedPropertyTracker, this, (int32)c::ENetFields_Private::v##_STATIC_ARRAY + i, active); \
} \
}
// Built on Runtime and Enable to Work with Array
#define DOREPLIFETIME_ACTIVE_OVERRIDE(c,v,active) \
{ \
static_assert(ValidateReplicatedClassInheritance<c, ThisClass>(), #c "." #v " is not accessible from this class."); \
static FProperty* sp##v = GetReplicatedProperty(StaticClass(), c::StaticClass(),GET_MEMBER_NAME_CHECKED(c,v)); \
for (int32 i = 0; i < sp##v->ArrayDim; i++) \
{ \
UE::Net::Private::FNetPropertyConditionManager::SetPropertyActiveOverride(ChangedPropertyTracker, this, sp##v->RepIndex + i, active); \
} \
}
- 이제 Health는 IsInvincible()이 false일 때에만 Replicate된다.
#include "DerivedActor.h"
#include "Net/UnrealNetwork.h"
ADerivedActor::ADerivedActor(const class FPostConstructInitializeProperties & PCIP) : Super(PCIP)
{
bReplicates = true;
}
void ADerivedActor::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
{
// Call the Super
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Add properties to replicated for the derived class
DOREPLIFETIME_CONDITION(ADerivedActor, Health, COND_Custom);
}
/* Function where the Custom condition is registered. */
void ADerivedActor::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker)
{
// Call the Super
Super::PreReplication(ChangedPropertyTracker);
/* Use a custom property replication condition In this case, a function IsInvincible() If the actor is invincible, don't replicate Health */
DOREPLIFETIME_ACTIVE_OVERRIDE(ADerivedActor, Health, !IsInvincible());
}
bool IsInvincible()
{
bool bIsInvincible = false;
// Custom logic to determine invincibility...
return bIsInvincible;
}
- Custom Condition은 매우 편리해 보이지만 2가지 큰 이유로 자주 사용하지 않는다.
- 첫째, 작업 시간과 소모 리소스가 크다
- 둘째, Connection 기준을 변경할 수 없다.
ELifetimeCondition
- DOREPLIFETIME_CONDITION(_NOTIFY) 매크로의 3번째 parameter로 전달하는 값들.
/** Secondary condition to check before considering the replication of a lifetime property. */
UENUM(BlueprintType)
enum ELifetimeCondition : int
{
COND_None = 0 UMETA(DisplayName = "None"), // This property has no condition, and will send anytime it changes
COND_InitialOnly = 1 UMETA(DisplayName = "Initial Only"), // This property will only attempt to send on the initial bunch
COND_OwnerOnly = 2 UMETA(DisplayName = "Owner Only"), // This property will only send to the actor's owner
COND_SkipOwner = 3 UMETA(DisplayName = "Skip Owner"), // This property send to every connection EXCEPT the owner
COND_SimulatedOnly = 4 UMETA(DisplayName = "Simulated Only"), // This property will only send to simulated actors
COND_AutonomousOnly = 5 UMETA(DisplayName = "Autonomous Only"), // This property will only send to autonomous actors
COND_SimulatedOrPhysics = 6 UMETA(DisplayName = "Simulated Or Physics"), // This property will send to simulated OR bRepPhysics actors
COND_InitialOrOwner = 7 UMETA(DisplayName = "Initial Or Owner"), // This property will send on the initial packet, or to the actors owner
COND_Custom = 8 UMETA(DisplayName = "Custom"), // This property has no particular condition, but wants the ability to toggle on/off via SetCustomIsActiveOverride
COND_ReplayOrOwner = 9 UMETA(DisplayName = "Replay Or Owner"), // This property will only send to the replay connection, or to the actors owner
COND_ReplayOnly = 10 UMETA(DisplayName = "Replay Only"), // This property will only send to the replay connection
COND_SimulatedOnlyNoReplay = 11 UMETA(DisplayName = "Simulated Only No Replay"), // This property will send to actors only, but not to replay connections
COND_SimulatedOrPhysicsNoReplay = 12 UMETA(DisplayName = "Simulated Or Physics No Replay"), // This property will send to simulated Or bRepPhysics actors, but not to replay connections
COND_SkipReplay = 13 UMETA(DisplayName = "Skip Replay"), // This property will not send to the replay connection
COND_Dynamic = 14 UMETA(Hidden), // This property wants to override the condition at runtime. Defaults to always replicate until you override it to a new condition.
COND_Never = 15 UMETA(Hidden), // This property will never be replicated
COND_NetGroup = 16 UMETA(Hidden), // This subobject will replicate to connections that are part of the same group the subobject is registered to. Not usable on properties.
COND_Max = 17 UMETA(Hidden)
};
ELifetimeRepnotifyCondition
- DOREPLIFETIME_CONDITION_NOTIFY 매크로의 4번째 parameter로 전달되는 값들
enum ELifetimeRepNotifyCondition
{
REPNOTIFY_OnChanged = 0, // Only call the property's RepNotify function if it changes from the local value
REPNOTIFY_Always = 1, // Always Call the property's RepNotify function when it is received from the server
};
SubObject
ReplicateSubobjects
class AMyActor : public AActor
{
UPROPERTY(Replicated)
UMySubObjectClass* MySubObject;
}
class UMySubObjectClass : public UObject
{
UPROPERTY(Replicated)
int32 Counter = 0;
}
void AMyActor::CreateMyClass()
{
MySubObject = NewObject<UMySubObjectClass>();
MySubObject->Counter = 10;
}
void AMyActor::ReplicateSubobjects(...)
{
Super::ReplicateSubobjects(...);
Channel->ReplicateSubobject(MySubObject); // Becomes a subobject here
}
- Channel에 Subobject를 등록하지 않는다면, Client에서 해당 Subobject는 항상 null일 것이다.
- Subobject를 Replicate 하더라도 그 안의 Property를 Replicate하려면 Specifier 선언을 따로 해줘야 한다.
Registered Subobject
- Subobject를 Owning Actor/ActorComponent List에 등록
- List에 등록 된 Object들은 Actor Channel에 의해 자동으로 replicate 된다.
- 등록하 때 ELifetimeCondition을 통해 Replicate condition을 부여할 수 있다.
- 이 방식은 ReplicateSubObjects 함수 사용에 대한 업무 부담을 줄여줍니다.
AMyActor::AMyActor()
{
bReplicateUsingRegisteredSubObjectList = true;
}
void AMyActor::CleanupSubobject()
{
if (MySubobject)
{
RemoveReplicatedSubobject(MySubObject);
}
}
void AMyActor::CreateMyClass()
{
CleanupSubobject();
MySubObject= NewObject<UMySubObjectClass>();
MySubObject->Counter = 10;
AddReplicatedSubObject(MySubObject);
}
void AMyActor::CreateMyDerivedClass()
{
CleanupSubobject();
MySubObject = NewObject<UMyDerivedSubObjectClass>();
AddReplicatedSubObject(MySubObject);
}
void AMyActor::ReplicateSubobjects(...)
{
//deprecated and not called anymore
}
- AddReplicatedSubObject 함수는 ReadyForReplication이나 BeginPlay, 또는 Subobject를 생성할 때 호출한다.
- 이 중 ReadyForReplication에서 함수 호출 시 주의할 점이 있따.
- ActorComponent에서 ReadyForReplication 함수는 InitComponent와 BeginPlay 사이에 호출된다.
- 즉슨, Component를 이 함수 안에서 등록하게 되면 BeginPlay에서는 RPC를 수행할 수 있다는 의미이다.
- Subobject를 수정하거나 삭제 할 때에는 반드시 RemoveReplicateSubObject 함수를 호출해야 한다.
- 해당 함수를 호출하지 않고 수정/삭제 시, Object의 Destruction 과정에서 한번 더 Destroy가 Mark된다.
- 이는 GC가 동작할 때 Crash를 유발할 가능성이 있다.
- 이 부분의 코드를 수정 할 때, net.SubObjects.CompareWithLegacy를 Console Command로 설정하면
Runtime에서 Registered SubObjectList와 이전 함수를 비교할 수 있다.- 차이점이 감지되면 ensure가 발생한다.
Components
- Replicated Component도 기본적으로 Subobject와 동일하다.
- Component의 경우에는 AllowActorComponentToReplicate 함수를 override 한다.
- 이 때 각 Component의 Replicate Condition은 내부 조건문에 맞춰 판정, 반환해야 한다.
- 만약 BeginPlay가 호출 된 이후에 Component의 ReplicateCondition을 바꾸고 싶다면,
SetReplicatedComponentNetCondition 함수를 이용한다. - Owning Component List는 Condition이 확인되기 전에 각 Connection에 Replicate 되어야 한다.
- 예를 들어 Componenet가 SkipOwner인 경우,
SubObject가 OwnerOnly이더라도 Owner에게 Replicate 되지 않는다.
- 예를 들어 Componenet가 SkipOwner인 경우,
ELifetimeCondition AMyWeaponClass::AllowActorComponentToReplicate(const UActorComponent* ComponentToReplicate) const
{
// Do not replicate some components while the object is on the ground.
if (!bIsInInventory)
{
if (IsA<UDamageComponent>(ComponentToReplicate))
{
return COND_Never;
}
}
Super::AllowActorComponentToReplicate(ComponentToReplicate);
}
void AMyWeaponClass::OnPickup()
{
// Now replicate the component to all
SetReplicatedComponentNetCondition(UDamageComponent, COND_None);
bIsInInventory = true;
}
Complex Replication Condition
- NetConditionGroupManager와 COND_NetGroup을 이용해 Replicate Condition을 새로 만들 수 있다.
- 이는 Subobject와 PlayerController가 여러 Group에 동시에 속해 있을 때,
이 중 하나의 Group에만 속해 있어도 Replicate된다.
FName NetGroupAlpha(TEXT("NetGroup_Alpha"))
- 원하는 Subobject를 위에서 추가한 NetGroupAlpha Group에 추가한다.
FNetConditionGroupManager::RegisterSubObjectInGroup(MyAlphaSubObject, NetGroupAlpha)
- Subobject를 Replicate 할 Client의 PlayerController를 이용해 같은 Group에 추가한다.
PlayerControllerAlphaOwner->IncludeInNetConditionGroup(NetGroupAlpha)
- 이제 PlayerControllerAlphaOwner의 Client는 Owner Actor가 해당 Client의 Connection에 Replicate 될 때마다
등록된 MyAlphaSubobject를 Replicate 받는다.
Client Subobject List
- Server가 Replicated Subobject List를 관리하는 것처럼, Client도 자체적으로 Subobject List를 관리해야 한다.
- 이는 특히 Client에서 Replay를 녹화할 때 중요하다.
- 이 경우, Client의 Actor는 Replay에 기록할 때 일시적으로 Local Authority Role로 전환된다.
- 그렇기에 Replay에 기록된 Actor은 Local Role에 상관 없이
Client에서 자체적으로 Subobject List를 유지해야 한다.
- 만약 Subobject가 Replicated Property라면, RepNotify를 사용함으로 더 쉽게 관리할 수 있따.
- Client는 RepNotify를 통해 SubObject가 변경되었음을 확인하여,
이전 포인터를 제거하고 새로운 것을 추가할 수 있다.
- Client는 RepNotify를 통해 SubObject가 변경되었음을 확인하여,
- Server의 Subobject List에서 Replicated Subobject를 제거하면
해당 Object의 Replicated Property가 Client에 Replicate 되지는 않는다.- 하지만 SubObject의 포인터는 UObject가 스스로를 Grabage라고 마크 하기 전까지 Net-Referencable하다.
- Server가 UObject가 Invalid하다 탐지하게 되면,
다음 Reflection 업데이트에서 Client로 하여금 자체적으로 해당 Subobject를 지우도록 알린다.
'UE5 > Network' 카테고리의 다른 글
[Network] Replication Execution Order (0) | 2024.06.28 |
---|---|
[Network] Remote Procedure Calls(RPCs) (0) | 2024.06.28 |
[Network] Network Property (0) | 2024.06.25 |
[Network] Network Driver (0) | 2024.06.18 |
[Network] DemoNetDriver 및 Streamer (0) | 2024.06.17 |