Dedicated Server -> 서버에서는 게임을 플레이하지 않음. Host하는 역할
Replicate되는 것은 네트워크 시스템에서 중요함.
만약 Server에서 캐릭터가 총알을 쏜다하더라도, 해당 총알 Actor 가 복제되지 않는다면 다른 클라이언트는 총알이 보이지가 않음.
총알 Actor가 replicated 되야함.
Server가 진짜 게임이고 Client는 이를 복제할 뿐임.
Actor의 변수값. 예를 들면 총알의 색깔 이런것도 복제가 되야함.
안 그러면 서버에선 총알이 빨강색인데 클라이언트에선 파랑색.. 이런 경우가 발생.
즉 Variable 에 관해서도 replicate 를 해주면 됨.
데이터 플로우로 볼 때, 게임을 제어해야 하는 서버는 데이터가 클라이언트로 복제되게 해야함.
HP를 예로 들면, 서버에서 업데이트 한 HP가 클라이언트에게 복제가 되는것임
그래서 우리는 서버인지, 클라이언트인지 확인할 필요가 있음.
HasAuthority로 이게 확인이 가능함.
Authority는 Server, Remote 는 Client를 의미함.
HP는 서버에서 바꿔줘야하니까, Server에서 해당 기능을 구현하면 됨
RepNotify에 대해 알아볼까?
RepNotify는 복제되는 변수의 값이 변경될 때, 이 함수를 실행하게 함.
예를 들면 신호등 액터가 있고, 신호등에는 StreetLight라는 변수가 있다.
해당 StreetLight가 replicated 되있어도, 클라이언트로의 default값 복제는 되지만 실시간으로 빨-노-초 변할 때마다 클라이언트의 신호등은 처음의 빨간색을 가르키고 있다. 이는 업데이트 될 때마다 그것을 전해주는 기능이 없기 때문이다.
그래서 해당 변수를 replicated-> RepNotify로 변경하고, OnRep_StreetLight 라는 함수를 만들어준다.
그렇게 되면, 해당 변수가 변할 때마다 OnRepStreetLight이 호출되고, 우리는 해당 함수에 street light를 변하게 해주는 로직을 작성해주면 된다.
변수 replicate와 Actor replicate도 아주 큰 부분이지만, 네트워킹 시스템의 중요한 부분이 있다.
바로 RPC 이다.
캐릭터가 총을 쏘고 싶다고 서버에 어떻게 말할까? 바로 서버 RPC라는 것을 이용해 말한다. 반대로 서버는 클라이언트 RPC라는 것을 사용하여 클라이언트와 다시 소통할 수 있다.
클라이언트가 누군가를 쐇을 때, 서버에서는 어? 얘가 맞았으니까 히트 마크를 띄워! 라는 등 서버와 클라이언트는 RPC로 서로 통신을 한다.
Multicast RPC라는 특수 유형의 RPC도 있다. 이것은 서버에서만 호출할 수 있는 RPC이며, 호출되면 게임에 연결된 모든 클라이언트에서 발생한다. 예를 들어 Server에서 Multicast를 이용하여 폭발을 일으키면, 모든 클라이언트가 폭발을 보게 될 것이다.
그럼 예시로 만들어볼까?
예시로, X키를 눌렀을 때 사용자의 손에 있던 컵케이크가 사라지게 해보겠다.
이를 위해서 난 HasCupcake라는 bool 변수를 선언하고, 해당 변수를 repnotify로 만든 뒤 OnRep_HasCupcake 함수에서 해당 메시가 게임에서 hide되도록 만들었다.
그리고 x키를 누르면 HasCupcake를 false로 바뀌게 만들어놨다.
이제 실행해볼까?
아무 일도 일어나지 않았다.
서버는 데이터를 제어하고, 모든 클라이언트에게 피드백을 제공해야한다.
이를 위해 RPC를 사용해야 한다.
X키를 눌렀을 때, 서버에게 지시를 해야한다.
블루프린트에서는, Custom event를 만든 뒤
Hide cupcake이라 명명하고, Replicates- Run on Server, Reliable를 만든다.
그런데 캐릭터의 무브먼트와 생김새는 어떻게 모든 클라이언트에 똑같이 보이는 걸까?
이는 캐릭터폰은 생성 시에 실제로 네트워크와 연결되어 있기 때문이다.
그래서 네트워크로 가져오기 위해 특별히 뭘 해줄 것이 없다
Replication Condition엔 여러가지가 있다. 그 이유는 게임 내의 모든 클라이언트에게 복제해줄 것인지? 아니면 일부에게만 보여줄것인지? 한 번만 복제할 것인지? 등과 같은 조건을 걸 수 있기 때문이다.
Net-Multicast 이벤트에 대해서 알아보자.
이것은 초보자에게 많이 쓰이지만, 초보자가 많이 빠지는 함정이 있으며 자주 사용하는 것을 지양한다. 특별한 경우에는 유용하지만 게임에 많은 버그가 발생할 수 있다.
커스텀 이벤트를 하나 만들고, hide cupcake multicast라 하고 replicate 설정을 multicast라 해보자.
그리고 Hidecupcake 이벤트에서 hide cupcake multicast를 호출하게 한다.
hide cupcake multicast에서 hidden시켜주는 작업을 해주는 로직을 만든다.
그러면 어떻게 될까? 정상적으로 작동한다! 컵케이크가 다른 모든 사용자에게 보이지 않게 작동한다.
하지만 항상 이 방법을 사용하면 안 된다. Multicast는 게임에 "있을 때" 만 동작한다. 즉, 10분 뒤에 새로운 사람들이 서버에 접속하면 그 사람들은 컵케이크를 들고 있는 모습을 볼 것이다. 그 당시의 네트워크 정보만을 보낸다.
그리고 최적화에도 문제가 존재한다. 관련성의 문제가 있는데, 예를 들면 맵의 반대쪽(아주 멀리 있다를 가정)하면 최적화를 위해 네트워크를 제공하지 않아, 컵케이크를 들고 있는 모습을 보게 된다.
그래서 Multicast를 사용하는 것 보다, Server-RPC를 추천한다.
Net-Multicast를 사용하지 않고 RepNotify를 사용해서 만들면, 맵의 가까운 쪽에 있는 사람들에게도 바로 적용되며, 멀리 있는 사람도 가까이 올 경우 바로 적용된다.
네트워크 범위는 엄청 방대하기 때문에, 사실상 모든 사람에게 적용된다고 봐도 무방하다.
그런데 또 한가지 문제가 된다. 해당 이벤트가 박스를 열어서 동전이 쫘르르 쏟아지는 이벤트라 해보자. 근데 아주 멀리 있는 사람이 나중에 근처를 왔을 때, 동전이 쫘르르 쏟아지는 효과가 발생한다. 그런데 이러면 안된다. 박스가 열린 것은 아주 오래 전의 이벤트이고, 현재의 이벤트가 아니기 때문이다. 하지만 해당 클라이언트에는 현재 일어나고 있는 이벤트로 보이게 된다.
어떻게 해야할까. Net Multicast와 RepNotify를 함께 사용하는 솔루션으로 이어진다. 박스가 열리는 것은 RepNotify를 이용해 구현하고, 해당 이벤트 발생 후 Multicast로 동전이 쏟아지는 이벤트를 구현하면 된다.
그렇다면
클라이언트에게는 박스가 오픈되었음이 모두 구현되고, multicast를 통해 그 때 실행해주었기 때문에 다른 사람이 해당 지역에 도달했을 때 동전이 쏟아지는 효과는 발생하지 않고 박스가 열린 모습만 볼 수 있다.
이걸 C++로 나타내볼까?
Health라는 변수를 복제하고 싶다.
UPROPERTY(ReplicatedUsing=OnRep_Health)
float Health;
void OnRep_Health();
를 선언해주고,
virtual void GetLifetimeReplicateProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicateProps(OutLifetimeProps);
DOREPLIFETIME(AActor, Health);
}
라 해준다.
그리고 해당 함수를 사용하려면,
#include "Net/UnrealNetwork.h"
헤더를 포함시켜줘야 한다.
BeginPlay()
에는
if (HasAuthority())
{
Health = 50.f;
}
를 만들어주는데, 이는 블루프린트에서의 Switch has Authority와 동일한 기능을 제공할 것이다.
그리고 해주어야 할 것은,
UFUNCTION(Server, Reliable)
void MyServerRPC();
만약에 건강 값이 변경되는 것에 대해 적용시키고 싶다면,
if (HasAuthority())
{
Health = 50.f;
OnRep_Health();
}
를 해주면 될 것이다.