개요
멀티플레이어 게임에서 레벨에 존재하는 수많은 액터들의 정보를 모든 클라이언트에게 실시간으로 100% 동기화하는 것은 서버에 엄청난 부하(Overhead)를 일으킨다. 이를 해결하기 위해 언리얼 엔진은 네트워크 대역폭(Bandwidth)을 효율적으로 관리하는 강력한 최적화 속성들을 제공한다. 오늘은 그중 가장 핵심이 되는 연관성(Relevancy), 우선순위(NetPriority), 그리고 휴면(NetDormancy)에 대해 학습했다.
1. Relevancy (연관성): "나한테 필요한 정보만 줘!"
레벨에 있는 수백 개의 액터 정보를 모든 클라이언트에게 다 보내면 부하가 심하므로, 서버는 각 클라이언트 커넥션과 '연관성'이 있는 액터만 골라서 레플리케이션 해준다.
액터 연관성을 판단하는 주요 기준
- Always Relevant: 거리에 상관없이 레벨 내 모든 플레이어에게 항상 레플리케이션 되도록 설정한다. (예: 거대한 보스 몬스터)
- NetCullDistance: 뷰어(Viewer)와의 거리에 따라 연관성을 결정한다. 가까우면 레플리케이션하고, 멀어지면 네트워크 전송을 끊는다.
- Owner: 해당 액터를 소유한 액터와는 연관성이 있다고 판단한다. (예: 캐릭터가 들고 있는 무기)
- NetUseOwnerRelevancy: 자신의 연관성 기준을 오너(Owner) 액터의 기준에 맞춘다. 캐릭터가 시야에서 안 보이는데 무기만 보일 리 없기 때문이다.
- OnlyRelevantToOwner: 오직 오너(Owner) 액터에게만 연관성을 가진다. 남들은 볼 필요 없는 개인용 길라잡이 액터 등에 쓴다.
구현 예시 (IsNetRelevantFor 커스텀)
특정 조건에서만 액터가 클라이언트에게 보이도록(레플리케이션 되도록) 가상 함수를 직접 오버라이드하여 커스텀하는 방법이다.
// MyCustomActor.h
protected:
virtual bool IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const override;
// MyCustomActor.cpp
bool AMyCustomActor::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
// 예시: 이 액터가 '은신' 상태라면, 오직 같은 팀원(또는 나 자신)에게만 연관성을 부여
if (bIsStealthMode)
{
// 뷰어(RealViewer)가 나와 같은 오너를 가졌는지(같은 팀인지) 판별하는 가상의 로직
if (RealViewer == GetOwner())
{
return true;
}
return false; // 남들에게는 네트워크 데이터를 아예 보내지 않음 (최적화 + 핵 방지)
}
// 은신 상태가 아니라면 엔진의 기본 연관성 검사 로직을 그대로 따름
return Super::IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
}
2. NetPriority (네트워크 우선순위): "대역폭이 꽉 찼어, VIP 먼저 태워!"
클라이언트에 보낼 수 있는 대역폭(NetBandwidth)은 한정되어 있다. 데이터가 너무 커서 포화 상태가 되면, 어떤 액터의 데이터를 먼저 보낼지 순서를 정해야 하는데 이때 사용하는 값이 NetPriority다.
NetPriority의 특징과 고찰
- 기본값: PlayerController는 3.0, Pawn은 2.0, 일반 Actor는 1.0으로 설정되어 있다.
- 순서 결정용: 이 값은 단순히 전송 순서를 정하는 데만 사용된다 (마치 비행기 좌석 등급과 같다).
- 상대적 빈도: NetPriority가 2.0인 액터는 1.0인 액터보다 2배 더 자주 업데이트'될 수도' 있다. 하지만 비행기(대역폭)가 널널하다면 굳이 차별하지 않고 동일한 빈도로 전송된다.
- 가중치 부여 (GetNetPriority): 액터의 현재 우선순위는
AActor::GetNetPriority()함수를 통해 런타임에 동적으로 계산된다. 내가 직접 조종하는 타겟이거나, 플레이어가 직접 바라보는 방향에 있는 액터일 경우 추가 가중치를 받아 우선순위가 크게 뛴다.
구현 예시 (생성자 우선순위 세팅)
생성자에서 해당 액터의 네트워크 우선순위를 직접 조절하여, 대역폭이 부족할 때 전송 순위를 낮추거나 높일 수 있다.
// MyCustomActor.cpp
AMyCustomActor::AMyCustomActor()
{
bReplicates = true;
// 기본 액터의 NetPriority는 1.0f 이다.
// 만약 이 액터가 게임 플레이에 크게 중요하지 않은 배경 기믹 요소라면 우선순위를 낮춘다.
// 반대로 날아오는 로켓 투사체처럼 매우 중요하다면 2.0f 이상으로 높일 수 있다.
NetPriority = 0.5f;
}
3. NetDormancy (휴면 상태): "나 당분간 안 변하니까 데이터 보내지 마!"
액터가 네트워크를 통해 레플리케이션 되긴 해야 하지만, 값이 아주 가끔씩만 변할 때 사용하는 강력한 최적화 기법이다.
왜 휴면(Dormancy)이 필요한가?
값이 자주 변하는 플레이어 캐릭터는 휴면 상태로 두면 오히려 껐다 켰다 하는 오버헤드가 발생한다. 하지만 파괴 가능한 상자나 상호작용 가능한 문처럼 한 번 스폰되면 한참 동안 값이 안 변하는 액터들은, 휴면 상태(NetDormancy)로 만들어 프로퍼티 레플리케이션과 RPC 동작을 완전히 멈춰두면 서버 자원을 엄청나게 절약할 수 있다.
구현 예시 (휴면 및 조건부 레플리케이션)
배경에 놓여있는 상자(Box) 액터가 평소에는 네트워크 자원을 전혀 먹지 않다가, 플레이어에게 피격당했을 때만 깨어나서(Awake) 정보를 갱신하는 실무 최적화 패턴이다.
// DXBox.cpp
ADXBox::ADXBox()
{
bReplicates = true;
// 1. 태어날 때부터 휴면 상태로 진입 (네트워크 비용 거의 0)
NetDormancy = DORM_Initial;
}
// 가상의 피격(데미지 처리) 이벤트 함수
void ADXBox::OnTakeDamage()
{
if (HasAuthority()) // 서버에서 데미지를 처리할 때
{
// 2. 상자가 맞았으니 휴면 상태를 강제로 깨우고(Awake) 다시 레플리케이션을 시작!
FlushNetDormancy();
// 체력 감소 로직...
CurrentHealth -= 10.0f;
}
}
// 프로퍼티 레플리케이션 등록 함수
void ADXBox::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// 3. 조건부 레플리케이션 (Conditional Property Replication)
// 상자의 '불빛 색상(ServerLightColor)' 변수는 중간에 색이 바뀔 일이 없으므로,
// 클라이언트가 처음 이 액터를 로드할 때(COND_InitialOnly) 딱 한 번만 동기화시킨다!
DOREPLIFETIME_CONDITION(ADXBox, ServerLightColor, COND_InitialOnly);
}'프로그래밍 > Unreal Engine 5' 카테고리의 다른 글
| [Unreal Engine 5] 싱글플레이어 vs 멀티플레이어(DedServer) GAS 아키텍처의 결정적 차이 (0) | 2026.03.30 |
|---|---|
| [Unreal Engine 5] 언리얼 엔진의 포인터 안전성 검사: Null 체크 vs IsValid vs C++17 (0) | 2026.03.27 |
| [UnrealEngine 5] 멀티플레이 최적화 : NetUpdateFrequency와 클라이언트 보간 (0) | 2026.03.24 |
| [Unreal Engine] 프로퍼티 레플리케이션 심화: NetLoadOnClient와 RepNotify(OnRep) 이해 (0) | 2026.03.23 |
| [Unreal Engine 5] Property Replication (0) | 2026.03.20 |