Unreal Engine

Unreal Engine - 멀티플레이 네트워크 최적화 1

gbleem 2025. 4. 22. 22:05

1. Recap


1 - 1. Server - Client 모델

언리얼 엔진은 멀티플레이 게임에서 Server-Client 구조를 사용한다.

  • 서버만이 결정을 내리며, Authority를 가진다.
  • 클라이언트끼리는 직접 통신하지 않는다.
  • 클라이언트에서 진행한 행동을 서버로 보내면, 서버는 해당 행동을 처리하고 그 결과를 다시 클라이언트로 보낸다.

1 - 2. Gameplay Framework

게임모드와 게임 스테이트는 서버에서 해당 로직을 체크한다.

  • 게임 스테이트는 게임 모드는 서버에만 있기 때문에, 게임모드의 결과를 가져와서 리플리케이트 하여 가지고 있게 된다.
  • 게임 스테이트를 통해서 게임모드의 결과값을 클라이언트에게 전해준다.

플레이어 컨트롤러는 서버, 그리고 소유하는 클라이언트에만 존재한다.

  • 플레이어 컨트롤러가 실행한 결과에 해당하는 값은 플레이어 스테이트가 저장하게 된다.
  • 게임 스테이트처럼 플레이어 스테이트를 통해 플레이어 컨트롤러의 값을 가져오는 중간 역할을 한다.

폰의 경우 모든 클라이언트가 함께 봐야하므로 서버, 모든 클라이언트에 다 존재한다.

HUD와 widget의 경우 서버에는 존재하지 않고, 소유한 클라이언트에만 존재한다.

  • 데디케이트 서버의 경우 렌더링하는 기능이 없기 때문이다.
  • 그렇기 때문에 플레이어 컨트롤러와 플레이어 스테이트를 통해서 업데이트 한다.

 

1 - 3. 리플리케이션과 RPC의 차이점

프로퍼티 리플리케이션

  • "서버에서 클라이언트"로 객체 상태를 자동 동기화해주는 기능
  • 계속 변할 수 있는 상태를 동기화할 때 사용한다.
    • 예를 들어, 캐릭터의 위치, 체력, 문이 열렸는지 닫혔는지 여부
  • 이벤트가 아니라 "변수"를 동기화할 때 사용한다. (값의 동기화)

RPC

  • 양뱡향 처리가 가능하다. (서버 -> 클라 / 클라 -> 서버)
  • "특정 순간에 실행되는 이벤트"를 원격으로 호출할 때 사용한다. (원격 함수 호출)
  • RPC 예시
    • server RPC
      • 캐릭터가 문을 열 때, 캐릭터가 아이템을 습득했을 때
      • 서버에게 유효성을 체크하고, 동작을 수행하게 된다.
    • client RPC
      • UI 업데이트
    • multicast RPC
      • 시각효과 소리 등 동시 업데이트

게임에서의 예시

  • 클라이언트가 server RPC를 통해 문을 열고 싶다는 이벤트를 보낸다.
  • 서버는 문을 열 수 있는 권한을 체크하여, 문의 상태를 나타내는 변수 bIsOpen(리플리케이션된 변수)의 값을 변경해주고, 문을 열어준다
  • 값이 변경됨에 따라 리플리케이션되어 모든 클라이언트에게 값의 변경을 알려준다.

 

 

2. Advanced Replication and RPC


2 - 1. 효율적인 Replication 방법

2 - 1 - 1. 큰 배열의 리플리케이션을 최적화하기 위한 구조체

언리얼 엔진은 기본적으로 리플리케이션에 있어서 주기가 짧고, 작은 데이터를 주고 받는 것에 최적화 되어 있다.

그렇기 때문에 매우 큰 용량의 데이터를 리플리케이션을 하기 위해서는 다른 방법을 사용해야 한다.

  • FastArraySerializer
    • 기본 array와의 차이점으로는 매번 동기화를 시키는 것이 아니라
    • 변화가 있는 경우, "변경된 값" 만 전송하는 시스템이다.
  • 사용법
    • Build.cs에 NetCore를 추가해주어야 한다.
    • 이후 #include "Net/Serialization/FastArraySerializer.h" 를 통해 사용한다.

