본문 바로가기
Unreal Engine/멀티플레이어 게임 개발

[Remote Procedure Call 기초와 멀티플레이 채팅 구현] Remote Procedure Call

by 4.41p._.m 2026. 8. 5.

1. 개요 및 RPC 동작 원리의 중요성

1-1. 왜 이것이 중요한가 

언리얼 엔진 멀티플레이어 게임을 확실하게 성공시키기 위해 RPC(Remote Procedure Call)의 동작 원리를 정확히 이해하고 C++로 구현하는 것은 중요한 포인트입니다. 단일 클라이언트 내부에서만 돌던 로직을 서버를 거쳐 다른 플레이어들에게 전송하는 흐름은 모든 멀티플레이어 콘텐츠(채팅, 데미지 처리, 동기화)의 핵심 기반이 됩니다.

언리얼 C++ 멀티플레이어 개발에서 RPC는 서버와 클라이언트 간의 네트워크 통신을 가능하게 하는 핵심 메커니즘입니다. 플레이어 간의 상호작용(예: 채팅, 공격 판정, 위치 동기화)을 구현하기 위해서는 함수를 호출하는 PC와 실제로 실행되는 PC를 분리하는 RPC의 동작 원리를 반드시 완벽히 이해해야 합니다.


2. RPC의 핵심 개념 및 3가지 유형

 

2-1. RPC의 개념과 필요성

RPC는 한 디바이스에서 함수를 호출하지만, 실제 실행은 네트워크로 연결된 다른 디바이스(서버 또는 다른 클라이언트)에서 처리되도록 하는 통신 기법입니다. 단일 플레이어 게임과 달리, 멀티플레이어 환경에서는 해킹 방지와 데이터 무결성을 위해 중요한 연산은 반드시 서버에서 실행되어야 하며, 그 결과는 모든 클라이언트로 전파되어야 합니다.

언리얼 엔진은 C++ 매크로(UFUNCTION) 설정을 통해 함수를 네트워크 통신용 RPC로 쉽게 전환할 수 있도록 지원합니다.

 

2-2. RPC의 3가지 핵심 유형

언리얼 엔진에서 사용하는 RPC는 크게 세 가지 종류로 나뉩니다. 각 유형은 호출하는 주체와 실행되는 위치가 명확히 구분됩니다.

RPC 유형 호출하는 곳 실행되는 곳 주요 용도
Server 클라이언트 서버 클라이언트의 요청을 서버에 전달 (예: 채팅 메시지 전송, 공격 입력)
Client 서버 특정 클라이언트 서버가 특정 플레이어에게만 개별 알림을 보낼 때 (예: 개인 UI 아이템 획득 표시)
NetMulticast 서버 서버 및 모든 클라이언트 서버가 발생한 이벤트를 모두에게 전파할 때 (예: 채팅 출력, 폭발 이펙트 재생)
  • Server RPC (WithValidation, Reliable): 클라이언트가 호출하지만, 실제 실행은 서버에서 이루어집니다. 보안과 네트워크 신뢰성이 중요하므로 검증(Validation) 단계와 패킷 유실을 방지하는 Reliable 설정이 필수적입니다.
  • Client RPC (Reliable): 서버가 특정 클라이언트를 대상으로 호출하여, 해당 클라이언트의 화면이나 UI를 업데이트할 때 사용합니다.
  • NetMulticast RPC (Reliable): 서버가 수신한 데이터를 바탕으로 호출하면, 연결된 모든 플레이어의 화면에 동일한 결과(예: 채팅 텍스트 추가)가 동시에 실행됩니다.

3. Server RPC 선언, 소유권(Ownership), 및 실행 메커니즘

언리얼 C++ 멀티플레이어 환경에서 클라이언트가 입력한 메시지를 전체에 퍼트리기 위해서는 Server RPC로 서버에 권한을 요청한 뒤 전파하는 구조를 가져야 합니다.

 

  1. 모듈 추가 및 UMG 바인딩 (Build.cs 및 매크로 설정): C++에서 UI를 제어하기 위해 ChatX.Build.cs에 "UMG" 모듈을 추가하고, 헤더에서 meta = (BindWidget)을 사용하여 위젯 에셋의 컴포넌트와 C++ 포인터를 연동합니다.
  2. Server RPC 선언 및 요청 (UFUNCTION 매크로 설정): C++ 헤더 파일에서 UFUNCTION(Server, Reliable)과 같이 지정합니다. 플레이어가 텍스트를 입력하고 엔터를 누르면, 클라이언트 권한을 가진 PlayerController가 Server RPC를 호출하여 서버로 메시지 데이터를 보냅니다. 이 함수는 실제 구현부에서 _Implementation 접미사를 붙여 구현해야 합니다.
  3. 소유권(Ownership) 확인 (호출 가능 조건): Server RPC는 해당 액터를 소유한 클라이언트(예: 자신의 PlayerController 또는 소유된 무기)에서 호출해야만 서버로 전달됩니다. 소유권이 없는 액터에서 호출하면 무시됩니다.
  4. 모든 화면으로의 전파: 서버에 도달한 메시지를 모든 참가자에게 퍼뜨리기 위해 NetMulticast RPC를 호출하거나, 서버에서 모든 컨트롤러를 순회하며 Client RPC를 호출합니다.

