개발 이야기 안하는 개발자

언리얼5_3_1 : 언리얼 네트워크 멀티플레이어 프레임워크 기초 본문

Unreal/이득우의 언리얼 프로그래밍

언리얼5_3_1 : 언리얼 네트워크 멀티플레이어 프레임워크 기초

07e 2024. 3. 29. 15:54
반응형

기능이 일부 제작된 3강 예제이다.

 

실행버튼 끝쪽에 more를 누르면 Numberof Players로 플레이 사람수를 정할 수 있다.

Play a Listen Server 를 누르게 되면 게임 실행방식은 이제 Listen Server 로 동작하게 된다.

 

Project 셋팅에서 Allow Late Joing을 활성화 한다

그럼 다음과 같이 톱니바키 실행버튼 (Add another Client)이 생기는데, 이 버튼을 누르면 다른 클라이언트의 화면이 활성화 되는 것을 볼 수 있다.

 

언리얼 네트워크 개요

 

언리얼 멀티 프로젝트는 새로운 엔진처럼 느낄정도로 새롭고 어렵다.

 

멀티플레이 프로젝트를 제작할 예정이라면 처음부터 구성을 염두해 두고 제작해야 한다.

멀티플레이 게임은 싱글플레이에서도 정상 작동한다.

하지만 반대로 싱글플레이 프로젝트를 멀티로 만들려고 한다면 다시 처음 만드는게 더 빠를 수 있다.

 

클라이언트 - 서버 모델

하나의 화면을 토데로 각 플레이어의 사용자 인터페이스를 포함하여 게임의 모든것을 제어하며 하나의 로컬 컴퓨터로 게임을 진행하게 된다.

특정한 판정이 일어날땐 서버가 판단해주어야 한다. 서버가 절대적으로 권위를 보유한다.

클라이언트는 각각 서버에서 자신이 소유한 폰을 원격 제어하여 게임 내 동작을 프록시저 호출을 보내게 된다.

 

싱글 플레이의 작동 방식은

게임 인스턴스가 게임의 테두리안에 월드라는 컨텐츠가 담기게 된다.

이러한 방식을 스탠드 얼론이라고 부른다.

 

 

 

멀티플레이어 게임의 구성이다. (클라이언트 - 서버 모델)

컴퓨터끼리 엮어 놓은 커다란 하나의 게임 서비스로 묶어놓은 형태이다.

게임 서버가 실제 컨텐츠를 담는 머신으로 보면 된다.

여기에 나오는 클라이언트들은 움직임에 대해 입력을 하면 서버로 데이터를 보낸다.

그리고 나온 결과물을 서버로 부터 받아 입력하면 출력되는 것 처럼 보이게 하는 방식이다.

각 클라이언트는 하나의 돍집적인 인스턴스를 가지지만, 모두 서버로 부터 복제된 허상이다.

 

 

위 그림에서 멀티게임중에 화면을 보면 중앙에 분수대가 오른쪽 클라이언트에선 보이지 않는다.

이처럼 서버에서 액터를 복사해서 보여주기를 원하는 지 원하지 않는지를 구별지을 수 있고,

이처럼 복사하는 행위 액터 리플리케이션이라고 부른다.

 

네트워크 모드

게임 인스턴스는 다음 네트워크 모드 중 하나를 취할 수 있다.

Standalone - 게임에 참여하는 클라이언트들은 모두 로컬 플레이어로 게임을 진행한다.

Client - 게임이 서버와 연결되어 실행되지만 서버측 논리는 실행되지 않는다.

Listen Server - 네트워크 멀티플레이어 세션을 호스팅하는 서버로 실행된다. 호스트 클라이언트가 서버가 되며 캐주얼 협동 및 경쟁 멀티플레이어에 자주 사용된다.

Dedicated Server - 로컬 플레이어가 없는 서버와 연결하고 부가적인 기능(그래픽, 사운드, 입력 등)을 모두 제외하고 실행하기 때문에 대규모 멀티플레이어가 필요한 게임에 자주 등장한다.

 

리슨서버는 서버가 로컬 플레이어가 되기 때문에 부정행위에 대한 우려가 제기될 수 있다.

하지만 간단한 기능으로 구현이 가능하기 때문에 빠르고 효율적으로 제작하고 관리를 따로 할 필요가 없다.

