멀티플레이어 게임
- 멀티플레이어 게임 동일한 게임이 여러 인스턴스에서 동시에 실행되어야 한다.
- 공유된 세계에 대해 일관된, 동일한 그림을 그려내야 한다

- Server는 각 Game Instance에 대해 Authority()를 가진다
- Server에서 무언가 변경되면 해당 변경 사항은 각 Game Instance에 전파되는데, 이 프로세스가 바로 Replication(복제)이다
Replication
- Unreal 자체에 내장된 first class 기능
- 소켓이나 패킷, 직렬화, 인코딩 등을 관리하지 않아도 된다.
- 특정 Property를 복제하겠다고 선언하면 된다.
NetMode
// World가 가지는 속성
ENetMode UWorld::GetNetMode() const;

- Playable? - 플레이 가능한가?
- GameInstance에 LocalPlayer가 있는가?
- Player의 Input을 처리하는가?
- Viewport를 Rendering 하는가?
- Authority? - Server인가?
- GameMode Actor가 포함된 World인가?
- GameMode는 게임 플레이 전반의 핵심 로직을 정의하는 non-visual class이다
- 이 Actor가 World에 있다는 것은 해당 World가 게임의 핵심 로직을 실행하고 관리하는 주체라는 의미이다
- 즉, 이 World는 서버(Authority) 역할을 수행하고 있다는 뜻이다
- Open to Client? - Client의 참여가 가능한가?
- 연결 시도에 대해 열려 있는가?
- 다른 플레이어가 클라이언트로서 참여할 수 있는가?

- Game이 실행되면 프로세스 수명을 가지는 GameInstance와 연결되어 URL(서버 주소 혹은 Map 이름)를 통해 Map을 로드한다
NM_Client

- GameInstance가 원격 서버에 연결되었다면, 해당 World는 NM_Client가 된다
NM_Standalone

- GameInstance가 로컬로 Map을 로드한 경우에는 NM_Standalone
- Server이자 Client 역할을 모두 수행한다
- 단일 플레이어 구성으로, Client가 연결할 수 없다
NM_ListenServer

- 로컬에서 Map을 로드하면서 ?Listen 옵션과 함께 실행
- Standalone과 동일하지만 Client가 연결할 수 있다
NM_DedicatedServer

- ServerOnly 콘솔 어플리케이션이다
- Viewport도 Local Player도 없다
Unreal Replication System
- 멀티플레이 게임을 실행하면 Background에서는 Replication System이 작동해 Sync를 맞춘다
- 각 GameInstance의 World를 구성하고, Replication System은 World에서 일어나는 일에 대해 검토 후 동시 실행을 처리한다
- 이 때 사용되는 class가 UNetDriver, UNetConnection, UNetChannel이다

UNetDriver
- 서버와 클라이언트는 각각 UGameEngine 객체를 갖는다
- 일단 서버가 부팅하면, NetDriver가 생성된다
- 클라이언트가 부팅하면, 역시 NetDriver가 생성된다
UNetConnection
- 서버와 클라이언트의 연결에 성공하면 NetConnection이 설정된다
- 서버는 각 원격 프로세스마다 개별 NetConnection을 가진다
- 각 클라이언트는 서버와 연결된 하나의 NetConnection을 가진다
UNetChannel
- 각 NetConnection에는 Channel이 포함된다
- 일반적으로 VoiceChannel, ControlChannel, ActorChannel 등이 있다
- 이제 어떤 Actor가 Replication 되기를 원한다면, 이 Actor가 Replication 되도록 세팅하면 된다
// IsNetRelevantFor(P0) => true;
bReplicates = true;
- 이 Actor에 대해 업데이트가 필요하다고 간주되면, 서버와 클라이언트는 이 Actor Channel을 열고 해당 Actor에 대한 정보를 교환한다
Actor Replication

- Actor가 Replication 될 때, 아래 3요소가 동기화 된다
Lifetime
- 서버가 Replicated Actor를 생성 또는 파괴하면, 클라이언트에서도 자체 사본을 생성 또는 파괴한다
Property Replication
- Actor의 Property 중 Replicated 속성을 가지고 있으면 이 값의 업데이트는 서버에서 클라이언트로 전파된다
Remote Procedure Call
- 함수가 Multicast RPC라면, 서버가 해당 함수를 처리할 때 클라이언트에서도 해당 함수를 호출해야 한다고 전한다
- Server RPC 또는 Client RPC로 단일 클라이언트와 서버 간의 통신을 할 수도 있다
Ownership
- 각 Actor는 다른 Actor를 Owner로 설정할 수 있다
- 보통은 Actor 생성 시에 정의된다
- SetOwner 함수로 Runtime에 변경도 가능하다
- NetConnection은 PlayerController를 나타내며, Player가 게임에 정상적으로 로그인하면 NetConnection은 PlayerController와 연결된다
- 서버 관점에서, 이 NetConnection은 PlayerController를 소유하는 것이며, 즉 PlayerController가 소유한 Actor까지 소유한다
- 즉, PlayerController가 소유하는 모든 Actor는 NetConnection이 소유하는 것이다
- 이로써 서버가 클라이언트에 연결된 Actor를 판별할 수 있다
Relevancy
// 모든 클라이언트에 대해서 서버는 항상 Actor의 변경점을 전파한다
bAlwaysRelevant = true;
- 어떤 Replicated 된 Actor의 변경점을 다른 클라이언트에 전파하는지 여부
- 몇몇 Actor는 모든 클라이언트에 대해 Relevant 할 수도 있다
- PlayerController와 같은 Actor는 자신을 소유한 Client에 대해서만 Relevant 하다
- 이 Relevancy는 Actor의 Owner를 inherit하도록 세팅할 수도 있다
// 특정 거리 이내의 Actor에 대해서만 업데이트하도록 세팅할 수도 있다
NetCullDistanceSquared = FMath::Sqaure(3000.f);
// 요 함수를 override 해서 조건을 커스터마이즈할 수 있다
bool AActor::IsNetRelevantFor(...)
{
// ...
}

