본문 바로가기

멀티플레이어 게임 최적화

복제량을 측정하고 예측·보간·권한 검증을 조정해 대역폭과 지연이 플레이 경험에 미치는 비용을 줄입니다.

복제와 RPC로 멀티플레이어 기능을 만들었다고 해서 게임이 바로 쾌적하게 돌아가지는 않습니다. 같은 집의 공유기에서 시험할 때는 잘 되던 게임이, 지연(latency)이 큰 해외 서버나 대역폭(bandwidth)이 좁은 모바일 회선, 패킷 손실(packet loss)이 있는 무선 환경에서는 캐릭터가 순간 이동하고 총알이 늦게 맞는 게임이 될 수 있습니다. 접속한 플레이어가 늘수록 서버가 보내야 할 데이터도 함께 늘어납니다. 지연을 이루는 네 가지 요소와, 대역폭과 실제 처리량의 차이는 데이터 전송의 기본 원리에서 자세히 다룹니다.

RPC와 서버-클라이언트 통신까지 기능을 갖췄으니, 이 글에서는 멀티플레이어 게임의 네트워크 비용을 줄이는 방법을 살펴봅니다. 네트워크 통계와 트레이스로 병목을 측정하고, 복제량을 조정한 뒤, 지연과 손실이 있는 조건에서 플레이 결과를 다시 확인하는 흐름이 중심입니다. 비용을 줄이는 변경이 필요한 상태까지 빼 버리지 않았는지 확인하는 방법도 함께 다룹니다.


대역폭 관리: 필요한 것만 보내기

네트워크 대역폭은 한정된 자원이므로, 이를 효율적으로 쓰는 것이 멀티플레이어 최적화의 중심입니다. 가장 먼저 할 일은 필요한 것만 복제하는 것입니다.

모든 변수를 복제하지 말고, 클라이언트와 꼭 맞춰야 하는 데이터만 UPROPERTY(Replicated)로 설정합니다. 서버에서만 쓰는 임시 계산 값이나 예측에 필요 없는 중간 값은 전송에서 뺄 수 있습니다. 다만 클라이언트가 예측을 하더라도, 권한 있는 상태와 비교해 보정하는 데 필요한 정보는 보내야 합니다.

조건부 복제도 적극적으로 씁니다. COND_OwnerOnly, COND_SkipOwner, COND_InitialOnly 같은 조건으로 특정 클라이언트에만 보내거나, 각 연결의 초기 복제 묶음에만 보내 대역폭을 아낍니다. 예를 들어 플레이어의 개인 인벤토리는 COND_OwnerOnly로 그 플레이어에게만 보내고, 다른 플레이어에게는 보내지 않습니다.

RPC는 줄여서 씁니다. RPC는 변수 복제보다 대역폭을 더 많이 쓸 수 있고, 특히 Reliable RPC는 재전송 때문에 더 그렇습니다. 자주 바뀌는 값에는 변수 복제를 먼저 검토하고, 한 번 일어나는 액션에만 RPC를 씁니다.

데이터의 크기와 전송 대상 줄이기

언리얼 엔진은 기본적으로 네트워크 패킷을 최적화하지만, 개발자가 데이터를 준비하는 방식으로 더 줄일 수 있습니다. 값을 표현하는 정밀도부터 봅니다. 값의 범위와 허용 오차를 먼저 정하고, 위치를 압축해 보내는 FVector_NetQuantize 같은 네트워크용 표현을 검토합니다. C++ 타입의 메모리 크기가 실제로 보내는 비트 수를 정하는 것은 아니므로, 직렬화된 결과를 측정해 비교합니다. 복제되는 구조체나 배열에 필요 없는 필드가 남아 있지 않은지도 확인합니다.

관련성(relevancy)은 연결마다 어떤 액터를 보낼지 고르는 규칙입니다. 소유 연결, 거리, 관련성 규칙에 따라 정해지며, bOnlyRelevantToOwner나 NetCullDistanceSquared 같은 설정으로 조절합니다. 카메라 밖에 있거나 다른 물체에 가려졌다는 이유만으로 저절로 빠진다고 가정하면 안 됩니다.

