PART 6 · 강의 4/8

Listen Server 아키텍처

원신 스타일 Co-op 게임을 위한 호스트 기반 아키텍처

01

Listen Server 아키텍처

호스트 플레이어가 서버 역할 겸임

Listen Server는 한 플레이어가 호스트(서버+클라이언트)가 되고, 다른 플레이어들이 게스트로 접속하는 구조입니다. 원신, 다크소울의 Co-op 시스템이 이 방식입니다.

Listen Server 특징
  • 호스트 월드 — 호스트의 게임 진행 상황이 기준
  • 호스트 이점 — 호스트는 레이턴시 0ms
  • 세션 의존성 — 호스트 종료 시 모든 게스트 연결 해제
  • 인원 제한 — 보통 2-4인 소규모
원신 스타일 Co-op

호스트 플레이어의 월드에 게스트가 입장합니다. 퀘스트, 아이템 획득 등은 호스트 기준으로 처리됩니다.

02

Co-op GameMode 구현

Listen Server 전용 GameMode

CoopGameMode.h #pragma once #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "CoopGameMode.generated.h" UCLASS() class MYGAME_API ACoopGameMode : public AGameModeBase { GENERATED_BODY() public: ACoopGameMode(); // 최대 4인 제한 static const int32 MAX_PLAYERS = 4; virtual void PostLogin(APlayerController* NewPlayer) override; virtual void Logout(AController* Exiting) override; // 입장 권한 시스템 UFUNCTION(BlueprintCallable) bool CanPlayerJoin(APlayerController* RequestingPlayer); protected: // 월드 상태 동기화 void SyncWorldStateToGuest(APlayerController* Guest); };
CoopGameMode.cpp #include "CoopGameMode.h" ACoopGameMode::ACoopGameMode() { // Listen Server 모드 설정 bUseSeamlessTravel = true; } void ACoopGameMode::PostLogin(APlayerController* NewPlayer) { Super::PostLogin(NewPlayer); // 인원 제한 체크 if (GetNumPlayers() > MAX_PLAYERS) { // 세션 가득 참 - 킥 if (GameSession) { GameSession->KickPlayer( NewPlayer, FText::FromString(TEXT("Session Full"))); } return; } // 게스트에게 호스트 월드 상태 동기화 if (!NewPlayer->HasAuthority()) { SyncWorldStateToGuest(NewPlayer); } UE_LOG(LogTemp, Log, TEXT("Player joined. Total: %d"), GetNumPlayers()); } void ACoopGameMode::Logout(AController* Exiting) { Super::Logout(Exiting); UE_LOG(LogTemp, Log, TEXT("Player left. Remaining: %d"), GetNumPlayers() - 1); } void ACoopGameMode::SyncWorldStateToGuest(APlayerController* Guest) { // 호스트의 퀘스트 진행도, 열린 지역 등 동기화 if (ACoopPlayerController* CoopPC = Cast<ACoopPlayerController>(Guest)) { // Client RPC로 월드 상태 전송 // CoopPC->Client_ReceiveWorldState(HostWorldState); } } bool ACoopGameMode::CanPlayerJoin(APlayerController* RequestingPlayer) { // 친구 목록 체크, 비밀번호 확인 등 return GetNumPlayers() < MAX_PLAYERS; }
03

세션 생성 및 참가

UGameplayStatics를 활용한 세션 관리

세션 호스팅 // 호스트로 게임 시작 (Listen Server) void UGameManager::HostGame(FString MapName) { UWorld* World = GetWorld(); if (!World) return; // "listen" 옵션으로 Listen Server 시작 FString URL = MapName + TEXT("?listen"); World->ServerTravel(URL); } // 게스트로 참가 void UGameManager::JoinGame(FString IPAddress) { APlayerController* PC = UGameplayStatics::GetPlayerController( GetWorld(), 0); if (!PC) return; // IP 주소로 직접 연결 PC->ClientTravel(IPAddress, TRAVEL_Absolute); }
역할 확인 // 현재 역할 확인 void CheckNetworkRole(AActor* Actor) { if (Actor->HasAuthority()) { // 서버 (Listen Server의 호스트 또는 Dedicated Server) UE_LOG(LogTemp, Log, TEXT("I am the Server")); } if (Actor->GetLocalRole() == ROLE_Authority) { UE_LOG(LogTemp, Log, TEXT("Authority role")); } // Listen Server 호스트 확인 UWorld* World = Actor->GetWorld(); if (World && World->GetNetMode() == NM_ListenServer) { UE_LOG(LogTemp, Log, TEXT("Running as Listen Server")); } }
NetMode 유형

NM_Standalone - 싱글플레이어
NM_DedicatedServer - 전용 서버
NM_ListenServer - 호스트
NM_Client - 게스트

04

호스트 마이그레이션 (심화)

호스트 종료 시 처리

Listen Server의 큰 단점은 호스트가 종료하면 모든 게스트가 연결 해제된다는 것입니다. 완전한 호스트 마이그레이션은 복잡하지만, 기본적인 처리는 가능합니다.

연결 해제 처리 // PlayerController.h virtual void OnNetCleanup(UNetConnection* Connection) override; // PlayerController.cpp void AMyPlayerController::OnNetCleanup(UNetConnection* Connection) { Super::OnNetCleanup(Connection); // 연결이 해제되면 메인 메뉴로 이동 if (GetLocalRole() == ROLE_AutonomousProxy) { // 게스트가 호스트와의 연결을 잃음 UGameplayStatics::OpenLevel( this, TEXT("MainMenu")); } } // 호스트 연결 해제 감지 void AMyGameState::HandleHostDisconnect() { // 호스트가 종료됨을 UI에 표시 if (AMyHUD* HUD = Cast<AMyHUD>( GetWorld()->GetFirstPlayerController()->GetHUD())) { HUD->ShowHostDisconnectedMessage(); } }
호스트 마이그레이션의 복잡성

완전한 호스트 마이그레이션(다른 게스트가 새 호스트가 됨)은 모든 게임 상태를 새 호스트에게 전송하고 재구성해야 하므로 매우 복잡합니다. 원신 같은 게임도 호스트가 나가면 세션이 종료됩니다.

SUMMARY

핵심 요약

  • Listen Server — 호스트 플레이어가 서버와 클라이언트 역할을 동시에 수행
  • ?listen 옵션 — ServerTravel 시 이 옵션으로 Listen Server 시작
  • HasAuthority() — 서버인지 확인하는 핵심 함수
  • GetNetMode() — 현재 네트워크 모드 확인
  • 호스트 종료 처리 — OnNetCleanup에서 연결 해제 시 로직 처리
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: Listen Server 기본 설정

DefaultEngine.ini에서 Listen Server를 설정하고, 호스트 플레이어가 서버 겸 클라이언트로 동작하는 환경을 구축하세요. 호스트와 일반 클라이언트의 HasAuthority() 차이를 확인하세요.

실습 2: 호스트 마이그레이션 대비

호스트 플레이어가 게임을 떠날 때의 시나리오를 처리하세요. GameSession의 HandleDisconnect를 오버라이드하고, 세이브 데이터를 저장한 후 다른 플레이어에게 알림을 보내세요.

심화 과제: Listen Server RPG Co-op 아키텍처

2-4인 Co-op RPG를 Listen Server로 구현하세요. 호스트의 로컬 지연이 0인 이점과 추가 CPU 부하의 트레이드오프를 고려하여, 전투/인벤토리/퀘스트의 Server Authority 정책을 설계하세요.