TIL_065 : NetMode, NetDriver/NetConnection, NetRole, 로컬 컨트롤러/플레이어

김펭귄·2025년 11월 19일

Today What I Learned (TIL)

목록 보기
65/105

오늘 학습 키워드

  • NetMode

  • NetDriver, NetConnection

  • OwnerShip

  • NetRole

  • 로컬 컨트롤러, 로컬 플레이어

  • PossessedBy

1. NetMode

  • 게임 프로세스가 현재 네트워크 상에서 어떤 역할을 하고 있는지를 의미

  • 스킬 사용, 데미지로직 등 게임에서 중요한 로직들은 클라이언트가 아니라 서버에서 진행되어야 함

  • 또 하나의 코드가 서버PC, 내PC, 다른 PC 등 여러 곳에 존재하며 동작하기도 함

    • PlayerCharacterBeginPlay()에 있는 코드는 서버, 모든 클라이언트 PC에서 동작함
    • 마찬가지로 PlayerController의 코드는 서버와 내 PC에 존재하고 동작
  • 따라서, 각 로직은 현재 자신이 서버에서 돌고 있는지 클라이언트에서 돌고 있는지를 알아야하고, 이때 NetMode를 사용

  1. NM_StandAlone : 싱글플레이어의 경우
  2. NM_Listen : 서버이면서 동시에 클라이언트일 경우
  3. NM_DedicatedServer : Dedicated 서버의 경우
  4. NM_Client : 클라이언트의 경우
// EngineBaseTypes.h	언리얼 엔진 코드. enum으로 정의하여 사용함

enum ENetMode
{
	/** Standalone: a game without networking, with one or more local players. 
    Still considered a server because it has all server functionality. */
	NM_Standalone,

	/** Dedicated server: server with no local players. */
	NM_DedicatedServer,

	/** Listen server: a server that also has a local player who is hosting the game,
    available to other players on the network. */
	NM_ListenServer,

	/**
	 * Network client: client connected to a remote server.
	 * Note that every mode less than this value is a kind of server,
     so checking NetMode < NM_Client is always some variety of server.
	 */
	NM_Client,

	/* ENetMode 열거형(enumeration)의 마지막 값으로, 
    실제 네트워크 모드를 나타내기보다는 사용 가능한 네트워크 모드의 총 개수를 정의하거나
    배열의 크기를 지정하는 데 사용되는 내부 상수 */
	NM_MAX,	
};
  • NetMode는 NetConnectionNetDriver을 이용하여 정해짐
// World.cpp

ENetMode UWorld::InternalGetNetMode() const	
{
	if (NetDriver != NULL)	
    {
    	const bool bIsClientOnly = IsRunningClientOnly();	// 클라만 존재하는지
        return bIsClientOnly ? NM_Client : NetDriver->GetNetMode();
    }
}
  • InternalGetNetMode()함수는 위에서 본 ENetMode열거형을 반환값으로 사용

  • NetDriver가 설정되어 있으면, 네트워킹하는 멀티플레이이므로, 함수들 통해 넷모드 반환

  • IsRunningClinetOnly()함수를 찾아보니 ForceInline 키워드가 존재.
    원래 Inline키워드로 함수호출의 오버헤드를 줄이고, 직접 내재화 시키는 방식인데 컴파일러가 스스로 판단하고 해주기에 안 될수도 있음.
    ForceInline 쓰면 무조건 해주므로 장점도 있지만, 강제이기에 조심해서 사용해야함

  • 암튼, IsRunningClientOnly()함수를 통해 클라만 있으면 먼저 NM_Client로 확정. 아니라면 GetNetMode()를 통해 확인

ENetMode UNetDriver::GetNetMode() const {
	// ... //
	return (IsServer() ? 
    	   (GIsClient ? NM_ListenServer : NM_DedicatedServer) :
           NM_Client);
}
  • NetMode 열거형에서 NM_Client말고는 다 서버의 일환으로 취급했음.
    따라서, 먼저 IsServer()로 서버 아니면 NM_Client반환.
    그리고 서버인데 클라도 있으면 Listener이고 클라 없으면 Dedicated를 반환