휴면(dormancy)은 잘 바뀌지 않는 액터를 복제 검사에서 빼는 기능입니다. 관련성과는 다른 기준이며, C++에서는 휴면 중인 액터의 복제 속성을 바꾸기 전에 액터를 깨우거나 FlushNetDormancy로 갱신해야 변경을 잃지 않습니다.


단순한 모델로 비용 가늠하기

변경의 효과를 대략 가늠할 때는 단순한 계산 모델이 도움이 됩니다. 연습을 위해 속성 묶음 하나가 32바이트이고, 초당 10번 전송되며, 액터 100개가 각각 연결 3개에 같은 빈도로 전달된다고 가정합시다. 그러면 페이로드는 32×10×100×3 = 96,000바이트/초입니다. 대상 액터를 50개로, 빈도를 초당 5번으로 줄이면 32×5×50×3 = 24,000바이트/초가 되어 75% 줄어듭니다.

이 모델에는 패킷 헤더, 압축, 값이 실제로 바뀌었는지, 재전송, 관련성, 우선순위가 빠져 있습니다. 그러니 이 수치는 변경의 방향을 가늠하는 데만 쓰고, 실제 비용은 같은 장면에서 측정해 비교합니다. 또 NetUpdateFrequency는 업데이트를 검토하는 빈도의 상한일 뿐, 실제 전송 횟수를 보장하는 값으로 읽으면 안 됩니다.


지연을 숨기는 예측과 보간

네트워크 지연은 물리적으로 피할 수 없습니다. 플레이어가 입력한 뒤 결과가 늦게 보이는 렉(lag)을 줄이기 위해 몇 가지 기법을 씁니다.

클라이언트 예측(client-side prediction)은 플레이어가 입력했을 때 서버의 응답을 기다리지 않고 클라이언트가 먼저 결과를 예측해 적용하는 방식입니다. 이동이나 총 발사가 그 예입니다. 나중에 서버가 확정한 결과가 도착하면 클라이언트는 자신의 예측이 맞았는지 확인하고, 틀렸다면 보정(correction)해 상태를 맞춥니다. 이 과정은 복잡하고 버그가 생기기 쉽지만 반응성을 크게 높여 줍니다. UCharacterMovementComponent는 이동 예측을 기본으로 처리합니다.

보간(interpolation)은 다른 플레이어의 캐릭터나 복제된 액터가 네트워크 업데이트 사이에 툭툭 튀듯 움직이지 않도록, 과거의 위치와 새로 받은 위치 사이를 부드럽게 이어 주는 방식입니다. 예측한 뒤 서버의 정확한 값이 왔을 때 예측과의 차이를 조정하는 보정은, 오차의 크기와 설정에 따라 부드럽게 맞추거나 바로 위치를 맞출 수 있습니다.


네트워크를 측정하고 디버깅하기

최적화는 측정 없이는 할 수 없습니다. 언리얼 엔진은 네트워크를 살펴보는 여러 도구를 제공합니다. 지원되는 개발 환경에서는 stat net으로 네트워크 통계를 볼 수 있습니다.

네트워킹 인사이트(Networking Insights)는 언리얼 인사이트(Unreal Insights)로 네트워크 트레이스를 분석하는 도구입니다. 실행할 때 -trace=net -NetTrace=1 같은 인자로 기록하며, 에디터에서는 -tracehost=localhost 같은 수집 연결 설정도 확인합니다. 패킷, 속성, RPC마다의 비용을 비교할 수 있습니다. 예전의 네트워크 프로파일러(Network Profiler)를 지원하는 빌드에서는 netprofile enable과 netprofile disable로 기록하는데, 엔진 버전과 빌드 구성을 먼저 확인해야 합니다.

로그는 log LogNet Verbose나 log LogNet VeryVerbose로 지원되는 범위를 넓힙니다. 지연과 손실을 시험하려면 에디터의 네트워크 에뮬레이션(Network Emulation) 설정에서 조건을 걸 수 있으며, 버전마다 화면과 지원 항목이 다를 수 있습니다. 이 글은 이런 조건에서 실제로 측정한 결과를 담고 있지 않습니다.

전송 비용을 줄인 뒤 상태 정확성도 다시 확인합니다

휴면 액터→wake/FlushNetDormancy→속성 대입→연결별 수신 조건 확인의 명시적 선후 관계이다. 최종 전송 성공으로 표시하지 않는다.

