아래 글에서는 언리얼 엔진 멀티플레이어에서 자주 언급되는 네트워크 역할(Roles) 개념과 이를 시각화하기 위한 오버헤드 위젯(Overhead Widget) 구현 과정을 정리했습니다.
로컬/원격 역할(Local/Remote Role)과 Authority, AutonomousProxy, SimulatedProxy 등의 핵심 키워드를 파악해 두시면 네트워크 로직 작성 시 큰 도움이 될 것입니다.
1. 네트워크 역할(Roles) 기초
언리얼 엔진은 Server-Authority 모델을 사용합니다. 즉, 서버가 게임의 최종 권한을 가지며, 각 클라이언트는 서버와 동기화하며 게임 오브젝트를 제어합니다.
1.1 폰/캐릭터의 버전이 여러 개!
- 로컬 머신(클라이언트 or 서버)
- 내가 직접 조종하는 캐릭터도 있고, 다른 플레이어 캐릭터가 복사본 형태로 있을 수 있습니다.
- 서버
- 모든 캐릭터(플레이어)의 권위 있는 상태가 존재합니다.
- 다른 클라이언트
- 나 아닌 다른 플레이어들이 조종 중인 캐릭터가 또 다른 복사본 형태로 표시됩니다.
즉, 3명의 플레이어가 있다면, 실제로는 **각 플레이어 PC에 3개의 캐릭터(복사본)**가 존재하여 위치/애니메이션 등을 싱크합니다.
1.2 ENetRole 상수
언리얼 엔진은 ENetRole이라는 열거형을 사용하여 “현재 이 오브젝트(캐릭터)가 어떤 권한을 가지고 있는지” 구분합니다.
상수 의미
| ROLE_Authority | 서버에서 권한을 가진 상태. (서버가 곧 Authority) |
| ROLE_AutonomousProxy | 내 로컬 클라이언트에서 내가 조종 중인 캐릭터 |
| ROLE_SimulatedProxy | **다른 곳(타 클라이언트/서버)**에서 제어하는 캐릭터의 복사본 |
| ROLE_None | 권한이 없는 경우(일반적으로 사용 빈도 낮음) |
TIP:
- “내가 조종 중인 캐릭터”는 클라이언트 관점에서 AutonomousProxy가 되며, 서버 관점에서는 해당 캐릭터가 Authority 상태입니다.
- 결국 어느 컴퓨터(서버 or 클라이언트)에서 캐릭터를 바라보느냐에 따라 LocalRole, RemoteRole이 달라집니다.
언리얼 엔진(주로 UE4, UE5)에서 ENetRole은 액터(Actor)가 네트워크 상에서 어떤 ‘역할(Role)’을 갖는지를 나타내는 열거형(enum)입니다. 멀티플레이 환경에서 한 액터가 어떤 권한(authority)을 가지고 어떤 식으로 동작해야 하는지를 엔진 내부에서 구분할 때 사용됩니다. 보통 언리얼 엔진에서 액터의 네트워크 동작을 이해할 때 Role, RemoteRole, 그리고 ENetRole에 대한 이해가 필수적입니다. 아래에서는 ENetRole의 멤버와 각각이 의미하는 바를 정리해보겠습니다.
1. ENetRole의 주요 멤버
언리얼 엔진 4/5 기준으로 액터의 네트워크 역할을 표시하는 ENetRole에는 대표적으로 다음 멤버들이 있습니다.
- ROLE_None
- ROLE_SimulatedProxy
- ROLE_AutonomousProxy
- ROLE_Authority
엔진 버전과 소스상 정의에 따라 ROLE_MAX라는 멤버도 존재하지만, 실제로 쓰이는 역할은 위 네 가지입니다.
1) ROLE_None
- 설명: 네트워크 관점에서 ‘역할이 없다’는 것을 의미합니다.
- 사용 맥락:
- 네트워크에 전혀 참여하지 않는 액터일 때.
- 서버/클라이언트 간에 동기화(Replication)를 하지 않는 액터일 때.
- 특징:
- 이 액터는 네트워크로부터 아무런 메시지도 주고받지 않습니다.
- 엔진 내부에서 Network Role이 전혀 필요 없는 특수한 경우에 해당합니다.
2) ROLE_SimulatedProxy
- 설명: 서버에서만 authoritative(권한을 가진) 데이터를 제어하고, 클라이언트 쪽에서는 단순히 ‘서버의 상태를 복제(Replicate)받아 시뮬레이션하는’ 역할입니다.
- 사용 맥락:
- 다른 클라이언트가 소유 중인 액터를 현재 클라이언트가 ‘보여주기만’ 해야 할 때.
- 물리나 애니메이션만 간단히 업데이트하고, 근본적인 위치/상태 같은 중요한 게임 데이터는 서버가 결정할 때.
- 특징:
- 클라이언트가 이 액터를 ‘직접 조작’하지 않습니다. 서버가 결정한 상태를 그대로 복제(Replicate)받아 보여주는 역할입니다.
- 대표적인 예시로, 내가 조종하지 않는 다른 플레이어 캐릭터가 내 화면에서 Simulated Proxy로 동작합니다.
3) ROLE_AutonomousProxy
- 설명: 클라이언트 스스로가 이 액터를 제어할 수 있는 역할로, 대부분 “내가 소유(Ownership)한 플레이어 캐릭터/파우너(Pawn)”가 이 상태를 갖습니다.
- 사용 맥락:
- 플레이어가 직접 입력을 넣어 움직이는 캐릭터일 때. (특정 클라이언트가 조작하는 액터)
- 특징:
- 해당 클라이언트에서 직접 액터의 움직임, 입력, 상호작용 등을 처리하고, 그 결과를 서버에 전송(Replication)합니다.
- 서버는 이를 받아서 authoritative한 결정을 내리고, 다시 다른 클라이언트에게도 상태를 동기화해줍니다.
- 즉, 클라이언트는 “내 액터”에 대해서 좀 더 즉각적인 제어권을 행사하고, 서버와의 동기화는 빠른 응답성 유지가 핵심입니다.
4) ROLE_Authority
- 설명: 해당 액터에 대해 ‘권한(authority)’이 있는, 즉 최종적인 결정권을 갖는 역할입니다. 주로 서버에서 액터가 갖는 역할입니다.
- 사용 맥락:
- 멀티플레이 게임에서 서버는 모든 핵심 게임 로직(위치, 체력, 충돌 판정, 점수 처리 등)에 대한 권위를 가집니다.
- 특징:
- 액터의 모든 결정(데이터 변경, 함수 호출 등)을 궁극적으로 서버에서 수행하고, 그 결과를 클라이언트에 전송합니다.
- 일반적으로 “Listen Server” 형태로 플레이하면 서버 + 로컬 플레이가 한 프로세스 내에 공존하기 때문에, 해당 액터는 ROLE_Authority와 동시에 내 PC 상에서도 제어가 가능합니다(호스트 플레이 시).
- “Dedicated Server”에서는 클라이언트가 전혀 없고, 전용 서버 프로세스만 ROLE_Authority를 갖습니다.
2. Role과 RemoteRole
언리얼에서 액터의 Role에는 실제로 다음과 같은 두 가지 프로퍼티가 존재합니다.
- Role: 현재 객체(액터)를 기준으로 본 자신의 역할.
- RemoteRole: 원격에서 이 액터를 어떻게 바라보고 있는지(또는 바라볼 것인지) 나타내는 역할.
이 두 값이 ‘서버에서 보았을 때’, ‘클라이언트에서 보았을 때’ 각각 다르게 설정되며, 복제를 통해 동기화가 일어납니다.
예를 들어,
- 서버에서 어떤 액터가 자기 자신(서버 입장)에 대해서 Role = ROLE_Authority라면, 그 액터의 RemoteRole은 ROLE_SimulatedProxy 또는 ROLE_AutonomousProxy가 됩니다. (클라이언트에서 그 액터를 어떻게 바라봐야 하는지)
- 클라이언트에서 자기 소유 캐릭터라면, Role = ROLE_AutonomousProxy, RemoteRole = ROLE_Authority 식으로 값이 잡히게 됩니다.
3. 동작 메커니즘
- 권위(Authority) 액터에서 상태 변경
- 서버에서 액터가 이동, 애니메이션 변경, 변수 값 수정 등 게임 로직상의 변화가 발생합니다.
- 서버는 자신을 ROLE_Authority로 인식하고, 해당 변화를 “복제(Replicate)”하도록 설정해둡니다.
- 복제(Replication) → 클라이언트
- 서버는 이 액터를 클라이언트들이 ROLE_SimulatedProxy 또는 소유하고 있는 클라이언트는 ROLE_AutonomousProxy로 바라보게끔 상태를 동기화(Replication)해줍니다.
- 각 클라이언트는 RemoteRole로부터 적절한 값을 할당받고, Role 값이 갱신됩니다.
- 클라이언트에서 AutonomousProxy가 입력 처리
- 어떤 클라이언트에서 이 액터를 직접 조작한다면(ROLE_AutonomousProxy), 입력 처리가 일어나고, 그 결과를 서버로 보냅니다.
- 서버가 Authority에서 최종 결정
- 서버에서 수신한 입력 값, 위치 등을 검증하거나 보정한다음 최종 값을 계산합니다.
- 그 결과를 다시 클라이언트에게 복제합니다.
4. 활용 예시
- 플레이어 캐릭터(나 자신이 조종하는 경우)
- 서버 입장: Role = ROLE_Authority
- 내 클라이언트 입장: Role = ROLE_AutonomousProxy
- RemoteRole: 적절히 ROLE_Authority, ROLE_SimulatedProxy가 할당
- 다른 플레이어 캐릭터(내가 조종하지 않는 경우)
- 서버 입장: Role = ROLE_Authority
- 내 클라이언트 입장: Role = ROLE_SimulatedProxy
- (그래서 다른 플레이어의 움직임은 서버가 보내준 정보 그대로, 또는 간단한 보간/예측을 통해 시뮬레이션)
- 탄환(Projectile)이나 아이템(Item) 등
- 서버가 권한을 가지고(ROLE_Authority) 위치, 충돌 판정을 업데이트합니다.
- 클라이언트는 보정 정보를 받아(ROLE_SimulatedProxy) 위치/상호작용을 보여주기만 합니다.
5. UE4 → UE5에서의 변화
언리얼 엔진 5에서 네트워크 관련 내부 구조가 약간씩 리팩토링되었으나, 기본적인 ENetRole의 동작 방식과 멤버 자체는 큰 틀에서 여전히 유사하게 유지되고 있습니다. 다만, UE5의 “리플리케이션 그래프(Replication Graph)”와 같은 고급 기능을 쓸 경우 역할 설정보다는 ‘채널, 권한 분산 방식’ 등에 좀 더 구체적인 설정이 들어갈 수 있습니다.
6. 정리
- ENetRole은 멀티플레이 게임에서 “서버와 클라이언트 간 권한, 제어 권역”을 구분하는 핵심 열거형입니다.
- ROLE_Authority가 최종 결정권을 가지며, 클라이언트가 자신의 액터를 소유(Ownership)할 경우 ROLE_AutonomousProxy를 사용합니다. 그 외 다른 원격 액터들은 주로 ROLE_SimulatedProxy로 동작합니다.
- Role과 RemoteRole 개념을 함께 이해해야, 액터의 동기화(Replication)와 입력 처리 과정을 제대로 알 수 있습니다.
언리얼에서 멀티플레이어 시스템을 다룰 때, ENetRole을 정확히 이해하고, 어느 시점에 어떤 Role을 가질지를 인지함으로써 예측(prediction), 보정(correction) 로직, RPC 호출(Server, Client, Multicast) 등을 원활하게 설계할 수 있게 됩니다.
2. 로컬 역할 vs 원격 역할
- LocalRole: “현재 이 오브젝트를 내 컴퓨터(로컬)에서 어떤 권한으로 다루고 있는가?”
- 서버에서 보면 자기 자신이 Authority이고, 클라이언트에서 내가 조종 중인 캐릭터는 AutonomousProxy 등.
- RemoteRole: “내가 보기에는 이 오브젝트가 원격(네트워크 반대편)에서 어떤 권한을 갖고 있는가?”
- 예: 서버 입장에선 다른 클라이언트의 캐릭터가 원격에서 AutonomousProxy로 동작 중임을 알 수 있고,
클라이언트 입장에선 서버의 캐릭터를 Authority로 인식할 수 있습니다.
- 예: 서버 입장에선 다른 클라이언트의 캐릭터가 원격에서 AutonomousProxy로 동작 중임을 알 수 있고,
시각 자료 제안
- “LocalRole / RemoteRole”을 기준으로 서버/클라이언트 각각의 캐릭터가 어떻게 보이는지 다이어그램
- 예: 서버가 캐릭터 A를 Authority로 보고, 클라이언트1에서 캐릭터 A는 SimulatedProxy 등으로 나타남
3. 오버헤드 위젯(Overhead Widget)으로 역할 표시하기
멀티플레이어 디버깅 시, “각 캐릭터가 **어떤 역할(Authority/Proxy)**인지” 시각적으로 확인하면 아주 편리합니다. 이를 위해 캐릭터 머리 위에 간단한 UI(위젯)를 띄워보겠습니다.
3.1 C++ UUserWidget 클래스 생성
- C++ Class → All Classes → UUserWidget 상속
- 예: OverheadWidget.h / .cpp 작성
// OverheadWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "OverheadWidget.generated.h"
class UTextBlock;
UCLASS()
class YOURPROJECT_API UOverheadWidget : public UUserWidget
{
GENERATED_BODY()
public:
// 위젯에 문자열을 표시하기 위한 함수
UFUNCTION(BlueprintCallable)
void SetDisplayText(const FString& TextToDisplay);
// 캐릭터의 Role 정보를 받아 UI에 표시
UFUNCTION(BlueprintCallable)
void ShowPlayerNetRole(APawn* InPawn);
protected:
// 위젯이 월드에서 제거될 때(레벨 전환 등) 호출되는 가상 함수
virtual void NativeDestruct() override;
// 위젯 블루프린트의 TextBlock과 바인딩할 변수
UPROPERTY(meta = (BindWidget))
UTextBlock* DisplayText;
};
// OverheadWidget.cpp
#include "OverheadWidget.h"
#include "Components/TextBlock.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/Actor.h"
void UOverheadWidget::NativeDestruct()
{
Super::NativeDestruct();
RemoveFromParent(); // 뷰포트에서 제거
}
void UOverheadWidget::SetDisplayText(const FString& TextToDisplay)
{
if (DisplayText)
{
DisplayText->SetText(FText::FromString(TextToDisplay));
}
}
void UOverheadWidget::ShowPlayerNetRole(APawn* InPawn)
{
if (!InPawn) return;
// 예시: LocalRole 기준
ENetRole LocalRole = InPawn->GetLocalRole();
FString RoleText;
switch (LocalRole)
{
case ROLE_Authority:
RoleText = TEXT("Authority");
break;
case ROLE_AutonomousProxy:
RoleText = TEXT("Autonomous Proxy");
break;
case ROLE_SimulatedProxy:
RoleText = TEXT("Simulated Proxy");
break;
default:
RoleText = TEXT("None");
break;
}
// 최종 표시 문자열 포맷 (ex: "Local Role: Authority")
FString FinalString = FString::Printf(TEXT("Local Role: %s"), *RoleText);
SetDisplayText(FinalString);
}
3.2 위젯 블루프린트 생성 & 바인딩
- Content Browser → User Interface → Widget Blueprint 생성
- 새로 만든 블루프린트의 Parent Class를 **UOverheadWidget**으로 설정
- 그래프 상단의 Class Settings → Parent Class → OverheadWidget
- 화면에 표시할 TextBlock(이름: DisplayText)를 두고, Varible 속성 활성화
- DisplayText는 .h 파일의 UPROPERTY(meta = (BindWidget))와 이름 동일해야 함
이제 C++의 SetDisplayText, ShowPlayerNetRole 함수를 통해 TextBlock의 내용을 동적으로 변경할 수 있습니다.
4. 캐릭터에 위젯 달기
4.1 UWidgetComponent 활용
- 캐릭터 C++ 클래스(또는 블루프린트)에 UWidgetComponent* OverheadWidget; 변수를 추가
- CreateDefaultSubobject<UWidgetComponent>로 생성 후, 캐릭터의 헤드 위치에 붙이기
- 블루프린트로 열어, Widget Class를 WBP_OverheadWidget(방금 만든 위젯 블루프린트)로 지정
- Draw Size, Pivot, Space(화면or월드) 등을 조정해 머리 위에 잘 보이도록 세팅
예시(C++)
// MyCharacter.h
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="HUD", meta=(AllowPrivateAccess="true"))
class UWidgetComponent* OverheadWidget;
// MyCharacter.cpp (Constructor)
AMyCharacter::AMyCharacter()
{
OverheadWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverheadWidget"));
OverheadWidget->SetupAttachment(RootComponent);
OverheadWidget->SetWidgetSpace(EWidgetSpace::Screen); // 화면 고정 or World
OverheadWidget->SetDrawSize(FVector2D(200.f, 50.f));
}
4.2 캐릭터 청사진에서 역할 표시 호출
- 예: BeginPlay나 Input 이벤트 등에서 OverheadWidget에 접근해 ShowPlayerNetRole(this) 호출
- 혹은 우리가 원하는 타이밍(Spawn 시점 등)에 실행하여, 로컬/원격 역할 표시 문자열 업데이트
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
if (OverheadWidget && OverheadWidget->GetUserWidgetObject())
{
UOverheadWidget* OWidget = Cast<UOverheadWidget>(OverheadWidget->GetUserWidgetObject());
if (OWidget)
{
OWidget->ShowPlayerNetRole(this);
}
}
}
5. 테스트 & 결과
5.1 멀티플레이어 모드 실행
- Play 창에서 Number of Players를 2~3 이상으로 설정
- 1명은 Listen Server, 나머지는 Client로 두고 실행
- 화면에 표시된 “Local Role: Authority”(서버) / “Local Role: Autonomous Proxy”(내가 조종 중인 클라이언트 캐릭터) / “Local Role: Simulated Proxy”(타인이 조종 중인 캐릭터) 등 역할 구분을 눈으로 확인할 수 있습니다.
5.2 원격 역할(RemoteRole)도 확인해보기
- 코드에서 InPawn->GetRemoteRole()를 사용하면, 다른 쪽 기기에서 이 캐릭터가 어떤 권한으로 동작 중인지 알 수 있습니다.
- 서버 관점에서 보면, 다른 클라이언트 캐릭터의 RemoteRole이 ROLE_AutonomousProxy일 수 있고,
클라이언트 관점에선 서버 캐릭터의 RemoteRole을 ROLE_Authority로 인식하는 식입니다.
6. 추가 팁 & 정리
- 디버그 용도: 멀티플레이어 디버깅 시 “이 캐릭터가 현재 어떤 역할인지” 파악하는 것이 중요합니다. 이 오버헤드 위젯처럼 머리 위 텍스트로 표시해놓으면 동기화 문제나 RPC 호출 문제 디버깅에 큰 도움이 됩니다.
- WidgetComponent 최적화: 실제 게임에선 위젯 컴포넌트 남발 시 퍼포먼스가 낮아질 수 있으니, 필요한 부분에만 배치하거나 LOD 시스템 등을 병행하세요.
- 더 나아가기: LocalRole/RemoteRole 외에도 GetIsLocallyControlled() 같은 함수를 활용해 “이 Pawn을 조종하는 컨트롤러가 로컬 플레이어인지” 쉽게 파악할 수 있습니다.
마무리
- 멀티플레이어에서는 각 기기가 자신이 조종하는 캐릭터를 AutonomousProxy로, 다른 사람 캐릭터를 SimulatedProxy로 인식합니다.
- 서버는 모든 캐릭터를 Authority로 두고, 클라이언트가 직접 조종 중인 캐릭터를 원격에서 AutonomousProxy로 보고 있습니다.
- 이 개념을 직관적으로 확인하기 위해 오버헤드 위젯을 만들어 캐릭터 머리 위에 표시하면 큰 도움이 됩니다.
위 과정을 직접 구현해보면, 자연스럽게 언리얼 엔진의 네트워크 구조를 이해할 수 있을 것입니다. 궁금한 점은 댓글로 남겨주세요. 즐거운 언리얼 멀티플레이어 개발 되시길 바랍니다!
'UnraealEngine > Multiplayer Shooter' 카테고리의 다른 글
| 44~46 (0) | 2025.03.13 |
|---|---|
| 41.Seamless Travel and Lobby (0) | 2025.03.12 |
| 25. Join Sessions from the Menu (0) | 2025.03.12 |
| 22. Callbacks to our Subsystem Functions (0) | 2025.03.12 |
| 23. More Subsystem Delegates (0) | 2025.03.12 |