2 - 1 - 2. Replication Conditions

리플리케이션 하는 조건을 설정해주는 방법

이러한 조건을 통해 불필요한 bandwidth를 줄일 수 있다.

  • COND_InitialOnly : 클라이언트가 처음 연결될 때 한 번만 리플리케이션
  • COND_OwnerOnly : 액터의 owner 클라이언트에게만 리플리케이션
  • COND_SkipOwner : 액터의 owner를 제외한 모든 클라이언트에게 리플리케이션
  • COND_AutonomousOnly : autonomous 액터에게만 리플리케이션
  • COND_InitailOrOwner
  • COND_custom

2 - 1 - 3. 조각내서 전송하기

어쩔 수 없이 큰 변수를 리플리케이션 해야하는 경우에 있어서는 RPC를 통해서 조각내어 전송하는 방식이 필요하다.

  • Reliable RPC를 통해서 데이터를 보낸 후, 클라이언트에서는 받은 데이터를 재조립한다.
  • 그러나 이런 방식을 써야하는 상황에 도달한다면, 과연 리플리케이션을 해야하는 데이터가 맞을지 고민해봐야 한다.

2 - 1 - 4. Object Referencing

큰 데이터 자체를 보내지 않고, reference나 ID를 보내서 client 측에서 준비하도록 한다.

  • 서버에서는 id를 보낸 후, 클라이언트에서는 로컬에 저장해 둔 데이터를 받은 id를 통해 해당 맵을 로드하는 방식

 

2 - 2. Reliable vs Unreliable

2 - 2 - 1. Reliable

Reliable로 보낸 RPC는 전송 대상에게 "반드시" 도달하게 된다.

  • 동일 채널의 Reliable RPC는 보낸 순서대로 실행한다.
  • 그렇기 때문에 Reliable RPC를 남용하면 성능의 저하가 발생할 수 있다.
    • 한 프레임에서 실행하지 못한 RPC는 다음 프레임으로 넘어가기 때문에
    • 계속해서 RPC가 쌓이게 되면서(밀림 현상) 성능의 저하가 발생하는 것이다.
  • Reliable RPC는 게임 내에서 중요한 로직에만 써야 한다.
    • 게임 시작 및 종료, 사망처리, 아이템 획득 등

2 - 2 - 2. Unreliable

Unreliable RPC는 오버헤더가 적고, 전송되지 못한(Drop)된 경우 다음 것들이 밀리는 현상이 없다.

  • 빈번하게 업데이트가 들어오거나 약간 손실되어도 괜찮은 이벤트에 사용
  • 비주얼, 사운드 등

2 - 2 - 3. 결론 및 예시

모든 경우 Unreliable로 설정하고, 필요할 때 Reliable로 바꾸기

  • server RPC
    • 플레이어 입력과 같이 자주 발생하는 것은 Unreliable로 해도 괜찮다.
    • NPC와의 대화처럼 간혹 들어오는 입력은 Reliable로 설정
  • Client RPC
    • 중요한 알림, 보상 획득 등은 Reliable로 설정
    • 피격 소리나 이팩트 등은 Unreliable로 설정
  • Multicast RPC
    • 가끔 발생하는 중요한 이벤트 (보스 소환 알림) 은 Reliable로 설정
    • 사소하고 빈번한 효과 (총구 섬광, 폭발)은 Unreliable로 설정

프로퍼티 리플리케이션

  • 프로퍼티 리플리케이션도 구조적으로는 Unreliable이다.
  • 그러나 다음 틱에 또 보내기 때문에 최신값이 계속해서 보내진다.

결론

  • 변동이 적고 반드시 알아야 하는 경우 "Reliable RPC"를 사용
  • 지속적인 상태를 나타내는 경우 "프로퍼티 리플리케이션" 사용
  • 하지만, Reliable RPC가 너무 많은 경우 RepNotify를 통해 값이 변경된 순간 즉시 반영되도록 하거나 NetUpdateFrequency를 높이는 방법이 있다.