Unreal Engine은 Server가 모든 권한을 가지는(server-authoritative) client-server model을 채용하고 있다.
이 model에서 client는 중앙화 된 Server에 접속하게 된다.
Client가 Server에 접속을 하게 되면 그에 대응하는 NetConnection이 생성한다.
이 때 NetConnection은 PlayerController의 Owner가 된다.
Client가 Server에서 Play를 시작하면, Player Controller은 Client가 Control 할 pawn을 possess한다.
이 때 PlayerController는 Pawn의 Owner가 된다.
Actor의 Owning Connection은 Actor를 소유한 PlayerController의 Owning Connection과 연관되어 있다.
Owner와 Owning Connection은 어느 Client에서 Replicate와 RPC를 전달해야하는지 판단한다.
용처
Actor Replication
Actor의 변경사항을 어느 Connection에 Replicate하는지 판단이 필요.
PlayerController와 같이 Actor를 소유한 Connection에서만 Replicate를 받는 경우에는 bOnlyRelevantToOwner 트리거가 True로 설정되어 있다.
PlayerController의 경우에는 이 플래그가 기본적으로 설정되어 있다.
Property Replication
Owner에 따른 Property Replication 조건이 붙은 경우에 사용된다.
RPCs
Multicast RPC가 아니라면 Client RPC가 어느 Client에 전달되어야 하는지 판단이 필요
NetRole이 ROLE_AutonomousProxy인 Actor
해당 Actor의 Owner가 아닌 NetConnection에 대한 Property Replication이 발생하면 Role이 ROLE_SimulatedProxy로 변경된다.
Role
/** The network role of an actor on a local/remote network context */
UENUM(BlueprintType)
enum ENetRole : int
{
/** No role at all. */
ROLE_None UMETA(DisplayName = "None"),
/** Locally simulated proxy of this actor. */
ROLE_SimulatedProxy UMETA(DisplayName = "Simulated Proxy"),
/** Locally autonomous proxy of this actor. */
ROLE_AutonomousProxy UMETA(DisplayName = "Autonomous Proxy"),
/** Authoritative control over the actor. */
ROLE_Authority UMETA(DisplayName = "Authority"),
ROLE_MAX UMETA(Hidden),
};
NetRole
ROLE_SimulatedProxy
수동적인(Approximate) 프록시라는 의미
클라이언트에서 모조(Simulated) 프록시로 동작한다.
기본적인 물리 동작만 Simulation하고 자체적인 판단을 하지 않는다.
ROLE_AutonomousProxy
능동적인(Autonomous) 프록시라는 의미
클라이언트에서 Simulation이 아닌 Prediction 로직을 갖는다.
ROLE_Authority
해당 Connection에서 Actor에 대한 완전하고 절대적인(Authoritative) 제어권을 갖는다는 뜻
Actor Role
Local Role
현재 자신 PC에서의 NetRole을 지칭
Remote Role
자신이 원격으로 연결되어 있는 PC에서의 NetRole을 지칭
Server/Client에서의 Role 값
Server
Unreal Engine은 Replicate Actor에 대해 모든 Authority를 Server가 갖는다.
때문에 Server의 모든 Actor는 LocalRole이 ROLE_Authority가 된다.
특정 PlayerController가 Actor에 대한 Ownership을 가진 경우, RemoteRole은 ROLE_AutonomousProxy가 된다.
그 외의 Actor들에 대해서는 ROLE_SimulatedProxy가 된다.
Client
해당 Client의 Connection이 Ownership을 갖는 Actor의 경우, Prediction이 가능하기 때문에 LocalRole이 ROLE_AutonomousProxy가 된다.
반대로 자신이 OwnerShipe을 갖지 않는 Actor의 경우, Simulation만이 가능하기 때문에 LocalRole이 ROLE_SimulatedProxy가 된다.
어느 Actor든, Replicate가 되면 Authority는 Server가 가지기 때문에 RemoteRole이 ROLE_Authority가 된다.
단, Client에서만 존재하는 Local Actor의 경우에는 LocalRole이 ROLE_Authority, RemoteRole이 ROLE_None이다.
Dormancy
가장 영향력이 강한 Network Optimize 중 하나
Project에서 자주 Replicate 되지 않는 Actor가 많을수록 효과적이다.
NetDriver는 Connection의 모든 Replicated Actor를 수집하고, 이를 Iterate하여 Replicate 대상을 결정한다.
Dormancy는 특정 Actor를 Dormant 상태로 두어 NetDriver로부터 Actor가 수집되지 않도록 한다.
Actor가 많을수록 Iterate 비용이 부담되기에 Actor 수집을 필터링하는 것은 매우 효과적이다.
사용법
Actor의 Constructor에서 NetDormancy를 초기화
보통은 DORM_DormantAll로 초기화
Actor가 Map에 배치된 경우, DORM_Initial로 초기화
NetDormancy가 DORM_DormantAll/DORM_Initial일 때에는 Dormant 상태가 되어 Replicate가 되지 않는다.
Dormant 상태의 Actor의 값을 변경하더라도 Awake/Flush를 하면 변경사항이 보존되지 않는다.
Replicated Property를 변경하기 전 FlushNetDormancy/ForceNetUpdate/SetNetDormancy 함수를 호출.
일반적으로는 값을 변경 후 함수 호출을 해도 Replicate 될 수 있다.
하지만 정식 스펙 상으로는 올바른 사용법도 아니니 이를 지양해야 한다.
대표적으로 Fast Array의 값을 변경한 후 함수를 호출하면 변경사항이 Replicate되지 않는다.
Replicated Property 값을 변경
다시 한번 강조하지만 이는 값이 자주 변경되지 않는 Replicated Actor에 적합하다.
너무 자주 값이 바뀌면 Dormant<->Awake/Flush 오버헤드가 발생.
Dormancy는 Relavancy Handling과 달리 Dormant 상태가 되더라도 Server/Client 양쪽에서 Actor가 존재한다.
Dormant Actor은 Relavancy 검증에서 제외된다.
ENetDormancy
/** Describes if an actor can enter a low network bandwidth dormant mode */
UENUM(BlueprintType)
enum ENetDormancy : int
{
/** This actor can never go network dormant. */
DORM_Never UMETA(DisplayName = "Never"),
/** This actor can go dormant, but is not currently dormant. Game code will tell it when it go dormant. */
DORM_Awake UMETA(DisplayName = "Awake"),
/** This actor wants to go fully dormant for all connections. */
DORM_DormantAll UMETA(DisplayName = "Dormant All"),
/** This actor may want to go dormant for some connections, GetNetDormancy() will be called to find out which. */
DORM_DormantPartial UMETA(DisplayName = "Dormant Partial"),
/** This actor is initially dormant for all connection if it was placed in map. */
DORM_Initial UMETA(DisplayName = "Initial"),
DORM_MAX UMETA(Hidden),
};
Awake Method
NetDormancy 변수는 Public 접근자로 선언되어 있지만, 가급적 Awake Method 사용을 권장한다.
변경사항을 NetDriver에 알리는 기능이 Awake Methond에 내장되어 있다.
SetNetDormancy
/** Puts actor in dormant networking state */
UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Networking")
ENGINE_API void SetNetDormancy(ENetDormancy NewDormancy);
void AActor::SetNetDormancy(ENetDormancy NewDormancy)
{
if (IsNetMode(NM_Client))
{
return;
}
if (IsPendingKillPending())
{
return;
}
ENetDormancy OldDormancy = NetDormancy;
NetDormancy = NewDormancy;
const bool bDormancyChanged = (OldDormancy != NewDormancy);
if (UWorld* MyWorld = GetWorld())
{
if (FWorldContext* const Context = GEngine->GetWorldContextFromWorld(MyWorld))
{
// Tell driver about change
if (bDormancyChanged)
{
for (FNamedNetDriver& Driver : Context->ActiveNetDrivers)
{
if (Driver.NetDriver != nullptr && Driver.NetDriver->ShouldReplicateActor(this))
{
Driver.NetDriver->NotifyActorDormancyChange(this, OldDormancy);
}
}
}
// If not dormant, flush actor from NetDriver's dormant list
if (NewDormancy <= DORM_Awake)
{
// Since we are coming out of dormancy, make sure we are on the network actor list
MyWorld->AddNetworkActor(this);
for (FNamedNetDriver& Driver : Context->ActiveNetDrivers)
{
if (Driver.NetDriver != nullptr && Driver.NetDriver->ShouldReplicateActor(this))
{
Driver.NetDriver->FlushActorDormancy(this);
}
}
}
}
}
}
Dormant Actor의 NetDormancy 값을 수정해 Awake/Dormant 상태를 변경할 수 있다.
Map에 배치된 Dormant Actor의 경우, Awake 이후 DORM_Initial 대신 DORM_DormantAll로 설정되어야 한다.
Dormant Actor가 매 Frame마다 움직일 때 사용하면 적합하다.
FlushNetDormancy
/** Forces dormant actor to replicate but doesn't change NetDormancy state (i.e., they will go dormant again if left dormant) */
UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category="Networking")
ENGINE_API void FlushNetDormancy();
/** Removes the actor from the NetDriver's dormancy list: forcing at least one more update. */
void AActor::FlushNetDormancy()
{
if (IsNetMode(NM_Client) || NetDormancy <= DORM_Awake || IsPendingKillPending())
{
return;
}
QUICK_SCOPE_CYCLE_COUNTER(NET_AActor_FlushNetDormancy);
bool bWasDormInitial = false;
if (NetDormancy == DORM_Initial)
{
// No longer initially dormant
NetDormancy = DORM_DormantAll;
bWasDormInitial = true;
}
// Don't proceed with network operations if not actually set to replicate
if (!bReplicates)
{
return;
}
if (UWorld* const MyWorld = GetWorld())
{
// Add to network actors list if needed
MyWorld->AddNetworkActor(this);
if (FWorldContext* const Context = GEngine->GetWorldContextFromWorld(MyWorld))
{
for (FNamedNetDriver& Driver : Context->ActiveNetDrivers)
{
if (Driver.NetDriver != nullptr && Driver.NetDriver->ShouldReplicateActor(this))
{
Driver.NetDriver->FlushActorDormancy(this, bWasDormInitial);
}
}
}
}
}
Dormant Actor의 변경사항을 Replicate
Actor의 NetDormancy를 변경하지 않고도 연결된 Updates를 강제로 Replicate한다.
단, NetDormancy가 DORM_Initial인 Actor는 NetDormancy가 DORM_DormantAll로 변경된다.
BP에서는 Dormant Actor의 Replicated Property를 수정하면 자동으로 FlushNetDormancy가 호출된다.
5.4 버전 기준, ActorComponent 한정으로 Replicated Property를 수정해도 FlushNetDormancy가 호출되지 않는다.
ForceNetUpdate
/** Force actor to be updated to clients/demo net drivers */
UFUNCTION( BlueprintCallable, Category="Networking")
ENGINE_API virtual void ForceNetUpdate();
void AActor::ForceNetUpdate()
{
UNetDriver* NetDriver = GetNetDriver();
if (GetLocalRole() == ROLE_Authority)
{
// ForceNetUpdate on the game net driver only if we are the authority...
if (NetDriver && NetDriver->GetNetMode() < ENetMode::NM_Client) // ... and not a client
{
NetDriver->ForceNetUpdate(this);
if (NetDormancy > DORM_Awake)
{
FlushNetDormancy();
}
}
}
// Even if not authority, other drivers (like the demo net driver) may need to ForceNetUpdate
if (UWorld* MyWorld = GetWorld())
{
if (FWorldContext* const Context = GEngine->GetWorldContextFromWorld(MyWorld))
{
for (FNamedNetDriver& Driver : Context->ActiveNetDrivers)
{
if (Driver.NetDriver != nullptr && Driver.NetDriver != NetDriver && Driver.NetDriver->ShouldReplicateActor(this))
{
Driver.NetDriver->ForceNetUpdate(this);
}
}
}
}
}
FlushNetDormancy가 호출되고, 다음 NetUpdate에서 해당 Actor가 Replication 대상으로 고려된다.
Actor가 단일 Frame에서 간헐적으로 1회성 Update를 발생하는 경우에 유용
Actor가 Flush/Awake 된 후에 다시 Dormant 설정을 하더라도 즉시 Dormant 상태가 되지 않는다.
때문에 여러 Update를 전송할 수 있다.
해당 Actor와 SubObject에서 Replicate 되어야 할 Updates가 없을 때까지 Replicate 한다.
또한 DormancyHysteresis가 활성화 된 경우에도 Dormant 상태가 즉각 적용되지 않는다.
bool UActorChannel::ReadyForDormancy(bool suppressLogs)
{
// We need to keep replicating the Actor and its subobjects until none of them have
// changes, and would otherwise go Dormant normally.
if (!bIsInDormancyHysteresis)
{
for (auto MapIt = ReplicationMap.CreateIterator(); MapIt; ++MapIt)
{
if (!MapIt.Value()->ReadyForDormancy(suppressLogs))
{
return false;
}
}
}
if (DormancyHysteresis > 0 && Connection && Connection->Driver)
{
bIsInDormancyHysteresis = true;
const double TimePassed = Connection->Driver->GetElapsedTime() - LastUpdateTime;
if (TimePassed < DormancyHysteresis)
{
return false;
}
}
return true;
}
bool FObjectReplicator::ReadyForDormancy(bool bSuppressLogs)
{
if (GetObject() == nullptr)
{
UE_LOG(LogRep, Verbose, TEXT("ReadyForDormancy: Object == nullptr"));
return true; // Technically, we don't want to hold up dormancy, but the owner needs to clean us up, so we warn
}
// Can't go dormant until last update produced no new property updates
if (!bLastUpdateEmpty)
{
if (!bSuppressLogs)
{
UE_LOG(LogRepTraffic, Verbose, TEXT(" [%d] Not ready for dormancy. bLastUpdateEmpty = false"), OwningChannel->ChIndex);
}
return false;
}
if (FSendingRepState* SendingRepState = RepState.IsValid() ? RepState->GetSendingRepState() : nullptr)
{
if (SendingRepState->HistoryStart != SendingRepState->HistoryEnd)
{
// We have change lists that haven't been acked
return false;
}
if (SendingRepState->NumNaks > 0)
{
return false;
}
if (!SendingRepState->bOpenAckedCalled)
{
return false;
}
if (SendingRepState->PreOpenAckHistory.Num() > 0)
{
return false;
}
// Can't go dormant if there are unAckd property updates
for (FPropertyRetirement& Retirement : SendingRepState->Retirement)
{
if (Retirement.Next != nullptr)
{
if (!bSuppressLogs)
{
UE_LOG(LogRepTraffic, Verbose, TEXT(" [%d] OutAckPacketId: %d First: %d Last: %d "), OwningChannel->ChIndex, OwningChannel->Connection->OutAckPacketId, Retirement.OutPacketIdRange.First, Retirement.OutPacketIdRange.Last);
}
return false;
}
}
}
return true;
}
Awake Method를 사용해야 하는 경우
Actor가 Awake 되면 Replicated Property의 값을 Shadow State를 Reinitialize한다.
Shadow State는 변경된 Property와 Replicated Property를 비교하는데 사용한다.
때문에 Domant Actor의 Replicated Property 값을 변경 하더라도 Awake 과정에서 변경사항이 탐지되지 않는다.
Dormancy with Replication Graph
ReplicationGraph를 사용하더라도 Dormancy는 Default NetDriver와 동일하게 동작해야 하기 때문에, Project에서는 Actor의 Dormant/Awake 세팅과 FlushNetDormancy 호출이 동일하게 이루어진다.
ReplicationGraphNode에서 Actor List를 수집할 때 Dormant Actor가 반환되더라도 아래 함수에서 건너뛴다.
void UReplicationGraph::ReplicateActorListsForConnections_Default(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewerArray& Viewers)
{
//-------Skip-------//
// Skip if dormant on this connection. We want this to always be the first/quickest check.
if (ConnectionData.bDormantOnConnection)
{
DO_REPGRAPH_DETAILS(PrioritizedReplicationList.GetNextSkippedDebugDetails(Actor)->bWasDormant = true);
if (bDoCulledOnConnectionCount)
{
DormancyClassAccumulator.Increment(Actor->GetClass());
}
continue;
}
//-------Skip-------//
}
ReplicationGraphNode는 Dormant Actor에 대한 특별한 Handling이 포함될 수 있다.
이를 통해 Node의 Dormant Actor 처리 시간, 메모리 뿐 아니라 Actor List의 크기도 줄일 수 있다.
예를 들어 ReplicationGraphNode_GridSpatialization2D의 경우, Dormant Actor은 Static으로 취급하고 Awake Actor은 Dynamic으로 취급하는 Handling이 포함되어 있다.
이러한 Handling은 보통은 정지 및 Dormant이지만 가끔 Grid를 통과하는 Actor에 유용하다.
Debug
Log
LogNetDormancy 로그 카테고리를 활성화하여 Dormant 정보를 가져올 수 있다.
상세도를 높이면 Actor의 NetDormancy가 Flush될 때처럼 더 자세한 정보가 기록된다.
Console Command
NetPriority
Unreal Engine의 Network Update는 Bandwidth 제한으로 모든 Actor의 Replicate를 보장하지 않는다.
만약 Update 용량이 Bandwidth를 초과하면 자체적인 Load Balancing을 통해 NetPriority를 할당한다.
NetPriority가 높을수록 더 중요한 Actor이므로 더 많은 Bandwidth가 할당된다.
Actor의 NetPriority 구하기
/** Priority for this actor when checking for replication in a low bandwidth or saturated situation, higher priority means it is more likely to replicate */
UPROPERTY(Category=Replication, EditDefaultsOnly, BlueprintReadWrite)
float NetPriority;
Actor의 Replicate 빈도 차이는 NetPriority의 비율과 일치하다.
NetPriority가 3.0인 Actor는 1,0인 Actor보다 3배의 빈도로 Update 된다.
일반적으로 Actor는 1.0, Pawn과 PlayerController는 3.0의 초기값을 가진다.
Current NetPriority 구하기
/**
* Function used to prioritize actors when deciding which to replicate
* @param ViewPos Position of the viewer
* @param ViewDir Vector direction of viewer
* @param Viewer "net object" owned by the client for whom net priority is being determined (typically player controller)
* @param ViewTarget The actor that is currently being viewed/controlled by Viewer, usually a pawn
* @param InChannel Channel on which this actor is being replicated.
* @param Time Time since actor was last replicated
* @param bLowBandwidth True if low bandwidth of viewer
* @return Priority of this actor for replication, higher is more important
*/
ENGINE_API virtual float GetNetPriority(const FVector& ViewPos, const FVector& ViewDir, class AActor* Viewer, AActor* ViewTarget, UActorChannel* InChannel, float Time, bool bLowBandwidth);
/**
* Defines in NetworkDistanceConstants.h
* CLOSEPROXIMITY: 500
* NEARSIGHTTHRESHOLD: 2000
* MEDSIGHTTHREHOLD: 3162
* FARSIGHTTHRESHOLD: 8000
*/
float AActor::GetNetPriority(const FVector& ViewPos, const FVector& ViewDir, AActor* Viewer, AActor* ViewTarget, UActorChannel* InChannel, float Time, bool bLowBandwidth)
{
if (bNetUseOwnerRelevancy && Owner)
{
// If we should use our owner's priority, pass it through
return Owner->GetNetPriority(ViewPos, ViewDir, Viewer, ViewTarget, InChannel, Time, bLowBandwidth);
}
if (ViewTarget && (this == ViewTarget || GetInstigator() == ViewTarget))
{
// If we're the view target or owned by the view target, use a high priority
Time *= 4.f;
}
else if (!IsHidden() && GetRootComponent() != NULL)
{
// If this actor has a location, adjust priority based on location
FVector Dir = GetActorLocation() - ViewPos;
float DistSq = Dir.SizeSquared();
// Adjust priority based on distance and whether actor is in front of viewer
if ((ViewDir | Dir) < 0.f)
{
if (DistSq > NEARSIGHTTHRESHOLDSQUARED)
{
Time *= 0.2f;
}
else if (DistSq > CLOSEPROXIMITYSQUARED)
{
Time *= 0.4f;
}
}
else if ((DistSq < FARSIGHTTHRESHOLDSQUARED) && (FMath::Square(ViewDir | Dir) > 0.5f * DistSq))
{
// Compute the amount of distance along the ViewDir vector. Dir is not normalized
// Increase priority if we're being looked directly at
Time *= 2.f;
}
else if (DistSq > MEDSIGHTTHRESHOLDSQUARED)
{
Time *= 0.4f;
}
}
return NetPriority * Time;
}
Base NetPriority에 Viewer와의 거리, 마지막 Replicate 이후 시간 등을 복합적으로 판단해 결정된다.
만약 NetPriority를 Customize하고 싶다면 이 함수를 Override해야 한다.
단, 이는 매우 높은 숙련도와 이해도를 요구한다.
Reference
NetRelevancy
Level 상의 Actor들 중 Server상에서 시야 안에 들어오는 Actor들이나 Client에 영향을 Actor들만 Replicate하는 방식
Runtime 중에 Spawn/Replicate 되는 Actor들은 Relevant 하지 않으면 Client에서 제거된다.
제거된 Actor의 경우에는 Client에서 더이상 보이지 않는다.
Actor의 현재 Relevancy
/**
* Checks to see if this actor is relevant for a specific network connection
*
* @param RealViewer - is the "controlling net object" associated with the client for which network relevancy is being checked (typically player controller)
* @param ViewTarget - is the Actor being used as the point of view for the RealViewer
* @param SrcLocation - is the viewing location
*
* @return bool - true if this actor is network relevant to the client associated with RealViewer
*/
ENGINE_API virtual bool IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const;
bool AActor::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
if (bAlwaysRelevant || IsOwnedBy(ViewTarget) || IsOwnedBy(RealViewer) || this == ViewTarget || ViewTarget == GetInstigator())
{
return true;
}
else if (bNetUseOwnerRelevancy && Owner)
{
return Owner->IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
}
else if (bOnlyRelevantToOwner)
{
return false;
}
else if (RootComponent && RootComponent->GetAttachParent() && RootComponent->GetAttachParent()->GetOwner() && (Cast<USkeletalMeshComponent>(RootComponent->GetAttachParent()) || (RootComponent->GetAttachParent()->GetOwner() == Owner)))
{
return RootComponent->GetAttachParent()->GetOwner()->IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
}
else if(IsHidden() && (!RootComponent || !RootComponent->IsCollisionEnabled()))
{
return false;
}
if (!RootComponent)
{
UE_LOG(LogNet, Warning, TEXT("Actor %s / %s has no root component in AActor::IsNetRelevantFor. (Make bAlwaysRelevant=true?)"), *GetClass()->GetName(), *GetName() );
return false;
}
return !GetDefault<AGameNetworkManager>()->bUseDistanceBasedRelevancy ||
IsWithinNetRelevancyDistance(SrcLocation);
}
Network Driver는 IsNetRelevantFor를 통해 Actor가 각 Connection과 Relevant한지를 판단한다.
Relevancy를 Customize하고 싶다면 이 함수를 Override해야 한다.
다만 Override 작업은 높은 이해도와 난이도를 요구한다.
참고로 Actor를 상속받은 Class 중 Pawn과 PlayerController는 위 함수를 Override하여 다른 로직으로 동작한다.
Actor Relevant 생성
/** Forces this actor to be net relevant if it is not already by default */
ENGINE_API virtual void ForceNetRelevant();
void AActor::ForceNetRelevant()
{
if ( !NeedsLoadForClient() )
{
UE_LOG(LogSpawn, Warning, TEXT("ForceNetRelevant called for actor that doesn't load on client: %s" ), *GetFullName() );
return;
}
if (RemoteRole == ROLE_None)
{
SetReplicates(true);
bAlwaysRelevant = true;
if (NetUpdateFrequency == 0.f)
{
NetUpdateFrequency = 0.1f;
}
}
ForceNetUpdate();
}
Actor에서 ForceNetRelevant를 호출해 강제로 해당 Actor에 Relevancy를 부여할 수 있다.
Customize Relevancy Settings
Actor를 상속받은 Class는 사진의 옵션 또는 C++에서 Relevancy Setting을 Customize 할 수 있다.
bAlwaysRelevant
모든 Client에서 조건 없이 Replicate 된다.
bNetUseOwnerRelevancy
해당 Actor의 Owner에게 Relevant 할 때에 Replicate 된다.
모든 Client에게 Replicate 되지 않고, Owner와 Relevant한 Client들에게만 Replicate 된다.
bOnlyRelevantToOwner
해당 Actor의 Owner에게 Relevant 할 때에 Owner에게만 Replicate 된다.