unreal 8기

2026-6-11 TIL

왕건 2026. 6. 11. 18:56

TIL: Unreal Engine 접속 알림과 NetMulticast RPC 이해하기

오늘 배운 내용

오늘은 Unreal Engine 멀티플레이에서 플레이어가 접속했을 때 모든 클라이언트에 알림을 출력하는 흐름을 정리했다.

핵심은 GameMode, GameState, PostLogin, NetMulticast RPC의 역할을 구분하는 것이었다.

처음에는 GameMode에서 바로 Multicast RPC를 호출하면 될 것 같았다.

하지만 GameMode는 서버에만 존재하기 때문에 클라이언트에 직접 알림을 전달하는 용도로는 적절하지 않았다.

그래서 서버에서 접속을 감지하는 역할은 GameMode가 맡고, 모든 클라이언트에 알림을 전달하는 역할은 GameState가 맡는 구조로 구현해야 한다.

1. GameMode는 서버에서 접속을 감지한다

플레이어가 서버에 접속하면 GameMode의 PostLogin()이 호출된다.

GameMode는 서버에만 존재하는 클래스이기 때문에 플레이어 접속, 게임 규칙, 승패 판정과 같은 서버 권한 로직을 처리하기 좋다.

void ACXGameModeBase::PostLogin(APlayerController* NewPlayer)
{
    Super::PostLogin(NewPlayer);

    // 새 플레이어 접속 처리
}

중요한 점은 GameMode가 클라이언트에 복제되지 않는다는 것이다.

따라서 GameMode에서 플레이어 접속을 감지할 수는 있지만, GameMode 자체를 이용해 클라이언트 화면에 직접 알림을 출력하는 것은 적절하지 않다.

2. GameState는 모든 클라이언트에 복제된다

GameState는 게임 전체의 상태를 서버와 모든 클라이언트가 공유할 때 사용하는 클래스다.

예를 들어 점수, 제한 시간, 라운드 상태, 전체 공지와 같은 정보는 GameState에서 관리하기 좋다.

GameMode
- 서버에만 존재
- 플레이어 접속 감지
- 게임 규칙 처리

GameState
- 서버와 모든 클라이언트에 존재
- 게임 전체 상태 전달
- 전체 공지와 접속 알림 처리

따라서 접속 알림을 모든 클라이언트에 보여주고 싶다면 GameMode에서 접속을 감지한 뒤, GameState의 NetMulticast RPC를 호출하는 구조가 적절하다.

3. 접속 알림의 전체 흐름

접속 알림은 다음과 같은 순서로 처리된다.

플레이어 접속
    ↓
GameMode::PostLogin() 호출
    ↓
서버가 GameState를 가져옴
    ↓
GameState의 NetMulticast RPC 호출
    ↓
서버와 모든 클라이언트에서 RPC 실행
    ↓
각 플레이어 화면에 접속 알림 출력

GameMode에서는 GetGameState()를 이용해 현재 GameState를 가져온 뒤 Multicast RPC를 호출한다.

void ACXGameModeBase::PostLogin(APlayerController* NewPlayer)
{
    Super::PostLogin(NewPlayer);

    ACXGameStateBase* CXGameStateBase =
        GetGameState<ACXGameStateBase>();

    if (IsValid(CXGameStateBase))
    {
        CXGameStateBase->MulticastRPCBroadcastLoginMessage(
            TEXT("XXXXXXX")
        );
    }
}

이 코드는 새로운 플레이어가 접속했을 때 서버에서 실행된다.

이후 GameState에 선언된 MulticastRPCBroadcastLoginMessage()를 호출해 모든 클라이언트에 접속 사실을 전달한다.

4. NetMulticast RPC는 서버에서 호출해야 한다

NetMulticast RPC는 서버가 호출하면 서버와 현재 연결된 모든 관련 클라이언트에서 실행된다.

헤더 파일에는 UFUNCTION의 NetMulticast 지정자를 사용한다.

UFUNCTION(NetMulticast, Reliable)
void MulticastRPCBroadcastLoginMessage(
    const FString& InNameString
);

CPP 파일의 구현 함수에는 함수 이름 뒤에 _Implementation을 붙인다.

void ACXGameStateBase::
MulticastRPCBroadcastLoginMessage_Implementation(
    const FString& InNameString)
{
    const FString NotificationString =
        InNameString + TEXT(" has joined the game.");

    // 각 플레이어 화면에 메시지 출력
}

중요한 점은 NetMulticast RPC를 서버에서 호출해야 한다는 것이다.

서버에서 NetMulticast 호출
-> 서버와 모든 관련 클라이언트에서 실행

클라이언트에서 NetMulticast 호출
-> 호출한 클라이언트에서만 실행

클라이언트가 직접 NetMulticast RPC를 호출하면 다른 클라이언트에 전달되지 않는다.

