기존에는 AFDGameMode 하나에 PostLogin에서 5초 타이머로 AssignRoles()를 호출하는 구조
여기에 로비 화면을 추가하려고 하다 보니 Lobby에서 Start 버튼 누르면 Demo 레벨로 이동한다는 로직을 어디에 구현해야 할지 모르겠다는 생각이 들었음
처음엔 하나의 AFDGameMode 안에서 현재 레벨 이름을 보고 분기하는 방식을 생각했는데 레벨이 ServerTravel로 전환되면 그 시점에 존재하던 GameMode 인스턴스는 파괴되고, 새 레벨에 새 GameMode 인스턴스가 생긴다는 걸 알게됨
그래서 레벨마다 전담 GameMode를 따로 두는 구조로 설정
StartMaps -> AFDStartGameMode (메뉴, 거의 로직 없음)
LobbyMaps -> AFDLobbyGameMode (인원 체크, 방장 관리, Start 처리)
DemoMaps -> AFDGameMode (기존 본게임 로직 그대로 유지)
World Settings에서 레벨별로 GameMode Override를 지정해두면 Unreal이 알아서 그 레벨에 맞는 GameMode를 띄워줌
에디터 작업
- StartMaps World Settings -> GameMode Override -> BP_StartGameMode
- LobbyMaps World Settings -> GameMode Override -> BP_LobbyGameMode
로비 GameMode: 방장 전용 Start, 서버 권위 검증
- 누가 들어오고 나가는지 추적
- 첫 입장자를 방장으로 지정
- 방장이 Start를 누르고 인원이 충분하면 본게임으로 이동
bIsHost는 PlayerState에 Replicated로 선언해 서버가 정한 값을 클라이언트가 그대로 받아 화면에만 반영하게 했고 Start 요청이 들어왔을 때 GameMode가 다시 한번 진짜 방장이 보낸 요청이 맞는지 검증하도록 함
void AFDLobbyGameMode::TryStartMatch(APlayerController* Requester)
{
const AFDPlayerState* FDPS = Requester ? Requester->GetPlayerState<AFDPlayerState>() : nullptr;
if (!FDPS || !FDPS->bIsHost) return; // 방장 아니면 그냥 무시
if (GameState->PlayerArray.Num() < MinPlayersToStart) return; // 인원 부족도 무시
GetWorld()->ServerTravel(NextLevelName);
}
클라이언트(위젯)는 PlayerController를 거쳐 Server RPC로 이 함수를 호출할 뿐 실제 판단은 전부 서버에서 이뤄진다
클라이언트가 UI를 변조해서 요청을 보내더라도 서버가 다시 검증하기 때문에 안전하다
방장이 도중에 나가는 경우도 처리했다
Logout에서 나간 사람이 방장이었는지 확인하고 맞다면 남은 사람 중 한 명에게 방장을 위임한다
이때 Super::Logout() 호출 이후에 위임 로직을 둬야 한다는 게 중요함
PlayerArray에서 나가는 사람을 실제로 제거하는 처리가 부모 클래스 안에서 일어나기 때문에 순서를 바꾸면 막 나간 사람이 다시 방장으로 뽑히는 버그가 생긴다

PlayerController도 레벨별로 분리
- AFDStartPlayerController : 메뉴 위젯만
- AFDLobbyPlayerController : Start RPC 처리
- AFDPlayerController (본게임) : 여러가지 로직들
알게된 것
- GameInstance: 닉네임, 매칭 설정, 다음 맵으로 이동하는 트리거, 로딩화면 표시/해제 같은 것
- 각 레벨의 GameMode: 그 레벨 안에서만 의미 있는 규칙 (Scouting 60초, InGame 300초 등)
- PlayerState: 같은 매치 세션 안에서 레벨 넘어가도 유지되는 개인 데이터 (RoleTag, bIsAlive 등)
단, Seamless Travel 켜져 있을 때만
로딩화면은 보통 GameInstance가 레벨 이동을 시작/완료하는 시점(PreLoadMap/PostLoadMap 델리게이트)에 맞춰 위젯을 띄웠다 내리는 식으로 구현됨
- 포인터/레퍼런스로만 다룰 때 (AFDStartPlayerController*, TWeakObjectPtr<APlayerState>) -> 전방선언으로 충분
- 멤버에 접근하거나(FDPS->bIsHost), Cast<>하거나, 상속하거나, 값으로 객체를 만들 때 -> 풀 #include 필요
'프로젝트 > Funny Or Die (멀티플레이 술래잡기 게임)' 카테고리의 다른 글
| 정찰 단계(Scouting) 구현하기 (0) | 2026.07.01 |
|---|---|
| Seamless Travel 이후 캐릭터 입력이 먹지 않던 문제 (0) | 2026.07.01 |
| 로비 UI 협업 (0) | 2026.06.30 |
| 술래/숨는자 역할 배정 - 멀티플레이어 캐릭터 분기 스폰 구현 (0) | 2026.06.30 |
| 기술 설계 초안 (0) | 2026.06.26 |
