본문 바로가기

개발/내일배움캠프

언리얼 RPC 기초 정리

언리얼 RPC 기초 정리

1. RPC란?

RPC는 Remote Procedure Call 의 약자다.

직역하면 “원격 프로시저 호출”이며, 현재 실행 중인 머신이 아닌 다른 네트워크 대상에서 함수를 실행하도록 요청하는 방식이다.

언리얼 멀티플레이에서는 서버와 클라이언트가 서로 다른 머신에서 실행되기 때문에, 특정 로직을 원하는 위치에서 실행하기 위해 RPC를 사용한다.

예를 들어 클라이언트에서 공격 입력을 받았을 때 실제 데미지 판정은 서버에서 해야 한다.

이때 클라이언트는 서버에게 다음과 같이 요청한다.

Client
 → Server RPC 호출
 → Server에서 공격 처리

즉, RPC는 단순 함수 호출이 아니라 네트워크를 통해 다른 실행 주체에게 함수 실행을 요청하는 기능이다.


2. 언리얼에서 RPC의 주요 용도

언리얼에서 RPC는 주로 다음 상황에서 사용한다.

클라이언트 → 서버 요청
서버 → 특정 클라이언트 알림
서버 → 모든 클라이언트에 일시적 이벤트 전파

대표적인 예시는 다음과 같다.

  • 클라이언트가 서버에 공격 요청
  • 클라이언트가 서버에 상호작용 요청
  • 서버가 특정 클라이언트에게 UI 알림 전송
  • 서버가 모든 클라이언트에게 사운드, 파티클, 애니메이션 이벤트 재생 요청
  • 서버가 모든 클라이언트에게 피격 이펙트 출력 요청

단, 게임 상태 자체를 장기적으로 동기화하는 데는 RPC보다 Property Replication이 더 적합한 경우가 많다.

예를 들어 체력, 마나, 점수, 인벤토리 수량, 상태이상 여부처럼 “현재 상태”를 나타내는 값은 보통 RPC만으로 관리하지 않고 Replicated Property로 동기화한다.

일시적 이벤트:
RPC 사용

지속적으로 유지되어야 하는 상태:
Property Replication 사용

예시:

피격 사운드 재생:
NetMulticast RPC 가능

현재 체력 값:
Replicated Property 권장

(GPT가 추가해줬으나, 다음 강의에서 다루는 내용임)


3. Call과 Invoke의 차이

일반적인 함수 호출은 보통 Call 이라고 표현한다.

Attack();

이 경우 함수는 현재 코드 흐름에서 직접 호출되고, 현재 실행 중인 머신에서 바로 실행된다.

반면 RPC는 직접 실행되는 것이 아니라, 네트워크 시스템을 통해 간접적으로 실행 요청이 전달된다.

이런 경우를 Invoke 라고 표현할 수 있다.

정리하면 다음과 같다.

Call:
현재 실행 위치에서 함수를 직접 호출한다.

Invoke:
함수 포인터, 델리게이트, 동적 바인딩, RPC처럼 간접적인 방식으로 실행을 요청한다.

다만 실제 언리얼 코드에서는 RPC 함수도 문법적으로는 일반 함수처럼 호출한다.

Server_Attack();

하지만 내부적으로는 네트워크 시스템을 통해 서버에서 _Implementation 함수가 실행된다.

따라서 겉보기에는 Call처럼 보이지만, 개념적으로는 원격 실행을 요청하는 Invoke에 가깝다.


4. RPC를 이해하기 위해 Ownership이 중요한 이유

멀티플레이에서는 여러 클라이언트가 존재한다.

따라서 어떤 클라이언트가 어떤 Actor를 소유하고 있는지 구분해야 한다.

언리얼 RPC는 Actor의 Ownership에 따라 호출 가능 여부와 실행 위치가 달라진다.

특히 클라이언트가 Server RPC를 호출하려면, 해당 RPC를 호출하는 Actor가 그 클라이언트에게 소유되어 있어야 한다.


5. Ownership 기준의 Actor 분류

언리얼 공식 문서에서는 RPC 동작을 설명할 때 Actor의 소유 상태를 기준으로 구분한다.

대표적으로 다음과 같이 이해할 수 있다.


5.1 Client-Owned Actor

Client-Owned Actor 는 특정 클라이언트가 소유한 Actor를 의미한다.

일반적으로 해당 클라이언트의 PlayerController, 그 PlayerController 가 Possess한 Pawn 또는 Character, 그리고 그 아래 Owner 체인에 포함된 Actor들이 여기에 해당한다.

예시:

ClientConnection
 └─ PlayerController
     └─ PlayerCharacter
         └─ Weapon

