리슨 서버 컨트롤러 구조

언리얼 거토
|2025. 3. 20. 15:52

1. 리슨 서버

  • 리슨 서버(Listen Server)는 게임을 호스팅하면서 동시에 플레이어로 참여하는 서버
  • 즉, 호스트(Host)는 서버 역할을 하면서 직접 게임도 플레이하고, 게스트(Guest)들은 이 서버에 접속하여 게임을 진행

 

2. 리슨 서버 컨트롤러 구조

 

Host: 

  • PlayerController00 (서버 컨트롤러 + 클라이언트 컨트롤러 역할)
    • 서버에 존재하는 호스트 플레이어의 컨트롤러.
    • 동시에 서버 관리 기능도 수행.
    • 게스트 플레이어가 접속하면 해당 플레이어의 PlayerController를 추가로 생성함.
  • PlayerController01 (게스트용 컨트롤러 생성)
    • 게스트가 접속하면 서버에서 해당 플레이어를 위한 PlayerController를 생성.
    • 클라이언트와 연결(Net Connection) 및 PlayerState를 관리.
    • RPC 및 상태 동기화를 처리함.

Guest:

  • PlayerController01 (클라이언트 컨트롤러)
    • 게스트 플레이어의 입력을 처리.
    • 서버의 PlayerController01과 연결되어 RPC 및 상태 동기화를 수행.

 

 

2. UI Widget을 활용한 채팅 프로그램 예시

이를 바탕으로 각 클라이언트에 채팅 위젯이 할당된 경우, 채팅을 주고 받는 과정을 살펴보겠습니다.

 

2-1. 리슨 서버 채팅 프로그램 구조

2-2. 리슨 서버 채팅 프로그램 클래스 역할

  • 각 클라이언트의 컨트롤러에는 채팅을 주고 받는 기능이 있습니다.
//BaseBallPlayerController.h
UCLASS()
class BASEBALLGAME_API ABaseBallPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    UFUNCTION(Server, Reliable, WithValidation)
    void ServerSendChatMessage(const FString& Message);
    void ServerSendChatMessage_Implementation(const FString& Message);
    bool ServerSendChatMessage_Validate(const FString& Message) { return true; }

	void OnChatMessageReceived(const FString& Message);
    virtual void BeginPlay() override;

private:
    UChatWidget* PlayerChatWidget;
    void InitializeChatWidget();
};

 

  • GameState에서는 PlayerController의 호출을 받아 모든 클라이언트에게 채팅 메세지를 BroadCast(전파)합니다.
//BaseBallGameState.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "BaseBallGameState.generated.h"

UCLASS()
class BASEBALLGAME_API ABaseBallGameState : public AGameStateBase
{
    GENERATED_BODY()

public:
    ABaseBallGameState();

    UPROPERTY()
    TArray<FString> ChatMessages;

    UFUNCTION(NetMulticast, Reliable)
    void MulticastOnChatMessageReceived(const FString& Message);
    void MulticastOnChatMessageReceived_Implementation(const FString& Message);
    
    void ServerAddChatMessage(const FString& Message);
};

 

2-3. 리슨 서버 채팅 프로그램 동작

(1). 클라이언트에서 채팅을 입력하면 Client - Server RPC를 활용하여 GameState에 전달합니다. 

//BaseBallPlayerController.cpp
void ABaseBallPlayerController::ServerSendChatMessage_Implementation(const FString& Message)
{
    ABaseBallGameState* GameState = GetWorld()->GetGameState<ABaseBallGameState>();
    if (GameState)
    {
        GameState->ServerAddChatMessage(Message);
    }
}

 

(2). GameState는 서버에서 생성되며 클라이언트와 동기화되기 때문에, NetMultiCast RPC를 호출하면 Host와 Guest 모두 해당 함수를 실행하게 됩니다. 따라서 GameState에서 채팅 메시지를 받은 후 NetMultiCast를 사용해 각 클라이언트의 PlayerController를 찾아야 할 때, Host에서는 Host의 PlayerController만, Guest에서는 Guest의 PlayerController만 찾도록 처리하면 됩니다. 이를 통해 각 클라이언트에 적절한 위젯 업데이트가 이루어집니다.

 

//BaseBallGameState.cpp
void ABaseBallGameState::MulticastOnChatMessageReceived_Implementation(const FString& Message)
{
	//각 클라이언트의 PlayerController에 캐스팅
    ABaseBallPlayerController* LocalBPC = Cast<ABaseBallPlayerController>(GetWorld()->GetFirstPlayerController());

    if (LocalBPC)
    {
        LocalBPC->OnChatMessageReceived(Message); // UI 업데이트
    }

}

 

(3). 다시 각 클라이언트의 PlayerController로 돌아와서 실제로 UI Widget 업데이트를 하는 코드입니다.

//BaseBallPlayerController.cpp
void ABaseBallPlayerController::OnChatMessageReceived(const FString& Message)
{
    if (PlayerChatWidget)
    {
        PlayerChatWidget->AddChatMessage(Message); // Update UI
    }
}

 

3. HasAuthority(), IsLocalPlayerController 관련

구분 HasAuthority() IsLocalPlayerController()
의미 서버에서 실행되는 객체인가? 내 화면을 담당하는 컨트롤러인가?
서버에서 실행되면? ✅ true ❌ false
클라이언트에서 실행되면? ❌ false ✅ true
사용 용도 서버/클라이언트 여부 판별 UI 업데이트, 로컬 플레이어 판단

 

기본적으로 HasAuthority()와 IsLocalPlayerController()는 위와 같은 판단 기준으로 각 결과값을 가지게 됩니다.

그러나 리슨 서버의 Host 컨트롤러의 경우 서버 컨트롤러의 역할과 클라이언트 컨트롤러 역할을 둘다 가집니다. 그래서 서버와 클라이언트가 분리된 전용 서버(Dedicated Server)와는 다르게 아래와 같은 결과값을 가지게 됩니다.

 

리슨 서버 컨트롤러 유형 HasAuthority() 값 IsLocalPlayerController() 값
Host 컨트롤러 ✅ true ✅ true
Guest 컨트롤러 ❌ false ✅ true

✏️ 멀티플레이 개념은 PlayerController가 전부다..