UNREAL C++ · CH8-4

전송 비용을 줄인 뒤 상태 정확성도 다시 확인합니다

속성·액터·RPC·예측 비용의 관측 결과에 따라 변경을 선택합니다. 같은 장면과 연결 조건에서 비용과 플레이 동작을 함께 비교하며, 아래 항목은 수치가 측정된 벤치마크가 아닙니다.

휴면 속성은 변경 전에 깨운 뒤 상태를 갱신한다C++주어진휴면액터의변경흐름. 휴면상태→wake또는FlushNetDormancy→속성대입→연결별복제조건확인.최종노드는수신조건을확인하는단계이며즉시전송성공보장이아니다. mutation뒤늦게wake하는순서로읽지않는다.
휴면 액터잘 바뀌지 않는 상태
연결 관계휴면 액터 · 잘 바뀌지 않는 상태 → 먼저 깨우기 · Wake / Flush
먼저 깨우기Wake / Flush
연결 관계먼저 깨우기 · Wake / Flush → 속성 변경 · 새 권위 상태 대입
속성 변경새 권위 상태 대입
연결 관계속성 변경 · 새 권위 상태 대입 → 수신 조건 확인 · 관련성 · 연결 · 시점
수신 조건 확인관련성 · 연결 · 시점
비용을 줄인 뒤 다시 확인할 동작
관측할 비용검토할 변경다시 확인할 동작
속성·액터 전송량필요한 상태, 소유 조건과 관련성 범위를 좁힙니다.접속 직후·소유권 변경·거리 재진입에도 필요한 상태가 도착하는지 확인합니다.
변하지 않는 액터의 검사 비용변경 빈도가 낮은 액터에 휴면을 검토합니다.속성 변경 전에 깨우거나 flush하고, 재수면 뒤에도 값이 유실되지 않는지 확인합니다.
RPC 빈도·재전송 비용중복 호출과 페이로드를 줄이고 이벤트 의미에 맞는 신뢰성을 고릅니다.지연·손실에서도 영구 상태가 어긋나지 않는지, 복구할 상태 경로가 있는지 확인합니다.
예측·보정 비용표현 정밀도와 이동 업데이트 정책을 조정합니다.반응성뿐 아니라 권위 상태와의 오차, 순간 보정과 시각적 품질을 함께 비교합니다.
속성·액터 전송량
검토할 변경: 필요한 상태, 소유 조건과 관련성 범위를 좁힙니다.
다시 확인할 동작: 접속 직후·소유권 변경·거리 재진입에도 필요한 상태가 도착하는지 확인합니다.
변하지 않는 액터의 검사 비용
검토할 변경: 변경 빈도가 낮은 액터에 휴면을 검토합니다.
다시 확인할 동작: 속성 변경 전에 깨우거나 flush하고, 재수면 뒤에도 값이 유실되지 않는지 확인합니다.
RPC 빈도·재전송 비용
검토할 변경: 중복 호출과 페이로드를 줄이고 이벤트 의미에 맞는 신뢰성을 고릅니다.
다시 확인할 동작: 지연·손실에서도 영구 상태가 어긋나지 않는지, 복구할 상태 경로가 있는지 확인합니다.
예측·보정 비용
검토할 변경: 표현 정밀도와 이동 업데이트 정책을 조정합니다.
다시 확인할 동작: 반응성뿐 아니라 권위 상태와의 오차, 순간 보정과 시각적 품질을 함께 비교합니다.

권위 상태를 복구할 경로와 지연·손실 조건을 함께 확인합니다. 관련성은 화면 가림과 같지 않고 휴면은 속성 변경 전에 깨우거나 flush합니다. World Partition의 셀 스트리밍은 서버 분산·핸드오프를 자동 구현하지 않습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목휴면 액터를 깨운 뒤 속성 상태를 갱신한다
관계·조건 설명C++ 예제에 주어진 휴면 액터의 변경 흐름이다. 휴면 상태→wake 또는 FlushNetDormancy→속성 대입→연결별 복제 조건 확인 순서다. 최종 노드는 수신 조건을 확인하는 단계이며 즉시 전송 성공을 보장하지 않는다. 속성을 변경한 뒤 늦게 wake하는 순서로 읽지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
휴면 상태

