다같이 모여서 게임을 해봤는데 로비에 5명이 모여 게임을 시작하면 게임 맵에서 술래/숨는 사람 역할이 배정될 때 가끔 한두 명이 역할도 없고 캐릭터도 없이 덩그러니 떨어져 있음
혼자 PIE로 돌릴 땐 멀쩡했는데 팀원들과 EOS로 붙어서 테스트하면 랜덤하게 터짐
// FDLobbyGameMode::TryStartMatch
if (GameState->PlayerArray.Num() < MinPlayersToStart) return; // 3명 미만이면 무시
GetWorld()->ServerTravel(NextLevelName);
ServerTravel은 서버가 접속자 전원을 데리고 다음 레벨로 이동하는 엔진 함수
여기까지는 문제없음 모두가 데려가짐
현재 로비 게임모드는 bUseSeamlessTravel을 켜지 않았어서 로비 -> 게임은 하드 트래블
하드 트래블에서는 각 클라이언트가 접속을 완전히 끊었다가 새 맵을 로드한 뒤 서버에 다시 붙는다
즉 클라들은 한 명씩, 제각각의 속도로 재접속하는 것
한마디로 로비 -> 게임 맵으로 ServerTravel 후 서버가 클라 재접속을 기다리지 않고 고정 타이머로 역할 배정을 해버려서 느리게 접속한 플레이어가 역할 없이 게임에 남음
서버는 새 맵이 뜨자마자 StartPlay가 돈다
// FDGameMode::StartPlay
GetWorldTimerManager().SetTimer(PhaseTimerHandle, this, &AFDGameMode::AssignRoles, 5.f, false);
맵 뜨고 5초 뒤에 역할 배정
누군가의 인터넷이 느리거나 EOS 릴레이 핸드셰이크가 삐끗해서 5초 안에 재접속을 못 끝내면 그 순간 AssignRoles가 도는데 PlayerArray에는 그 사람이 없는 것
// AssignRoles는 지금 접속해 있는 사람만 순회
TArray<APlayerState*> Players = GameState->PlayerArray; // ← 재접속 늦은 사람은 빠져 있음
const int32 TaggerIndex = FMath::RandRange(0, Players.Num() - 1);
게임 맵이 정확히 몇 명이 다 와야 하는지를 알아야 함
그래야 "3명 있는데 2명은 오는 중"과 "3명이 전부"를 구분할 수 있음
GameMode, GameState, PlayerState는 레벨이 바뀌면 전부 파괴되고 새로 생성됨
레벨을 건너 살아남는 객체는 GameInstance 하나뿐
전체 흐름
- 로비: 시작이 확정되는 순간(ServerTravel 직전)의 PlayerArray.Num()을 GameInstance에 실어둔다
- 게임 맵: StartPlay에서 그 값을 꺼내 그 인원이 다 모일 때까지 폴링으로 기다린 뒤 역할 배정에 들어간다
단, 한 명이 계속 안 와도 멈추지 않도록 최대 대기시간(타임아웃) 안전장치를 둔다 - 덤: 매치가 시작(로스터 확정)된 뒤 접속하려는 난입자는 PreLogin에서 거부
코드
1. GameInstance
// FDGameInstance.h
// GameInstance는 레벨이 바뀌어도 안 죽으니 이 값이 그대로 게임 맵까지 건너감
int32 ExpectedPlayerCount = 0;
2. 로비 : ServerTravel 직전에 인원 확정
// FDLobbyGameMode::TryStartMatch, ServerTravel 바로 위
const int32 StartCount = GameState->PlayerArray.Num();
if (UFDGameInstance* GI = GetGameInstance<UFDGameInstance>()) // 월드의 GameInstance를 타입 캐스트해 꺼냄
{
GI->ExpectedPlayerCount = StartCount;
}
GetWorld()->ServerTravel(NextLevelName);
3. 게임 맵 : 고정 타이머를 전원 대기 폴링으로 교체
void AFDGameMode::StartPlay()
{
Super::StartPlay();
if (const UFDGameInstance* GI = GetGameInstance<UFDGameInstance>())
{
ExpectedPlayerCount = GI->ExpectedPlayerCount; // 로비가 실어보낸 목표 인원 회수
}
PlayerWaitElapsed = 0.f;
// 5초 고정 대신 전원 모일 때까지 0.5초 간격 반복 폴링 (SetTimer 마지막 인자 true = 반복)
GetWorldTimerManager().SetTimer(
WaitTimerHandle, this, &AFDGameMode::WaitForPlayers, PlayerWaitInterval, true);
}
void AFDGameMode::WaitForPlayers()
{
PlayerWaitElapsed += PlayerWaitInterval;
const int32 Current = GameState->PlayerArray.Num();
const bool bEveryoneHere = (ExpectedPlayerCount > 0) && (Current >= ExpectedPlayerCount);
const bool bTimedOut = (PlayerWaitElapsed >= MaxPlayerWaitTime);
// 아직 덜 모였고 타임아웃도 아니면 다음 폴링까지 대기
if (!bEveryoneHere && !bTimedOut)
{
return;
}
// 시작 처리
bRosterLocked = true;
GetWorldTimerManager().ClearTimer(WaitTimerHandle);
AssignRoles();
}
4. 난입 차단 : PreLogin
void AFDGameMode::PreLogin(const FString& Options, const FString& Address,
const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{
// PreLogin은 PostLogin보다 먼저 불리는 입장 심사대
// ErrorMessage에 값을 채우면 엔진이 그 접속을 승인하지 않음
if (bRosterLocked)
{
ErrorMessage = TEXT("Match already in progress");
return;
}
Super::PreLogin(Options, Address, UniqueId, ErrorMessage);
}
락이 걸린 뒤(=매치 시작 후)의 접속만 튕겨냄
'프로젝트 > Funny Or Die (멀티플레이 술래잡기 게임)' 카테고리의 다른 글
| FunnyorDie - 멀티플레이 술래잡기 게임 개발 정리 (0) | 2026.07.20 |
|---|---|
| 포획된 하이더를 사라지게 하고 살아있는 사람 시점으로 넘기기 (0) | 2026.07.18 |
| 게임 시작 UI 디자인 (0) | 2026.07.18 |
| 순위창 UI 제작 (0) | 2026.07.18 |
| 순위표 WBP 생성 (0) | 2026.07.18 |
