언리얼 엔진 의 리플리케이트된 서브오브젝트(Replicated Subobjects) 는 UObject 파생 클래스 및 여기에 포함된 리플리케이트된 프로퍼티를 리플리케이트할 수 있는 방법을 제공합니다. 컴포넌트 및 서브오브젝트 리플리케이션에 사용되는 이전 시스템은 가상 함수 AActor::ReplicateSubobjects 를 사용합니다. 이 새 시스템을 사용하면 이제 액터가 서브오브젝트를 소유 액터 또는 액터 컴포넌트 의 목록에 등록할 수 있게 되며, 이렇게 등록된 서브오브젝트의 리플리케이션은 액터 채널에 의해 자동으로 처리됩니다.
아래에서는 두 시스템을 모두 살펴봅니다. 먼저, ReplicateSubobjects 함수를 사용하는 두 예시를 다룹니다. 그런 다음 새로운 등록된 서브오브젝트 목록(Registered Subobjects List) 을 소개하고 사용 방법을 보여주는 코드 샘플을 설명합니다. 마지막으로 복합 리플리케이션 조건 및 클라이언트에 등록된 서브오브젝트 목록 관리 등 추가 주제에 대해 알아봅니다.
서브오브젝트 리플리케이트 개요
컴포넌트 및 서브오브젝트 리플리케이션에 사용되는 이전 시스템은 가상 함수 AActor::ReplicateSubobjects 에 의존했습니다. 리플리케이트된 서브오브젝트가 있는 액터의 경우, 액터에서 각 리플리케이트된 컴포넌트 및 서브오브젝트에 대해 ReplicateSubobject 및 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); // 여기에서 서브오브젝트가 됨
}
설명
위 코드 샘플에서 액터는 MySubObject 콘텐츠를 ReplicateSubobjects 함수의 서브오브젝트로 만듭니다. 이 단계에서 포인터는 넷 레퍼런스(net-reference)가 가능합니다. 그런 다음 액터가 리플리케이트될 때마다 Counter 변수가 클라이언트로 리플리케이트됩니다. Channel->ReplicateSubobject(MySubObject) 를 통해 MySubObject 를 서브오브젝트로 만들지 않았다면 MySubObject 변수는 클라이언트에서 항상 null 입니다.
코드 샘플
class UMyDerivedSubObjectClass : public UMySubObjectClass
{
UProperty(Replicated)
float Timer;
}
void AMyActor::CreateMyDerivedClass()
{
MySubObject = NewObject<UMyDerivedSubObjectClass>();
MySubObject->Counter = 100;
Cast<UMyDerivedSubObjectClass>(MySubObject)->Timer = 60;
}
설명
CreateMyDerivedClass 가 CreateMyClass 뒤에 호출된 경우, 새 포인터는 다음 ReplicateSubObjects 호출 시 리플리케이트된 서브오브젝트가 됩니다. 클라이언트 측에서는 MySubObject 변수가 변경되어 UMyDerivedSubObjectClass 타입이 되며 Timer 및 Counter 변수가 클라이언트로 리플리케이트됩니다.
등록된 서브오브젝트 목록 개요
액터는 이제 서브오브젝트를 소유 액터 및 액터 컴포넌트에서 목록에 등록하는 새로운 메서드를 갖추고 있습니다. 이러한 등록된 서브오브젝트의 리플리케이션은 액터 채널에 의해 자동으로 처리됩니다. 등록된 서브오브젝트 목록은 등록 시 서브오브젝트에 대해 ELifetimeCondition 지정을 허용합니다. 이 프로세스를 통해 사용자는 ReplicateSubobjects 에서 이 로직을 구현하지 않고도 서브오브젝트가 언제 어디에서 리플리케이트될지 제어할 수 있습니다. 또한 액터가 가상 함수 AActor::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(...)
{
//지원 중단되고 더 이상 호출되지 않음
}
설명
-
액터 클래스에
bReplicateUsingRegisteredSubObjectList = true프로퍼티를 설정합니다.AMyActor::AMyActor() { bReplicateUsingRegisteredSubObjectList = true; } -
ReadyForReplication,BeginPlay에서 또는 새 서브오브젝트 생성 시AddReplicatedSubObject를 호출합니다. 액터 컴포넌트 클래스에서 리플리케이트된 서브오브젝트를 사용할 때는 유의할 점이 몇 가지 있습니다. 액터 컴포넌트 클래스 내에서ReadyForReplication은InitComponent와BeginPlay사이에 호출됩니다. 여기에 컴포넌트를 등록하면 원격 프로시저 호출(Remote Procedure Calls, RPC)을 컴포넌트의BeginPlay내에서 일찍 호출할 수 있습니다. -
서브오브젝트를 수정 또는 삭제할 때마다
RemoveReplicatedSubObject를 호출합니다.void AMyActor::CleanupSubObject() { if (MySubObject) { RemoveReplicatedSubObject(MySubObject) } }이 마지막 단계는 매우 중요합니다. 레퍼런스가 제거되지 않는 한 목록에는 변경되거나 디스트럭션으로 표시된 서브오브젝트에 대한 원시 포인터가 여전히 남아 있게 됩니다. 따라서 그 결과로 인해 오브젝트가 가비지 콜렉션된 후 충돌을 유발합니다.
기존 코드를 변환할 때 net.SubObjects.CompareWithLegacy 콘솔 변수, 즉 CVar를 설정하여 런타임에서 새 목록을 기존 메서드와 비교하고 차이가 탐지된 경우 ensure 문을 트리거합니다.
리플리케이트된 액터 컴포넌트
이 시스템을 사용하는 리플리케이트된 액터 컴포넌트 는 리플리케이트된 서브오브젝트이기도 하므로 위와 동일한 방식으로 처리됩니다. 액터 컴포넌트에 리플리케이션 컴포넌트를 설정하려면 소유 액터 클래스가 AllowActorComponentToReplicate 를 구현하고 특정 컴포넌트에 필요한 ELifetimeCondition 을 반환해야 합니다. SetReplicatedComponentNetCondition 을 직접 호출하여 BeginPlay 이후 컴포넌트의 조건을 변경할 수 있습니다.
AllowActorComponentToReplicate 가 새 조건을 반환하게 해야 합니다. 그렇지 않으면 UpdateAllReplicatedComponents 가 액터에서 호출되는 경우 조건이 리셋됩니다.
액터 컴포넌트와 함께 리플리케이트된 서브오브젝트 사용
코드 샘플
ELifetimeCondition AMyWeaponClass::AllowActorComponentToReplicate(const UActorComponent* ComponentToReplicate) const
{
// 오브젝트가 지면에 있는 동안에는 일부 컴포넌트를 리플리케이트하지 않음
if (!bIsInInventory)
{
if (IsA<UDamageComponent>(ComponentToReplicate))
{
return COND_Never;
}
}
Super::AllowActorComponentToReplicate(ComponentToReplicate);
}
void AMyWeaponClass::OnPickup()
{
// 이제 컴포넌트를 모두에 리플리케이트함
SetReplicatedComponentNetCondition(UDamageComponent, COND_None);
bIsInInventory = true;
}
설명
위 예시에서 소유 액터 클래스는 AMyWeaponClass 입니다. 현재 무기가 액터의 인벤토리에 있는지 여부를 기반으로 UActorComponent ComponentToReplicate 에 대한 리플리케이션 조건을 설정해야 합니다. 이를 위해 소유 액터 클래스 AMyWeaponClass 가 AllowComponentToReplicate 를 구현합니다.
무기가 바닥에 있으면 액터의 인벤토리에 있지 않은 것입니다. 따라서 대미지를 유발하는 컴포넌트를 리플리케이트하지 않아야 합니다. 이 경우 반환된 ELifetimeCondition 은 이러한 컴포넌트가 절대로 리플리케이트되지 않아야 함을 지정하는 COND_Never 입니다. 무기를 줍는 등 대미지 컴포넌트의 조건을 변경하려는 경우 SetReplicatedComponentCondition 이 직접 호출되어 컴포넌트가 항상 리플리케이트되는 COND_None 으로 리플리케이션 조건을 설정합니다.
ELifetimeCondition 에서 지원되는 조건 목록에 대한 자세한 내용은 조건식 프로퍼티 리플리케이션을 참고하세요.
액터 컴포넌트 서브오브젝트 목록
액터 컴포넌트도 자체적으로 리플리케이트된 서브오브젝트 목록을 가질 수 있습니다. 액터 컴포넌트는 서브오브젝트 등록 및 등록 해제 시 액터와 동일한 API를 사용합니다. 액터 컴포넌트 내 서브오브젝트도 리플리케이션 조건을 가질 수 있습니다.
소유 컴포넌트는 리플리케이트된 서브오브젝트의 조건이 체크되기 전에 연결로 리플리케이트되어야 합니다. 예를 들어 서브오브젝트에 COND_OwnerOnly 조건이 있는 경우 COND_SkipOwner 조건을 사용하는 컴포넌트에 등록되면 절대 리플리케이트되지 않습니다.
복합 리플리케이션 조건
리플리케이트된 서브오브젝트 시스템은 서브오브젝트에 대한 커스텀 리플리케이션 조건 생성을 지원합니다. 이는 NetConditionGroupManager 및 COND_NetGroup 서브오브젝트를 통해 구현되며 서브오브젝트와 플레이어 컨트롤러는 동시에 여러 그룹의 일부가 될 수 있습니다. 이 경우 서브젝트는 클라이언트 그룹 중 최소 하나의 부분인 경우 클라이언트로 리플리케이트됩니다.
리플리케이션 그룹 구현 및 사용
COND_NetGroup조건으로 서브오브젝트를 등록합니다.-
리플리케이션 조건을 나타낼 FName을 생성합니다.
FName NetGroupAlpha(TEXT("NetGroup_Alpha")) -
원하는 서브오브젝트를 리플리케이션 그룹에 추가합니다.
FNetConditionGroupManager::RegisterSubObjectInGroup(MyAlphaSubObject, NetGroupAlpha) -
서브오브젝트를 동일한 그룹에서 리플리케이트할 클라이언트를 추가합니다. 이를 위해 클라이언트의
PlayerController를 사용합니다.PlayerControllerAlphaOwner->IncludeInNetConditionGroup(NetGroupAlpha)
PlayerControllerAlphaOwner 의 클라이언트는 이제 소유 액터가 해당 연결로 리플리케이트될 때마다 이 특수 서브오브젝트를 수신합니다.
클라이언트 서브오브젝트 목록
서버는 리플리케이트된 서브오브젝트 목록을 유지해야 하지만, 클라이언트의 액터 및 컴포넌트도 서브오브젝트 목록을 로컬로 관리해야 합니다. 이는 프로젝트가 클라이언트에서 리플레이를 녹화하는 경우 특히 중요합니다. 이 경우 액터를 리플레이로 녹화할 때 클라이언트의 액터는 일시적으로 로컬 권한 역할로 교체됩니다. 그 결과, 리플레이 녹화된 액터는 서브오브젝트 목록을 로컬 NetRole과 무관하게 클라이언트에서 관리해야 합니다.
해당 서브오브젝트가 리플리케이트된 프로퍼티인 경우 클라이언트에서 서브오브젝트 목록을 관리하는 것은 프로퍼티에 RepNotify 함수를 사용하여 쉽게 처리할 수 있습니다. 클라이언트가 RepNotify를 사용하여 서브오브젝트의 변경 시기를 식별하므로 이전 서브오브젝트 포인터를 제거하고 새 포인터를 추가할 수 있습니다.
서버가 목록에서 서브오브젝트를 제거할 때 해당 오브젝트의 리플리케이트된 프로퍼티가 클라이언트로 전송되지는 않지만, 서브오브젝트의 포인터는 UObject 자체가 가비지로 표시되기 전까지 여전히 넷 레퍼런스 가능합니다. 서버에서 UObject가 유효하지 않음을 탐지하면 클라이언트에게 알려 다음 리플렉션 업데이트 시 서브오브젝트를 로컬로 삭제하게 합니다.
리플리케이트된 서브오브젝트 목록 시스템은 UActorChannel::KeyNeedsToReplicate() 를 지원하지 않습니다. 서브오브젝트의 리플리케이트된 프로퍼티로 푸시 모델 리플리케이션을 사용하는 것을 권장합니다. 푸시 모델 리플리케이션을 새 시스템과 함께 사용하면 최소한 RepKeys를 사용하는 것과 같은 효율성을 얻을 수 있습니다.