액터 리플리케이션
특정 플레이어에 속한 액터의 정보를 네트워크 내 다른 플레이어에게 복제하는 작업
클라이언트-서버 모델에서는 대부분 서버에서 클라이언트로 전달
프로퍼티 리플리케이션(속성), RPC(함수)를 통해 수행
가장 중요한 것은 데이터 전송량을 최소화하는 것
주의사항
리플리케이트된 액터를 스폰 또는 소멸할 때는 반드시 서버에서 수행
언리얼 엔진이 제공하는 movementcomponent는 리플리케이트용으로 빌드되어 있으므로 가급적 사용
중요한 작업은 서버를 통해서 진행되는지 확인
리플리케이션은 서버에서 호출이 되지 않기 때문에 리플리케이션 노티파이를 통해 서버와 클라이언트가 병렬로 실행되도록 재활용할 수 있음
기본 액터의 로딩
클라이언트가 초기화될 때 모든 액터 정보를 서버로부터 받는 것은 비효율적이므로 배경에 관련된 액터는 맵을 통해 서버와 클라이언트가 가지고 있는 파일의 유효성 검사만 진행하고 네트워크 없이 스스로 로딩
고정 액터에 대해 NetLoadOnClient 속성을 체크해야 함(기본값)
고정으로 제공하는 액터 : 레벨을 구성하는 배경 액터
동적으로 생성하는 액터 : 플레이어 컨트롤러와 폰
프로퍼티 리플리케이션
고정 액터 중, 변경 사항이 발생하는 액터는 네트워크 데이터를 최소화하기 위해 변경 사항을 보내는 것보다 변경을 유발한 프로퍼티를 전달
리플리케이션 프로퍼티 지정
1. 액터의 bReplicates를 true로 설정 / 움직여야하는 경우 bReplicatesMovement를 true로 설정(폰은 true가 기본값)
2. 네트워크로 복제할 프로퍼티의 UPROPERTY에 Replicated 키워드 설정
3. GetLifetimeReplicatedProps 함수에 DOREPLIFETIME 매크로를 사용해 프로퍼티 추가
Lifetime은 액터 채널의 Lifetime을 의미하여, 활성화된 액터 채널로 전송할 프로퍼티를 의미
리플리케이션 콜백 함수 호출
클라이언트에 속성이 복제될 때 콜백 함수가 호출되도록 구현
틱마다 프로퍼티를 업데이트하면 틱의 코드가 커지고, 네트워크 전송 주기가 틱보다 느리다면 불필요한 업데이트가 발생하기 때문에 콜백 함수를 사용
UPROPERTY의 Replicated 키워드 대신 ReplicatedUsing = {콜백 함수 이름} 으로 변경
호출될 콜백 함수는 UFUNCTION으로 선언해야 함
콜백 함수 이름은 OnRep_ 접두사를 가짐
콜백 함수는 클라이언트에서만 호출됨
UFUNCTION()
void OnRep_ServerRotationYaw();
UPROPERTY(ReplicatedUsing=OnRep_ServerRotationYaw)
float ServerRotationYaw;
OnActorChannelOpen 함수가 호출된 후 커넥션에 대해 번치 정보를 해석해서 어떤 리플리케이션을 작업해야하는지 설정한 후 리플리케이션이 동작함
C++ OnRep vs 블루프린트 RepNotify
C++ OnRep
클라이언트에서만 호출됨
명시적으로 호출 가능하고, 값이 변경될 때만 호출됨
Blueprint RepNotify
서버, 클라이언트 모두 호출됨
서버에서도 호출되므로 명시적 호출 불가능
서버는 항시 호출되고 클라이언트는 값이 변경될 때만 호출됨
언리얼 인사이트
언리얼 프로그램의 다양한 퍼포먼스를 체크할 수 있는 프로파일링 도구
프로그램 프로파일링 뿐만 아니라 네트워크 상태도 확인할 수 있음(Network Insights)
언리얼 인사이트 구동
1. UnrealInsight 파일이 있는 디렉토리 Epic Games\UE_5.4\Engine\Binaries\Win64를 환경 변수로 설정
2. 배치파일 생성
NetTrace=1 Trace=Net은 네트워크 인사이트를 실행하기 위한 옵션
cd는 현재 디렉토리를 의미하는 변수
UnrealEditor.exe %cd%\ArenaBattle.uproject -NetTrace=1 -Trace=Net
3. 배치파일 실행
4. 언리얼 인사이트 실행
5. 커넥션 설정
6. 트레이스 오픈
InComing : 수신되는 데이터
OutGoing : 송신되는 데이터
액터 리플리케이션의 빈도(Frequency)
클라이언트와 서버 간에 진행되는 통신 빈도
NetUpdateFrequency로 리플리케이션 빈도의 최대치 설정
네트워크 빈도는 최대치일뿐 이를 보장하지 않고, 서버의 성능이 네트워크 빈도보다 낮을 경우 서버의 성능으로 결정됨
일반적으로 그래픽 기능이 없는 데디케이티드 서버가 더 좋은 성능
주요 액터에 설정된 NetUpdateFrequency 값
Actor : 100.0
Pawn : 100.0
PlayerController : 100.0
GameState : 10.0
PlayerState : 1.0
규칙적으로 움직이는 액터의 네트워크 데이터 줄이는 예제
클라이언트에서 이전 복제된 데이터에 기반해 현재 틱에서 회전 값을 예측하고 예측된 값을 보간해 회전
if (ClientTimeBetweenLastUpdate > KINDA_SMALL_NUMBER)
{
const float EstimateRotationYaw = ServerRotationYaw + RotationRate * ClientTimeBetweenLastUpdate;
const float LerpRatio = ClientTimeSinceUpdate / ClientTimeBetweenLastUpdate;
FRotator ClientRotator = RootComponent->GetComponentRotation();
ClientRotator.Yaw = FMath::Lerp(ServerRotationYaw, EstimateRotationYaw, LerpRatio);
RootComponent->SetWorldRotation(ClientRotator);
}
적응형(Adaptive) 네트워크 업데이트
유의미한 업데이트가 없으면 빈도를 줄여서 부하를 줄이는 기법
MinNetUpdateFrequencey로 리플리케이션 빈도의 최소를 설정(기본값은 2)
최소값과 최대값 사이에서 현재 액터에 맞는 최적의 전송 타이밍을 설정
// DefaultEngine.ini
[SystemSettings]
net.UseAdaptiveNetUpdateFrequency=1
액터 리플리케이션 연관성 설정
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/actor-relevancy-and-priority-in-unreal-engine?application_version=5.1
연관성(Relevancy)
서버의 관점에서 현재 액터가 클라이언트의 커넥션에 관련된 액터인지 확인하는 작업
대형 레벨에 존재하는 모든 액터 정보를 클라이언트에게 보내는 것은 불필요하므로 클라이언트와 연관있는 액터만 체계적으로 모아 통신 데이터를 최소화하는 방법
연관성 규칙으로 액터 세트를 완벽하게 추정해낼 수 없으므로 오차가 있을 수 있음
지연 시간, 패킷 손실 등으로 오차가 발생할 수 있음
클라이언트에게 연관 액터를 취합할 때 가장 중요한 것은 클라이언트 화면에 보이는 액터들을 묶어서 전달하는 것
따라서 연관성 판별을 위한 특별한 액터 정의
Viewer : 클라이언트 커넥션을 담당하는 플레이어 컨트롤러
View Target : 플레이어 컨트롤러가 빙의한 폰
Instigator(가해자) : 나에게 데미지를 가한 액터
Owner : 액터를 소유하는 액터
예를 들면 View Target이 들고 있는 무기는 View Target과 함께 보여줘야 하기 때문에 연관성을 가진다고 판정하고 이러한 개념을 설명할 때 Owner를 사용
서버에서는 틱마다 모든 커넥션과 액터에 대해 연관성을 점검하므로 부하가 많이 걸리는 작업
클라이언트의 Viewer와 관련있는 Viewer와의 일정 거리 내에 있는 액터를 파악하고 액터 묶음 정보를 클라이언트에게 전송
서버의 부하를 줄이기 위해 필요한 연관된 액터를 판정하는 프로퍼티 제공
AlwaysRelevant : 항상 커넥션에 대해 연관성을 가짐, GameState, PlayerState
NetUseOwnerRelevancy : 자신의 연관성은 오너의 연관성으로 판정
OnlyRelevantToOwner : Viewer, ViewTarget이 오너일 때 연관성을 가짐
NetCullDistance : 뷰어와의 거리에 따라 연관성을 판정, 기본값은 15000cm ^ 2
IsRelevantFor 가상 함수를 오버라이드하여 연관성을 재정의할 수 있음
우선권(Priority)
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/actor-relevancy-and-priority-in-unreal-engine?application_version=5.1
클라이언트에 보내는 대역폭(NetBandWidth)는 한정되어 있으므로 클라이언트에 보낼 액터 중 우선권이 높은 액터의 데이터를 우선 전달하도록 설계됨
우선권 값인 액터의 NetPriority 프로퍼티를 활용해 전송 순서를 결정함
Actor : 1.0
Pawn : 3.0
PlayerController : 3.0
네트워크 트래픽 포화(Saturation)
우선권 설정 로직(GetNetPriority)
마지막으로 패킷을 보낸 후의 경과 시간과 최초 우선권 값을 곱해 최종 우선권 값을 계산하여 클라이언트에 보낼 액터 목록을 정렬
네트워크가 포화될때 까지 정렬된 순서대로 리플리케이션을 수행
네트워크가 포화되면 해당 액터는 다음 서버 틱으로 넘김
보내지 못한 액터들은 경과 시간이 늘어나므로 높은 우선권을 가지게 됨
현재 뷰타겟인 경우 4배
잘 보이는 경우 2배
잘 안보이는 경우 상황에 따라 0.2 또는 0.4배
플레이어 컨트롤러가 뷰어인경우 4배
전체 대역폭 조절 설정
// DefaultEngine.ini
[/Script/Engine.GameNetworkManager]
TotalNetBandWidth=32000 // byte
네트워크에 대한 상세한 로그 설정
// DefaultEngine.ini
[Core.Log]
LogNetTraffic=Log
액터의 휴면(Dormancy) 상태
액터의 데이터 전송을 최소화하기 위해 연관성과 더불어 제공하는 속성
액터가 휴면 상태라면 연관성이 있더라도 액터 리플리케이션(RPC)을 수행하지 않음
DORM_Never : 휴면 불가
DORM_Awake : 휴면이 아닌 상태
DORM_DormantAll : 언제나 휴면 상태, 필요시 깨울 수 있음
DORM_DormantPartial : 특정 조건을 만족할 경우에만 리플리케이션을 수행
DORM_Initial : 휴면 상태로 시작하고 필요한 때 깨우도록 설정할 수 있음
프로퍼티 리플리케이션을 사용할 때는 DORM_Initial만 고려하는 것이 좋음
// 휴면 상태 해제
FlushNetDormancy();
조건식 프로퍼티 리플리케이션
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/conditional-property-replication-in-unreal-engine?application_version=5.1
프로퍼티가 최적화 방해를 방지하기 위해 리플리케이션이 등록되면 해제시킬 수 없음
프로퍼티가 변경되지 않으면 리플리케이트하지 않음
DOREPLIFETIME_CONDITION를 통해 ELifetimeCondition enum(조건)과 함께 등록하여 조건이 만족하지 않는 경우 리플리케이트하지 않음
DOREPLIFETIME_ACTIVE_OVERRIDE는 자유롭게 조건을 설정 가능하지만 조건이 자주 바뀐다면 느려질 수 있으므로 주의
액터 리플리케이션 플로우
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/detailed-actor-replication-flow?application_version=4.27
액터 리플리케이션 대부분은 UNetDriver::ServerReplicateActors에서 발생
1. 서버가 각 클라이언트에 연관성 있는 액터를 수집
2. 접속된 각 클라이언트에 변경된 프로퍼티가 있으면 전송
NextUpdateTime은 NetUpdateFrequency와 관련
bPendingNetUpdate는 포화 상태에서 패킷을 보내지 못하는 상황으로 우선권과 관련
ConsiderList에는 클라이언트에 보낼 수 있는 조건을 만족한 액터를 모아둔 목록
액터 컴포넌트 리플리케이션
언리얼에서 리플리케이션의 주체는 액터로, 액터가 소유하는 언리얼 오브젝트(Subobject)에 대해 리플리케이션이 가능
SetIsReplicated(true) : 리플리케이션을 지정
ReadyForReplication : 리플리케이션이 준비되면 호출되는 이벤트 함수, PostNetInit과 유사하게 호출됨