아래 글에서 이어지는 내용
https://gbleem.tistory.com/148#4.%20Replication-1-3
Unreal Engine - 데디케이티드 서버 2
1. 로그를 통한 흐름 분석1 - 1. 로그인 흐름 분석GameModeBase와 PlayerController에서 로그를 찍어보면 아래와 같이 정리해볼 수 있다.맨 처음 네모는 서버에서만 생성되는 GameMode 로직이다.아래 네모는 C
gbleem.tistory.com
- Replication
- Frequency
- Relevancy
- NetPriority
- NetDormancy
1. Relevancy (연관성)
1 - 1. 개념
레벨에 있는 모든 액터의 정보를 모든 클라에게 실시간으로 전송하는 것은 부하가 매우 클 것이다.
- 이 부하를 줄이기 위해서 "클라이언트의 커넥션" 에 "소유된 액터" 인지를 체크하는 방식을 사용한다.
- 클라이언트 커넥션에 소유되었다면, 직접적으로 관련된 액터이므로 리플리케이션 해주는 방식
연관성을 판단하는 기준
- Pawn
- Viewer: 클라이언트 커넥션이 소유하고 있는 플레이어 컨트롤러
- ViewTarget: 플레이어 컨트롤러가 빙의한 폰
- 어떤 액터가 Viewer, ViewTarget과 연관성이 있는지를 체크해본다.
- Actor
- Owner
- 해당 액터를 소유하고 있는 액터. (Relevancy 있음)
- 무기를 소유하는 캐릭터
- Instigator
- 해당 액터에게 영향을 끼친 폰. (Relevancy 있음)
- 데미지를 가한 폰
- AlwaysRelevant
- 해당 액터가 모든 클라이언트에게 항상 관련이 있게끔 설정
- 보스몬스터 액터를 레벨 내의 모든 플레이어에게 리플리케이션 할 때
- NetUseOwnerRelevancy
- 해당 액터의 연관성을 오너 액터의 연관성으로 대신할 때
- 무기 액터의 경우, 캐릭터의 오너로 대체 가능하다 (캐릭터가 안보이는데 무기만 보일수는 없음)
- OnlyRelevantToOwner
- 오너 액터에게만 연관성을 가진다. 다른 액터와는 연관성 없음
- 길라잡이 액터, 내가 갈 길을 알려주는 액터는 나에게만 보이면 된다.
- NetCullDistance
- 뷰어와의 거리에 따라 연관성 여부를 체크
- 일정 거리 안으로 들어오면 리플리케이션해주고, 멀면 하지않음
- Owner
1 - 2. 실습
코드
cube.h
virtual bool IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const override;
float NetCullDistance;
cube.cpp
ADXCube::ADXCube()
:ServerRotationYaw(0.f)
,RotationSpeed(30.f)
,AccDeltaSecondSinceReplicated(0.f)
,NetCullDistance(1000.f)
{
...
//net cull distance
SetNetCullDistanceSquared(NetCullDistance * NetCullDistance);
}
void ADXCube::Tick(float Deltaseconds)
{
Super::Tick(Deltaseconds);
...
DrawDebugSphere(GetWorld(), GetActorLocation(), NetCullDistance / 2.f, 16, FColor::Green, false, -1.f);
}
bool ADXCube::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
bool bIsNetRelevant = Super::IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
if (!bIsNetRelevant)
{
DX_LOG_NET(LogDXNet, Log, TEXT("%s is not relevant for(%s %s)"), *GetName(), *RealViewer->GetName(), *ViewTarget->GetName());
}
return bIsNetRelevant;
}
결과
- 캐릭터 1이 포함되지 않은 경우 아래와 같은 모습
- 두 캐릭터 모두 포함되지 않은 경우
2. NetPriority
클라이언트에서 보낼 수 있는 대역폭은 한정되어있다.
그렇기 때문에, NetPriority(우선순위)에 따라 "우선순위가 높은 액터의 데이터를 우선 전달" 하도록 설계되어 있다.
- PlayerController : 3
- Pawn : 2
- Actor : 1
NetPriority는 AActor::GetNetPriority() 함수를 통해서 계산된다.
- NetCullDistance와 같은 연관성 관련 속성을 반영하여 계산
- "포화상태가 아니라면" NetPriority 값을 기준으로 정렬된 순서대로 리플리케이션 한다.
- 만약 "포화상태가 된 경우" NetPriority가 가장 낮은 액터는 다음 서버틱에서 리플리케이션된다.
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;
}
3. NetDormancy
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/actor-network-dormancy-in-unreal-engine
3 - 1. 개념
NetDormancy(휴면) 상태는 프로퍼티 리플리케이션이나 RPC가 동작하지 않는 상태이다.
서버의 최적화를 위해 사용한다.
특히, 액터가 리플리케이션 되었지만 자주 수정되지는 않을 때 활용할 수 있다.
단, 캐릭터처럼 자주 수정되는 액터를 NetDormancy로 두면 오히려 오버헤드가 발생할 수 있다.
NetDormancy 상태
- DORM_Never : 절대 휴면하지 않음
- DORM_Awake : 휴면상태가 아니지만, 휴면상태가 될 수는 있는 상태
- DORM_DormantPartial : 일부 커넥션에 대해서만 휴면 상태
- DORM_Initial : 휴면 상태로 시작하고, 필요할 때 깨울 수 있는 상태
- DORM_DormantAll : 모든 커넥션에게 휴면 상태
3 - 2. 실습
아래 코드를 적용하면 point light가 cube에 생기기는 하지만, 색이 변하는 것을 볼 수가 없다.
그 이유는 cube 액터를 DORM_Init 상태로 두었기 때문이다.
cube.h
public:
virtual void BeginPlay() override;
UFUNCTION()
void OnRep_ServerLightColor();
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UPointLightComponent> PointLight;
UPROPERTY(ReplicatedUsing = OnRep_ServerLightColor)
FLinearColor ServerLightColor;
cube.cpp
ADXCube::ADXCube()
:ServerRotationYaw(0.f)
,RotationSpeed(30.f)
,AccDeltaSecondSinceReplicated(0.f)
,NetCullDistance(1000.f)
{
...
//net dormancy
PointLight = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLight"));
PointLight->SetupAttachment(SceneRoot);
SetNetDormancy(DORM_Initial);
}
void ADXCube::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
...
DOREPLIFETIME(ThisClass, ServerLightColor);
}
void ADXCube::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
{
FTimerHandle TimerHandle01;
GetWorld()->GetTimerManager().SetTimer(TimerHandle01, FTimerDelegate::CreateLambda
([&]() -> void
{
float RandomR = FMath::RandRange(0.f, 1.f);
float RandomG = FMath::RandRange(0.f, 1.f);
float RandomB = FMath::RandRange(0.f, 1.f);
ServerLightColor = FLinearColor(RandomR, RandomG, RandomB, 1.f);
OnRep_ServerLightColor();
}
), 1.f, true);
}
}
void ADXCube::OnRep_ServerLightColor()
{
if (HasAuthority())
{
DX_LOG_NET(LogDXNet, Log, TEXT("OnRep_ServerLightColor(): %s"), *ServerLightColor.ToString());
}
PointLight->SetLightColor(ServerLightColor);
}
위의 코드 작성 후, point light의 색을 변하게 하려면 아래와 같이 깨우는 설정이 필요하다.
- 휴면 상태를 깨우는 방법으로는 NetDormancy 프로퍼티를 설정하거나 FlushNetDormancy() 함수를 사용하는 방식이 존재한다.
- 이번 실습에서는 FlushNetDormancy() 함수를 통해 깨우도록 코드를 작성하였다.
- 주의점)
- NetDormancy가 DORM_Initial인 상태에서
- FlushNetDormancy() 함수를 호출하면,
- 해당 액터의 NetDormancy가 DORM_DormantAll 로 변경된다.
void ADXCube::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
{
...
FTimerHandle TimerHandle02;
GetWorld()->GetTimerManager().SetTimer(TimerHandle02, FTimerDelegate::CreateLambda
([&]() ->void
{
FlushNetDormancy();
}
), 5.f, false);
}
}
추가) DOREPLIFETIME_CONDITION()
- DOREPLIFETIME 으로 등록하면, 다시 해제가 불가능하다.
- 그렇기 때문에 추가적인 조건을 더하여, 세밀하게 조정하기 위해서는 DOREPLIFETIME_CONDITION()을 사용해야 한다.
- 그러나 이 방식의 경우, 조건식이 너무 자주 바뀌면 오버헤드가 발생할 수 있다.
- 예시
DOREPLIFETIME_CONDITION(ThisClass, ServerLightColor, COND_InitialOnly);
4. Actor Replication Flow
'Unreal Engine' 카테고리의 다른 글
Unreal Engine - 데디케이티드 서버 5 (RPC, Replication) (0) | 2025.04.10 |
---|---|
Unreal Engine - 데디케이티드 서버 4 (RPC) (0) | 2025.04.07 |
Unreal Engine - 데디케이티드 서버 2 (1) | 2025.04.03 |
Unreal Engine - Save Game (0) | 2025.04.02 |
Unreal Engine - 야구게임(데디케이티드 서버) (0) | 2025.03.29 |