데이케이트 서버는 전용 서버가 필요하기 때문에 관리하기 어렵고 비싸다. 하지만 공정성이 보장된다.

 

게임 서버 기반 로그인 플로우

서버가 초기화 되면 아무것도없기 때문에 현재 Standalone 상태이다.

플레이어 컨트롤러를 활성화 함으로써 게임에 참여한 상태가 되었다.

이렇게 해놓으면 이제 리슨서버 상태가 되었다. 

누군가가 요청을 진행하면들을 수 있다.

새로운 클라이언트가 서버에게 접속을 요청한다.

서버는 이를 받아들인다.

입력받은 플레이어 컨트롤러를 복사한다.

그렇게 하고 클라이언트에게 플레이어 컨트롤러를 복사해서 전달한다.

이렇게 해서 서버는 접속하는 클라이언트를 받았고 로그인에 성공시켜 게임을 진행하도록 했다.

 

네트워크용 매크로 제작

접근하기 쉽게 ArenaBattle.h에 로그 카테고리를 추가한다.

DEFINE_LOG_CATEGORY(LogABNetwork);

CPP 에서도 같이 LogABNetwork를 확장하는 내용을 추가하고 로그 매크로도 define 한다.

- Net의 상태를 찍어 CLient, Server, Standalone 의 상태를 띄운다. 클라이언트라면 PID도 같이 띄운다. (몇번째 플레이어)

- 호출하는 위치를 띄우도록 정의

- 로그 기능 정보

#define LOG_NETMODEINFO ((GetNetMode() == ENetMode::NM_Client) ? *FString::Printf(TEXT("CLIENT%d"), GPlayInEditorID) : ((GetNetMode() == ENetMode::NM_Standalone) ? TEXT("STANDALONE") : TEXT("SERVER"))) 
#define LOG_CALLINFO ANSI_TO_TCHAR(__FUNCTION__)
#define AB_LOG(LogCat, Verbosity, Format, ...) UE_LOG(LogCat, Verbosity, TEXT("[%s] %s %s"), LOG_NETMODEINFO, LOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))

DECLARE_LOG_CATEGORY_EXTERN(LogABNetwork, Log, All);

아무곳에 다음 스크립트를 넣어 테스트를 진행한다.

AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

 

다음은 게임모드에서 정리한 내용이다.

...
	virtual void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override;
	virtual APlayerController* Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override;
	virtual void PostLogin(APlayerController* NewPlayer) override;
	virtual void StartPlay() override;
...

PreLogin과 PoseLogin, StartPlay , Login 은 모두 gameMode가 가지고 있는 함수들이다.

이들 모두 로그인에 사용되는 큰 역할을 한다.

 

PreLogin - 클라이언트의 접속 요청을 처리하느 ㄴ함수

Login - 접속을 허용한 클라이언트에 대응하는 플레이어 컨트롤러를 만드는 함수

PoseLogin - 플레이어 입장을 위해 플레이어에 필요한 기본 설정을 모두 마무리하는 함수

StartPlay - 게임의 시작을 지시하는 함수

BeingPlay - 게임 모드의 StartPlay를 통해 게임이 시작될 때 모든 액터에서 호출하는 함수.

void AABGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("============================================================"));
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	Super::PreLogin(Options, Address, UniqueId, ErrorMessage);

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

APlayerController* AABGameMode::Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	APlayerController* NewPlayerController = Super::Login(NewPlayer, InRemoteRole, Portal, Options, UniqueId, ErrorMessage);

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));

	return NewPlayerController;
}

void AABGameMode::PostLogin(APlayerController* NewPlayer)
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	Super::PostLogin(NewPlayer);

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

void AABGameMode::StartPlay()
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	Super::StartPlay();

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));

}

 

 

Gamemode에서 StartPlay를 하면 본격적으로 시작이 된다.

그런데 클라이언트에는 게임 모드가 없다.

서버는 이를 위해서 게임 스테이트라고 하는 특별한 액터를 사용한다.

게임 스테이트는 게임 모드와 다르게 서버와 클라이언트 모두에게 존재한다.

게임 모드는 직접 스스로 게임을 시작하지않고 게임 스테이트에 명령을 지시해 시작하도록 하고