내 클라이언트에서 내가 조종하는 캐릭터는 Client-Owned Actor로 볼 수 있다.

이런 Actor에서는 클라이언트가 Server RPC를 호출할 수 있다.

내 캐릭터:
Client-Owned Actor
→ Server RPC 호출 가능

5.2 Owned by Different Client

내가 아닌 다른 클라이언트가 소유한 Actor를 의미한다.

예를 들어 내 화면에 보이는 다른 플레이어의 캐릭터가 여기에 해당한다.

다른 플레이어의 캐릭터:
Owned by Different Client

이 Actor는 내 클라이언트 입장에서는 SimulatedProxy 로 보이는 경우가 많다.

내가 소유하지 않은 Actor에서 Server RPC를 호출하려고 해도 일반적으로 정상적으로 처리되지 않는다.

다른 플레이어 캐릭터에서 Server RPC 호출:
내 클라이언트는 소유자가 아님
→ 호출 불가 또는 무시됨

 


5.3 Server-Owned Actor

Server-Owned Actor 는 서버에 존재하지만 특정 클라이언트의 PlayerController 에 소유되지 않은 Actor를 의미한다.

예시는 다음과 같다.

  • GameMode
  • GameState
  • 월드에 배치된 문
  • 중립 오브젝트
  • 몬스터
  • 서버가 스폰했지만 특정 플레이어에게 Owner가 설정되지 않은 Actor

이런 Actor는 서버 권한으로 관리된다.

클라이언트가 이 Actor에서 직접 Server RPC를 호출하는 구조는 적절하지 않다.

클라이언트가 중립 오브젝트와 상호작용해야 한다면 보통 다음 흐름을 사용한다.

클라이언트의 PlayerController 또는 Character
 → Server RPC 호출
 → 서버에서 대상 오브젝트 검증
 → 서버에서 상호작용 처리

5.4 Unowned actor

Owner가 설정되지않은 Actor.

강사님 말로는 5.3의 Server-Owned Actor와 동일하다고 함.


6. UFUNCTION RPC 키워드

언리얼에서는 UFUNCTION() 매크로에 RPC 관련 키워드를 붙여 원격 호출 함수를 선언한다.

대표적인 키워드는 다음과 같다.

Server
Client
NetMulticast

7. Server RPC

Server RPC 는 클라이언트에서 서버로 함수를 실행 요청할 때 사용한다.

예를 들어 클라이언트가 공격 버튼을 눌렀을 때, 실제 공격 판정은 서버에서 처리해야 한다.

UFUNCTION(Server, Reliable)
void Server_Attack();

구현부는 _Implementation 을 붙여 작성한다.

void AMyCharacter::Server_Attack_Implementation()
{
    // 서버에서 실행되는 공격 로직
}

호출 예시:

void AMyCharacter::InputAttack()
{
    Server_Attack();
}

이 함수는 클라이언트에서 호출했지만 실제 실행은 서버에서 이루어진다.

단, 이 Server RPC를 호출하는 Actor는 해당 클라이언트가 소유한 Actor여야 한다.


8. Client RPC

Client RPC 는 서버에서 특정 클라이언트로 함수를 실행 요청할 때 사용한다.

주로 특정 플레이어에게만 보여줄 UI, 알림, 카메라 효과, 사운드 등을 실행할 때 사용한다.

UFUNCTION(Client, Reliable)
void Client_ShowHitNotice();

구현 예시:

void AMyPlayerController::Client_ShowHitNotice_Implementation()
{
    // 해당 클라이언트에서만 실행
}

Client RPC는 일반적으로 해당 Actor를 소유한 클라이언트에서 실행된다.

예를 들어 서버가 특정 PlayerController 에서 Client RPC를 호출하면, 그 PlayerController를 소유한 클라이언트에서 실행된다.

서버
 → 특정 PlayerController의 Client RPC 호출
 → 해당 클라이언트에서만 실행

9. NetMulticast RPC

NetMulticast RPC 는 서버에서 호출했을 때 서버와 모든 관련 클라이언트에서 실행되는 RPC다.

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayHitEffect();

구현 예시:

void AMyCharacter::Multicast_PlayHitEffect_Implementation()
{
    // 서버와 클라이언트들에서 실행
    PlayHitEffect();
}

주로 다음과 같은 일시적 연출에 사용한다.

  • 공격 이펙트
  • 피격 이펙트
  • 폭발 파티클
  • 사운드 재생
  • 애니메이션 이벤트

단, NetMulticast 는 모든 클라이언트에게 전파될 수 있으므로 호출 빈도를 주의해야 한다.

특히 Tick마다 호출하거나, 매우 자주 발생하는 로직에 사용하는 것은 좋지 않다.

좋은 예:
폭발 이펙트 1회 재생