bool UNetDriver::IsServer() const {
	return ServerConnection == NULL;
}
  • 모든 클라이언트는 서버와 연결되었기에 ServerConnection이 있고, 서버는 본인이 서버니까 없음. 따라서 ServerConnection으로 서버인지를 판단.
    그래서 StandAlone, ListenServer, DedicatedServer는 서버로 취급됨
    (싱글플레이도 ServerConnection 설정이 안 되어있으므로 서버)

NetMode 확인해보는 코드

static FString GetNetModeString(const AActor* InWorldContextActor)
{
	FString NetModeString = TEXT("None");

	if (IsValid(InWorldContextActor))
	{
		ENetMode NetMode = InWorldContextActor->GetNetMode();
		if (NetMode == NM_Client)
			NetModeString = TEXT("Client");
		else if (NetMode == NM_Standalone)
			NetModeString = TEXT("StandAlone");
		else
			NetModeString = TEXT("Server");
	}

	return NetModeString;
}

2. NetDriver, NetConnection

  • 위에서 NetMode를 얻기 위해 사용했던 객체들

  • NetDriver는 언리얼 엔진에서 네트워크 통신을 총괄하는 핵심 클래스

  • NetDriver는 서버와 클라이언트 사이의 데이터 전송을 관리하며, 여러 NetConnection을 통제

  • 다른 PC와 연결되면 NetConnection이 생성됨. 이 연결을 NetDriver가 소유하고 관리

  • 서버의 경우 클라가 접속하면 NetDriver 객체에 ClientConnection객체가 생성되고 관리함.
    그래서 서버는 접속한 클라 개수만큼의 ClientConnection이 생김

  • 당연히, ServerConnection은 없음(NULL)
    그래서 위에서 본 IsServer()에서 이 ServerConnection으로 서버인지를 판단

  • 클라의 경우 서버에 접속했으므로 NetDriver객체에 ServerConnection객체가 하나만 생성되고 관리됨.
    ClientConnection은 없음

// UNetDriver.h

UCLASS(/*...*/)
class UNetDriver : public UObject, public FExec
{
	// ... //
	
	/** Connection to the server (this net driver is a client) */
    // 서버의 경우는 없고 클라는 하나만 있으므로 포인터변수로 선언
	UPROPERTY()
	TObjectPtr<class UNetConnection> ServerConnection;

	/** Array of connections to clients (this net driver is a host) */
    // 서버의 경우만 여러 개 가지니까 배열로 선언
	UPROPERTY()
	TArray<TObjectPtr<UNetConnection>> ClientConnections;
}
  • NetDriver의 역할

    • 네트워크 연결 수락 및 종료 관리
    • 패킷 송수신 및 처리
    • 동기화 및 네트워크 복제 지원
  • NetDriver는 서버가 네트워크 시작할 때 월드의 Listen함수를 호출하여 생성되고, 이 객체를 통해 통신이 된다

  • 클라이언트의 경우 서버와 달리 서버에 접속을 요청할 때, NetDriver가 생성됨

  • 싱글게임의 경우 Listen호출 안 하니 NetDriver생성 안 됨

GetNetConnection()

// AActor.cpp
UNetConnection* AActor::GetNetConnection() const
{
	return Owner ? Owner->GetNetConnection() : nullptr;
}
  • Actor의 경우 Owner가 있을때만 NetConnection을 가져오고 없다면 nullptr임.

  • 다시 말해, ActorNetConnection을 가지려면, 액터가 반드시 Owner를 가져야 하며, 그 오너가 NetConnection을 소유하고 있어야 함

  • 액터는 자신의 NetConnection은 없고, 대신 액터의 Owner가 NetConnection을 소유하는 구조