- 그외에도 NetUpdateFrequency 및 NetPriority 등으로 업데이트 주기와 우선 순위를 할당할 수 있다
- NetPriority 에서 후순위로 할당된 경우, Transfer Byte 용량이 넘어서면 업데이트 전파가 안될 수 있다
- Skip 처리된 Actor와 플레이어에 가까운 Actor들은 높은 우선순위 보정을 받기 때문에 결국은 모두 업데이트가 되긴 한다
- 그러나 위와 같은 설정은 즉시 클라이언트로의 업데이트를 보장하지 않는다
RPC in Detail
UFUNCTION(Client)
void Client_DoSomething();
UFUNCTION(Server)
void Server_DoSomething();
UFUNCTION(Multicast)
void Multicast_DoSomething();
Client RPC

- 서버에서 Client RPC를 호출하면, 함수의 실제 구현부는 해당 함수를 호출한 Actor의 OwningClient 쪽에서만 실행된다
Server RPC

- 클라이언트에서 Server RPC를 호출하면, 함수의 실제 구현부는 Server에서만 실행된다
- 서버가 클라이언트로부터 데이터를 수집하는 방법
Multicast RPC

- 서버에서 Multicast RPC를 호출하면, 서버 그리고 서버와 연결된 모든 클라이언트에서 실행된다

- 이 경우에는 Relevancy 세팅에 의해 호출 여부가 결정된다
Reliable / Unreliable
UFUNCTION(Server, Reliable)
void Server_DoSomething();
UFUNCTION(Server, Unreliable)
void Server_DoSomething();
- Reliable: RPC 전파와 그 순서를 보장한다
- 과용할 경우 보틀넥 현상 혹은 패킷 자체가 loss 될 수 있다
- Unreliable: RPC 전파를 보장하지 않는다
WithValidation
// Server_DoSomething2가 Server RPC라 가정하자
// 이 함수는 Client 사이드에서 실행한다
void ASomeActor::DoSomething1()
{
// ...
Server_DoSomething2();)
}
// 이 함수는 Server 사이드에서 실행한다
void ASomeActor::Server_DoSomething2_Implementation()
{
// ...
}
// UFUNCTION(..., WithValidation)
bool ASomeActor::Server_DoSomething3_Validation()
{
// 이 Server RPC가 fail을 반환하면, client는 그 즉시 게임에서 퇴장당한다
return bCheated == false;
}
Replicated Property in Detail
UFUNCTIONN(ReplicatedUsing=OnRep_OnUpdate)
int32 Property;
// Property 값이 업데이트 될때마다 Client 사이드에서 호출한다
UFUNCTION()
void OnRep_OnUpdate();
// Blueprint에서는 서버에서의 갑 변경 시 서버사이드에서도 호출하지만, C++ 구현 시에는 직접 호출해줘야 한다
{
//...
if (HasAuthority())
{
Property = 32;
OnRep_OnUpdate();
}
}
Authority
enum ENetRole
{
ROLE_None,
ROLE_SimulatedProxy,
ROLE_AutonomousProxy,
ROLE_Authority
}
//
{
// 이 GameInstance가 NM_Standalone 이거나
// NM_DedicatedServer 혹은 NM_ListenServer 이거나
// NM_Client이고 이 Actor가 이 Client에서 스폰되었거나 (이 Client에만 존재하거나)
if (HasAuthority())
{
// 업데이트 가능
}
// 이 GameInstance가 NM_Client 이거나
// 이 Actor가 Server에서 스폰되었거나
else
{
}
}
ROLE_None
- 네트워크 상에서 아무런 의미 있는 역할도 하지 않는다
ROLE_SimulatedProxy
- 서버로부터 복제된 데이터를 수신해 자신의 상태를 업데이트한다
- 제어권은 서버에 있고, 현재 GameInstance(Client)에서는 모방해 보여주는 역할
HasAuthority() == false
IsLocallyControlled() == false
ROLE_AutonomousProxy
- 로컬 플레이어가 직접 조종한다
- 자신의 입력을 서버에 보내고, 서버에서 받은 데이터로 업데이트 한다
- 제어권 자체는 여전히 서버에 있지만, 서버에 요청을 보내거나 Local Predict로 서버에 요청 전송 및 응답 전에 움직임을 처리하기도 한다
HasAuthority() == false
IsLocallyControlled() == true
ROLE_Authority
- 모든 상태 변경의 최종적인 결정 권한을 가지고 있으며, 다른 클라이언트들에게 이 액터의 상태를 전파하는 역할
HasAuthority() == true
// 1. 서버가 Dedicdated이고 Player가 접속하지 않은 경우
IsLocallyControlled() == false
// 2. 서버가 Listen이고 서버 사이드의 플레이어가 해당 액터를 조종하는 경우
IsLocallyControlled() == true
RemoveRole / LocalRole

IsLocallyControlled
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;
}

출처