속성 갱신 전에 액터의 휴면을 확인한다.

  • Actor: dormant
  • 변경: 아직 대입 전
  • 휴면 중 값 대입만으로 전송이 보장되지 않는다.
깨움 뒤 대입

wake/FlushNetDormancy 뒤 속성을 바꾼다.

  • 순서: wake→대입
  • 상태: 수신 검토 가능
  • 갱신의 선후 관계가 필요한 사례다.
연결별 조건

각 연결의 관련성·조건을 확인한다.

  • 검사: 연결별 수신 조건
  • 전송 성공: 보장하지 않음
  • 깨움이 모든 클라이언트에 즉시 도착하는 일을 보장하지는 않는다.

전송 비용을 줄인 뒤 상태 정확성도 다시 확인합니다

휴면 액터→wake/FlushNetDormancy→속성 대입→연결별 수신 조건 확인의 명시적 선후 관계이다. 최종 전송 성공으로 표시하지 않는다.

UNREAL C++ · CH8-4

전송 비용을 줄인 뒤 상태 정확성도 다시 확인합니다

속성·액터·RPC·예측 비용의 관측 결과에 따라 변경을 선택합니다. 같은 장면과 연결 조건에서 비용과 플레이 동작을 함께 비교하며, 아래 항목은 수치가 측정된 벤치마크가 아닙니다.

휴면 속성은 변경 전에 깨운 뒤 상태를 갱신한다C++주어진휴면액터의변경흐름. 휴면상태→wake또는FlushNetDormancy→속성대입→연결별복제조건확인.최종노드는수신조건을확인하는단계이며즉시전송성공보장이아니다. mutation뒤늦게wake하는순서로읽지않는다.
휴면 액터잘 바뀌지 않는 상태
연결 관계휴면 액터 · 잘 바뀌지 않는 상태 → 먼저 깨우기 · Wake / Flush
먼저 깨우기Wake / Flush
연결 관계먼저 깨우기 · Wake / Flush → 속성 변경 · 새 권위 상태 대입
속성 변경새 권위 상태 대입
연결 관계속성 변경 · 새 권위 상태 대입 → 수신 조건 확인 · 관련성 · 연결 · 시점
수신 조건 확인관련성 · 연결 · 시점
비용을 줄인 뒤 다시 확인할 동작
관측할 비용검토할 변경다시 확인할 동작
속성·액터 전송량필요한 상태, 소유 조건과 관련성 범위를 좁힙니다.접속 직후·소유권 변경·거리 재진입에도 필요한 상태가 도착하는지 확인합니다.
변하지 않는 액터의 검사 비용변경 빈도가 낮은 액터에 휴면을 검토합니다.속성 변경 전에 깨우거나 flush하고, 재수면 뒤에도 값이 유실되지 않는지 확인합니다.
RPC 빈도·재전송 비용중복 호출과 페이로드를 줄이고 이벤트 의미에 맞는 신뢰성을 고릅니다.지연·손실에서도 영구 상태가 어긋나지 않는지, 복구할 상태 경로가 있는지 확인합니다.
예측·보정 비용표현 정밀도와 이동 업데이트 정책을 조정합니다.반응성뿐 아니라 권위 상태와의 오차, 순간 보정과 시각적 품질을 함께 비교합니다.
속성·액터 전송량
검토할 변경: 필요한 상태, 소유 조건과 관련성 범위를 좁힙니다.
다시 확인할 동작: 접속 직후·소유권 변경·거리 재진입에도 필요한 상태가 도착하는지 확인합니다.
변하지 않는 액터의 검사 비용
검토할 변경: 변경 빈도가 낮은 액터에 휴면을 검토합니다.
다시 확인할 동작: 속성 변경 전에 깨우거나 flush하고, 재수면 뒤에도 값이 유실되지 않는지 확인합니다.
RPC 빈도·재전송 비용
검토할 변경: 중복 호출과 페이로드를 줄이고 이벤트 의미에 맞는 신뢰성을 고릅니다.
다시 확인할 동작: 지연·손실에서도 영구 상태가 어긋나지 않는지, 복구할 상태 경로가 있는지 확인합니다.
예측·보정 비용
검토할 변경: 표현 정밀도와 이동 업데이트 정책을 조정합니다.
다시 확인할 동작: 반응성뿐 아니라 권위 상태와의 오차, 순간 보정과 시각적 품질을 함께 비교합니다.

