언리얼 엔진 4 는 다수의 멀티플레이어 함수성이 내장되어 있어, 네트워크를 통해 플레이 가능한 기본적인 블루프린트 게임을 쉽게 구성할 수 있습니다. 간단히 뛰어들어 멀티플레이어 게임 플레이를 시작해 볼 수 있습니다. 기본적인 멀티플레이어 기능이 지원되는 로직 대부분은 Character 클래스 및 삼인칭 템플릿에서 사용하는 CharacterMovementComponent 에 내장된 네트워킹 기능 지원 덕입니다.
게임플레이 프레임워크 검토
게임에 멀티플레이어 함수성을 추가하려면, 엔진에 제공되는 주요 게임플레이 클래스의 역할과 서로, 특히나 멀티플레이어 컨텍스트에서 어떻게 작동하는지를 이해하는 것이 중요합니다:
- GameInstance 게임 인스턴스
- GameMode 게임 모드
- GameState 게임 스테이트
- Pawn (및 거기서 상속되는 Character), 폰과 캐릭터
- PlayerController 플레이어 컨트롤러
- PlayerState 플레이어 스테이트
자세한 정보는 게임플레이 프레임워크 문서를 참고하시고, 멀티플레이어 게임을 디자인할 때는 최소한 다음의 팁 정도는 유념해 주시기 바랍니다:
- GameInstance 는 엔진 세션 도중에만 존재합니다. 즉 엔진 시작 시 생성되며, 종료될 때까지 소멸 또는 대체되지 않습니다. 서버와 각 클라이언트마다 별도의 GameInstance 가 존재하며, 이 인스턴스는 서로 통신하지 않습니다. GameInstance 는 게임 세션 외부에 존재하므로, 특정 유형의 지속성 데이터를 저장하기에 좋은 곳입니다. 이를테면 플레이어 전반적인 통계 (지금까지 이긴 게임 횟수), 계정 정보 (특수 아이템 잠김/해제 상태), 언리얼 토너먼트같은 경쟁형 게임에서 돌아가며 플레이할 맵 목록 등을 말합니다.
- GameMode 오브젝트는 서버에서만 존재합니다. 일반적으로 클라이언트가 명시적으로 알 필요가 없는 게임 관련 정보를 저장합니다. 예를 들어 "로켓 런처만" 나오는 특수 규칙 게임에서, 클라이언트는 이 정보를 알 필요가 없지만, 맵에 무기를 무작위로 스폰할 때 서버는 "로켓 런처" 카테고리에서만 선택해야한다 알아야 할 것입니다.
- GameState 는 서버와 클라이언트에 존재하며, 서버는 GameState 상의 리플리케이티드 변수를 사용하여 모든 클라이언트에서 게임에 대한 데이터를 최신 상태로 유지할 수 있습니다. 예로, 야구 게임에서 GameState 를 통해 각 팀의 점수와 현재 이닝을 리플리케이트할 수 있습니다.
- 각 클라이언트에는 그 머신의 플레이어마다 하나의 PlayerController 가 존재합니다. 이는 서버와 연결된 클라이언트 사이에 리플리케이트되지만, 다른 클라이언트에는 리플리케이트되지 않으므로, 서버는 모든 플레이어에 대한 PlayerController 를 갖고 있고, 로컬 클라이언트는 로컬 플레이어에 대한 PlayerController 만 갖습니다. 클라이언트가 접속되어 있는 도중에는 PlayerController 가 존재하여 Pawn 에 할당되지만, Pawn 과는 달리 소멸되거나 리스폰되지는 않습니다. 특정 플레이어에게만 감지된 게임 이벤트에 반응하여 미니맵에 핑을 찍도록 서버가 클라이언트에게 알리는 등의 작업처럼, 다른 클라이언트에 리플리케이트할 필요 없는 클라이언트와 서버 사이 통신에 적합합니다.
- PlayerState 는 게임에 접속된 모든 플레이어에 대해 서버와 클라이언트 양쪽에 존재합니다. 이 클래스는 소유중인 클라이언트만이 아닌 모든 클라이언트가 알아야 하는, 이를테면 프리-포-올 게임에서 각 플레이어의 현재 점수와 같은 리플리케이티드 프로퍼티에 사용할 수 있습니다. PlayerController 처럼 개별 Pawn 에 할당되지만, Pawn 과는 달리 소멸 및 리스폰되지 않습니다.
- Pawn (및 Character 포함) 역시 서버와 모든 클라이언트에 존재하며, 리플리케이티드 변수 및 이벤트를 갖고 있습니다. 특정 변수나 이벤트에 대해 PlayeyController 를 쓸 것이냐, PlayerState 를 쓸 것이냐, Pawn 을 쓸 것이냐 하는 것은 상황에 따라 다르지만, 주로 고려할 것은 PlayerController 와 PlayerState 는 플레이어가 접속된 시간동안 그리고 게임이 새 레벨을 로드하지 않는 한 내도록 유지되는 반면, Pawn 은 그렇지 않을 수 있습니다. 예를 들어 Pawn 이 게임플레이 도중 죽는 경우, 보통 소멸되고 새로운 Pawn 으로 대체되는 반면, PlayerController 와 PlayerState 는 계속해서 존재하며 새로운 Pawn 의 스폰이 끝나면 거기에 할당됩니다. Pawn 의 생명력은 그래서 Pawn 자체에 저장되는데, 실제 그 Pawn 인스턴스에만 해당되며, 새로운 Pawn 으로 대체되면 리셋시켜야 하기 때문됩니다.
액터 리플리케이션
UE4 의 네트워킹 기술의 핵심은 액터 리플리케이션입니다. "Replicates" (리플리케이트) 옵션이 True 로 설정된 액터는 서버에서 그 서버에 연결된 클라이언트로 자동 동기화됩니다. 꼭 이해해야 할 점은, 액터는 서버에서 클라이언트로만 리플리케이트되지, 클라이언트에서 서버로 액터를 리플리케이트시키는 것은 불가능하다는 점입니다. 물론, 클라이언트에서 서버로 데이터를 전송하는 것이 가능은 하며, 리플리케이트되는 "Run on server" 이벤트를 통해 이루어집니다.
블루프린트에서 액터 리플리케이트 에서 구체적인 예제를 확인해 보시고, 액터 리플리케이션 도 참고해 보시기 바랍니다.
오소리티
월드의 모든 액터에 대해서, 접속된 플레이어 중 하나는 해당 액터에 대해 오소리티가 있는 것으로 간주됩니다. 서버에 존재하는 모든 액터에 대해서, 서버는 모든 리플리케이티드 액터를 포함해서 해당 액터에 대해 오소리티를 갖습니다. 결과적으로 클라이언트에서 Has Authority 함수가 실행되고, 타깃이 그에게 리플리케이트된 액터인 경우, False 를 반환하게 됩니다. 또한 Switch Has Authority 편의 매크로를 사용하여 리플리케이트되는 액터에서 서버와 클라이언트에 따라 다른 동작을 하도록 하는 분기를 빠르게 만들 수도 있습니다.
변수
액터상의 변수에 대한 디테일 패널에 보면 리플리케이션 드롭다운이 있어 변수 리플리케이션 방법을 조정할 수 있습니다.
옵션설명
None | 없음 - 새 변수의 기본값으로, 이 값을 네트워크를 통해 클라이언트에 전송하지 않는다는 뜻입니다. |
Replicated | 리플리케이트됨 - 서버가 이 액터를 리플리케이트하면, 이 변수를 클라이언트에 전송합니다. 받는 클라이언트의 변수 값은 자동으로 업데이트되어, 다음 번 접근할 때 서버상에 있던 값을 반영합니다. 물론 실시간 네트워크를 통해 플레이할 때는, 네트워크 지연시간만큼 업데이트에 걸리는 시간이 지연됩니다. 기억할 것은 리플리케이티드 변수는 한 방향, 즉 서버에서 클라이언트로만 갑니다! 클라이언트에서 서버로 데이터를 전송하는 방법은 "이벤트" 부분을 확인하세요. |
RepNotify | 리플 알림 - Replicated 옵션처럼 변수 리플리케이션은 하지만, 추가로 블루프린트에 OnRep_<변수명> 함수가 생성됩니다. 이 함수는 이 변수의 값이 변할 때마다 서버와 클라이언트에서 엔진에 의해 자동 호출됩니다. 이 함수는 게임의 필요에 따라 원하는 대로 자유롭게 구현해도 됩니다. |
엔진의 내장 클래스 내 변수 다수에는 이미 리플리케이션이 켜져 있어서, 여러가지 기능이 멀티플레이어 상황에서도 자동으로 작동합니다.
블루프린트에서 변수 리플리케이트 에서 변수 리플리케이션 관련 구체적인 예제 안내 및 프로퍼티 리플리케이션 문서도 참고해 보시기 바랍니다.
스폰 및 소멸
리플리케이트되는 액터가 서버에 스폰되면, 클라이언트에 통신하여 거기서도 자동으로 그 액터 사본을 스폰합니다. 하지만 일반적으로 리플리케이션은 클라이언트에서 서버로 이루어지지 않기에, 리플리케이트되는 액터가 클라이언트에 스폰되는 경우 그 액터는 해당 클라이언트에만 존재하게 됩니다. 서버도 다른 클라이언트도 그 액터 사본을 받지 않습니다. 하지만 스폰하는 클라이언트는 그 액터에 대한 오소리티를 갖게 됩니다. 게임플레이에 영향을 끼치지 않는 장식성 액터같은 것에는 유용할 수 있지만, 게임플레이에 영향을 끼치고 리플리케이트시켜야 하는 액터의 경우 서버에서 스폰되도록 하는 것이 가장 좋습니다.
리플리케이트되는 액터를 소멸(destroy)시키는 상황도 비슷합니다: 서버가 하나를 소멸시키면, 모든 클라이언트에서도 각자의 사본을 소멸시킵니다. 클라이언트는 오소리티를 가진, 즉 직접 스폰시킨 액터를 자유롭게 소멸시킬 수 있는데, 다른 플레이어에 리플리케이트되지도 않았고 영향을 끼치지도 않을 것이기 때문입니다. 클라이언트가 오소리티를 갖지 않은 액터를 소멸 시도하는 경우, 그 소멸 요청은 무시됩니다. 여기서의 핵심 요점은 액터 스폰에도 마찬가지입니다: 리플리케이트되는 액터를 소멸시켜야 하는 경우, 서버에서 소멸시켜 주세요.
이벤트 리플리케이션
블루프린트에서 액터와 그 변수 리플리케이트에 추가로, 클라이언트와 서버 너머로 이벤트를 실행시킬 수도 있습니다.
블루프린트에서 함수 리플리케이트 에서 구체적인 예제에 대한 안내 및 RPC 문서도 참고해 보시기 바랍니다.
RPC (Remote Procedure Call) 이라는 용어가 보이는데, 블루프린트에서 리플리케이트되는 이벤트는 본질적으로 엔진 내에서 RPC 로 (보통 C++ 에서는 그렇게 불립니다) 컴파일된다는 점만 알아두시면 됩니다.
오너십
멀티플레이어 작업시 이해해야 할 중요한 개념, 특히나 리플리케이트되는 이벤트 관련해서는, 어떤 접속을 특정 액터 또는 컴포넌트의 오너로 간주할 것인지 입니다. 우리 용도상 "Run on server" 이벤트는 클라이언트가 소유하는 액터 (또는 그 컴포넌트)에서만 호출 가능하다는 것을 압니다. 이게 무슨 뜻이냐면, 다음 액터 또는 그 액터 중 하나의 컴포넌트에서 "Run on server" 이벤트를 전송할 수만 있다는 뜻입니다:
- 클라이언트의 PlayerController 자체,
- 클라이언트의 PlayerController 가 빙의된 Pawn, 또는
- 클라이언트의 PlayerState.
마찬가지로 "Run on owning client" 이벤트를 전송하는 서버의 경우, 그 이벤트 역시 이 액터 중 하나에서 호출되어야 합니다. 그렇지 않으면 서버는 이벤트를 전송할 클라이언트를 알지 못하게 되어, 서버에서만 실행될 것입니다!
이벤트
커스텀 이벤트의 디테일 패널에서, 이벤트 리플리케이션 방식을 설정할 수 있습니다.
옵션설명
Not Replicated | 리플리케이트 안됨 - 기본값으로, 이 이벤트에 대한 리플리케이션이 없다는 뜻입니다. 클라이언트에서 호출되는 경우 해당 클라이언트에서만 실행되고, 서버에서 호출되는 경우 서버에서만 실행됩니다. |
Multicast | 멀티캐스트 - 서버에서 멀티캐스트 이벤트가 호출되면, 타깃 오브젝트를 어느 접속에서 소유했는지와 무관하게 접속된 모든 클라이언트에 리플리케이트됩니다. 클라이언트가 멀티캐스트 이벤트를 호출하는 경우, 리플리케이트되지 않은 것으로 간주, 호출한 클라이언트에서만 실행합니다. |
Run on Server | 서버에서 실행 - 이 이벤트가 서버에서 실행된 경우, 서버에서만 실행됩니다. 클라이언트에서 클라이언트가 소유한 타깃으로 실행된 경우, 서버에 리플리케이트되어 실행됩니다. "Run on Server" 이벤트는 클라이언트가 서버에 데이터를 전송하기 위한 주요 메서드입니다. |
Run on Owning Client | 소유 클라이언트에서 실행 - 서버에서 호출된 경우, 이 이벤트는 타깃 액터를 소유한 클라이언트에서 실행됩니다. 서버는 액터 자체를 소유할 수 있으므로, "Run on Owning Client" 이벤트는 그 이름과 무관하게 서버에서 실행 가능합니다. 클라이언트에서 호출된 경우, 이벤트는 리플리케이트되지 않은 것으로 간주, 호출한 클라이언트에서만 실행됩니다. |
다음 표는 이벤트 호출 방법에 따라 여러가지 리플리케이션 모드가 이벤트 실행 위치에 어떠한 영향을 끼치는지 나타냅니다.
서버에서 이벤트가 호출된 경우, 타깃이 왼쪽 열이라 한다면, 실행되는 곳은...
리플리케이트 안됨멀티캐스트서버에서 실행소유 클라이언트에서 실행
클라이언트 소유 타깃 | 서버 | 서버와 모든 클라이언트 | 서버 | 타깃의 소유 클라이언트 |
서버 소유 타깃 | 서버 | 서버와 모든 클라이언트 | 서버 | 서버 |
미소유 타깃 | 서버 | 서버와 모든 클라이언트 | 서버 | 서버 |
이벤트가 클라이언트에서 호출된 경우, 타깃이 왼쪽 열이라 한다면, 실행되는 곳은...
리플리케이트 안됨멀티캐스트서버에서 실행소유 클라이언트에서 실행
호출 클라이언트에 소유된 타깃 | 호출 클라이언트 | 호출 클라이언트 | 서버 | 호출 클라이언트 |
다른 클라이언트에 소유된 타깃 | 호출 클라이언트 | 호출 클라이언트 | 버림 | 호출 클라이언트 |
서버 소유 타깃 | 호출 클라이언트 | 호출 클라이언트 | 버림 | 호출 클라이언트 |
미소유 타깃 | 호출 클라이언트 | 호출 클라이언트 | 버림 | 호출 클라이언트 |
위 표에서 볼 수 있듯이, 클라이언트에서 호출되면서 서버에서 실행 설정되지 않은 이벤트는 리플리케이트되지 않는 것으로 간주됩니다.
클라이언트에서 서버로 리플리케이트되는 이벤트를 전송하는 유일한 방법은 클라이언트에서 서버로 정보를 통신하는 방법뿐인데, 일반적인 액터 리플리케이션은 서버에서 클라이언트로만 이루어지도록 디자인되었기 때문입니다.
또한 멀티캐스트 이벤트는 서버에서만 전송 가능하다는 점도 눈여겨 봅시다. 언리얼의 클라리언트-서버 모델로 인해, 클라이언트는 다른 클라이언트와 직접 접속되지 않으며, 서버에만 접속됩니다. 그러므로 클라이언트에서 다른 클라이언트로 멀티캐스트 이벤트를 직접 전송하는 것은 불가능하고, 서버와의 통신이 반드시 있어야만 합니다.
리플리케이트되는 이벤트를 서버에서 실행 이벤트 하나, 멀티캐스트 이벤트 하나, 총 두 개를 사용하여 이러한 작동방식을 흉내낼 수는 있습니다. 서버에서 실행 이벤트의 구현은 필요하다면 인증을 거친 후 멀티캐스트 이벤트를 호출합니다.
그러면 멀티캐스트 이벤트 구현은 접속된 모든 플레이어에게 실행시키고자 하는 로직을 수행합니다.
인증을 전혀 하지 않는 예제의 경우, 다음 그림을 참고하세요:
소유중인 클라에서 서버로 ForwardMulticast 를 콜하고 서버에선 전체 클라로 Server Multicast 를 보내는 예제이다
참가중 고려사항
게임 상태 변화 통신시 리플리케이티드 이벤트 사용시 염두에 둬야 할 점 한 가지는, 참가중 상태를 지원하는 게임을 어떻게 처리할 것인지 입니다. 플레이어가 진행중인 게임에 참가할 때, 참가 전에 일어난 리플리케이트되는 이벤트는 새 플레이어에게 실행되지 않을 것입니다. 여기서 한 가지 중요한 점은, 게임이 참가중 상태 처리를 제대로 하도록 하기 위해서는, 리플리케이트되는 변수를 통해 중요 게임플레이 데이터를 동기화시키는 것이 최선이라는 점입니다. 꽤 자주 겪게 되는 패턴은 클라이언트가 월드에서 어떤 동작을 하고, 서버는 "서버에서 실행" 이벤트를 통해 그 동작을 알린 뒤, 그 이벤트의 구현에서 서버는 그 동작에 따른 몇 가지 리플리케이트되는 변수를 업데이트합니다. 그러면 그 동작을 수행하지 않은 다른 클라이언트에서도 리플리케이트되는 변수를 통해 동작의 결과를 확인할 수 있게 됩니다. 추가로 동작이 벌어진 이후 참가중 상태였던 클라이언트도 월드 상태를 제대로 확인할 수 있게 되는데, 서버에서 리플리케이트되는 변수의 가장 최신 값을 받게 되기 때문입니다. 서버가 이벤트 전송만 했다면, 참가중 상태의 플레이어는 그런 동작이 있었는지 알지 못할 것입니다!
신뢰성
리플리케이트되는 이벤트에 대해 Reliable (신뢰성)인지 Unreliable (비신뢰성)인지 선택할 수 있습니다.
신뢰성 이벤트는 (위의 오너십 규칙을 따른다는 가정하에) 목적지에 반드시 도달한다는 보장이 있습니다만, 그로 인해 보다 많은 대역폭을 필요로 하고, 지연시간이 길어질 수 있습니다. 신뢰성 이벤트를 너무 자주, 이를테면 매 틱마다 전송하지 않도록 하세요. 신뢰성 이벤트에 대한 엔진의 내부 버퍼가 넘칠(overflow) 수가 있으며, 그러한 현상이 발생하면 관련된 플레이어는 접속이 끊어질 것입니다!
비신뢰성 이벤트는 이름이 암시하는 바대로 목적지에 도달하지 못할 수 있는데, 예를 들면 네트워크에서의 패킷 손실이라든가 엔진에서 우선권이 높은 부분을 먼저 전송하기 위해 트래픽을 차지하기로 결정한 경우입니다. 그에 따라 비신뢰성 이벤트는 신뢰성보다 대역폭을 적게 차지하며, 자주 호출해도 보다 안전합니다.
ref : https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Blueprints/
'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글
UE4 Get IP Adress C++ (0) | 2022.07.22 |
---|---|
UFUNCTION BlueprintCallable, BlueprintImplementableEvent, .. (0) | 2022.07.19 |
UE5 특징 (0) | 2022.06.16 |
lambda with timers (1) | 2022.04.24 |
비히클 셋업 (0) | 2022.03.16 |