명령을 받은 게임 스테이트는 월드에 속한 모든 액터들에게 BeginPlay()를 호출하도록 이렇게 지시를 하게 된다.

클라이언트에게 복제된 게임 스테이트는 클라이언트에서 복제되며 바로 모든 액터들에게 BeginPlay하도록 명령한다.

 

게임 스테이트를 하나 제작해본다.

class ARENABATTLE_API AABGameState : public AGameStateBase
{
	GENERATED_BODY()
	
public:
	virtual void HandleBeginPlay() override;

	virtual void OnRep_ReplicatedHasBegunPlay() override;
};

HandleBeginPlay - 원격으로 호출되는 메소드이다.

OnRep_ReplicatedHasBegunPlay - bReplicatedHasBegunPlay를 클라이언트가 서버에게 전송한다,

이후 서버가 이를 받아 값이 변경된 것을 체크해서 게임을 시작하게 되는 것이다.

 

void AABGameState::HandleBeginPlay()
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	Super::HandleBeginPlay();

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

void AABGameState::OnRep_ReplicatedHasBegunPlay()
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	Super::OnRep_ReplicatedHasBegunPlay();

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

 

게임 모드도 Stat을 알아야 하기 때문에 추가해준다.

...
GameMode.cpp

AABGameMode::AABGameMode()
{
	static ConstructorHelpers::FClassFinder<APawn> DefaultPawnClassRef(TEXT("/Script/Engine.Blueprint'/Game/ArenaBattle/Blueprint/BP_ABCharacterPlayer.BP_ABCharacterPlayer_C'"));
	if (DefaultPawnClassRef.Class)
	{
		DefaultPawnClass = DefaultPawnClassRef.Class;
	}

	static ConstructorHelpers::FClassFinder<APlayerController> PlayerControllerClassRef(TEXT("/Script/ArenaBattle.ABPlayerController"));
	if (PlayerControllerClassRef.Class)
	{
		PlayerControllerClass = PlayerControllerClassRef.Class;
	}

	GameStateClass = AABGameState::StaticClass();
}

 

디버그를 찍어서 어떻게 실행되는지 확인해본다.

 

혼자 진행하다가 셋팅이 끝난다면 서버상태가 되어 입력을 받을 수 있는 상태가 되었음으로 Standalone에서 Server로 바뀐것을 확인할 수 있다.

이후 클라이언트를 추가하게 되면 다음과 같이 뜬다.

 

액터의 준비와 게임의 시작

 

게임 서버와 무관하게 액터 설정의 초기화 PostnitializeComponents

원격 클라이언트에서 네트워크 관련 설정의 초기화가 끝나면 호출되는 메소 PostNetInit

게임 진행에 필요한 초기화는 BeginPlay

 

PostNetInit 함수는 클라이언트에서만 호출된다.

네트워크 관련 설정이 초기화 되면 호출되기 때문이다.

 

StartPlay를 호출하지 않으면 BeginPlay가 호출되지 않는다.

 

GameMode에서 ErrorMessage에 특별한 메세지를 넣으면 실패로 간주하고 접속을 하지 않는다.

void AABGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("============================================================"));
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	Super::PreLogin(Options, Address, UniqueId, ErrorMessage);
	ErrorMessage = TEXT("Server is full");

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

이렇게 에러메세지에 값을 추가하게 되면 문제로 간주하고 서버에 들어가지 못한다.

 

네트워크 통식을 담당하는 주요 클래스

PlayerController 클래스 - 네트워크 통신에 접근가능한 게임 내 대표 액터

UNetConnection 클래스 - 주고받는 패킷 데이터의 인코딩 디코딩, 네트워크 통신량 조절, 채널 관리

UNetDriver 클래스 - 로우레벨에서의 소켓 관리와 패킷 처리, 네트워크 통신 설정

 

현재 월드에 NetDriver가 있는지 확인하는 것으로 클라이언트-서버, 또는 스탠드얼론으로 판단한다.

서버는 월드의 Listen함수를 호출해 넷드라이버를 생성함으로 네트워크 기능을 시작한다.

UWorld::InternalGetNetMode() 

UWorld::Listen()

 

넷드라이버의 커넥션 관리

