나만의 작은 도서관
[TIL][C++] 251111 MMO 서버 개발 134일차: [언리얼] GameMode와 내부 Property들의 역할 본문
Today I Learn
[TIL][C++] 251111 MMO 서버 개발 134일차: [언리얼] GameMode와 내부 Property들의 역할
pledge24 2025. 11. 12. 02:32주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
[언리얼] GameMode와 내부 Property들의 역할
시작하기 앞서…
- GameMode와 내부에 존재하는 Property들은 언리얼 네트워크 시스템을 밀접한 관련이 있다. 즉, 서버가 따로 없는 싱글플레이어 게임인 경우 GameMode 대부분의 기능이 사용되지 않으므로, 아래의 설명들은 멀티플레이어 게임을 기준으로 설명한다.
게임 모드(GameMode)란?

- 게임의 규칙을 결정하고 정보를 처리하는 액터(Actor).
- 서버 전용 클래스이며, 클라이언트에는 존재하지 않는다.
- 언리얼에서 제공하는 게임 모드 클래스는 AGameModeBase와 AGameMode가 있으며, 현재 시점(UE5.3)에서 아래와 같은 역할을 한다.
- AGameModeBase
- AGameModeBase는 모든 GameMode의 베이스 클래스.
- 4.14 이전에는 AGameMode가 해당 역할을 했지만, 지금은 AGameModeBase의 파생 클래스가 되었다.
- AGameMode
- AGameModeBase 보다 다양한 기능이 들어있는 AGameModeBase의 파생 클래스.
- 맵 진입(EnteringMap), 시작 대기 중(WatingToStart), 진행 중(InProgress), 경기 후 대기(WatingPostMatch) 등 경기 상태에 대한 부가 함수를 지원한다.
- AGameModeBase
게임 모드에서 결정하는 규칙(Rule)이란?
- 진행할 게임의 전반적인 규칙을 의미한다. 게임의 전반적인 규칙에 대해 LOL로 예를 들면 아래와 같은 규칙들을 의미한다.
- 한 팀에 팀원은 5명. 레드팀과 블루팀이 있다.
- 캐릭터는 각 팀의 우물에서 스폰되며, 시작 시 500 골드(맞나?)를 받는다.
- 상대 진영의 넥서스를 먼저 파괴하거나, 상대가 항복을 하면 승리한다.
- 대충 이런 식으로 게임 모드에서 정하는 규칙은 튜토리얼에서 이야기할 법한 게임 규칙이라 보면 된다. 이를 정리해서 주요 기능을 알아보면 아래와 같다.
- 주요 기능
- 플레이어가 게임에 진입하는 방식 처리(Ex. 게임 시작 시 스폰 위치 결정)
- 액터 간의 상호작용 발생 시 처리하는 방식 처리
- 최대 플레이어 또는 관전자 수 조절
- 게임 플레이 도중 일시 정지(Pause) 처리
- 게임 시작 시 시네마틱 모드 처리
클라이언트 측은 절대 게임 모드가 존재하거나 접근하려고 하면 안 된다.
- 이게 항상 강조되는 거 같던데, 어찌 보면 당연한 거다. 게임의 규칙을 관리하는 객체를 클라이언트에게 노출하면 얼마나 끔찍한 일들이 일어나겠는가? 실제 회사 운영 방법 전반을 고객한테 오픈하는 것과 동일한 것이다.
여러 게임 모드의 활용
- 게임 모드를 활용하면 같은 맵에서 여러 규칙으로 진행되는 게임들을 만들 수 있다. 맵에서 사용하는 게임 모드를 교체하면 다른 게임 규칙이 적용되기 때문에, 원하는 방식에 맞게 AGameMode(또는 AGameModeBase)를 상속받아 게임 모드를 만든다면, 게임 형식, 미션 유형, 승리 조건 등을 바꿔 데스매치, 폭파 임무, 정령 전 등 다양한 게임을 즐길 수 있다.
- (참고로, 게임 모드는 여러 개를 동시에 사용할 수도 있다.)
게임 모드의 프로퍼티(Property)

- 게임 모드가 클라이언트와 서버 간 통신에 핵심적인 역할을 하는 만큼, 다양한 클래스 프로퍼티들을 가지고 있게 된다. 이 중에서 에디터로 노출되는 프로퍼티는 위 사진처럼 구성되어 있다.
- 디폴트 폰 클래스(APawn)
- HUD 클래스(AHUD)
- 플레이어 컨트롤러 클래스(APlayerController)
- 게임 스테이트 클래스(AGameState)
- 플레이어 스테이트 클래스(APlayerState)
- 관전자 클래스(ASpectatorPawn)
디폴트 폰 클래스(APawn)

