2026-06-26 TIL (85일차)
2026-06-26 TIL (85일차)
[UE] 멀티플레이 게임 프레임워크
1. 멀티플레이 디버깅 환경 세팅
멀티플레이 로직을 짤 때 “지금 이 코드가 서버에서 도는가, 클라이언트에서 도는가?”를 파악하는 것은 매우 중요합니다.
- PIE(Play In Editor) 세팅: 서버와 클라이언트의 로그를 하나의 출력창에서 편하게 보기 위해,
Launch Separate Server는 끄고(false),Run Under One Process는 켭니다(true). - 커스텀 매크로 활용: 엔진의 기본 로그만으로는 헷갈리기 쉬우므로,
DX_LOG_NET,DX_LOG_ROLE과 같은 커스텀 매크로를 정의하여 [현재 넷모드(Server/Client)]와 [로컬/리모트 권한(Role)]이 로그 앞에 명확히 찍히도록 설정하는 것이 디버깅의 핵심입니다.
2. 네트워크 기본 용어 정리
언리얼 네트워크의 뼈대를 구성하는 4가지 기본 단위입니다.
- NetConnection (넷 커넥션): 모든 멀티플레이 데이터가 드나드는 “단 하나의 거대한 통로”입니다. 서버는 접속한 유저 수만큼 가지며, 클라이언트는 서버와 통신할 단 1개만 가집니다.
- Channel (채널): 커넥션 내부를 용도에 맞게 쪼갠 전용 차선입니다. (액터 동기화를 위한
ActorChannel, 음성 통신용VoiceChannel등) - Packet (패킷): 일반적인 네트워크 프로그래밍에서 사용되는 통상적인 데이터 전송 단위입니다.
- Bunch (번치): 언리얼 엔진 고유의 패킷 단위입니다. 하나의 RPC 호출, 하나의 프로퍼티 복제 등이 각각의 번치로 포장되어 채널을 타고 전송됩니다.
3. 핵심 동기화 메커니즘 (접속, 시작, 빙의)
① 접속(Login) 과정의 이해
접속 통제는 서버에만 존재하는 GameMode가 전담합니다.
- 유저가 접속을 시도하면
PreLogin에서 검문하여 밴(Ban) 여부나 정원 초과를 확인해 에러 메시지로 튕겨낼 수 있습니다. - 접속이 완료된 후
PostLogin이 호출될 때, 비로소 서버의ClientConnections배열에 해당 유저가 안전하게 추가됩니다.
② 게임의 시작 (BeginPlay) 동기화
클라이언트 컴퓨터에는 GameMode 액터가 아예 존재하지 않습니다. 그렇다면 클라이언트는 도대체 언제 BeginPlay를 실행할까요?
- 서버의
GameMode::StartPlay가 불리면, 서버 측GameState::HandleBeginPlay를 호출합니다. - 이때
bReplicatedHasBegunPlay라는 상태 변수를 true로 바꾸는데, 이 변수가 클라이언트로 복제(Replication)됩니다. - 값이 도착하면 클라이언트의
OnRep_ReplicatedHasBegunPlay이벤트가 발동하며, 비로소 클라이언트 측 액터들의BeginPlay가 일제히 실행됩니다.
③ 빙의(Possess)와 소유권(Owner) 동기화
플레이어가 폰(Pawn)을 조종할 때, 클라이언트에서는 PossessedBy 함수가 직접 호출되지 않습니다.
- 서버:
PossessedBy함수가 불리며 폰의 Owner를 컨트롤러로 설정합니다. - 클라이언트: 서버에서 변경된 Owner 값이 네트워크를 타고 복제되어 넘어오면,
OnRep_Owner이벤트가 발동하여 클라이언트 화면의 폰도 자신의 주인이 누구인지 알게 됩니다.
4. 멀티플레이 프레임워크 함수 호출 순서 (Life Cycle)
유저가 방에 접속을 시도하는 순간부터 게임 연산(BeginPlay)이 시작되기까지의 전체 흐름입니다. 누구의 컴퓨터에서 실행되는지를 눈여겨보세요.
| 순서 | 함수명 (실행 위치 / 주체) | 설명 |
|---|---|---|
| 1 | PreLogin (서버 / GameMode) | 유저의 접속 요청을 가장 먼저 심사하고 차단 여부를 결정합니다. |
| 2 | Login (서버 / GameMode) | 접속이 허락된 유저를 위해 PlayerController 객체를 생성합니다. |
| 3 | PostLogin (서버 / GameMode) | 네트워크 회선(Connection)이 완전히 연결되었습니다. 보통 여기서 캐릭터(Pawn)를 스폰합니다. |
| 4 | PostInitializeComponents (공통 / 모든 액터) | 액터에 붙어있는 컴포넌트(메시, 콜리전 등)의 초기화가 완료된 시점입니다. |
| 5 | PostNetInit (클라 / 대상 액터) | 서버에 있던 액터가 클라이언트에 스폰되고 네트워크 변수 복제가 막 끝난 순간입니다. |
| 6 | PossessedBy (서버 / Pawn) | 서버에서 컨트롤러가 캐릭터에 빙의하며 Owner 속성을 설정합니다. |
| 7 | OnRep_Owner (클라 / Pawn) | 서버에서 바뀐 Owner 정보가 도착하여 클라이언트의 소유권이 동기화됩니다. |
| 8 | StartPlay (서버 / GameMode) | 방 세팅이 끝났을 때 서버가 전체 게임 시작을 선언합니다. |
| 9 | HandleBeginPlay (서버 / GameState) | 서버의 모든 액터에게 BeginPlay를 명령하고 시작 상태 변수를 켭니다. |
| 10 | OnRep_ReplicatedHasBegunPlay (클라 / GameState) | 시작 변수가 클라이언트에 도착하면, 클라이언트 엔진이 이를 인지하고 시작을 지시합니다. |
| 11 | BeginPlay (공통 / 모든 액터) | 모든 준비가 끝나고, 본격적으로 게임 로직과 연산이 시작됩니다! |
This post is licensed under CC BY 4.0 by the author.