class UNetConnection* APawn::GetNetConnection() const
{
	if ( Controller ) 
		return Controller->GetNetConnection(); 
	return Super::GetNetConnection();
}
  • 폰의 경우는 컨트롤러가 있으면 컨트롤러의 패밀리에 들어가 NetConnection을 가짐

  • 컨트롤러가 없으면 ActorGetNetConnection호출하여 똑같이 실행

UNetConnection* APlayerController::GetNetConnection() const
{
	return (Player != NULL) ? NetConnection : NULL;
}
  • 플레이어 컨트롤러는 해당 플레이어의 NetConnection을 직접 관리하는 객체

  • 따라서 연결된 플레이어가 있으면 NetConnection가지는 거니까 바로 반환

  • 플레이어가 없으면 Owner가 없는것이므로 NULL반환

3. Ownership

  • 위에서 언급했듯이, 서버에서 하나의 ClientConnection은 하나의 PlayerController를 소유

  • 그리고 컨트롤러가 Possess한 캐릭터는 컨트롤러를 Owner로 설정함

  • 또 이 캐릭터가 무기를 소유하여 Owner관계를 맺으면 ClientConnection으로부터 Weapon까지를 하나의 패밀리라고 부르게 되며, 이 소유관계에 속한 액터들은 GetNetConnection()을 이용하여 자신의 NetConnection을 얻을 수 있다

4. NetRole

  • 이전에 공부한 NetMode는 월드 단위, 해당 월드가 서버PC의 월드인지, 클라이언트PC의 월드인지에 대한 정보

  • 하지만, 실제 우리가 작성할 대부분의 코드는 액터관련 코드이고 이 액터가 서버PC, 클라이언트PC 등 어디에서 스폰되었는지를 확인하는 것이 NetRole

Authority vs Proxy

  • 서버에서 스폰된 액터의 NetRoleAuthority. 권한을 가졌다는 뜻

  • 이 액터가 클라에 복제되면 NetRoleAutonomous Proxy. 허상이라는 뜻

Local Role vs Remote Role

  • 하나의 액터는 2개의 Role을 가짐

  • 액터의 Local Role은 내 PC에서의 해당액터의 NetRole

  • 내 액터가 NetConnection을 통해 다른 PC에서 복제되었을 때의 다른 PC에서의 Local Role이 내 액터의 Remote Role

  • 서버에서 스폰된 액터의 Local RoleAuthority, Remote RoleAutonomous Proxy

  • 클라에서 복제된 액터의 Local RoleAutonomous Proxy, Remote RoleAuthority

NetRole 종류

  1. None
  • 보통 레플리케이션 되지 않는 액터를 의미
  • LocalRoleAuthority이고, RemoteRoleNone
  1. Authority
  • 게임에 중대한 영향을 끼칠 수 있는 권한을 가진 액터
  • 서버에서 스폰된 액터의 LocalRole
  1. Autonomous Proxy
  • 서버에 있는 Authority액터의 복제본
  • 서버로부터 데이터도 받지만, 서버로 송신(요청)도 가능
  • 예시) PlayerController
  1. Simulated Proxy
  • 똑같이 복제본이지만, 서버로부터 데이터 받기만 하고 송신은 불가능
  • 예시) 내 PC에서의 친구의 PlayerCharacter
// EngineTypes.h
/** The network role of an actor on a local/remote network context */
UENUM(BlueprintType)
enum ENetRole : int
{
	/** No role at all. */
	ROLE_None UMETA(DisplayName = "None"),
	/** Locally simulated proxy of this actor. */
	ROLE_SimulatedProxy UMETA(DisplayName = "Simulated Proxy"),
	/** Locally autonomous proxy of this actor. */
	ROLE_AutonomousProxy UMETA(DisplayName = "Autonomous Proxy"),
	/** Authoritative control over the actor. */
	ROLE_Authority UMETA(DisplayName = "Authority"),
	ROLE_MAX UMETA(Hidden),
};