나쁜 예:
매 프레임 위치 보정용 Multicast 호출

위치, 체력, 상태값처럼 지속적으로 유지되어야 하는 값은 NetMulticast 보다 Property Replication을 사용하는 것이 좋다.


10. Reliable과 Unreliable

RPC는 신뢰성 옵션으로 Reliable 과 Unreliable 을 지정할 수 있다.


10.1 Reliable

Reliable 은 해당 RPC가 반드시 도착해야 하는 경우에 사용한다.

UFUNCTION(Server, Reliable)
void Server_UseItem();

적합한 예시는 다음과 같다.

  • 아이템 사용 요청
  • 장비 교체 요청
  • 상호작용 요청
  • 중요한 UI 알림
  • 게임 진행에 중요한 이벤트

하지만 Reliable 을 무조건 많이 쓰면 안 된다.

Reliable RPC는 도착 보장을 위해 큐에 쌓이며, 과도하게 사용하면 네트워크 지연이나 연결 문제가 발생할 수 있다.

따라서 자주 호출되는 이펙트, 사운드, 이동 관련 이벤트에는 신중하게 사용해야 한다.


10.2 Unreliable

Unreliable 은 패킷 손실 상황에서 해당 RPC가 도착하지 않을 수 있다.

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayFootstepSound();

하지만 그만큼 부담이 적고, 일시적인 연출에 적합하다.

적합한 예시는 다음과 같다.

  • 발소리
  • 짧은 이펙트
  • 단발성 사운드
  • 카메라 흔들림
  • 자주 호출되는 시각적 연출

중요하지 않고 다음 이벤트로 덮여도 문제가 없는 경우에는 Unreliable 이 적합하다.


11. Reliable을 사용해야 하는 경우와 주의점

“물리, 데미지, 스폰처럼 영향이 큰 로직에는 Reliable 사용을 권장”이라는 부분은 조금 더 정확히 구분해야 한다.

영향이 큰 로직을 단순히 Reliable RPC로 처리한다기보다는, 서버에서 권위 있게 처리하고 그 결과를 Replication으로 동기화하는 구조가 더 중요하다.

예를 들어 데미지 처리는 다음처럼 설계하는 것이 일반적이다.

클라이언트:
공격 입력

클라이언트 → 서버:
Server_Attack RPC

서버:
공격 가능 여부 검증
충돌 판정
데미지 적용
체력 값 변경

서버 → 클라이언트:
Replicated Health 값 동기화

즉, 데미지 자체를 Multicast RPC로 모든 클라이언트에게 “적용하라”고 보내는 것이 아니라, 서버에서 체력을 변경하고 그 체력 값을 복제하는 방식이 안전하다.

스폰도 마찬가지다.

서버에서 Actor를 Spawn하고, 해당 Actor가 Replicate되도록 설정하면 클라이언트에 자동으로 복제된다.

서버:
SpawnActor

Actor:
bReplicates = true

클라이언트:
복제된 Actor 수신

따라서 중요한 상태 변화는 다음 기준으로 생각하는 것이 좋다.

중요한 요청:
Server Reliable RPC 사용 가능

중요한 결과 상태:
Property Replication 또는 Actor Replication 사용 권장

 

GPT가 급발진해서 추가해준 내용

강의 다음시간에 Property Replication 배우고 다시 작성


12. WithValidation

WithValidation 은 Server RPC 에 검증 함수를 추가하기 위한 UFUNCTION 키워드다.

사용 예시는 다음과 같다.

UFUNCTION(Server, Reliable, WithValidation)
void Server_Attack();

이렇게 선언하면 다음 두 함수를 구현해야 한다.

bool AMyCharacter::Server_Attack_Validate()
{
    return true;
}

void AMyCharacter::Server_Attack_Implementation()
{
    // 서버에서 실행될 실제 RPC 로직
}

_Validate() 함수는 RPC가 실제로 실행되기 전에 호출되는 검증 함수다.

반환값이 true 이면 _Implementation() 이 실행되고, false 이면 RPC 실행이 거부된다.

예를 들어 공격 RPC라면 다음과 같은 내용을 검증할 수 있다.

bool AMyCharacter::Server_Attack_Validate()
{
    return CanAttack();
}

검증할 수 있는 항목의 예시는 다음과 같다.

  • 현재 공격 가능한 상태인가?
  • 쿨타임이 끝났는가?
  • 대상이 유효한가?
  • 거리가 유효한가?
  • 필요한 자원이 충분한가?

즉, WithValidation 은 클라이언트가 보낸 Server RPC 요청을 서버에서 검증한 뒤 실제 실행 여부를 결정하기 위한 기능이다.


13. RPC 작성 기본 형태