권위 상태를 복구할 경로와 지연·손실 조건을 함께 확인합니다. 관련성은 화면 가림과 같지 않고 휴면은 속성 변경 전에 깨우거나 flush합니다. World Partition의 셀 스트리밍은 서버 분산·핸드오프를 자동 구현하지 않습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목휴면 액터를 깨운 뒤 속성 상태를 갱신한다
관계·조건 설명C++ 예제에 주어진 휴면 액터의 변경 흐름이다. 휴면 상태→wake 또는 FlushNetDormancy→속성 대입→연결별 복제 조건 확인 순서다. 최종 노드는 수신 조건을 확인하는 단계이며 즉시 전송 성공을 보장하지 않는다. 속성을 변경한 뒤 늦게 wake하는 순서로 읽지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
휴면 상태

속성 갱신 전에 액터의 휴면을 확인한다.

  • Actor: dormant
  • 변경: 아직 대입 전
  • 휴면 중 값 대입만으로 전송이 보장되지 않는다.
깨움 뒤 대입

wake/FlushNetDormancy 뒤 속성을 바꾼다.

  • 순서: wake→대입
  • 상태: 수신 검토 가능
  • 갱신의 선후 관계가 필요한 사례다.
연결별 조건

각 연결의 관련성·조건을 확인한다.

  • 검사: 연결별 수신 조건
  • 전송 성공: 보장하지 않음
  • 깨움이 모든 클라이언트에 즉시 도착하는 일을 보장하지는 않는다.

위 그림의 위쪽은 C++에서 휴면 중인 액터의 속성을 바꿀 때의 순서를 보여 줍니다. 잘 바뀌지 않는 상태를 가진 휴면 액터를 먼저 깨우거나 FlushNetDormancy로 갱신하고, 그다음 새 권한 상태를 속성에 넣고, 마지막으로 연결마다 관련성, 연결, 시점 같은 수신 조건을 확인합니다. 마지막 단계는 조건을 확인하는 단계일 뿐 즉시 전송에 성공했다는 뜻이 아니며, 값을 바꾼 뒤에 늦게 깨우는 순서로 읽으면 안 됩니다. 단계 그림도 휴면을 확인하고, 깨운 뒤에 대입하고, 연결별 조건을 확인하는 선후 관계를 따라갑니다.

그림의 표는 비용을 줄인 뒤 다시 확인할 동작을 정리합니다. 속성과 액터의 전송량이 크다면 필요한 상태와 소유 조건, 관련성 범위를 좁히되, 접속 직후나 소유권이 바뀐 뒤, 거리를 벗어났다 다시 들어온 뒤에도 필요한 상태가 도착하는지 확인합니다. 바뀌지 않는 액터의 검사 비용에는 휴면을 검토하되, 속성을 바꾸기 전에 깨우고 다시 잠든 뒤에도 값이 사라지지 않는지 봅니다. RPC의 빈도와 재전송 비용은 중복 호출과 페이로드를 줄이고 이벤트에 맞는 신뢰성을 고르되, 지연과 손실이 있어도 영구 상태가 어긋나지 않고 상태를 되살릴 경로가 있는지 확인합니다. 예측과 보정의 비용은 표현 정밀도와 이동 업데이트 정책으로 조정하되, 반응성뿐 아니라 권한 상태와의 오차, 순간 보정과 화면 품질을 함께 비교합니다.

그림 아래의 설명처럼 권한 상태를 되살릴 경로와 지연, 손실 조건을 함께 확인해야 합니다. 관련성은 화면에서 가려진 것과 같지 않고, 휴면 액터는 속성을 바꾸기 전에 깨우며, 월드 파티션의 셀 스트리밍이 서버 분산이나 서버 간 인계를 자동으로 해 주지는 않습니다. 이 표의 항목은 실제로 측정한 벤치마크가 아니라, 같은 장면과 연결 조건에서 비용과 동작을 함께 비교하라는 확인 목록입니다.


서버 권한 유지와 치트 방지

