언리얼 게임플레이 프레임워크의 핵심 클래스들인 GameMode / GameState / PlayerState / PlayerController / Pawn·Character 은 각각 멀티플레이어에서 존재하는 위치가 다르다. 이번에는 이 클래스들의 복제와 존재 여부를 알아보려고 한다.
GameMode 는 게임의 규칙을 정의하는 클래스다. 무엇을 어디에 spawn 할지(GameState / PlayerController / PlayerState / HUD / default pawn 의 기본 클래스), 어느 PlayerStart 에서 pawn 을 띄울지, 팀전인지, 승리 조건이 무엇인지 등의 게임의 골격이 전부 여기서 결정된다.
GameMode의 주요한 내용들은 다음과 같다.
GameMode 가 아예 없다. 즉, 서버에만 존재하고 복제되지 않으며 클라에서 접근하면 null 이 나온다.GameMode 에 복제 변수를 둬도 클라로 안 가고, Client / Multicast RPC 도 동작하지 않는다.HasAuthority() 체크도 의미가 없다 (항상 true).GameState 등)를 경유해야 한다.| 클래스 | 특징 |
|---|---|
GameModeBase | 기본형, 클래스 지정, spawn, 입퇴장 처리 |
GameMode | base 상속 + match state(시작/종료, delayed start) 추가 |
경기 시작 / 종료를 내포하는 매치 개념이 필요 없으면 GameModeBase 로 충분하다. 시작 대기 같은 흐름이 필요하면 GameMode 를 쓴다.
GameMode 가 비복제라 클라와 공유할 상태를 담기엔 부적합하다. 그 역할을 하는 게 GameState 다. 팀 점수, 누가 어느 팀인지, 남은 시간 같은 게임 전체의 전역 상태를 담는다.
GameState의 특징은 다음과 같다.
GameState 는 복제되는 액터이기 때문에 서버와 모든 클라가 접근할 수 있다. 서버에서 복제 변수를 바꾸면 클라로 내려간다.참고로 GameMode를 쓰면 반드시 GameState 를 써야 한다. base 끼리, non-base 끼리만 호환된다.
GameState 에 팀 배열 두 개를 복제 변수로 두고, GameMode::PostLogin 에서 접속한 플레이어를 더 적은 팀에 넣으면 자동으로 균등 분배된다.
// ATestProjectGameState.h
UPROPERTY(Replicated)
TArray<TObjectPtr<APlayerController>> Team1;
UPROPERTY(Replicated)
TArray<TObjectPtr<APlayerController>> Team2;
// ATestProjectGameState.cpp
void ATestProjectGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, Team1);
DOREPLIFETIME(ThisClass, Team2);
}
void ATestProjectGameState::AddTeamMember(APlayerController* Player)
{
// 균등 배치
if (Team1.Num() > Team2.Num())
Team2.Add(Player);
else
Team1.Add(Player);
}
// ATestProjectGameMode.cpp
// 플레이어 접속 후 불리는 콜백 함수
void ATestProjectGameMode::PostLogin(APlayerController* NewPlayer)
{
Super::PostLogin(NewPlayer);
if (ATestProjectGameState* MPGameState = GetGameState<ATestProjectGameState>())
MPGameState->AddTeamMember(NewPlayer);
}

4인 접속 시 정확히 2:2 로 나뉜다. 클라에서 GameState 를 얻을 땐 UGameplayStatics::GetGameState(this) 로 가져온다 (index 가 없어서 멀티에서도 안전하다).
PlayerState 는 플레이어 한 명의 현재 정보를 담으며, 특징은 다음과 같다.
PlayerState 가 모두에게 복제되므로 클라에서 다른 인원의 점수/팀도 볼 수 있다.PlayerController 와 같은 owning net connection 을 공유한다. PS 는 PC 에 할당된 액터라 PC 의 connection 을 물려받는다. 덕분에 PlayerState 도 RPC 를 보낼 수 있다SetNetUpdateFrequency(10) 등으로 올린다.PlayerController(PC)는 Pawn에 빙의함으로써 조종할 수 있는 클래스로 특징은 다음과 같다.
Pawn / Character 은 게임 세계에서 실제로 돌아다니는 아바타이다. PC 가 빙의해서 키·마우스로 조종한다.
Pawn / Character의 특징으로는 다음과 같다.
UGameplayStatics 에는 다음과 같이 index 를 받는 static accessor 가 있다.
GetPlayerControllerGetPlayerCharacterGetPlayerPawnGetPlayerState 등등...이때 인덱스로 보통 0을 넘기는데, 문제는 이 index 0 이 머신마다 다른 대상을 가리킨다.
| 실행 환경 | GetPlayerController(.., 0) 가 가리키는 것 |
|---|---|
| Standalone | 자신 PC |
| Dedicated server | 첫 번째 클라의 PC |
| Listen host | 보장 없음 |
올바른 대상을 항상 보장받으려면 관계로 접근한다.
// index 0 — 머신마다 다른 대상
APlayerController* PC = UGameplayStatics::GetPlayerController(this, 0);
// 관계 기반 — 항상 올바른 대상
APlayerController* MyPC = Cast<APlayerController>(GetController());
자주 쓰는 관계 접근들:
| 출발 | 함수 | 도착 |
|---|---|---|
| Actor / Component | GetOwner() | 소유 액터 |
| Pawn | GetController() | 그 Pawn 의 컨트롤러 |
| Controller | GetPawn() | 그 컨트롤러의 Pawn |
| Widget / HUD | GetOwningPlayer() | owning PC |
참고로 GetGameState 는 게임당 하나뿐이라 index 가 없어 안전하다.
참고 자료
언리얼 게임플레이 프레임워크 문서: https://dev.epicgames.com/documentation/unreal-engine/gameplay-framework-quick-reference-in-unreal-engine