RPC 함수는 헤더 파일에 선언하고, CPP 파일에서 _Implementation 함수를 구현한다.

Server RPC 예시

// MyCharacter.h

UFUNCTION(Server, Reliable)
void Server_Attack();
// MyCharacter.cpp

void AMyCharacter::Server_Attack_Implementation()
{
    if (!CanAttack())
    {
        return;
    }

    ExecuteAttack();
}

Client RPC 예시

// MyPlayerController.h

UFUNCTION(Client, Reliable)
void Client_ShowMessage(const FString& Message);
// MyPlayerController.cpp

void AMyPlayerController::Client_ShowMessage_Implementation(const FString& Message)
{
    ShowMessageOnUI(Message);
}

NetMulticast RPC 예시

// MyCharacter.h

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayAttackEffect();
// MyCharacter.cpp

void AMyCharacter::Multicast_PlayAttackEffect_Implementation()
{
    PlayAttackEffect();
}

14. RPC 사용 시 주의할 점

RPC는 매우 편리하지만 모든 네트워크 동기화를 RPC로 처리하면 구조가 불안정해진다.

특히 다음과 같은 실수를 조심해야 한다.


14.1 상태값을 RPC만으로 관리하지 않기

체력, 마나, 점수, 상태이상, 장비 상태 등은 RPC로만 관리하기보다 Replicated Property를 사용하는 것이 좋다.

잘못된 방향:
Multicast_SetHealth(50);

권장 방향:
서버에서 Health = 50;
Health를 Replicated로 동기화

14.2 클라이언트 요청을 그대로 믿지 않기

클라이언트에서 다음과 같은 요청을 보낸다고 해서 서버가 그대로 믿으면 안 된다.

Server_ApplyDamage(Target, 9999);

이런 구조는 치트에 취약하다.

더 좋은 구조는 다음과 같다.

Server_RequestAttack();

서버 내부:
공격 가능 여부 확인
거리 확인
무기 데이터 확인
충돌 판정
데미지 계산
데미지 적용

즉, 클라이언트는 “공격하고 싶다”고 요청하고, 실제 판정은 서버가 한다.


14.3 NetMulticast를 남발하지 않기

NetMulticast 는 모든 클라이언트에 실행될 수 있으므로 자주 호출하면 부담이 크다.

특히 다음과 같은 사용은 피하는 것이 좋다.

Tick마다 Multicast 호출
이동 보정용 Multicast 호출
상태값 동기화용 Multicast 호출

이런 경우에는 Replication이나 Movement Replication을 사용하는 것이 더 적절하다.


14.4 Reliable을 무조건 사용하지 않기

Reliable 은 도착 보장을 해주지만, 많이 쌓이면 네트워크 큐에 부담이 된다.

따라서 모든 RPC를 Reliable로 만드는 것은 좋은 습관이 아니다.

중요하고 낮은 빈도:
Reliable 가능

자주 발생하고 일시적인 연출:
Unreliable 권장

15. 정리

언리얼 RPC는 다른 네트워크 대상에서 함수를 실행하도록 요청하는 기능이다.

클라이언트에서 서버로 요청할 때는 Server RPC 를 사용한다.

서버에서 특정 클라이언트로 요청할 때는 Client RPC 를 사용한다.

서버에서 모든 클라이언트로 일시적 이벤트를 전파할 때는 NetMulticast RPC 를 사용한다.

RPC를 사용할 때는 Actor의 Ownership이 매우 중요하다.

클라이언트가 Server RPC를 호출하려면 해당 Actor가 그 클라이언트에게 소유되어 있어야 한다.

게임에 중요한 로직은 클라이언트가 아니라 서버에서 처리해야 한다.

체력, 점수, 상태이상 같은 지속 상태는 RPC보다 Property Replication으로 동기화하는 것이 좋다.

사운드, 파티클, 카메라 흔들림 같은 일시적 연출은 RPC로 처리하기 좋다.

Reliable 은 반드시 도착해야 하는 낮은 빈도의 요청에 사용하고, 일시적 연출이나 자주 발생하는 이벤트는 Unreliable 을 사용하는 것이 좋다.

WithValidation 는 값이 정상적인지, 이후 구현부를 실행할지를 검증을 추가하는 키워드이다.

언리얼 멀티플레이에서 RPC를 설계할 때는 다음 기준을 기억하면 좋다.

클라이언트 입력 요청:
Server RPC

서버가 특정 클라이언트에게 알림:
Client RPC

서버가 모든 클라이언트에게 일시적 연출 전파:
NetMulticast RPC

장기적으로 유지되어야 하는 상태:
Property Replication

서버에서 Spawn한 복제 Actor:
Actor Replication