Replication
Replication이란 Server와 Client사이의 명령과 데이터를 주고받는 것이다.
즉, Server와 Client의 동기화라고 생각하면 된다.
Replication의 방식은 두 가지이다.
- 프로퍼티 업데이트
- RPC (Remote Procedure Call).
프로퍼티 리플리케이션과 RPC 사이의 큰 차이점은, 프로퍼티는 변경될 때마다 자동으로 리플리케이트되는 반면, RPC는 실행될 때만 리플리케이트된다는 점이다.
쉽게 말하면 프로퍼티 업데이트는 자동, RPC는 수동이다.
프로퍼티 업데이트
예를 들어, 한 캐릭터의 체력을 나타내는 변수는 프로퍼티 업데이트 방식을 이용하여 데미지를 입었을 때 자동으로 Replication이 되게 할 수 있다.
하지만, 프로퍼티의 변화를 감지하는 작업에 CPU비용이 든다.
그렇기에 자주 변경되는 프로퍼티에 효과적이다.
각 Actor에는 Replicated 지정자를 포함하는 모든 프로퍼티 목록이 유지된다.
Server는 리플리케이트된 프로퍼티의 값이 변할 때마다 각 Client에 업데이트를 전송하며, Client는 Actor의 로컬 버전에 적용한다.
이 업데이트는 Server에서만 받으며, Client는 프로퍼티 업데이트를 Server나 다른 Client로 절대 전송하지 않습니다.
주의해야 하는 부분이 있는데, Client에서 리플리케이트된 변수의 값을 바꾸는 것은 조심해야 한다.
다음 번 Server가 변화를 감지하고 업데이트를 전송할 때까지 Server의 값과 달라지게 된다.
// .h
class ENGINE_API AActor : public UObject
{
UPROPERTY( replicated )
AActor * Owner;
};
========================================================================
// .cpp
AActor::AActor( const class FPostConstructInitializeProperties & PCIP ) : Super( PCIP )
{
bReplicates = true;
}
void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
DOREPLIFETIME( AActor, Owner );
}
GetLifetimeReplicatedProps함수에서 업데이트될 프로퍼티를 지정해 주어야 한다.
DOREPLIFETIME를 통해 소요 Actor와 변수를 전달하면 된다.
https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Actors/Properties/
RPC
RPC (Remote Procedure Call) 는 로컬에서 호출되지만 다른 머신에서 원격 실행되는 함수를 말한다.
네트워크 연결을 통해 클라이언트와 서버 사이에 메시지를 전송할 수 있다.
이 기능의 주요 용도는 휘발성인 비신뢰성 게임플레이 이벤트를 위한 것이다
사운드 재생, 파티클 스폰, 액터의 핵심적인 기능과는 무관한 일시적 효과와 같은 작업을 하는 이벤트를 포함한다.
RPC 사용 시 오너십 작동 방식을 이해해 두는 것이 중요하다. 대부분의 RPC 실행 장소를 결정하기 때문이다.
Multiplayer 환경에서 Framework들은 위의 사진과 같이 배치된다.
여기서 주의해야 할 부분이 몇 가지 있다.
- GameMode는 Server만이 소유하고 있다.
- PlayerController는 Client가 소유하지만 복사되어 Server에서도 갖고 있다.
- GameState는 모든 곳에서 접근이 가능하다.
RPC를 사용할 때는 위의 구조를 이해하고 사용해야 한다.
다음은 RPC 사용법이다.
함수를 RPC로 선언하려면 UFUNCTION 선언에 Server, Client, NetMulticast 키워드를 붙여주면 된다.
예를 들어 함수를 Server에서 호출되지만 Client에서 실행되는 RPC 로 선언하려면 다음과 같이 한다.
UFUNCTION( Client )
void ClientRPCFunction();
함수를 Client에서 호출되지만 Server에서 실행되는 RPC 로 선언하는 것은 Server 키워드를 사용하면 된다.
UFUNCTION( Server )
void ServerRPCFunction();
Multicast 라 불리는 특수 유형 RPC 함수가 하나 더 있다.
Multicast RPC는 Server에서 호출된 다음 Server와 현재 연결된 모든 Client에서도 실행된다.
멀티캐스트 함수를 선언하려면 NetMulticast 키워드를 사용하면 된다.
UFUNCTION( NetMulticast )
void MulticastRPCFunction();
멀티캐스트 RPC 는 클라이언트에서도 호출 가능하지만, 이 경우 로컬에서만 실행된다.
함수 이름에 Client, Server, Multicast 키워드를 붙이는 것은 관례이다.
이렇게 하면 어느 머신에서 실행되는지 쉽게 알 수 있기 때문이다.
이러한 함수들을 정의할 때는 _Implemetation을 붙여주어야 한다.
void MulticastRPCFunction_Implementaion()
{
}
RPC를 사용할 때는 조건과 주의사항이 있다.
- Actor에서 호출되어야 한다.
- Actor는 반드시 replicated여야 한다.
- UFUNCTION(Client)의 경우, 해당 Actor를 실제 소유하고 있는 Client에서만 함수가 실행된다.
- UFUNCTION(Server)의 경우, Client는 RPC 가 호출되는 Actor 를 소유해야 한다.
- Multicast RPC는 예외이다:
- Server에서 호출되는 경우, 로컬에서 실행될 뿐만 아니라 현재 연결된 모든 Client에서도 실행된다.
- Client에서 호출되는 경우, 로컬에서만 실행되며, Server에서는 실행되지 않는다.
호출 액터의 소유권에 따라 주어진 유형의 RPC 실행 위치를 나타낸다.
서버에서 호출된 RPC
액터 소유권 | 리플리케이트 안됨 | NetMulticast | Server | Client |
클라이언트 소유 액터 | 서버에서 실행 | 서버와 모든 클라이언트에서 실행 | 서버에서 실행 | 액터의 소유 클라이언트에서 실행 |
서버 소유 액터 | 서버에서 실행 | 서버와 클라이언트에서 실행 | 서버에서 실행 | 서버에서 실행 |
미소유 액터 | 서버에서 실행 | 서버와 모든 클라이언트에서 실행 | 서버에서 실행 | 서버에서 실행 |
클라이언트에서 호출된 RPC
액터 소유권 | 리플리케이트 안됨 | NetMulticast | Server | Client |
호출하는 클라이언트에 소유 | 호출하는 클라이언트에서 실행 | 호출하는 클라이언트에서 실행 | 서버에서 실행 | 호출하는 클라이언트에서 실행 |
다른 클라이언트에 소유 | 호출하는 클라이언트에서 실행 | 호출하는 클라이언트에서 실행 | 드롭됨 | 호출하는 클라이언트에서 실행 |
서버 소유 액터 | 호출하는 클라이언트에서 실행 | 호출하는 클라이언트에서 실행 | 드롭됨 | 호출하는 클라이언트에서 실행 |
미소유 액터 | 호출하는 클라이언트에서 실행 | 호출하는 클라이언트에서 실행 | 드롭됨 | 호출하는 클라이언트에서 실행 |
또한, 기본적으로 RPC 는 비신뢰성이다.
RPC 호출이 원격 머신에서 확실히 실행되도록 하기 위해서는 Reliable 키워드를 붙이면 됩니다:
UFUNCTION( Client, Reliable )
void ClientRPCFunction();
최근, 악성 데이터/입력 감지를 위한 관문 역할을 위해 RPC에 인증(validation) 함수를 추가하는 기능이 생겼다.
RPC에 대한 인증 함수가 악성 파라미터를 감지한 경우, 해당 RPC 를 호출한 Client/Server 연결을 끊도록 시스템에 알리는 것이다.
RPC 에 대해 인증 함수를 선언하려면, UFUNCTION 선언문에 WithValidation 키워드를 추가해 주면 된다.
// .h
UFUNCTION( Server, WithValidation )
void SomeRPCFunction( int32 AddHealth );
=============================================================
// .cpp
bool SomeRPCFunction_Validate( int32 AddHealth )
{
if ( AddHealth > MAX_ADD_HEALTH )
{
return false; // This will disconnect the caller
}
return true; // This will allow the RPC to be called
}
void SomeRPCFunction_Implementation( int32 AddHealth )
{
Health += AddHealth;
}
https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Actors/RPCs/