이번 작업에서는 로비에서 팀 전원이 Ready 상태가 되면 매치메이킹 큐에 팀을 올리고, 큐에 쌓인 팀 수가 임계치에 도달하면 GameSession을 시작하도록 서버 주도 로직을 직접 구현했습니다.
// filepath: Source/CrimsonMoon/Private/Game/CMGameStateLobby.cpp
void ACMGameStateLobby::ReadyMatchmaking(FCMLobbyTeamInfo* TeamInfo)
{
if (!TeamInfo)
{
UE_LOG(LogTemp, Warning, TEXT("ACMGameStateLobby::ReadyMatchmaking - TeamInfo is null"));
return;
}
if (!HasAuthority())
{
UE_LOG(LogTemp, Warning, TEXT("ACMGameStateLobby::ReadyMatchmaking - Should be called on server only"));
return;
}
// 이미 대기열에 있으면 무시
if (MatchmakingQueueTeamIDs.Contains(TeamInfo->TeamID))
{
UE_LOG(LogTemp, Verbose, TEXT("ACMGameStateLobby::ReadyMatchmaking - Team %d already queued"), TeamInfo->TeamID);
return;
}
// 준비 수 증가 (과증가 방지)
TeamInfo->ReadyPlayerCount = FMath::Clamp(TeamInfo->ReadyPlayerCount + 1, 0, TeamInfo->CurrentPlayerCount);
if (TeamInfo->CurrentPlayerCount > 0 && TeamInfo->ReadyPlayerCount >= TeamInfo->CurrentPlayerCount)
{
UE_LOG(LogTemp, Log, TEXT("ACMGameStateLobby::ReadyMatchmaking - Team %d is fully ready (%d/%d)"), TeamInfo->TeamID, TeamInfo->ReadyPlayerCount, TeamInfo->CurrentPlayerCount);
EnterMatchmakingQueue(TeamInfo);
}
else
{
UE_LOG(LogTemp, Log, TEXT("ACMGameStateLobby::ReadyMatchmaking - Team %d ready progress: %d/%d"), TeamInfo->TeamID, TeamInfo->ReadyPlayerCount, TeamInfo->CurrentPlayerCount);
}
}
void ACMGameStateLobby::EnterMatchmakingQueue(FCMLobbyTeamInfo* TeamInfo)
{
if (!TeamInfo)
{
UE_LOG(LogTemp, Warning, TEXT("ACMGameStateLobby::EnterMatchmakingQueue - TeamInfo is null"));
return;
}
if (!HasAuthority())
{
UE_LOG(LogTemp, Warning, TEXT("ACMGameStateLobby::EnterMatchmakingQueue - Should be called on server only"));
return;
}
if (TeamInfo->TeamID < 0)
{
UE_LOG(LogTemp, Warning, TEXT("ACMGameStateLobby::EnterMatchmakingQueue - Invalid TeamID"));
return;
}
if (!MatchmakingQueueTeamIDs.Contains(TeamInfo->TeamID))
{
MatchmakingQueueTeamIDs.Add(TeamInfo->TeamID);
UE_LOG(LogTemp, Log, TEXT("ACMGameStateLobby::EnterMatchmakingQueue - Team %d entered queue. QueueSize=%d"), TeamInfo->TeamID, MatchmakingQueueTeamIDs.Num());
}
else
{
UE_LOG(LogTemp, Verbose, TEXT("ACMGameStateLobby::EnterMatchmakingQueue - Team %d already in queue. QueueSize=%d"), TeamInfo->TeamID, MatchmakingQueueTeamIDs.Num());
}
// 기준 팀 수에 도달하면 게임 세션 시작
if (MatchmakingQueueTeamIDs.Num() >= RequiredTeamsToStart)
{
StartGameSession();
// 간단 구현: 시작 후 대기열 초기화 (중복 트리거 방지)
MatchmakingQueueTeamIDs.Empty();
}
}
void ACMGameStateLobby::StartGameSession()
{
UE_LOG(LogTemp, Log, TEXT("ACMGameStateLobby::StartGameSession - Starting game session (RequiredTeamsToStart=%d)"), RequiredTeamsToStart);
}
// filepath: Source/CrimsonMoon/Private/Game/CMGameStateLobby.cpp
void ACMGameStateLobby::AddTeamToLobby(ACMPlayerStateLobby* NewPlayer)
{
FCMLobbyTeamInfo NewTeam;
NewTeam.TeamID = TeamIndexCounter;
NewTeam.CurrentPlayerCount = 1;
NewTeam.TeamMembers.Add(NewPlayer);
NewTeam.TeamLeader = NewPlayer;
// 먼저 맵에 저장하여 영속적인 스토리지에 배치한 후, 그 참조를 캐싱합니다
LobbyTeams.Add(TeamIndexCounter, NewTeam);
FCMLobbyTeamInfo& StoredTeam = LobbyTeams[TeamIndexCounter];
NewPlayer->SetCachedTeamInfo(&StoredTeam);
PlayerTeamMapping.Add(NewPlayer->GetPendingNickname(), StoredTeam.TeamID);
UE_LOG(LogTemp, Log, TEXT("ACMGameStateLobby::AddTeamToLobby - Added new team with ID %d"), StoredTeam.TeamID);
TeamIndexCounter++;
}
void ACMGameStateLobby::JoinTeam(const FName& JoinedPlayer, const FName& JoiningPlayer)
{
if (ACMPlayerControllerLobby* PC = CurrentLookingPlayers[JoiningPlayer])
{
int32 TeamIndex = PlayerTeamMapping[JoinedPlayer];
if (LobbyTeams.Contains(TeamIndex))
{
FCMLobbyTeamInfo& TeamInfo = LobbyTeams[TeamIndex];
TeamInfo.CurrentPlayerCount++;
if (ACMPlayerStateLobby* PS = PC->GetPlayerState<ACMPlayerStateLobby>())
{
TeamInfo.TeamMembers.Add(PS);
PS->SetCachedTeamInfo(&TeamInfo);
PS->SetCanBeInvited(false);
}
UE_LOG(LogTemp, Log, TEXT("ACMGameStateLobby::JoinTeam - Player %s joined team %d"), *JoinedPlayer.ToString(), TeamIndex);
}
}
}

팀 전원이 Ready가 되면 큐에 진입하고, 큐의 팀 수가 임계치에 도달하면 세션을 시작하는 핵심 사용자 여정을 서버 권한, 중복 방지, 복제 타이밍을 고려하여 직접 구현했습니다. 현재 구조는 확장에 용이하며, Ready 취소/퇴장 처리와 실제 세션 전환(맵 트래블)을 보강하면 실전 매치메이킹 파이프라인으로 무리 없이 연결될 것으로 판단됩니다.