- 게임에 참가한 플레이어의 기본 캐릭터 클래스이다.
- 폰(Pawn)은 컨트롤러로 조종할 수 있는 액터(Actor)를 의미한다.
- 게임 플레이 모드에 진입하면 컨트롤러에 빙의(Possess)된 디폴트 폰이 스폰된다. 디폴트 폰은 빙의된 컨트롤러의 종류에 따라 플레이어가 직접 조종할 수도, AI가 조종할 수도 있다.
- 게임에 존재하는 모든 플레이어가 같은 몸뚱이(?)를 사용한다면 디폴트 폰 클래스를 사용해도 무방하지만, 플레이어마다 생김새부터 선택한 캐릭터의 메커니즘까지 크게 다르다면, 디폴트 폰 클래스를 비워두고 따로 생성하는 로직을 갖게 된다.
HUD 클래스(Head-Up Display, AHUD)


- HUD는 플레이어 화면 가장 앞에 표시되는 UI로, 킬로그, Hp 바 등 게임 내 오브젝트에 가려지지 않는 UI를 표현할 때 사용한다.
- 전통적으로 HUD는 유저와의 상호작용(Ex. 클릭, 호버)을 하지 않는 UI를 의미하기 때문에, AHUD 클래스가 표시하는 UI들도 유저와의 상호작용이 안된다. 그런데 시간이 지나면서 상호작용이 가능한 HUD가 필요해지기도 했고, UE4에 AHUD보다 좋은 UMG(Unreal Montion Graphic)라는 새로운 UI 제작 툴이 개발되면서, UMG로 HUD를 만드는 것이 추세가 되었다. 따라서, 요즘에는 GameMode에 AHUD 클래스 프로퍼티를 비워두고 UMG로 만든 HUD 위젯을 PlayerController에 넣어놓고 BeginPlay에 뷰포트에 추가하는 방식으로 진행한다.
플레이어 컨트롤러 클래스(APlayerController)

- 플레이어의 입력(Input)과 카메라, UI, 서버 통신을 담당하는 플레이어 대표 객체.
- 클라이언트, 서버 양쪽에 존재하지만 권한은 다르다.
- 주요 기능
- 클라이언트 측 PC: 입력을 처리하고, HUD/UI를 제어한다.
- 서버 측 PC: 클라이언트의 명령을 수신하고 검증한다.
- PlayerController는 네트워크에서 플레이어의 세션 단위를 나타내며, RPC(Remote Procedure Call)를 통해 서버와 클라이언트 간 요청을 주고받는다.
게임 상태 클래스(AGameState)

- 현재 진행 중인 게임의 상태 데이터를 관리하는 “공유” 컨테이너 객체
- GameMode때와 똑같이 AGameStateBase와 AGameState가 있는데, AGameStateBase는 기본 상태를, AGameState는 좀 더 고수준의 게임 정보(Ex. 스코어, 플레이어 목록 등)를 포함한다.
- AGameState는 서버에서 생성되며, 모든 클라이언트에게 복제(Replicate)된다.
- 주요 기능
- Replicated 변수를 통해 클라이언트와 동기화
- 현재 매치 상태(MatchState) 관리
- 연결된 모든 APlayerState 목록 관리(PlayerArray)
- AGameState가 저장하는 상태 데이터 예시
- 게임 플레이 시간
- 접속한 플레이어 목록
- 각 플레이어의 상태(APlayerState)
- 게임 시작 여부
- 현재 GameMode의 기반 클래스
플레이어 상태 클래스(APlayerState)

