PART 6 - 강의 2/3
Server Travel
Seamless Travel과 맵 전환 시 데이터 유지
01
Travel 타입
Seamless vs Non-Seamless
비교
┌────────────────────┬──────────────────────┬──────────────────────┐
│ │ Non-Seamless │ Seamless │
├────────────────────┼──────────────────────┼──────────────────────┤
│ 연결 유지 │ X (재접속) │ O │
│ Actor 유지 │ X │ O (설정 시) │
│ 로딩 화면 │ 필수 │ 선택적 │
│ Transition Map │ 불필요 │ 권장 │
│ 구현 복잡도 │ 낮음 │ 높음 │
│ 사용 케이스 │ 로비-게임 전환 │ 레벨 간 이동 │
└────────────────────┴──────────────────────┴──────────────────────┘
C++
// Non-Seamless Travel (기본)
GetWorld()->ServerTravel("/Game/Maps/GameMap");
// Seamless Travel
GetWorld()->ServerTravel("/Game/Maps/NextLevel?listen", true); // bAbsolute = true
// GameMode에서 Seamless 활성화
AMyGameMode::AMyGameMode()
{
bUseSeamlessTravel = true;
}
02
Seamless Travel 구현
Actor 유지와 Transition Map
C++
// GameMode에서 유지할 Actor 지정
void AMyGameMode::GetSeamlessTravelActorList(
bool bToTransition,
TArray<AActor*>& ActorList)
{
Super::GetSeamlessTravelActorList(bToTransition, ActorList);
// 게임 상태 유지
if (AMyGameState* GS = GetGameState<AMyGameState>())
{
ActorList.Add(GS);
}
// 모든 PlayerController 유지 (기본 동작)
for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator();
It; ++It)
{
if (APlayerController* PC = It->Get())
{
ActorList.AddUnique(PC);
}
}
}
// PlayerController에서 Pawn 유지 설정
void AMyPlayerController::GetSeamlessTravelActorList(
bool bToTransition,
TArray<AActor*>& ActorList)
{
Super::GetSeamlessTravelActorList(bToTransition, ActorList);
// Pawn 유지
if (GetPawn())
{
ActorList.Add(GetPawn());
}
}
Transition Map
Seamless Travel 시 빈 Transition Map을 사용하면 구 맵 언로드와 신 맵 로드 사이에 Actor들이 임시로 보관됩니다. Project Settings에서 설정하세요.
03
Travel 간 데이터 유지
GameInstance 활용
C++
// GameInstance는 Travel을 통해 유지됨
UCLASS()
class UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
// 플레이어 세션 데이터
UPROPERTY()
TMap<FUniqueNetIdRepl, FPlayerPersistentData> PlayerDataMap;
// Travel 전 데이터 저장
void SavePlayerData(APlayerController* PC)
{
if (!PC || !PC->PlayerState)
return;
FUniqueNetIdRepl PlayerId = PC->PlayerState->GetUniqueId();
FPlayerPersistentData& Data = PlayerDataMap.FindOrAdd(PlayerId);
// 데이터 수집
if (AMyPlayerState* PS = Cast<AMyPlayerState>(PC->PlayerState))
{
Data.Score = PS->Score;
Data.Inventory = PS->Inventory;
}
}
// Travel 후 데이터 복원
void RestorePlayerData(APlayerController* PC)
{
if (!PC || !PC->PlayerState)
return;
FUniqueNetIdRepl PlayerId = PC->PlayerState->GetUniqueId();
if (FPlayerPersistentData* Data = PlayerDataMap.Find(PlayerId))
{
if (AMyPlayerState* PS = Cast<AMyPlayerState>(PC->PlayerState))
{
PS->Score = Data->Score;
PS->Inventory = Data->Inventory;
}
}
}
};
04
Travel 이벤트
Travel 전후 처리
C++
// GameMode에서 Travel 완료 후 처리
void AMyGameMode::PostSeamlessTravel()
{
Super::PostSeamlessTravel();
// 이전 맵에서 유지된 플레이어들 처리
for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator();
It; ++It)
{
if (APlayerController* PC = It->Get())
{
// 새 맵에서 스폰 위치 지정
AActor* StartSpot = FindPlayerStart(PC);
if (APawn* Pawn = PC->GetPawn())
{
Pawn->SetActorLocation(StartSpot->GetActorLocation());
}
}
}
}
// PlayerController에서 Travel 처리
void AMyPlayerController::PostSeamlessTravel()
{
Super::PostSeamlessTravel();
// UI 재설정
if (IsLocalController())
{
CreateHUD();
}
// 클라이언트에게 Travel 완료 알림
ClientTravelComplete();
}
UFUNCTION(Client, Reliable)
void AMyPlayerController::ClientTravelComplete()
{
// 로딩 화면 숨기기 등
if (UMyGameInstance* GI = GetGameInstance<UMyGameInstance>())
{
GI->HideLoadingScreen();
}
}
SUMMARY
핵심 요약
- Seamless Travel - 연결 유지하며 맵 전환, bUseSeamlessTravel 활성화
- GetSeamlessTravelActorList - Travel 시 유지할 Actor 지정
- GameInstance - Travel을 통해 유지되는 글로벌 데이터 저장소
- PostSeamlessTravel - Travel 완료 후 초기화 로직
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: Seamless Travel 구현
bUseSeamlessTravel = true로 설정하고, GetSeamlessTravelActorList()를 오버라이드하여 맵 전환 시 유지할 Actor(PlayerState, 인벤토리)를 지정하세요. 2개의 맵 간 전환을 테스트하세요.
실습 2: Non-Seamless Travel 처리
ServerTravel()로 맵 전환 시 AGameModeBase::PostLogin()에서 플레이어 데이터를 복원하는 로직을 구현하세요. SaveGame 시스템을 사용하여 전환 전 데이터를 저장하세요.
심화 과제
TransitionMap을 사용한 로딩 화면이 있는 Seamless Travel 시스템을 구현하세요. PostSeamlessTravel()에서 월드 초기화를 수행하고, 모든 클라이언트가 로딩을 완료할 때까지 게임 시작을 지연하는 동기화 로직을 구현하세요.