Game Development/Unreal Engine

[UE5] 클라이언트-서버 모델

Dlaiml 2024. 6. 23. 19:03

언리얼 엔진에서 싱글 플레이 게임에 대해서만 학습하다 보니 멀티플레이 게임, 네트워크에 대해 알고 싶어져 멀티플레이에 대해 학습을 시작하였다. 

이번 포스팅에서는 언리얼 엔진의 멀티플레이어 원리를 간단하게 다루려한다.

 

언리얼 엔진의 멀티플레이어는 클라이언트-서버 모델을 기반으로 한다

우선 클라이언트, 서버에 대해 이야기하기 전에 Input과 State에 대해 알아보자.

 

Input & State

최근 직접 WinAPI로 간단한 게임 제작을 하면서, State / Tick / Input의 개념을 언리얼 엔진을 학습할 때 보다 더 깊게 이해하게 되었다. 게임은 Tick 단위로 사용자에게 보이며 State는 그 사이 Input에 의해 업데이트된다는 것이다.

Tick과 Tick 사이에는 사용자에게 노출되지 않는 엔진만의 시간이 있다.

현재 State(현재 캐릭터의 위치, 체력 등 게임의 모든 상태)를 Input(=Action) (키 입력, 이동속도, 중력) 등 여러 요소를 고려하여 다음 State로 업데이트하는 과정이 이루어진다.

 

싱글 플레이 게임에서 이를 처리하는 것은 비교적 간단하다. 하지만 멀티플레이 게임에서는 여러 플레이어가 각자 다른 네트워크 환경에서 접속하기 때문에 이를 처리하기가 쉽지 않다.

 

멀티 플레이에서 State management는 매우 중요한데 각 플레이어가 다른 State를 보고 있다면 많은 문제가 생기게 된다. 예시로 0.1초가 중요한 FPS에서 동일한 시각에 A는 B의 체력이 30으로 C는 B의 체력이 0으로 State가 동기화되지 않는 다면 플레이하기가 불가능할 정도의 문제를 일으킨다. 

 

즉 모든 플레이어의 Input(Action)을 취합하여 모든 플레이어가 납득할 수 있는 State로 업데이트가 필요하다. A는 왼쪽으로 회피하는 Input을,  B는 A의 위치에 총을 쏘는 Input을 입력하였다면 다음 Tick의 State는 A가 회피하여 B의 공격을 피하게 되는 State가 되어야 한다.

 

이를 취합하고 처리해 주는 역할을 서버가 담당한다.  (이외에도 네트워크 최적화, 예측-보정, 일관성 유지 등 많은 역할을 한다)

 

Peer-to-Peer vs Client-Server

서버를 사용하지 않고 클라이언트끼리의 통신으로 이를 처리하는 Peer-to-Peer 방식은 Tick과 Tick 사이에 모든 플레이어가 서로 Input을 교환하여야 하는 방식인데,  서로의 IP가 노출되는 보안문제, 네트워크 환경이 가장 나쁜 클라이언트의 응답을 기다려야 하는 레이턴시 문제, 플레이어가 많아지면 생기는 트래픽 문제, 클라이언트에서 데이터 변조 문제 등 문제가 많아 여러 상용게임 엔진에서는 클라이언트-모델 방식을 지원하고 있다.

 

클라이언트-서버 모델은 서버가 각 클라이언트와 통신하여 믿을 수 있는 State로 업데이트 후 클라이언트에게 이를 전달하는 방식이다.

 

 

Client-Server UE5

리슨 서버 UE4Editor.exe ProjectName MapName?Listen -game
데디케이티드 서버 UE4Editor.exe ProjectName MapName -server -game -log
클라이언트 UE4Editor.exe ProjectName ServerIP -game

 

리슨 서버(Listen Server)는 클라이언트 중 하나가 호스트가 되어 서버 역할을 겸하는 방식으로 설정이 간편하고 비용이 저렴하나 보안문제나 호스트의 환경에 따라 서버가 영향을 받는다는 문제가 있다.

 

데디케이티드 서버(Dedicated Server)는 게임 서버가 별도의 컴퓨터나 클라우드 환경에서 독립적으로 실행되는 방식으로 게임 데이터를 관리, 클라이언트 간의 동기화만 수행하는 서버를 말한다. 리슨 서버와 반대로 비용이 비싸며, 설정이 복잡하다는 단점이 있다.

 

 

언리얼 엔진 에디터 실행 파일의 경로와 uproject 파일 경로, 맵 이름 -server -game -log를 입력해 보자. (중괄호가 포함된 구문은 각자 환경에 맞게 입력)

{"언리얼엔진에디터폴더\UnrealEditor.exe"} {"프로젝트경로\프로젝트명.uproject"} /Game/ThirdPerson/Maps/ThirdPersonMap -server -log

 

 

언리얼 엔진 콘솔창이 켜지면서 서버를 구동하는 로그가 출력되는 것을 볼 수 있다.

 

 

다음으로 클라이언트 접속을 위한 명령어를 입력해보자. (로컬 IP로 간단하게 테스트)

{"언리얼엔진에디터폴더\UnrealEditor.exe"} {"프로젝트경로\프로젝트명.uproject"} {로컬IP} -game

 

Standalone으로 게임이 실행되며 한번 더 입력할 경우 또 다른 플레이어가 서버에 입장하여 다른 플레이어의 이동을 실시간으로 확인할 수 있다.

 

 

데디케이티드 서버를 실행할 때 콘솔창을 확인해 보면 다른 클라이언트의 접속 로그가 남은 것을 확인할 수 있다.

 

서버에서 State를 처리하고 이를 Client로 복사하는 방법은 HasAuthory로 서버인지 확인하고 맞다면 State를 변경하는 로직을 실행한다.

 

// ..
void ATest::BeginPlay()
{
	Super::BeginPlay();
	if (HasAuthority())
	{
		SetReplicates(true);
		SetReplicateMovement(true);
	}
}

// ..


// ..
if (HasAuthority())
{
    SetActorLocation(GetActorLocation() + DirVector * Speed * DeltaTime);
}
// ...

 

Actor는 SetReplicates, SetReplicateMovement를 사용하여 클라이언트로 복제가 가능하며 UPROPERTY에서 Replicated, ReplicatedUsing  proeprty specifier를 사용하여 속성의 복제도 지정할 수 있다.

 

 

https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/Networking/QuickStart/

 

Multiplayer Programming Quick Start

Create a simple multiplayer game in C++.

docs.unrealengine.com

클라이언트-서버의 State 전달 및 동기화와 관련된 자세한 내용은 다음에 위 문서를 바탕으로 더 상세하게 작성하려 한다.

 

 

Reference

[0] https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/Networking/Server/

 

Client-Server Model

An overview of the role of the server in multiplayer.

docs.unrealengine.com

[1] https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/Networking/QuickStart/

 

Multiplayer Programming Quick Start

Create a simple multiplayer game in C++.

docs.unrealengine.com

[2] Udemy - Unreal 4 C++ Multiplayer Master: Intermediate Game Dev