넷드라이버는 다수의 커넥션(Connection)을 관리하고 있으며, 서버와 클라이언트에 따라 다르게 동작한다.

클라이언트에서 넷드라이버는 하나의 서버 커넥션을 가지고, 서버에서 넷드라이버는 다수의 클라이언트 커넥션을 가진다.

UNetDriver::IsServer() - Serverconnection이 있는지 확인하고, 있으면 클라이언트고 없으면 서버이다.

 

게임모드에서 PostLogin 로직에서 NetDriver를 통해 커넥션을테스트해본다.

void AABGameMode::PostLogin(APlayerController* NewPlayer)
{
	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));

	Super::PostLogin(NewPlayer);

	UNetDriver* NetDriver = GetNetDriver();
	if (NetDriver)
	{
		for (const auto& Connection : NetDriver->ClientConnections)
		{
			AB_LOG(LogABNetwork, Log, TEXT("Client Connections : %s"), *Connection->GetName());
		}
	}
	else
	{
		AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("No NetDriver"));
	}

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

 

컨트롤러에서 PostNetInit에서도 NetDriver를 확인해본다.

PostNetInit은 서버쪽 초기화가 끝나면 받는 메소드라서 클라이언트에게만 호출되는 메소드이다.

void AABPlayerController::PostNetInit()
{
	AB_LOG(LogABNetwork, Log, TEXT("%s %s"), TEXT("Begin"), *GetName());

	Super::PostNetInit();

	UNetDriver* NetDriver = GetNetDriver();
	if (NetDriver)
	{
		AB_LOG(LogABNetwork, Log, TEXT("Server Connection : %s"), *NetDriver->ServerConnection->GetName());
	}
	else
	{
		AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("No NetDriver"));
	}

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

 

 

커넥션과 오너쉽

언리얼 오너쉽 문서

 

네트워크에서 주고받는 데이터들은 다음과 같은 고도화 작업을 거친다.

커넥션 : 모든 데이터를 전달하는 네트워크 통로 

패킷 : 네트워크를 통해 전달되는 단위 데이터. 숫자 혹은 문자로 구성

채널 : 언리얼 엔진 아키텍쳐에 따라 구분된 데이터를 전달하는 논리적인 통로 (하나의 커넥션엔 여러개의 채널이 있다)

번치 : 언리얼 엔진의 아키텍쳐에 사용되는 데이터. 하나의 명령에 대응하는 데이터 묶음.

 

데이터 통신을 관리하기위한 대표 액터로 플레이어 컨트롤러가 주로 사용된다.

커넥션을 담당하는 대표 액터는 커넥션에 대한 오너십을 가진다고 표현한다.

 

서버에는 많은 컨트롤러가 존재한다.

그리고 클라이언트는 하나의 컨트롤러만 존재하는데 이때 중요한건 누가 소유하고 조종하는지에 대한 오너십이다.

 

어떤 액터가 통신을 하기 위해서는 자신을 소유한 액터가 커넥션을 소유하고 있어야 한다.

플레이어 컨트롤러가 넷커넥션을 소유하고있고, 넷커넥션 역시 플레이어 컨트롤러를 소유하고 있다.

AActor::GetNetConnection()

APlayerController::GetNetConnection()

 

플레이어 컨트롤러가 캐릭터에 빙의하면 캐릭터의 오너로 설정된다.

서버 : 빙의(Possess)함수를 호출해 오너십을 설정한다.

클라이언트 : 오너십이 설정된 캐릭터의 속성이 배포되면서 자신이 조종하는 캐릭터에 오너십이 설정된다.

 

캐릭터가 무기 액터를 소유하게 되면 무기 액터도 플레이어 컨트롤러가 조종가능한 패밀리 범위로 들어오기 때문에 통신이 가능한 상태가 된다.

- 플레이어 컨트롤러 : 통신 가능

- 플레이어 컨트롤러가 소유한 액터 : 통신 가능

- 플레이어 컨트롤러가 소유한 액터가 소유한 무기 액터 : 통신 가

 

Controller.h에서 Possess를 찍어본다.

빙의가 시작되면 어떻게 찍히는지 확인해 본다.

	virtual void OnPossess(APawn* InPawn) override;
