네트워크 최적화
Relevancy, Priority, Dormancy로 대역폭 관리
Actor Relevancy
필요한 클라이언트에게만 복제
Relevancy(관련성)는 어떤 클라이언트에게 어떤 액터를 복제할지 결정합니다. 멀리 있는 액터를 모든 클라이언트에게 복제하면 대역폭 낭비입니다.
UCLASS()
class MYGAME_API AOptimizedActor : public AActor
{
GENERATED_BODY()
public:
AOptimizedActor();
// Relevancy 오버라이드
virtual bool IsNetRelevantFor(
const AActor* RealViewer,
const AActor* ViewTarget,
const FVector& SrcLocation
) const override;
protected:
// 커스텀 관련성 거리
UPROPERTY(EditDefaultsOnly)
float CustomNetCullDistanceSquared;
UPROPERTY(Replicated)
int32 OwningTeamID;
};
AOptimizedActor::AOptimizedActor()
{
bReplicates = true;
// 기본 컬링 거리 (약 12km)
NetCullDistanceSquared = 150000000.0f;
// 커스텀 컬링 거리 (약 3km)
CustomNetCullDistanceSquared = 10000000.0f;
}
bool AOptimizedActor::IsNetRelevantFor(
const AActor* RealViewer,
const AActor* ViewTarget,
const FVector& SrcLocation) const
{
// 1. 기본 관련성 체크
if (!Super::IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation))
{
return false;
}
// 2. 거리 기반 체크
float DistSq = (GetActorLocation() - SrcLocation).SizeSquared();
if (DistSq > CustomNetCullDistanceSquared)
{
return false;
}
// 3. 팀 기반 체크 (예: 적 기지는 해당 팀에게만)
if (const APawn* ViewerPawn = Cast<APawn>(ViewTarget))
{
if (const AMyPlayerState* PS =
ViewerPawn->GetPlayerState<AMyPlayerState>())
{
if (OwningTeamID != INDEX_NONE && PS->TeamID != OwningTeamID)
{
return false; // 다른 팀은 볼 수 없음
}
}
}
return true;
}
Actor Priority
중요한 액터 우선 복제
대역폭이 제한되면 우선순위가 높은 액터부터 복제됩니다.
float APriorityActor::GetNetPriority(
const FVector& ViewPos,
const FVector& ViewDir,
AActor* Viewer,
AActor* ViewTarget,
UActorChannel* InChannel,
float Time,
bool bLowBandwidth) const
{
// 기본 우선순위
float Priority = NetPriority;
// 마지막 복제 이후 시간에 따라 증가 (기아 방지)
Priority *= (Time + 2.0f);
// 뷰어와의 거리에 따라 조정
float DistSq = (GetActorLocation() - ViewPos).SizeSquared();
if (DistSq < 1000000.0f) // 10m 이내
{
Priority *= 4.0f; // 매우 높은 우선순위
}
else if (DistSq < 10000000.0f) // 100m 이내
{
Priority *= 2.0f;
}
// 시야 내에 있으면 우선순위 증가
FVector ToActor = (GetActorLocation() - ViewPos).GetSafeNormal();
float DotProduct = FVector::DotProduct(ViewDir, ToActor);
if (DotProduct > 0.5f) // 시야 내
{
Priority *= 1.5f;
}
return Priority;
}
Network Dormancy
변경되지 않는 액터 휴면 처리
Dormancy(휴면)는 변경되지 않는 액터의 복제를 일시 중지하여 서버 CPU를 절약합니다.
ADormantActor::ADormantActor()
{
bReplicates = true;
// 레벨에 배치된 액터: DORM_Initial
// 동적 스폰 액터: DORM_DormantAll
NetDormancy = DORM_Initial;
bIsOpen = false;
}
void ADormantActor::WakeForInteraction()
{
// 중요: 속성 변경 전에 깨워야 함!
FlushNetDormancy();
// 이제 속성 변경 가능
bIsOpen = !bIsOpen;
// 서버에서 직접 OnRep 호출 (서버는 OnRep 자동 호출 안됨)
if (HasAuthority())
{
OnRep_IsOpen();
}
}
void ADormantActor::ReturnToDormancy()
{
// 상호작용 완료 후 다시 휴면
SetNetDormancy(DORM_DormantAll);
}
void ADormantActor::OnRep_IsOpen()
{
if (bIsOpen)
{
PlayOpenAnimation();
}
else
{
PlayCloseAnimation();
}
}
FlushNetDormancy()는 반드시 속성을 변경하기 전에 호출해야 합니다. 순서가 바뀌면 변경사항이 복제되지 않습니다.
DORM_Never— 절대 휴면하지 않음DORM_Awake— 현재 깨어있음DORM_DormantAll— 모든 연결에 대해 휴면DORM_DormantPartial— 일부 연결에 휴면DORM_Initial— 초기 복제 후 휴면
복제 빈도 조절
NetUpdateFrequency 설정
AMyActor::AMyActor()
{
bReplicates = true;
// 초당 최대 복제 횟수 (기본값: 100)
NetUpdateFrequency = 10.0f; // 초당 10회로 제한
// 최소 복제 간격
MinNetUpdateFrequency = 2.0f; // 최소 초당 2회
}
// 동적으로 빈도 조절
void AMyActor::AdjustUpdateFrequency(bool bInCombat)
{
if (bInCombat)
{
// 전투 중: 높은 빈도
NetUpdateFrequency = 30.0f;
}
else
{
// 평화로운 상태: 낮은 빈도
NetUpdateFrequency = 5.0f;
}
}
플레이어 캐릭터: 30-60Hz
NPC: 10-20Hz
정적 오브젝트: 1-5Hz
환경 요소: Dormancy 사용 권장
핵심 요약
- Relevancy — IsNetRelevantFor 오버라이드로 복제 대상 클라이언트 결정
- Priority — GetNetPriority로 대역폭 부족 시 중요한 액터 우선
- Dormancy — 변경 없는 액터를 휴면 처리하여 서버 CPU 절약
- NetUpdateFrequency — 초당 복제 횟수 제한
- FlushNetDormancy — 휴면 액터 속성 변경 전 필수 호출
도전 과제
배운 내용을 직접 실습해보세요
AActor::IsNetRelevantFor()를 오버라이드하여, 먼 거리의 NPC는 복제하지 않도록 구현하세요. NetCullDistanceSquared를 설정하여 관련성 범위를 조절하세요.
중요도에 따라 Actor의 NetUpdateFrequency를 조정하세요. 플레이어(100Hz), 보스 몬스터(30Hz), 일반 NPC(10Hz), 환경 오브젝트(1Hz)로 차등 설정하고 대역폭 감소를 측정하세요.
stat Net, Net Saturation Warning을 모니터링하고, Unreal Insights의 네트워크 채널 뷰를 사용하여 어떤 Actor/Property가 대역폭을 많이 사용하는지 분석하세요. Dormancy와 Push Model 복제를 적용하여 최적화하세요.