언리얼 엔진 본캠프 19주차-1 언리얼 엔진 네트워크 : 최적화 - Replication & RPC

정재훈·2025년 4월 21일
0

unreal engine

목록 보기
37/45

1. Unreal Engine Server

언리얼은 기본적으로 멀티플레이를 위해 Server-Client 구조 사용

  • Server(인스턴스)는 권한을 가지고, 게임 상태를 결정
  • Client(인스턴스)는 이 게임 상태를 Server로부터 업데이트 받음 = Client들 간 "직접" 통신 ❌
    • 서버와 클라이언트는 일반적으로는 다른 하드웨어이지만, 현재 개발환경에서는 하나의 하드웨어(내 컴퓨터) 내에서 동시에 구동

Standalone : 네트워킹을 고려하지 않은 인스턴스
Dedicated Server : Server 전용 인스턴스
Listen Server : Server와 Client 역할을 동시에
Client : Host가 아닌 플레이어


2. Multiplayer Framwork

게임 진행에 대한 결정권은 서버가 가지고 있음

  • GameMode : 게임 진행에 대한 규칙이 정의되어 있는 곳 => Client 인스턴스에는 없음(리플리케이션 안함)
  • GameState : GameMode에서 결정된 결과, 시간 저장 => 결과만 Client 인스턴스에 리플리케이션(접근할 수 있더라도 권한을 가지는 건 아님)
  • PlayerState : PlayerController가 실행한 결과가 저장됨, 다른 클라이언트의 PlayerState도 가짐(상대방의 HP를 알아야함)
    • 다른 클라이언트간의 공유해야할 것은 PlayerState에 정의
  • PlayerController : 서버와 클라이언트간 통신 창구, 클라이언트는 자기 자신의 PlayerController만 가짐
    • 공유하지 않을 것은 PlayerController에 정의
    • Pawn, HUD, Widget의 Owner
  • Pawn : 플레이하는 캐릭터
    • Pawn도 공유, 만약 공유하지 않는다? 내 화면에 다른 플레이어의 캐릭터가 안보일 거임
  • HUD/Widget : Dedicated Server 기준 서버는 뭔가를 렌더링할 게 없음 => Client 인스턴스에만 존재 => PlayerController랑 PlayerState가 UI 업데이트 담당

▶️ Unreal Multiplayer Framwork는 공유할 것과 공유하지 않을 것을 명확히 구분

  • 공유할 것은 GameState나 PlayerState, 공유하지 않을 것은 GameMode나 PlayerController에 정의하자고 정해놓음
    • 왜 그럴까 생각해보면, GameMode와 PlayerController는 게임 진행에 직접적으로 관여를 한다고 할까? 예를 들어, GameMode나 PlayerController를 공유한다고 하면
      • PlayerController 같은 경우에는 캐릭터를 움직이는 데, 내가 상대 캐릭터까지 움직일 수 있다? 말이 안됨
      • GameMode 같은 경우에는 나랑 상대방 둘 다 스테이지를 클리어 했는데 나만 다음으로 넘어가고 상대방은 못넘어가게 한다? 말이 안됨

★ Client가 아닌 Server의 값을 신뢰해야함


3. 리플리케이션과 RPC

리플리케이션 : [Server에서 Client로] 객체를 "자동" 동기화해주는 것(서버에서 값이 바뀌면 자동으로 클라이언트에 반영)

  • 오직 Server → Client 방향만 ( Client → Server는 RPC )
  • 계속 변할 수 있는 상태 동기화에 적합
  • 매 프레임 또는 어느 주기(NetUpdateFrequency)마다 자동으로 동기화

RPC : 원격 머신의 함수를 호출하는 명시적인 방법

  • 종류에 따라 방향이 바뀜
    • ServerRPC : Client에서 호출, Server에서 실행
    • ClientRPC : Server에서 호출, "특정" Client에서 실행
    • MulticastRPC : Server에서 모든 Client, "모든" Client에서 실행(서버 포함)
  • 필요한 순간 직접 호출

State, 상태 - Replication
Event, 동작 - RPC

Property Replication은 뭉쳐서(bunch) 보내지고, 순서를 보장 ❌(기본 Reliable이라 도달은 보장)

  • => 순서를 보장해야할 것 같으면 RPC 사용

Reliable RPC : 도달 보장 ⭕, 같은 액터에서 발신/도착 순서 보장 ⭕ but 사용 시, RPC 호출들이 한 프레임에 이루어지지 않을 수 있음

  • Reliable을 남용한다면 어떤 문제가 생길까?
    • Reliable RPC Buffer가 가득차서 Overflow 발생 => 패킷 손실 => 이후 RPC 대기 => 네트워크 지연, 성능 저하 => 심하면 연결 끊김
  • Reliable RPC Buffer : 패킷 전송 순서와 재전송을 보장하기 위해 Reliable RPC를 임시로 저장하는 Queue, 기본 한도 : 패킷 256개

Unrealiable RPC : 도달 보장 ❌, 발신 순서 보장 ⭕, 도착 순서 보장 ❌, RPC 호출들이 한 프레임에 이루어지지 않을 수 있음

Unreal Engine은 게임 네트워크 전송에는 TCP를 사용하지 않고, 전부 UDP 기반

  • Reliable도 UDP를 사용하고, UDP 위에서 신뢰성을 보장해줄 뭔가를 추가하여 사용한다고 함

호출자가 다르면, 호출자 간의 RPC 실행 순서는 Reliable/Unreliable에 관계없이 보장되지 않음

일단 Unreliable로 설정하고, 개발하는 게임의 특징, 상황에 맞게 필요한 경우만 Reliable로 설정