NetRole 확인해보는 코드

static FString GetRoleString(const AActor* InActor)
{
	FString RoleString = TEXT("None");

	if (IsValid(InActor))
	{
		FString LocalRoleString = UEnum::GetValueAsString(TEXT("Engine.ENetRole"), 
        												  InActor->GetLocalRole());
		FString RemoteRoleString = UEnum::GetValueAsString(TEXT("Engine.ENetRole"),
        												   InActor->GetRemoteRole());

		RoleString = FString::Printf(TEXT("%s / %s"), 
        							*LocalRoleString,
                                    *RemoteRoleString);
	}

	return RoleString;
}
  • 이 함수를 PlayerCharacterBeginPlay()PossessedBy()에서 호출 하여 서버에서 확인해보면 서로 다른 NetRole이 나옴

  • 먼저 서버에서 캐릭터의 LocalRoleAuthority

  • 그리고 RemoteRoleBeginPlay()시점에는 아직 캐릭터가 컨트롤러에 연결되지 않았으므로, Simulated Proxy이고, PossessedBy()에선 컨트롤가 연결되었으므로 Autonomous Proxy로 바뀜

  • 즉, 컨트롤러가 캐릭터를 소유해 캐릭터의 Owner가 컨트롤러가 됨

  • NetConnection은 컨트롤러를 직접 소유, 관리하므로 이 둘도 Owner관계여서, ClientConnection->컨트롤러->캐릭터 다 Autonomous Proxy Role을 가진다

  • NetMode처럼 NetRole도 패밀리에 속하면 같은 Role가짐

NetRole 관련 함수

  • AActor::HasAuthority() : LocalRoleAuthority인지 아닌지

  • APawn::IsLocallyControlled() : 컨트롤러가 로컬컨트롤러인지 반환.
    클라에서의 컨트롤러인지를 의미. Listener의 경우도 서버/클라 같이니까 참

bool APawn::IsLocallyControlled() const
{
	return ( Controller && Controller->IsLocalController() );
}

bool AController::IsLocalController() const
{
	const ENetMode NetMode = GetNetMode();

	if (NetMode == NM_Standalone)
	{
		// Not networked.
		return true;
	}
	
	if (NetMode == NM_Client && GetLocalRole() == ROLE_AutonomousProxy)	// 리슨서버 배제
	{
		// Networked client in control.
		return true;
	}

	// 리슨 서버
	if (GetRemoteRole() != ROLE_AutonomousProxy && GetLocalRole() == ROLE_Authority)
	{
		// Local authority in control.
		return true;
	}

	return false;
}
  • NetRole을 이용하여, 로컬컨트롤러인지 확인하고, 맞으면 입력 로직 및 UI 처리

Local Role과 Remote Role을 구분한 이유

  • 서버에 접속하여 생긴 캐릭터 A와 그냥 서버에서 생성한 캐릭터 B는 둘 다 Authority

  • 하지만 A는 ClientConnectionOwnership관계로 인해, Autonomous가 되고, B는 ClientConnection과 연결 안 되기에 Simulated가 되어 서로 구분이 가능해진다

5. 로컬 컨트롤러/플레이어

  • 로컬 컨트롤러

    • 현재 클라에서 작동 중인 컨트롤러
    • 플레이어 컨트롤러나 AI 컨트롤러를 포함
  • 로컬 플레이어

    • 현재 클라이언트에 실제로 존재하고 입력을 받는 유저 단위 객체
    • 일반적으로, 클라에서 자신의 컨트롤러를 소유하며, 이 컨트롤러가 캐릭터(폰)을 소유함
    • UI, 입력, 세션 관리 등 플레이어 관련 시스템적인 역할을 담당

6. PossessedBy()

  • 이 함수는 서버에서만 실행되고, 클라이언트에서는 실행되지 않음
profile
반갑습니다

0개의 댓글