4. Worked Examples: C++ 기반 Chat RPC 구현 패턴

이제 C++ 채팅 프로젝트의 구조를 바탕으로, 클라이언트가 입력한 메시지를 서버를 거쳐 모든 사람에게 전달하는 2가지 RPC 구현 패턴과 실제 C++ 구현 코드를 살펴보겠습니다.

 

4-1. 패턴 1: Server RPC + NetMulticast RPC 전파 방식

서버가 메시지를 수신한 후 NetMulticast를 통해 한 번에 모든 클라이언트에게 전파하는 일대다 동기화 방식입니다.

 

4-1-1. 헤더 파일 (CXPlayerController.h) 함수 선언

PlayerController 내부에 서버로 보낼 RPC와 모두에게 퍼뜨릴 RPC를 선언합니다.

// CXPlayerController.h
UCLASS()
class CHATX_API ACXPlayerController : public APlayerController {
    GENERATED_BODY()
public:
    // 클라이언트가 호출 -> 서버에서 실행
    UFUNCTION(Server, Reliable, WithValidation)
    void Server_SendChatMessage(const FString& Message);

    // 서버가 호출 -> 모든 클라이언트에서 실행
    UFUNCTION(NetMulticast, Reliable)
    void Multicast_DisplayChatMessage(const FString& Message);
};

 

4-1-2. 소스 파일 (CXPlayerController.cpp) 구현 및 _Implementation 패턴

Server RPC는 보안 검증을 위한 _Validate와 실제 로직을 위한 _Implementation 함수를 정의해야 합니다.

// CXPlayerController.cpp
bool ACXPlayerController::Server_SendChatMessage_Validate(const FString& Message) {
    // 해킹 및 악성 텍스트 필터링 가능 (우선 true 반환)
    return true; 
}

void ACXPlayerController::Server_SendChatMessage_Implementation(const FString& Message) {
    // 서버가 메시지를 받으면 모든 클라이언트에게 전파
    Multicast_DisplayChatMessage(Message);
}

void ACXPlayerController::Multicast_DisplayChatMessage_Implementation(const FString& Message) {
    // 모든 클라이언트의 UI 또는 로그에 메시지 출력
    UE_LOG(LogTemp, Warning, TEXT("Chat: %s"), *Message);
}

 

4-2. 패턴 2: Server RPC + TActorIterator 순회 (Client RPC) 방식

서버에 도달한 메시지를 접속 중인 모든 플레이어에게 전달하려면, 서버에 존재하는 모든 APlayerController를 찾아내야 합니다. 이때 사용되는 효율적인 C++ 도구가 바로 TActorIterator입니다. 월드 내의 특정 액터 타입을 안전하게 순회할 수 있도록 도와줍니다.

 

4-2-1. PlayerController 헤더 선언 (CXPlayerController.h - RPC 및 위젯 등록)

RPC 함수를 선언할 때는 Server, Client 키워드와 함께 Reliable, WithValidation 속성을 명시해야 하며, 실제 구현부 함수 이름 뒤에는 _Implementation과 _Validate가 붙습니다.

// CXPlayerController.h
UCLASS()
class CHATX_API ACXPlayerController : public APlayerController {
    GENERATED_BODY()
public:
    // Server RPC
    UFUNCTION(Server, Reliable, WithValidation)
    void Server_SendChatMessage(const FString& Message);
    virtual void Server_SendChatMessage_Implementation(const FString& Message);
    virtual bool Server_SendChatMessage_Validate(const FString& Message);

    // Client RPC
    UFUNCTION(Client, Reliable)
    void Client_ReceiveChatMessage(const FString& Message);
    virtual void Client_ReceiveChatMessage_Implementation(const FString& Message);
};

 

4-2-2. PlayerController 소스코드 구현 (CXPlayerController.cpp - TActorIterator 활용)

서버 구현부(_Implementation)에서 TActorIterator를 사용해 모든 플레이어 컨트롤러를 찾아내어 Client RPC를 뿌려주는 핵심 루프입니다.

// CXPlayerController.cpp
#include "Player/CXPlayerController.h"
#include "EngineUtils.h" // TActorIterator 사용을 위해 필수

bool ACXPlayerController::Server_SendChatMessage_Validate(const FString& Message) {
    // 지나치게 긴 메시지나 악성 텍스트 필터링 가능
    return !Message.IsEmpty() && Message.Len() < 200;
}

void ACXPlayerController::Server_SendChatMessage_Implementation(const FString& Message) {
    // 서버에서 월드 내 모든 ACXPlayerController를 순회
    for (TActorIterator<ACXPlayerController> It(GetWorld()); It; ++It) {
        ACXPlayerController* TargetPC = *It;
        if (TargetPC) {
            // 각 클라이언트에게 메시지 전송 RPC 호출
            TargetPC->Client_ReceiveChatMessage(Message);
        }
    }
}

void ACXPlayerController::Client_ReceiveChatMessage_Implementation(const FString& Message) {
    // 클라이언트 단의 UI 혹은 로그에 메시지 출력 로직 처리
    UE_LOG(LogTemp, Log, TEXT("Received Chat: %s"), *Message);
}