최근 언리얼 엔진 멀티플레이에 대해 공부하는 중인데,
Standalone 환경에서는 크게 신경 쓰지 않아도 되던 부분들이 멀티플레이가 추가되면서 훨씬 복잡하게 느껴졌다.
특히 어떤 클래스가 어떤 역할을 담당해야 하는지, 서버와 클라이언트 중 어디에 존재하는지,
어떤 정보가 복제되어야 하는지를 구분하는 것이 중요했다.
또한 Owning, Possess, NetConnection 같은 개념도 함께 나오다 보니
단순히 기능을 구현하는 것보다 언리얼의 멀티플레이 구조를 먼저 이해할 필요가 있다고 느꼈다.
그래서 이번 글에서는 멀티플레이 구조에서 자주 등장하는GameMode, GameState, PlayerState, PlayerController, Pawn의 역할을 간단히 정리해보려고 한다.
GameMode
GameMode는 현재 레벨의 게임 규칙을 관리하는 클래스이다.
예를 들어 다음과 같은 게임 규칙을 처리할 수 있다.
- 게임 시작
- 승리 / 패배 조건
- 플레이어 스폰
- 점수 처리
- 플레이어 입장 / 퇴장 처리
중요한 점은 멀티플레이 환경에서 GameMode는 서버에만 존재한다는 것이다.
클라이언트에는 GameMode가 존재하지 않는다.
따라서 클라이언트가 직접 알아야 하는 정보나
UI에 표시해야 하는 게임 상태를 GameMode에만 저장하면 문제가 될 수 있다.
또한 GameMode의 생명 주기는 레벨에 맞춰진다.
레벨이 로드될 때 생성되고, 레벨이 종료되면 함께 파괴된다.
GameState
GameState는 모든 플레이어가 알아야 하는 게임 진행 상태를 저장하는 클래스이다.
예를 들어 다음과 같은 정보가 여기에 들어갈 수 있다.
- 남은 시간
- 현재 라운드
- 팀 점수
- 전체 게임 진행 상태
멀티플레이 환경에서 GameMode가 서버에서만 존재한다면,GameState는 서버에 원본이 존재하고 클라이언트로 복제된다.
즉, 서버가 게임 상태를 관리하고, 모든 클라이언트가 그 상태를 받아볼 수 있도록 하는 역할이다.
예를 들어 남은 시간이나 팀 점수처럼 모든 플레이어가 알아야 하는 정보는 GameState에 두는 것이 적합하다.
GameState 역시 레벨에 맞춰 생성되고 파괴된다.
PlayerState
PlayerState는 플레이어별로 공개해야 하는 상태를 저장하는 클래스이다.
예를 들어 다음과 같은 정보가 들어갈 수 있다.
- 플레이어 이름
- 점수
- 킬 수
- 데스 수
- 팀 정보
멀티플레이 환경에서 PlayerState는 서버에 원본이 존재하고, 모든 클라이언트로 복제된다.
즉, 내 정보뿐만 아니라 다른 플레이어에게도 보여야 하는 정보라면PlayerState에 저장하는 것이 적합하다.
예를 들어 스코어보드에 표시되는 플레이어 이름이나 점수는
모든 클라이언트가 알아야 하므로 PlayerState에 두기 좋다.
클라이언트는 GameState의 PlayerArray를 통해 모든 플레이어의 PlayerState를 확인할 수 있다.
또 하나 중요한 점은 Pawn이 파괴되어도 PlayerState는 유지될 수 있다는 것이다.
예를 들어 플레이어가 죽어서 캐릭터가 사라지고 다시 스폰되더라도,
플레이어의 점수나 이름 같은 정보는 계속 유지되어야 한다.
이런 정보는 Pawn보다 PlayerState에 두는 것이 더 적절하다.
PlayerController
PlayerController는 사용자와 Pawn 사이의 인터페이스 역할을 한다.
플레이어의 입력을 받고, 그 입력을 현재 빙의한 Pawn에게 전달한다.
즉, 플레이어가 조작하는 주체가 Pawn이라면,
그 조작을 연결해주는 역할이 PlayerController라고 볼 수 있다.
멀티플레이에서 PlayerController의 존재 방식도 중요하다.
멀티플레이 환경에서 서버에는 모든 플레이어의 PlayerController가 존재한다.
하지만 각 클라이언트에는 자기 자신의 PlayerController만 존재한다.
예를 들어 내 클라이언트에는 내 PlayerController는 있지만,
다른 플레이어의 PlayerController는 존재하지 않는다.
그래서 개별 클라이언트의 UI 처리나
해당 클라이언트에게만 보내는 Client RPC는 보통 PlayerController를 통해 처리할 수 있다.
정리하면 PlayerController는 플레이어 입력, 클라이언트별 처리,
현재 조종 중인 Pawn과의 연결을 담당하는 클래스이다.
Pawn
Pawn은 PlayerController에 의해 빙의되어 조종되는 객체이다.
플레이어가 실제로 움직이고 조작하는 캐릭터나 유닛은 보통 Pawn 또는 Character를 기반으로 만든다.
멀티플레이에서 Pawn은 서버에 원본이 존재하고, 클라이언트들에게 복제된다.
클라이언트 기준으로 보면 내 Pawn과 다른 플레이어의 Pawn은 역할이 다르다.
클라이언트 기준으로 내가 조종하는 Pawn은 Autonomous Proxy이고,
다른 플레이어의 Pawn은 Simulated Proxy로 볼 수 있다.
서버는 해당 Pawn에 대해 Authority를 가진다.
즉, 최종적인 상태 판단과 권한은 서버에 있고,
클라이언트는 자신의 입력을 보내거나 복제된 결과를 받아서 화면에 반영한다.
정리하면 Pawn은 실제 게임 월드에서 조종되는 대상이고,PlayerController는 그 Pawn을 조작하는 주체라고 볼 수 있다.
역할 비교
각 클래스를 간단히 비교하면 다음과 같다.
| 클래스 | 주요 역할 | 멀티플레이 환경에서의 존재 방식 |
|---|---|---|
GameMode |
현재 레벨의 게임 규칙 관리 | 서버에만 존재 |
GameState |
모든 플레이어가 알아야 하는 게임 진행 상태 | 서버 원본, 클라이언트로 복제 |
PlayerState |
플레이어별 공개 상태 저장 | 서버 원본, 클라이언트로 복제 |
PlayerController |
사용자 입력과 Pawn 사이의 인터페이스 | 서버에는 모든 컨트롤러, 클라이언트에는 자기 것만 존재 |
Pawn |
PlayerController에 빙의되어 조종되는 객체 | 서버 원본, 클라이언트로 복제 |
Standalone 환경에서는 서버와 클라이언트가 분리되어 있지 않기 때문에
이런 존재 위치 차이를 크게 의식하지 않아도 되는 경우가 많다.
하지만 멀티플레이 환경에서는 서버에만 존재하는 클래스, 클라이언트로 복제되는 클래스,
각 클라이언트가 자기 것만 가지는 클래스가 나뉘기 때문에 차이가 발생한다.
정리
GameMode는 현재 레벨의 게임 규칙을 관리하며 서버에만 존재한다.
GameState는 모든 플레이어가 알아야 하는 게임 진행 상태를 저장하고,
서버에서 클라이언트로 복제된다.
PlayerState는 플레이어별 공개 상태를 저장한다.
플레이어 이름, 점수, 킬 수처럼
다른 클라이언트도 알아야 하는 정보는 PlayerState에 두는 것이 적합하다.
PlayerController는 사용자 입력과 Pawn 사이의 인터페이스 역할을 한다.
서버에는 모든 플레이어의 PlayerController가 존재하지만,
각 클라이언트에는 자기 자신의 PlayerController만 존재한다.
Pawn은 PlayerController에 빙의되어 실제로 조종되는 객체이다.
서버에 원본이 존재하고, 클라이언트들에게 복제된다.
마무리
이번에 GameMode, GameState, PlayerState, PlayerController, Pawn을 정리하면서
멀티플레이에서는 단순히 기능을 구현하는 것보다
각 클래스의 역할과 존재 위치를 먼저 이해하는 것이 중요하다는 것을 알게 되었다.
Standalone 환경에서는 크게 의식하지 않았던 부분도
멀티플레이 환경에서는 서버에만 존재하는지, 클라이언트로 복제되는지,
특정 클라이언트만 가지고 있는지에 따라 구현 방식이 달라질 수 있었다.
결국 언리얼 멀티플레이를 제대로 이해하려면
클래스의 역할뿐만 아니라 Owning, Possess, NetConnection, RPC 같은 개념도
함께 연결해서 봐야 한다고 느꼈다.
앞으로는 이런 개념들이 실제 코드에서 어떻게 동작하는지도 하나씩 정리해봐야겠다.
'언리얼 엔진' 카테고리의 다른 글
| [언리얼 엔진] std::map과 TMap 차이 정리 (0) | 2026.06.11 |
|---|---|
| [언리얼 엔진] FString, FName, FText 차이 정리 (0) | 2026.06.10 |
| [언리얼 엔진] UCLASS와 USTRUCT의 차이 정리 (0) | 2026.06.08 |
| [언리얼 엔진] UObject, 언리얼 객체 시스템의 기반 정리 (0) | 2026.06.05 |
| [언리얼 엔진] 액터 컴포넌트로 체력 시스템 분리하기 (0) | 2026.05.10 |