따라서 모든 플레이어에게 알려야 하는 이벤트는 서버가 Multicast RPC를 호출해야 한다.

5. Listen Server에서 HasAuthority()가 문제가 된 이유

오늘 가장 헷갈렸던 부분은 다음 조건이었다.

if (HasAuthority() == false)

처음에는 클라이언트 화면에서만 메시지를 출력하기 위해 이 조건을 사용했다.

하지만 Listen Server 환경에서는 첫 번째 플레이어 창이 서버이면서 동시에 실제 플레이어 화면을 가진다.

첫 번째 창
- Listen Server
- 서버 권한 있음
- HasAuthority() == true
- 플레이어 화면도 존재함

두 번째 창
- Client
- 서버 권한 없음
- HasAuthority() == false

두 번째 클라이언트가 접속하면 GameState의 NetMulticast RPC는 첫 번째 Listen Server 창에서도 실행된다.

하지만 다음 조건이 있으면 첫 번째 창은 출력 코드를 실행하지 못한다.

if (HasAuthority() == false)
{
    // 메시지 출력
}

Listen Server는 HasAuthority()가 true이기 때문이다.

결과적으로 두 번째 클라이언트가 접속해도 첫 번째 Listen Server 화면에는 접속 알림이 나타나지 않는다.

6. HasAuthority() 조건을 제거해야 한다

첫 번째 Listen Server 화면에도 접속 알림을 표시하려면 다음 조건을 제거해야 한다.

if (HasAuthority() == false)

Multicast RPC는 이미 서버와 모든 클라이언트에서 각각 실행되기 때문에, 각 실행 환경에서 로컬 PlayerController를 가져와 메시지를 출력하면 된다.

void ACXGameStateBase::
MulticastRPCBroadcastLoginMessage_Implementation(
    const FString& InNameString)
{
    APlayerController* PC =
        UGameplayStatics::GetPlayerController(GetWorld(), 0);

    if (IsValid(PC))
    {
        ACXPlayerController* CXPC =
            Cast<ACXPlayerController>(PC);

        if (IsValid(CXPC))
        {
            const FString NotificationString =
                InNameString
                + TEXT(" has joined the game.");

            CXPC->PrintChatMessageString(
                NotificationString
            );
        }
    }
}

이렇게 수정하면 새로운 클라이언트가 접속했을 때 기존 Listen Server 플레이어의 화면에도 접속 메시지가 출력된다.

7. Dedicated Server만 제외해야 한다

Dedicated Server는 플레이어 화면이 없는 전용 서버다.

따라서 Dedicated Server에서는 PrintString이나 UI 출력 코드를 실행할 필요가 없다.

Dedicated Server만 제외하려면 HasAuthority()가 아니라 GetNetMode()를 확인하는 것이 정확하다.

if (GetNetMode() == NM_DedicatedServer)
{
    return;
}

Listen Server는 서버 권한을 가지면서 동시에 플레이어 화면도 있으므로 출력 대상에 포함해야 한다.

수정된 전체 코드는 다음과 같다.

void ACXGameStateBase::
MulticastRPCBroadcastLoginMessage_Implementation(
    const FString& InNameString)
{
    if (GetNetMode() == NM_DedicatedServer)
    {
        return;
    }

    APlayerController* PC =
        UGameplayStatics::GetPlayerController(
            GetWorld(),
            0
        );

    if (!IsValid(PC))
    {
        return;
    }

    ACXPlayerController* CXPC =
        Cast<ACXPlayerController>(PC);

    if (!IsValid(CXPC))
    {
        return;
    }

    const FString NotificationString =
        InNameString
        + TEXT(" has joined the game.");

    CXPC->PrintChatMessageString(
        NotificationString
    );
}

이 구조에서는 Dedicated Server만 화면 출력을 하지 않는다.

Listen Server와 일반 클라이언트에서는 각각 자신의 로컬 PlayerController를 찾아 접속 알림을 출력한다.

8. GetPlayerController(0)의 의미

다음 코드는 현재 실행 중인 컴퓨터의 첫 번째 로컬 PlayerController를 가져온다.

UGameplayStatics::GetPlayerController(
    GetWorld(),
    0
);

여기서 0은 서버에 접속한 전체 플레이어의 번호가 아니다.

현재 컴퓨터에서 생성된 로컬 플레이어의 인덱스다.

Listen Server 컴퓨터
GetPlayerController(0)
-> Listen Server 플레이어의 로컬 PlayerController

Client 1 컴퓨터
GetPlayerController(0)
-> Client 1의 로컬 PlayerController

Client 2 컴퓨터
GetPlayerController(0)
-> Client 2의 로컬 PlayerController

Multicast RPC가 각 컴퓨터에서 실행되기 때문에, 각 컴퓨터에서 GetPlayerController(0)을 호출하면 해당 컴퓨터의 로컬 플레이어 컨트롤러를 가져올 수 있다.