void AABPlayerController::OnPossess(APawn* InPawn)
{
	AB_LOG(LogABNetwork, Log, TEXT("%s %s"), TEXT("Begin"), *GetName());

	Super::OnPossess(InPawn);

	AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}

빙의가 될때 Possess 내부 함수를 들여다 보면 PossessedBy를 통해 빙의를 얻어온다

이것도 같이 확인해 본다.

캐릭터 플레이어.h

	virtual void PossessedBy(AController* NewController) override;
void AABCharacterPlayer::PossessedBy(AController* NewController)
{
	AB_LOG(LogABNetwork, Log, TEXT("%s %s"), TEXT("Begin"), *GetName());
    
    //소유자가 있는지 체크
	AActor* OwnerActor = GetOwner();
	if (OwnerActor)
	{
		AB_LOG(LogABNetwork, Log, TEXT("Owner : %s"), *OwnerActor->GetName());
	}
	else
	{
		AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("No Owner"));
	}

	//Super에서 권한 넘겨지는 기존 로직 호출
	Super::PossessedBy(NewController);

	//소유자가 있는지 체크
	OwnerActor = GetOwner();
	if (OwnerActor)
	{
		AB_LOG(LogABNetwork, Log, TEXT("Owner : %s"), *OwnerActor->GetName());
	}
	else
	{
		AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("No Owner"));
	}

	AB_LOG(LogABNetwork, Log, TEXT("%s %s"), TEXT("End"), *GetName());

}

 

Super의 PossessedBy를 진행하면 Owner가 생기는 것을 볼수 있다.

 

클라이언트에서 보면 클라이언트에선 Possess함수가 호출되지 않는다.

네트워크에 받을때 클라이언트쪽에서 Owner가 변경되었다는 것을 OnRep_Owner()를 통해 알게 된다.

캐릭터 플레이어.h

virtual void OnRep_Owner() override;
void AABCharacterPlayer::OnRep_Owner()
{
	AB_LOG(LogABNetwork, Log, TEXT("%s %s"), TEXT("Begin"), *GetName());

	Super::OnRep_Owner();

	AB_LOG(LogABNetwork, Log, TEXT("%s %s"), TEXT("End"), *GetName());
}

 

서버가 실행했을때 모습

클라이언트1 이 추가되었을때 모습

하나 더 추가되었을때 모습.

 

 

액터의 역할과 커넥션 핸드셰이킹

액터의 역할

Authority를 가진다 - 클라이언트-서버모델에서는 항상 서버에 있는 액터남 신뢰한다.

Proxy 액터 - 클라이언트의 액터는 대부분 서버 액터를 복제한 허상에 불과하다.

 

로컬 역할과 리모트 역할

리슨서버의 경우 플레이어로서 게임에도 참여하므로, 어플리케이션의 게임 로직을 사용한다.

어플리케이션의 게임 로직은 서버 액터에 대해서만 게임에 관련된 작업을 수행한다.

이를 구분하기 위해 현재 동작하는 어플리에키션에서의 역할을 로컬 역할(Lcoal Role), 커넥션으로 연결된 어플리케이션에서의 역할을 리모트 역할이라고 한다.

동일한 어플리케이션을 사용하는 서버와 클라이언트

서버와 클라이언트에서 동일한 아이덴티티를 가진 액터를 게임로직에서는 어떻게 구분할 것인가?  

 

None 액터가 존재하지 않음.

Authority 서비스를 대표하는 신뢰할 수 있는 역할. 게임 로직을 수행한다.

AutonomousProxy : Authority 를 가진 오브젝트의 복제품. 일부 게임 로직을 수행함.

SimulatedProxy : Authority를 가진 오브젝트의 복제품. 게임 로직을 전혀 수행하지 않음.

클라이언트의 Proxy는 크게 Autonomous와 Simulated로 구분된다.

Autonomous는 클라이언트의 입력 정보를 서버에 보내는 능동적인 역할을 일부 수행한다.

Simulated 는 일방적으로 서버로부터 데이터를 수신하고 이를 반영한다. (보통 배경액터)

Autonomous 역할을 하는 액터로는 플레이어 컨트롤러와 폰이 있다.

 

신뢰할수 있는 액터(Authority)를 가진 액터만 게임 로직을 수행할 수 있다.