Property Replication은 구조적으론 Unreliable과 동일한 방식으로 동작합니다만, 상태는 다음 틱에도 또 보내기 때문에 결국 최신 값은 전달됩니다.

Actor Property Replication is reliable. 즉, 클라이언트 버전의 액터 프로퍼티는 결국 서버의 값을 반영하지만, 클라이언트가 서버의 프로퍼티에 발생하는 모든 개별 변경 사항을 반드시 통지받지는 않습니다.

예를 들어 정수 프로퍼티의 값이 100에서 200으로, 다시 300으로 빠르게 변경되는 경우 클라이언트는 결국 300의 값으로 업데이트를 받게 되지만, 200으로 변경된 것을 클라이언트가 알 수 있다는 보장은 없습니다.

보증은 클라이언트의 프로퍼티가 결국 서버의 정확한 값을 반영한다는 것을 의미합니다. 액터가 반드시 모든 프레임을 복제하는 것은 아니며 패킷이 삭제될 수 있으므로 서버에서 이루어진 모든 업데이트가 클라이언트에 수신된다는 것을 의미하지는 않습니다.

여러 변수 간에 신뢰할 수 있는 순서가 필요한 경우, 구조체에 함께 저장하는 것이 좋습니다.

클라이언트가 자주 변경 사항을 수신해야 하는 경우, 액터의 NetUpdateFrequency를 높여 각 프레임을 복제하도록 할 수 있습니다.

두 액터가 같은 프레임에서 속성이 변경된 경우 업데이트 빈도 차이, 관련성/우선순위 차이, 패킷 손실 등 여러 가지 이유로 인해 같은 프레임에서 리플리케이트되거나 수신되지 않을 수 있습니다.

보다 보니까 2단 점프 재대로 안보이는 거랑도 연관 지을 수 있을 거 같음

4. 대용량 데이터의 Replication

언리얼의 Replication System은 기본적으로 "갱신 주기가 짧고, 크기가 작은 데이터에 최적화"되어 있음

대용량 데이터를 Replication하면
=> 네트워크 패킷 크기 제한 초과, 과도한 대역폭 사용 => Packet Loss, 연결 끊김 등과 같은 문제가 발생할 수 있음

대용량 데이터 Replication 방법
(변수 Replication은 Server → Client 밖에 없음)

  1. FastArraySerializer : 큰 Array의 Replication을 최적화 하기 위한 Struct

    • 전체가 아닌 변화된 부분만 전송
    • Blueprint에서 사용 불가능
    • Array의 추가/제거,수정 사항을 추적하여 Delata 형태(=변경된 내용만)로 전송
    • ▶️ CPU 부하와 Bandwidth를 줄일 수 있음
  2. Replication Condition

    • COND_InitialOnly : Client가 연결될 때 한 번만
    • COND_OwnerOnly : 해당 Actor의 소유주 Client에만 전송
    • COND_SkipOwner : 해당 Actor의 소유주를 제외한 모든 Client에게 전송
    • COND_AutonomousOnly : Autonomous Actor에게만 전송
    • COND_InitialOrOwner : 연결될 때 + 소유주에게만
    • COND_Custom : 커스터마이징 condition
    • ▶️ Bandwidth 소모 ↓
    • (RPC는 Condition이 없음. 왜? 동기화 타이밍을 로직을 짜면서 개발자가 정하기 때문)
  3. Chunking과 Manual RPC Streaming

    • 정말 대용량의 데이터를 Replication하면, Unreal의 내부 한계(bunch 당 64KB)를 초과할 수 있음 => 데이터를 더 작은 크기로 나눠야 함(Chunking)
    • ReliabileRPC 여러 개로 데이터를 나눠 전송하고(Steraming), Client에서 재조립
      • 전체 크기 자체를 최적화한건 아님
      • RPC 버퍼도 제한이 있기 때문에 사용 시 주의
    • 리플리케이션 소규모 데이터에 최적화되어 있기 때문에, 상황에 따라 리플리케이션을 사용해야 하나? 라는 고민이 필요한 시점
      • 소켓 통신, Online Subsystem 등
  4. Object Referencing 및 Dynamic Spawning

    • 데이터를 보내는 대신 Reference나 ID와 같은 "식별자"를 보내 Client에서 자체적으로 준비하게 하도록 함
    • 서버에서 데이터를 통째로 클라이언트에게 보내는게 아니라, 이미 클라이언트 로컬에 저장된 것을 식별자로 구분해서 로드하도록 함
    • 수많은 객체를 Replication 해야하는 경우에는, Replication을 위한 Manager 객체를 하나 만들고, 이 Manager를 Replication하면 횟수 ↓
      • Manager의 크기가 상당히 큼 => 다시 3번으로 돌아감
    • Relevancy : Manager를 만들지 않고, 개별 Actor로 나두고 특정 조건(거리 등)에 따라 개별적으로 Replication

리플리케이션 최적화 = 횟수, 용량을 줄이는 것

리플리케이션을 할 때는 데이터가 얼마나 자주 변하고, 누가 필요로 하는지를 항상 고려해야 함

게임 개발 상황에 맞게 최적화를 해야함







참조 사이트
1. https://dev.epicgames.com/community/learning/knowledge-base/ba80/unreal-engine-replication-ordering-guarantees
2. https://dev.epicgames.com/documentation/en-us/unreal-engine/property-replication?application_version=4.27
3. https://dev.epicgames.com/documentation/ko-kr/unreal-engine/replicated-object-execution-order-in-unreal-engine?application_version=5.3

profile
드가자

0개의 댓글