본문 바로가기

멀티플레이 공부

멀티플레이 공부(NetMode, NetConnection, NetDriver, Ownership)

NetMode

  • 해당 게임 프로세스가 네트워크 상에서 어떤 역할을 하고 있는지를 의미.
  • 싱글(NM_StandAlone), 서버(NM_Listen, NM_DedicatedServer), 클라이언트(NM_Client)

게임에 중대한 영향을 미치는 로직은 서버 컴퓨터에서 처리하는데 이유는 해킹에 취약해지기 때문이다.

 

NetMode는 작성하고 있는 로직이 서버에서 돌고 있는지 클라이언트에서 돌고 있는지 알아야 할 때 필요하다.

 

UWorld::InternalGetNetMode() 함수는 언리얼에서 구현해놓은 함수인데 코드를 살펴보면

// World.cpp

...

ENetMode UWorld::InternalGetNetMode() const
{
	if ( NetDriver != NULL ) // 넷드라이버가 설정되어 있으면
	{
		const bool bIsClientOnly = IsRunningClientOnly();
		return bIsClientOnly ? NM_Client : NetDriver->GetNetMode(); // 클라 아니면 서버 둘 중 하나다.
	}

	...
}
// NetDriver.cpp

ENetMode UNetDriver::GetNetMode() const
{
#if WITH_EDITOR
	if (World && World->WorldType == EWorldType::PIE && IsServer())
	{
		FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(World);
		if (WorldContext && WorldContext->RunAsDedicated) // 데디로 실행했다면
		{
			return NM_DedicatedServer; // 데디 서버 반환.
		}
	}
#endif

	return (IsServer() ? (GIsClient ? NM_ListenServer : NM_DedicatedServer) : NM_Client);
		// 서버인데, 클라이언트로 참여하고 있다? 리슨서버. 서버인데 참여하고 있지 않다? 데디서버.
}
// NetDriver.cpp

bool UNetDriver::IsServer() const
{
	// Client connections ALWAYS set the server connection object in InitConnect()
	// @todo ONLINE improve this with a bool
	return ServerConnection == NULL;
		// 클라이언트는 무조건 서버 커넥션을 가지고 있다.
}

 

  특징
StandAlone
  • 게임이 원격 클라이언트의 연결을 허용하지 않는 서버로 실행.
  • 즉, 커넥션이 없음. 게임에 참여하는 모든 플레이어는 로컬 플레이어.
  • 이 모드는 싱글 플레이 및 로컬 플레이 게임에 사용.
  • 로컬 플레이어에 맞게 서버 측 로직과 클라이언트측 로직을 모두 실행.
Client
  • 게임이 네트워크 멀티플레이 세션에서 서버에 연결된 클라이언트로 실행.
  • 서버 측 로직을 실행하지 않습니다. 서버로부터 복제된 Proxy를 보여주는 역할입니다.
Listen Server
  • 게임이 네트워크 멀티플레이어 세션을 호스팅 하는 서버로 실행.
  • 원격 클라이언트의 연결을 수락하고 로컬 플레이어를 서버에 직접 배치합니다.
    즉, 서버 자기 자신도 게임에 직접 참여.
  • 이 모드는 캐주얼 협동 및 경쟁 멀티플레이어에 자주 사용.
Dedicated Server
  • 게임이 네트워크 멀티플레이 세션을 호스팅 하는 서버로 실행.
  • 원격 클라이언트의 연결을 허용하지만 로컬 플레이어가 없습니다.
    따라서 그래픽, 사운드, 입력 및 기타 플레이어 중심 기능이 필요 없으니 삭제된다.
  • 이 모드는 지속적이고 안전한 대규모 멀티플레이어가 필요한 게임에 주로 사용.

NetConnection

넷 커넥션은 다른 PC와 연결이 발생하면 그에 대응하는 넷 커넥션 객체가 생성이 된다.

서버에 클라이언트가 접속하면 서버에는 클라이언트 커넥션, 클라이언트에는 서버 커넥션 객체가 생성이 된다.

 

두 PC(서버와 클라이언트)는 넷 커넥션 객체를 통해 통신을 하게 된다.

 

NetDriver는 생성된 넷 커넥션 객체를 소유하고 관리를 한다.

서버 PC에 생성된 넷 드라이버는 접속한 클라이언트의 수 만큼 넷 커넥션을 관리한다.

(서버에서는 1개의 넷 드라이버가 접속한 클라이언트의 수 만큼 넷 커넥션을 관리한다.)

클라이언트 PC에 생성된 넷 드라이버는 서버 커넥션 하나만 관리를 한다.

(클라이언트에는 하나의 서버만 연결이 되므로 넷 드라이버도 서버 커넥션 하나만 관리를 한다.)

 

이는 서버 - 클라이언트 구조의 특징이다.

// NetDriver.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) - unsorted, and ordering changes depending on actor replication */
	UPROPERTY()
	TArray<TObjectPtr<UNetConnection>> ClientConnections;
	
	...

위 코드는 언리얼에서 작성해놓은 코드인데 보면 알 수 있듯이 넷 드라이버가 서버 커넥션은 1개의 객체를 관리하고 클라이언트 커넥션은 배열로 관리하여 여러 개를 관리하도록 구현이 돼있다.

// AActor.cpp

UNetConnection* AActor::GetNetConnection() const
{
	return Owner ? Owner->GetNetConnection() : nullptr;
		// 여기서 오너는 액터/폰/컨트롤러 등등이 될 수 있음.
		// 코드에서 볼 수 있듯이 Owner가 지정되어 있지 않으면 통신이 안되게끔 되어 있음.
}
class UNetConnection* APawn::GetNetConnection() const
{
	// if have a controller, it has the net connection
	if ( Controller ) // 컨트롤러가 있다면
	{
		return Controller->GetNetConnection(); // 컨트롤러의 GetNetConnection()을 호출.
	}
	return Super::GetNetConnection(); // 없다면 AActor::GetNetConnection() 함수 호출.
}
UNetConnection* APlayerController::GetNetConnection() const
{
	// A controller without a player has no "owner"
	return (Player != NULL) ? NetConnection : NULL;
		// 플레이어가 존재한다면 NetConnection 객체를 반환.
}

Ownership

하나의 클라이언트 커넥션은 하나의 플레이어 컨트롤러를 소유한다.

즉, 플레이어 컨트롤러의 Owning Connection은 클라이언트 커넥션이다.

PlayerController가 빙의하는 폰의 Owner 속성은 해당 플레이어 컨트롤러로 설정된다.

폰에 무기 액터가 생성되고 무기 액터의 Owner 속성에 해당 폰을 설정할 수 있다.

클라이언트 커넥션에서 부터 무기 액터에 이르는 소유 관계패밀리 라고도 부른다.

소유 관계 속에 있는 액터가 본인의 OwningConnection을 얻으려면 AActor::GetNetConnection() 함수를 호출하면 된다.

이 소유 관계는 후에 배울 RPC와 Property Replication과 관련이 있다.

하나의 소유 관계를 패밀리라고도 부른다.

NetDriver

언리얼 네트워크 통신에서 로우 레벨 동작들을 관리하는 클래스다.

싱글플레이에서는 넷 드라이버 객체가 생성되지 않는다.

멀티플레이에서만 UWorld::Listen() 함수를 통해 넷 드라이버 객체가 생성이 된다.

멀티플레이에 참여하는 각 PC마다 넷드라이버 객체가 생성된다.