네트워크 최적화만큼 보안도 중요하며, 서버 권한을 올바르게 유지하는 것이 치트를 막는 핵심입니다. 피해 계산, 아이템 획득, 탄약 소모, 점수 부여처럼 게임의 핵심 규칙과 상태 변경은 반드시 서버에서만 처리합니다. 클라이언트가 조작한 데이터를 보내도 서버가 검증해 무시해야 합니다.

HasAuthority() 검사로 서버에서 만든 공유 액터의 권한 상태 변경을 제한합니다. 이것은 액터의 역할 검사이고, 클라이언트에서만 만든 로컬 액터도 권한 역할을 가질 수 있으므로 서버 프로세스인지 묻는 검사와 혼동하지 않습니다. 중요한 입력과 게임 규칙은 서버에서 검사하며, WithValidation은 선택 사항이고 false는 연결을 끊는다는 점, 정상적인 게임상 거절은 _Implementation의 분기로 처리할 수 있다는 점은 앞 글에서 본 그대로입니다.


연결 관리와 확장

플레이어의 접속과 해제, 매치메이킹은 네트워크 최적화에 간접적으로 영향을 줍니다. 세션은 온라인 서브시스템(Online Subsystem)의 IOnlineSubsystem에서 얻은 IOnlineSession 인터페이스 같은 것으로 만들고, 찾고, 참여합니다. 실제로 지원되는 기능과 로그인 절차는 선택한 플랫폼 서비스에 따라 다릅니다. 네트워크 문제로 플레이어의 연결이 끊겼을 때도 게임이 안정적으로 처리되고 다른 플레이어에게 영향을 주지 않도록 로직을 만들어야 합니다.

대규모 멀티플레이어 게임이라면 서버 한 대의 한계를 넘어 여러 서버를 함께 돌리는 부하 분산과 확장 설계가 필요합니다. 여러 서버에 영역을 나누고 플레이어가 이동할 때 상태를 넘겨주려면 별도의 서버와 세션 설계가 필요합니다. 엔진의 월드 파티션은 월드의 셀과 스트리밍을 관리할 뿐, 서버 분산이나 인계를 자동으로 구현하지 않습니다. 던전이나 전장처럼 특정 구역마다 독립된 서버 인스턴스를 만들어 부하를 나누는 방법도 있습니다.

더 깊이: 액터와 접속자가 아주 많을 때의 복제

기본 복제 방식에서는 복제되는 액터마다 연결된 클라이언트 하나하나에 갱신을 보낼지 판단해야 하므로, 액터와 연결이 많아지면 이 판단이 서버 CPU의 병목이 됩니다. Replication Graph 문서는 Fortnite Battle Royale이 접속자 100명과 복제 액터 약 50,000개로 경기를 시작한다는 예를 듭니다. 리플리케이션 그래프(Replication Graph) 플러그인은 이 문제를 줄이려고 만든 시스템으로, 계속 유지되는 리플리케이션 그래프 노드들이 클라이언트마다 보낼 액터 목록을 만듭니다. 노드가 만든 데이터는 여러 프레임에 걸쳐 보관하고 여러 연결이 함께 쓸 수 있으므로, 월드를 격자 칸으로 나눈 노드라면 클라이언트가 있는 칸의 미리 계산한 목록을 그대로 넘겨줄 수 있습니다.

리플리케이션 그래프를 쓰려면 DefaultEngine.ini의 [/Script/OnlineSubsystemUtils.IpNetDriver] 아래에 ReplicationDriverClassName으로 직접 만든 리플리케이션 드라이버 클래스를 지정하거나, 코드에서 CreateReplicationDriverDelegate에 함수를 연결합니다. 문서는 리플리케이션 그래프가 아직 분할 화면 게임에서 동작하지 않아 콘솔 빌드에서는 꺼져 있다고 적고 있습니다. 이와 별도로 엔진에는 기존 복제 시스템과 나란히 고를 수 있는 Iris라는 복제 시스템도 있지만, 기준 버전인 5.8 문서에서도 실험적(Experimental) 기능으로 표시되어 있으므로 출시할 프로젝트에 쓸 때는 주의해야 합니다.