9. GameMode와 GameState 클래스 설정도 확인해야 한다

C++ 코드를 작성하더라도 실제 맵에서 다른 GameMode나 GameState를 사용하고 있으면 작성한 코드가 실행되지 않는다.

월드 세팅에서 다음 항목을 확인해야 한다.

GameMode Override
-> BP_GameModeBase

Player Controller Class
-> BP_PlayerController

Game State Class
-> BP_GameStateBase

또한 Blueprint 클래스의 부모 클래스도 확인해야 한다.

BP_GameModeBase
-> Parent Class: CXGameModeBase

BP_GameStateBase
-> Parent Class: CXGameStateBase

BP_PlayerController
-> Parent Class: CXPlayerController

부모 클래스가 기본 GameModeBase나 GameStateBase로 설정되어 있다면 C++에서 작성한 PostLogin과 Multicast RPC가 실행되지 않는다.

10. Client RPC로도 전체 알림을 구현할 수 있다

모든 클라이언트에 알림을 전달하는 방법이 NetMulticast RPC만 있는 것은 아니다.

서버가 모든 PlayerController를 순회하면서 각 PlayerController의 Client RPC를 호출하는 방법도 있다.

GameMode::PostLogin()
    ↓
서버의 모든 PlayerController 순회
    ↓
각 PlayerController에서 Client RPC 호출
    ↓
각 PlayerController의 소유 클라이언트에서 실행
    ↓
각 클라이언트 화면에 알림 출력

예시는 다음과 같다.

for (TActorIterator<ACXPlayerController> It(GetWorld());
     It;
     ++It)
{
    ACXPlayerController* CXPlayerController = *It;

    if (IsValid(CXPlayerController))
    {
        CXPlayerController
            ->ClientRPCPrintChatMessageString(
                NotificationString
            );
    }
}

이 방식은 특정 클라이언트에만 메시지를 보내거나, 플레이어마다 다른 메시지를 보여줄 때 유리하다.

반면 모든 플레이어에게 같은 일회성 알림을 전달하는 경우에는 GameState의 NetMulticast RPC가 더 간단하다.

오늘 헷갈렸던 점

처음에는 HasAuthority()가 false인 곳만 플레이어 화면을 가진 클라이언트라고 생각했다.

하지만 Listen Server에서는 첫 번째 창이 서버 권한을 가지면서 동시에 플레이어 화면도 가진다.

따라서 화면 출력 여부를 판단할 때 HasAuthority()만 사용하면 Listen Server 플레이어가 출력 대상에서 제외될 수 있다.

또 GameMode는 플레이어 접속을 감지하기 좋은 위치지만 클라이언트에 복제되지 않는다.

그래서 GameMode가 접속을 감지하고, 모든 클라이언트에 복제되는 GameState가 NetMulticast RPC를 통해 알림을 전달하는 구조가 적절하다.

오늘의 핵심 정리

  1. GameMode는 서버에만 존재한다.
  2. 플레이어 접속은 GameMode의 PostLogin()에서 감지할 수 있다.
  3. GameState는 서버와 모든 클라이언트에 복제된다.
  4. 모든 플레이어에게 같은 알림을 보내려면 GameState의 NetMulticast RPC를 사용할 수 있다.
  5. NetMulticast RPC는 서버에서 호출해야 모든 클라이언트에 전달된다.
  6. 클라이언트에서 호출한 NetMulticast RPC는 다른 클라이언트에 전달되지 않는다.
  7. Listen Server는 서버 권한을 가지면서 플레이어 화면도 가진다.
  8. Listen Server의 HasAuthority()는 true다.
  9. HasAuthority() == false 조건을 사용하면 Listen Server 화면에서 메시지가 출력되지 않을 수 있다.
  10. 화면이 없는 전용 서버만 제외하려면 GetNetMode() == NM_DedicatedServer를 확인해야 한다.
  11. Multicast RPC 안에서 GetPlayerController(0)을 호출하면 각 컴퓨터의 첫 번째 로컬 PlayerController를 가져올 수 있다.
  12. 특정 클라이언트에 개별 메시지를 보내려면 PlayerController의 Client RPC를 사용할 수 있다.

정리하면 플레이어 접속 알림은 GameMode에서 접속을 감지하고, GameState의 NetMulticast RPC를 서버에서 호출해 모든 플레이어에게 전달하는 구조로 구현할 수 있다.

그리고 Listen Server 환경에서는 서버 권한을 가지고 있다는 이유만으로 화면 출력 대상에서 제외하면 안 된다는 점을 반드시 고려해야 한다.

'unreal 8기' 카테고리의 다른 글

2026-6-16 TIL  (0) 2026.06.16
2026-6-15 TIL  (0) 2026.06.15
2026-6-10 TIL  (0) 2026.06.10
2026-6-9 TIL  (0) 2026.06.09
2026-6-4 TIL  (0) 2026.06.04