AActor::HasAuthority()

 

Autonomous Proxy는 예외적으로 입력에 관련된 로직을 수행할 수 있음.

따라서 입력에 관련된 게임 로직의실행은 Authority와 AUtonomous Proxy에서만 허용됨.

이를 위해 언리얼에서 다음과 같은 함수를 제공한다.

AController::IsLocalController()

APawn::IsLocallyControlled()

 

 

서버에만 존재하는 액터 : 게임 모드

서버와 모든 클라이언트에 존재하는 액터 : 배경 액터와 폰

서버와 소유하는 클라이언트에만 존재하는 액터 : 플레이어 컨트롤러

클라이언트에만 존재하는 오브젝트 : 애니메이션 블루프린트 및 HUD

 

게임모드는 서버에만 존재하기 때문에 HasAuthority 함수를 호출할 필요 없음.

폰은 Autonomous와 Simluated가 혼재 되어 있다. API를 사용해 로직을 구분해야 한다.

애니메이션 재생이나 UI 관련 로직은 클라이언트에만 사용함.

(서버는 변경된 속성을 전달하고, 변경된 속성에 따라 애니메이션과 UI를 바꾸도록 설계)

 

ArenaBattle.h 에 추가

#define LOG_LOCALROLEINFO *(UEnum::GetValueAsString(TEXT("Engine.ENetRole"), GetLocalRole()))
#define LOG_REMOTEROLEINFO *(UEnum::GetValueAsString(TEXT("Engine.ENetRole"), GetRemoteRole()))
...

#define AB_LOG(LogCat, Verbosity, Format, ...) UE_LOG(LogCat, Verbosity, TEXT("[%s][%s/%s] %s %s"), LOG_NETMODEINFO, LOG_LOCALROLEINFO, LOG_REMOTEROLEINFO, LOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))

ENetRole이 위에 말한 Autonomous인지 Simulated인지 역할을 가지고 있는 열거형 변수이다.

앞에 Role이 내 입장에서 본 이 액터의 Role이고 뒤에 Role이 상대방의 Role이다

결과 이미지

 

게임 모드는 서버에게만 존재하기 때문에 클라이언트 입장에선 None이 나와야 한다.

컨트롤러도 AutonomousProxy로 변경된다.

 

하위와 같은 기능을 사용해서 내가 컨트롤 가능한 경우에만 기능을 사용한다.

	if (!IsLocallyControlled())
	{
		return;
	}

 

커넥션 핸드 셰이킹

네트워크에서 접속하는 두 컴퓨터가 잘 연결되어있는지를 확인하는 과정이다.

서로 데이터를 주고 받으며 서로를 확인한다.

 

커넥션을 허용하면 게임을 시작할 수 있도록 클라이언트와 서버는 준비과정을 거친다.

클라이언트는 맵을 로딩한다.

서버는 클라이언트를 댚하는 플레이어 컨트롤러를 생성한다.

 

DataChannel.h : 서버와 클라이언트가 통신하는 패킷들이 정리되어 있다.

UWorld::NotifyControlMessage : 서버

UPendingNetGame::NotifyControlMessage : 클라이언트

 

 

알아두면 좋은 언리얼 네트워크 시스템 구성

 

관련 소스 코드 : NetDriver.h

용도에 따라 패킷을 처리하는 다양한 NetDriver 클래스를 제공함.

- GameNetDriver : 게임 데이터를 처리하는데 사용하는 네트워크 드라이버

- DemoNetDriver : 게임 리플레이 데이터를 처리하는데 사용하는 네트워크 드라이버

- BeaconNetDriver : 게임 외 데이터를 처리하는데 사용하는 네트워크 드라이버

언리얼 엔진은 게임 데이터를 처리하는 게임넷드라이버로 IpNetDriver를 사용한다.

초기 접속에 관련된 데이터 패킷은 ControlChannel을 통해 분석된다.

 

언리얼 엔진에서 번치(Bunch)를 처리하는데 사용하는 주요 채널들

- ControlChannel : 클라이언트 서버간의 커넥션을 다룰때 사용하는 채널

- ActorChannel : 액터 리플리케이션 작업을 다룰때 사용하는 채널

- VoiceChannel : 음성 데이터를 전달할때 사용한다.

 

반응형