- 각 플레이어의 상태 데이터를 관리하는 “공유” 컨테이너 객체
- APlayerState는 서버에서 생성되며, 모든 클라이언트에게 복제(Replicate)된다.
- 각 클라이언트는 모든 플레이어의 APlayerState를 가지고 있는다. 예를 들어, 게임에 참여 중인 다른 플레이어가 5명이라면, 각 클라이언트는 본인 APlayerState까지 포함해서 총 6개의 APlayerState가 존재하게 된다.
- APlayerController가 “플레이어의 의식”이라면, APlayerState는 “플레이어의 신분증”과 같은 역할이다.
- APlayerState가 저장하는 상태 데이터 예시
- 플레이어 점수
- 플레이어 이름
- 소속 팀
APlayerState에 좀 더 알아보기: 캐릭터에 종속되지 않는 게임 정보는 어떻게?
- 인벤토리, 골드와 같은 정보는 캐릭터가 죽어도 유지되어야 하는 정보이다. 물론 원본 데이터는 서버가 가지고 있어 데이터 자체가 사라지진 않겠지만 Pawn 클래스 안에서 관리한다면(또는 부착된 전용 ActorComponent에서 관리한다면), Pawn을 리스폰할 때마다 불필요한 복제가 발생한다. 그래서 “캐릭터 인스턴스의 생명주기로부터 자유로우면서 소유한 클라이언트에게만 복제되는 것”을 원했다.
- 후보군은 여러 개 있다. 1) 맘에 들지 않지만 가능은 한 ActorComponent 부착방식, 2) 소유한 클라이언트에게만 복제되는 APlayerController, 3) 모든 클라이언트에게 복제되는 APlayerState 등이 있다. 그럼에도 APlayerState가 가장 적합하다고 생각하는 이유는 아래와 같다.
- ActorComponent 방식은 위에서 말했듯 비효율적이다.
- APlayerController는 복제에 대해선 안전하지만, 플레이어의 “제어(Control)”를 하기 위한 객체이므로 맥락상 데이터를 저장하는 공간으로 쓰는 건 좀 그렇다.
- APlayerState는 이름부터 플레이어의 상태를 저장하는 객체이다. Pawn과 상관없이 접속한 플레이어의 정보를 담기에 적합한 위치이다.
APlayerState의 문제점: 모든 클라이언트에게 내 APlayerState가 복제된다.
- 내 APlayerState는 현재 게임 내 존재하는 모든 플레이어의 클라이언트에 복제가 된다. 비공개 데이터를 다른 클라이언트가 가지고 있다면, 이건 문제가 있다.
- 다행히도 APlayerState에서 소유한 클라이언트에게만 복제하는 방식이 존재한다. 아래와 같이 DOREPLIFETIME_CONDITION(..., GoldAmount, COND_OwnerOnly)를 사용했다면, 이 변수의 값은 해당 APlayerState를 소유한 클라이언트에게만 복제된다.
UPROPERTY(ReplicatedUsing = OnRep_PrivateData, ReplicatedCondition = COND_OwnerOnly)
FString PrivateData;
DOREPLIFETIME_CONDITION(..., GoldAmount, COND_OwnerOnly)
- 즉, 복제는 논리적으로 막을 수 있다는 것. 하지만 해당 변수 자체가 없는 상태로 APlayerState를 만들지는 않는다. 그래서 다른 클라이언트에 존재하는 내 APlayerState는 값이 없는 빈 깡통인 상태인 비공개 변수들이 존재하게 된다.
관전자 클래스(Spectator Class)

- 플레이어가 게임에 직접 참여하지 않고 관전자/옵서버 역할로 참여할 때 필요한 기능을 제공하는 클래스.
- ex. Free Cam, 각 플레이어 시점 등등…
GameMode 관련 클래스 관계도

간단하게 알아보는 플레이어 입장 과정
- 서버가 새로 연결된 플레이어의 APlayerController 초기화
- 플레이어가 월드에 입장하기 위한 세팅 시작
- 폰 초기화
- 플레이어 컨트롤러를 폰에 빙의시킴
- -완-
간단하게 알아보는 게임 진행 중 과정
[클라이언트 입력] → APlayerController → (서버 통신)
↓
AGameMode (게임 규칙)
↓
AGameState (현재 상태)
↓
APlayerState (플레이어별 데이터)
↓
APawn (물리적 표현)
추가로 알면 좋은 네트워크 관련 객체들
AGameSession
- 게임 서버의 세션 매니저
- 서버 전용 클래스이며, 클라이언트에는 존재하지 않는다.
- 주요 기능
- 플레이어 접속 허용/거부 (ApproveLogin)
- 최대 플레이어 수 관리 (MaxPlayers)
- 세션 시작/종료 관리(HandleMatchHasStarted, HandleMatchHasEnded)
AGameNetworkManager
- 네트워크 품질 제어 및 성능 모니터링.
- 서버 성능과 네트워크 트래픽을 관리하는 엔진 내부 관리자 역할을 한다.
- 서버 전용 클래스이며, 클라이언트에는 존재하지 않는다.
- 주요 기능
- 클라이언트의 Ping, 패킷 손실, 속도 제한 모니터링
- 서버의 대역폭 제한, 업데이트 빈도 조절
- 딜레이가 심한 클라이언트 감지 및 제어(치트 감지, 움직임 예측에 적용)
