Listen Server 아키텍처
원신 스타일 Co-op 게임을 위한 호스트 기반 아키텍처
Listen Server 아키텍처
호스트 플레이어가 서버 역할 겸임
Listen Server는 한 플레이어가 호스트(서버+클라이언트)가 되고, 다른 플레이어들이 게스트로 접속하는 구조입니다. 원신, 다크소울의 Co-op 시스템이 이 방식입니다.
- 호스트 월드 — 호스트의 게임 진행 상황이 기준
- 호스트 이점 — 호스트는 레이턴시 0ms
- 세션 의존성 — 호스트 종료 시 모든 게스트 연결 해제
- 인원 제한 — 보통 2-4인 소규모
호스트 플레이어의 월드에 게스트가 입장합니다. 퀘스트, 아이템 획득 등은 호스트 기준으로 처리됩니다.
Co-op GameMode 구현
Listen Server 전용 GameMode
#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);
};
#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;
}
세션 생성 및 참가
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"));
}
}
NM_Standalone - 싱글플레이어
NM_DedicatedServer - 전용 서버
NM_ListenServer - 호스트
NM_Client - 게스트
호스트 마이그레이션 (심화)
호스트 종료 시 처리
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();
}
}
완전한 호스트 마이그레이션(다른 게스트가 새 호스트가 됨)은 모든 게임 상태를 새 호스트에게 전송하고 재구성해야 하므로 매우 복잡합니다. 원신 같은 게임도 호스트가 나가면 세션이 종료됩니다.
핵심 요약
- Listen Server — 호스트 플레이어가 서버와 클라이언트 역할을 동시에 수행
- ?listen 옵션 — ServerTravel 시 이 옵션으로 Listen Server 시작
- HasAuthority() — 서버인지 확인하는 핵심 함수
- GetNetMode() — 현재 네트워크 모드 확인
- 호스트 종료 처리 — OnNetCleanup에서 연결 해제 시 로직 처리
도전 과제
배운 내용을 직접 실습해보세요
DefaultEngine.ini에서 Listen Server를 설정하고, 호스트 플레이어가 서버 겸 클라이언트로 동작하는 환경을 구축하세요. 호스트와 일반 클라이언트의 HasAuthority() 차이를 확인하세요.
호스트 플레이어가 게임을 떠날 때의 시나리오를 처리하세요. GameSession의 HandleDisconnect를 오버라이드하고, 세이브 데이터를 저장한 후 다른 플레이어에게 알림을 보내세요.
2-4인 Co-op RPG를 Listen Server로 구현하세요. 호스트의 로컬 지연이 0인 이점과 추가 CPU 부하의 트레이드오프를 고려하여, 전투/인벤토리/퀘스트의 Server Authority 정책을 설계하세요.