
멀티플레이 개발의 가장 큰 혼란은 "똑같은 코드가 대체 어디서 실행되는 거지?"라는 질문에서 시작된다. 오늘은 이 혼란을 해결해 줄
NetMode라는 '네트워크 신분증'에 대해 학습했다. 더 나아가, 이 신분증이 어떤 원리로 발급되는지를 이해하기 위해 네트워크 통신의 핵심 3요소인NetDriver(네트워크 관리자),NetConnection(통신 파이프), 그리고Ownership(소유권)의 관계를 파헤쳤다. 이 개념들을 통해 왜 중요한 로직은 반드시 서버에서만 실행해야 하는지에 대한 기술적인 근거를 명확히 알게 되었다.
NetMode의 필요성을 이해하고, 현재 실행 위치(서버/클라이언트)를 구분하는 방법 알기NetDriver와 NetConnection의 역할과 관계 설명하기Ownership(소유권) 개념의 중요성 파악하기APlayerCharacter::BeginPlay() 함수에 로그를 하나 찍고 실행하면, 서버와 클라이언트 모두에서 로그가 찍히는 것을 볼 수 있다. 싱글플레이 환경에서는 당연했지만, 멀티플레이에서는 이 현상이 모든 문제의 시작점이 된다. "HP가 깎이는 로직을 모든 PC에서 실행해야 할까?" 정답은 '아니오'다.
NetMode 🆔NetMode는 현재 이 게임 인스턴스(프로세스)가 네트워크 상에서 어떤 역할을 수행 중인지 알려주는 열거형(Enum)이다. 월드(World) 객체가 이 속성을 가지고 있으며, 크게 4가지 상태로 나뉜다.
NM_Standalone: 싱글플레이 상태. 서버도, 클라이언트도 아니다.NM_ListenServer: 리슨 서버. 서버이면서 동시에 자기 자신이라는 로컬 플레이어(클라이언트)를 가진다.NM_DedicatedServer: 데디케이티드 서버. 플레이어 없이 오직 서버 로직만 실행한다.NM_Client: 클라이언트. 서버에 연결된 원격 플레이어다."플레이어의 HP를 변경하는 로직"처럼 게임의 규칙에 중대한 영향을 미치는 코드는 해킹 방지를 위해 반드시 서버에서만 실행되어야 한다. 이럴 때 우리는 NetMode를 확인하여 "여기가 서버가 맞나?"를 검사하는 코드를 추가해야 한다.
void AMyCharacter::TakeDamage(float Damage)
{
// GetWorld()->GetNetMode() 가 NM_ListenServer 또는 NM_DedicatedServer 일 때만 실행
// 혹은 더 좋은 방법인 HasAuthority() 체크를 사용!
if (HasAuthority())
{
HP -= Damage;
}
}
HasAuthority()는 NetMode를 직접 비교하는 것보다 더 권장되는 방식인데, 내부적으로 현재 액터가 '권위'를 가진, 즉 서버에 존재하는지를 판단해주는 편리한 함수다.
NetDriver와 NetConnection 🔌NetMode가 신분증이라면, 이 신분증은 어떻게 발급되는 걸까? 그 근간에는 NetDriver와 NetConnection이 있다.
NetDriver (네트워크 관리자): 멀티플레이 세션이 시작될 때 각 PC(서버, 클라이언트 모두)에 생성되는 객체다. 이름처럼 네트워크 통신의 모든 로우레벨 동작을 '운전'하고 관리한다. 서버의 NetDriver는 여러 클라이언트와의 연결을, 클라이언트의 NetDriver는 단 하나의 서버와의 연결을 관리한다.NetConnection (통신 파이프): 두 PC 간의 실제 연결 통로다. 클라이언트가 서버에 접속하면, 서버의 NetDriver에는 해당 클라이언트를 위한 ClientConnection 객체가 추가되고, 클라이언트의 NetDriver에는 서버를 향한 ServerConnection 객체가 생성된다. 즉, 서버는 접속한 클라이언트 수만큼 NetConnection을, 클라이언트는 오직 1개의 NetConnection을 갖는다.UNetDriver::IsServer() 함수 내부를 보면 ServerConnection == NULL 인지를 체크하는데, 클라이언트는 반드시 서버 커넥션을 가지므로 이 값이 false가 나오고, 서버는 이 변수가 NULL이므로 true가 반환되는 원리다.
Ownership 👑NetConnection이 물리적인 '랜선'이라면, Ownership은 이 랜선을 통해 어떤 데이터를 보낼 수 있는지 결정하는 '소유권' 정보다.
PlayerController는 자신만의 NetConnection을 소유한다. 그리고 이 PlayerController가 빙의(Possess)한 Pawn(캐릭터)은 자신의 Owner를 해당 PlayerController로 설정한다. 만약 이 Pawn이 무기를 장착하면, 무기 액터의 Owner는 Pawn으로 설정될 수 있다.
ClientConnection -> PlayerController -> Pawn -> Weapon
이렇게 연결된 소유 관계의 최상단에는 결국 NetConnection이 있다. 어떤 액터가 GetNetConnection() 함수를 호출하면, 이 소유 관계를 거슬러 올라가 자신의 주인이 누구인지, 즉 어떤 플레이어의 통신 파이프에 연결되어 있는지를 찾아낸다. 이 Ownership 체인은 나중에 배울 RPC(원격 함수 호출)가 오직 '자신이 소유한' 액터에 대해서만 클라이언트에서 서버로 호출할 수 있도록 제한하는 핵심적인 보안 장치가 된다.
| 개념 | 설명 | 비고 |
|---|---|---|
NetMode | 현재 게임 인스턴스의 네트워크 역할 (서버/클라이언트 등). | HasAuthority()로 서버인지 확인하는 것이 더 일반적이고 권장됨. |
NetDriver | 각 PC의 네트워크 통신을 총괄하는 관리자 객체. | 멀티플레이 시에만 생성되며, NetConnection들을 소유하고 관리함. |
NetConnection | 두 PC 사이의 실제 데이터 통신 채널(파이프). | 서버는 다수, 클라이언트는 단 1개의 NetConnection을 가짐. |
Ownership | 액터의 소유자 관계. RPC 호출 권한 등 보안의 기반이 된다. | PlayerController가 NetConnection과 액터들을 잇는 중요한 연결고리. |