정리

  • 꼭 맞춰야 하는 데이터만 복제하고, 조건부 복제로 대상을 좁히며, 자주 바뀌는 값은 RPC보다 변수 복제로 보냅니다.
  • 정밀도에 맞는 네트워크용 표현, 관련성, 휴면으로 데이터의 크기와 전송 대상을 줄이며, 휴면 액터는 속성을 바꾸기 전에 깨웁니다.
  • 단순한 계산 모델은 변경의 방향을 가늠하는 데만 쓰고, 실제 비용은 같은 장면에서 측정합니다. NetUpdateFrequency는 실제 전송 횟수를 보장하지 않습니다.
  • 클라이언트 예측과 보정, 보간으로 지연을 숨기며, UCharacterMovementComponent는 이동 예측을 기본으로 처리합니다.
  • stat net, 네트워킹 인사이트, 로그, 네트워크 에뮬레이션으로 측정하고, 비용을 줄인 뒤에는 필요한 상태가 여전히 도착하는지 함께 확인합니다.
  • 핵심 규칙은 서버에서만 처리하고, 여러 서버로의 확장은 월드 파티션이 아니라 별도의 서버와 세션 설계가 맡습니다.

연습 문제

1. 연결이 두 개라면

앞의 단순 모델에서 속성 묶음 32바이트, 초당 5번, 액터 50개, 연결 2개라면 페이로드는 얼마일까요?

해설 보기

32×5×50×2 = 16,000바이트/초입니다. 이 값도 헤더, 압축, 변경 여부, 재전송, 관련성, 우선순위를 뺀 모델의 값이므로, 실제 비용은 같은 장면에서 측정해 비교합니다.

2. 수치는 낮아졌지만

새 복제 정책이 소유자의 탄약 상태와, 접속 직후에 받아야 할 문 열림 상태까지 빼서 전송량이 더 낮아졌습니다. 이 변경을 성공한 최적화로 승인해도 될까요?

해설 보기

승인하면 안 됩니다. 수치가 낮아져도 필요한 권한 상태가 오지 않으면, 소유자는 자신의 탄약을 보정할 수 없고, 나중에 접속한 플레이어는 문이 열려 있다는 사실을 알 수 없습니다.

현재 상태를 전달하는 경로는 유지한 채 다른 곳에서 비용을 줄이고, 같은 장면에서 실제 네트워크 비용과 함께 지연, 손실, 재접속 상황의 동작까지 비교합니다. 지나간 NetMulticast 호출만으로는 새로 접속한 플레이어에게 영구 상태를 되살려 줄 수 없다는 점도 기억합니다.

3. 휴면 중에 바꾼 값

잘 바뀌지 않는 보물상자 액터를 휴면 상태로 두었습니다. 플레이어가 상자를 열자 서버의 C++ 코드가 bIsOpened를 true로 바꿨는데, 클라이언트에서는 상자가 계속 닫혀 보입니다. 무엇을 의심해야 할까요?

해설 보기

휴면 중인 액터의 속성을 깨우지 않고 바꾼 것을 의심합니다. 휴면 액터는 복제 검사에서 빠져 있으므로, 값을 바꾸기 전에 액터를 깨우거나 FlushNetDormancy로 갱신해야 변경이 클라이언트로 갈 수 있습니다.

FlushNetDormancy를 부른 뒤 bIsOpened를 바꾸도록 순서를 고치고, 그 뒤에도 각 연결의 관련성과 수신 조건이 맞는지 확인합니다. 깨운다고 모든 클라이언트에 즉시 도착한다는 보장은 없습니다.

네트워크 트레이스와 분석 방법은 Epic의 Networking Insights 문서에서, 관련성과 휴면은 Actor Network Dormancy 문서에서 확인할 수 있습니다.

이것으로 8장을 마칩니다. 8장에서는 클라이언트-서버 모델과 권한, 액터와 변수의 복제와 조건, Server, Client, NetMulticast RPC의 실행 위치와 검증, 그리고 대역폭을 줄이고 지연을 숨기는 멀티플레이어 최적화를 살펴봤습니다. 다음 장의 프로파일링 도구 사용법부터는 프로파일링 도구로 성능을 측정하고, 메모리, 렌더링, CPU와 GPU의 병목을 찾아 해결하는 방법을 다룹니다.