멀티플레이
모든 모드가 멀티플레이를 처리하는 특수 코드를 필요로 하진 않지만, 멀티플레이를 염두에 두고 모드를 제작하는 것이 중요합니다.
모드에서 멀티플레이를 지원하는 것과 다른 언리얼 엔진 프로젝트에서 멀티플레이를 구현하는 것은 비슷합니다.
모드에 멀티플레이를 지원하기 위한 특수 코드가 필요합니까?
간단한 답변: 시도하고 찾아내 보십시오!
자세한 답변: 복잡합니다. 최고의 방법은 모드를 테스트하고 작동 여부를 결정하는 것입니다. 하지만 일반적으로, 모드가 아이템, 제작법, 도면을 추가하거나 기존 게임 클래스의 부가 확장만 하는 경우 아마 멀티플레이 코드를 작성할 필요가 없을 것입니다. 사용자의 입력을 처리하거나 게임의 상태를 수정하는 맞춤 논리를 사용하기 시작할 때, 그 때부터 멀티플레이에 대해 생각해야 할 것입니다.
계속하기 전에
언리얼 엔진에서 복제는 처음에 매우 헷갈리는 체계일 수 있습니다. 따라서 이를 깊이 이해하는 것이 중요합니다.
이 Alex Forsythe의 영상은 언리얼 엔진의 복제 체계에 대한 훌륭한 소개를 제공해며, 이 페이지를 따르기 전 영상을 먼저 보는 것이 좋습니다. 아마 몇 시간동안 문제를 해결하는 미래의 당신을 구원해줄 것입니다.
이후 페이지에서는 여러분이 영상을 이해했다는 가정 하에 진행할 것입니다.
영상을 시청한 후, 언리얼 커뮤니티 위키의 복제 페이지를 확인하십시오. 여기서 모든 개념을 사용할 필요는 없지만, 작동하지 않을 때 무엇이 잘못되었는지 파악하는 데 도움이 될 것입니다.
언리얼의 자체 예제를 확인하는 것도 고려해 보십시오. 네트워킹 콘텐츠 예제 팩에서 확인할 수 있습니다.
부디, 계속하기 전 위 영상을 꼭 시청해 주십시오. |
ExampleMod
ExampleMod(시작 프로젝트에 포함됨)는 모드에 멀티플레이 기능을 올바르게 추가하는 몇 가지 예제를 포함하고 있습니다.
예제는 포괄적이지 않지만, 시작하는 데 도움이 될 것입니다.
권한 확인
영상에서는 네트워크 권한의 개념을 설명합니다.
코드에 권한이 있는지 확인하려면 'Has Authority' 액터를 사용하십시오. 비교할 액터가 없다면 'Get Game State' GameplayStatics의 반환값을 액터로 사용하는 것을 고려하십시오.
플레이어 컨트롤러 인덱스 0 사용 피하기
전용 서버는 플레이어 컨트롤러가 없으므로, 일반적으로 코드에서 인덱스 0으로 'Get Player Controller' GameplayStatics를 사용하는 것을 피해야 합니다. 예를 들어, 반환할 컨트롤러를 Has Authority의 입력으로 사용하지 말고, 이의 pawn을 가져와 Is Locally Human Controlled를 확인하지 마십시오.
플레이어 컨트롤러 인덱스 0을 사용자 인터페이스 관련 작업에 사용하는 것은 가능하지만, 해당 코드는 유효한 플레이어 컨트롤러 인덱스 0이 있는 측에서만 실행되어야 하며, 게임이 로컬 분할 화면 지원을 구현하면 깨질 수 있습니다(콘솔 지원이 계획되어 있으므로 가능성이 있습니다).
원격 프로시저 호출 (RPCs)
영상은 원격 프로시저 호출(RPC)이 무엇인지 설명합니다. 자세한 정보는 언리얼 엔진 문서에서 확인할 수 있습니다.
새티스팩토리 모드는 일반적으로 사용 사례에 따라 RPC가 두 곳 중 하나에 정의됩니다. 아래 표에는 각 사용 사례에 대한 정보가 포함되어 있습니다. 각 시스템을 더 자세히 설명하는 섹션을 읽어보십시오.
원격 호출 객체(RCO)를 사용할 때: | 복제된 모드 서브시스템을 사용할 때: |
---|---|
RPC가 하나의 클라이언트에서 서버에 전송함 |
RPC가 서버에서 하나 이상의 클라이언트에 전송함 |
클라이언트 보낸 어떤 행동으로 인해 RPC가 호출됨 |
RPC가 클라이언트의 행동 또는 자동으로 호출됨 |
RPC가 멀티캐스트임 |
RPC 실행
RPC를 호출하는 결과는 RPC의 유형과 호출하는 액터의 네트워크 소유권에 따라 달라집니다. 언리얼 문서에는 다양한 실행 결과를 요약한 훌륭한 표가 있습니다.
원격 호출 객체 (RCOs)
원격 호출 객체(RCO)는 모드 RPC를 정의하기 위한 편리한 장소입니다. 커피 스테인 스튜디오는 모드가 자체 RCO를 등록하는 것을 쉽게 하기 위해 맞춤 코드를 작성했습니다. 이 시스템의 내부 세부 사항은 부록에서 설명되어 있으므로 더 알고 싶다면 확인하십시오.
RCO는 각 플레이어 컨트롤러에 대해 서버에 의해 생성되지만, 중요하게도 해당 플레이어 컨트롤러는 RCO의 네트워크 소유권을 부여받습니다. 이로 인해 RCO는 RPC를 호출할 수 있는 능력을 가지게 되며, RPC는 연결에 의해 (전이적으로) 소유되는 객체에서만 작동합니다(즉, 연결에 의해 소유되는 플레이어 컨트롤러). 이 네트워크 소유권이 없으면 RPC는 실행되지 않습니다.
RCO는 게임 인스턴스 모듈의 Remote Call Objects
배열에 클래스를 나열하여 등록할 수 있습니다.
등록된 RCO는 플레이어 컨트롤러 인스턴스에서 런타임에
FGPlayerController::GetRemoteCallObjectOfClass
를 통해 검색할 수 있으며,
이는 블루프린트와 C++ 모두에서 접근 가능합니다.
어떤 이유로 RCO를 수동으로 등록해야 하는 경우,
FGPlayerController::RegisterRemoteCallObjectOfClass
또는
FGGameMode::RegisterRemoteCallObjectClass
를 통해 수행할 수 있습니다.
RCO에서 멀티캐스트 사용 금지!
RCO는 플레이어 컨트롤러에 의해 소유되며, 모든 연결에 존재하지는 않습니다. 따라서 멀티캐스트 RPC에 RCO를 사용해서는 안 됩니다. 이를 발동하면 클라이언트 플레이어가 소프트락되고 충돌할 수 있습니다! 멀티캐스트를 사용하려면 복제된 하위 시슽템에서 수행하십시오.
이렇게 하면 안 되는 이유는 ExampleMod의 CC_ExampleReplication
채팅 명령에서 보여줍니다.
블루프린트 예제
원격 호출 객체는 BP Remote Call Object
에서 상속해야 합니다.
ExampleMod는 Widget_MultiplayerDemoBuilding
에서 블루프린트 원격 호출 객체 사용을 보여줍니다.
C++ 예제
원격 호출 객체는 FGRemoteCallObject
에서 상속해야 합니다.
이대로 시도하면 여전히 작동하지 않을 것입니다. 언리얼은 이상하므로 한 가지 더 해야 합니다.
RCO에 복제되는 UPROPERTY
의 어떤 종류를 추가해야 합니다.
이는 GetLifetimeReplicatedProps
함수에 추가해야 합니다.
이 속성은 존재하기만 하면 되며, 특별히 작업할 필요는 없습니다.
다음은 하나의 RPC가 있는 간단한 RCO를 보여주는 작은 C++ 예제입니다.
UCLASS()
class DOCMOD_API UDocModRCO : public UFGRemoteCallObject {
GENERATED_BODY()
public:
UFUNCTION(Server, WithValidation, Reliable)
void SetSomeStuffOfTheDocMachineRPC(ADocMachine* machineContext, bool bSomeData);
UPROPERTY(Replicated)
bool bDummy = true;
};
RPC의 매개변수는 예제일 뿐이지만, 대부분의 경우 실제로는 주어진 컨텍스트의 상태를 변경할 수 있도록 하나의 컨텍스트 매개변수를 전달하고 싶습니다. 이 예제 함수로 GUI가 전달된 기계의 카운터를 재설정할 수 있도록 허용할 수 있습니다. 컨텍스트가 없으면 어떤 기계의 카운터를 재설정해야 하는지 알 수 없습니다.
RPC의 구현은 이 튜토리얼에서 다루지 않으므로, 이는 전적으로 당신이 결정해야 합니다.
하지만 아래는 GetLifetimeReplicatedProps
함수의 짧은 예제입니다.
#include "Net/UnrealNetwork.h"
void UDocModRCO::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UDocModRCO, bDummy);
}
RCO의 인스턴스를 얻으려면 AFGPlayerController::GetRemoteCallObjectByClass
함수를 호출하고 RCO의 클래스를 전달하십시오.
RCO를 사용하는 대부분의 컨텍스트는 이미 플레이어 컨트롤러(플레이어 컨텍스트)에 접근할 수 있습니다.
예를 들어, UWidgets(GUI)에서 Get Owning Player를 통해 플레이어 컨트롤러를 얻을 수 있습니다.
플레이어 컨텍스트에 접근하는 데 문제가 있는 경우,
코드의 구조를 평가하고 현재 위치가 RCO 호출을 수행하기에 적합한지 결정하십시오.
복제된 서브시스템이 더 나은 선택일 수 있으며,
이미 RCO를 가진 다른 코드 부분에서 RCO를 전달할 수 있을지도 모릅니다.
다음은 C++에서 RPC를 호출하는 예제입니다.
ADocMachine* machine = GetMachine(); // 어딘가에서 컨텍스트 객체를 가져옵니다.
UWorld* world = machine->GetWorld(); // 어딘가에서 월드 컨텍스트를 가져옵니다.
// 플레이어 컨트롤러에서 RCO 인스턴스를 가져옵니다.
// 주의: GetFirstPlayerController는 게임이 분할 화면 지원을 추가하면 깨질 수 있습니다!
UDocModRCO* rco = Cast<AFGPlayerController>(world->GetFirstPlayerController())->GetRemoteCallObjectByClass(UDocModRCO::StaticClass());
rco->SetSomeStuffOfTheDocMachineRPC(machine, false); // RCO의 RPC를 호출합니다.
AFGPlayerController::GetRemoteCallObjectByClass
가 실제로 무언가를 반환하는지 확인하는 것도 고려하십시오.
RCO가 아직 등록되지 않은 경우와 같은 여러 조건에서 nullptr을 반환합니다.
복제된 모드 서브시스템
모드 서브시스템은 새티스팩토리 모드 로더에 의해 구현된 개념입니다.
자세한 내용은 서브시스템 페이지에서 확인하십시오.
서브시스템이 복제되는지 여부는 Replication Policy
란을 통해 구성합니다.
복제된 서브시스템은 모든 연결에 존재하므로 멀티캐스트 RPC를 구현하기에 좋은 장소입니다.
블루프린트 예제
ExampleMod는 ReplicationExampleSubsystem
에서 멀티캐스트 RPC를 사용하여 CC_ExampleReplication
채팅 명령을 구현합니다.
C++ 예제
현재 제공된 예제가 없습니다. 대신 오픈 소스 모드를 확인해보십시오.
복제 세부 사항 구성 요소
복제 세부 사항 구성 요소는 이전에 멀티플레이어 클라이언트에 인벤토리를 복제하는 데 중요한 역할을 했습니다.
1.0 배포에서는 이를 조건부 속성 복제로 대체했으며, 이는 커피 스테인이 클라이언트에 전송되는 불필요한 데이터 양을 줄이기 위해 작성한 시스템입니다. 인벤토리 구성 요소를 복제하는 방법에 대한 자세한 내용은 링크된 페이지를 참고하십시오.
복제된 맵
알 수 없는 이유로 언리얼은 TMaps를 복제할 수 있는 시스템을 제공하지 않습니다. 이를 해결하기 위해 여러 가지 접근 방식을 구현할 수 있습니다:
-
키와 값에 대한 속성을 가진 맞춤 구조체의 배열을 복제합니다. 호스트는 일반 맵을 사용하고, 맵 변경에 따라 이 배열을 업데이트합니다. 클라이언트에서는 OnRep 콜백을 구현하고 배열에서 맵을 구성합니다.
-
키가 값에서 계산될 수 있는 경우, 예를 들어 이름별로 FGBuildables를 포함하는 맵의 경우, 값의 배열만 복제하고 OnRep 콜백에서 이를 기반으로 맵을 구성합니다.
-
더 성능이 좋은 접근 방식은 맵을 보유하는 맞춤(복제) 구조체를 생성한 다음, 부분 업데이트의 복제를 효율적으로 처리하기 위해 사용자 정의 NetSerialize 및 NetDeltaSerialize 오버라이드를 작성하는 것입니다. 이러한 접근 방식은 결코 쉬운 일이 아닙니다. 맵이 너무 자주 업데이트되어 배열로 변환하는 오버헤드가 중요하다면, 모든 데이터를 복제할 필요가 있는지 재고하고, 네트워크 문제를 먼저 고려하십시오.
하나의 키 배열과 하나의 값 배열을 복제하는 것은 권장되지 않습니다. 각 배열의 변경 사항이 동시에 도착할 것이라는 보장이 없기 때문입니다.
부록
다양한 주제에 대한 추가 정보입니다.
클라이언트-서버 원격 프로시저 호출에 대한 주의 사항
원격 프로시저 호출(RPC)을 발동하는 것이 처음 생각보다 간단하지 않다는 것을 알 수 있습니다. 그 이유는 간단합니다. 클라이언트에서 RPC를 호출할 수 있으려면 호출 객체가 객체의 권한을 가져야 합니다. 이는 객체가 플레이어 연결에 의해 소유되는 경우에만 해당됩니다. 예를 들어, 플레이어 컨트롤러는 플레이어 연결에 의해 소유됩니다.
모드 제작자는 플레이어 컨트롤러에 직접적으로 더 많은 기능을 추가할 수 없으므로, 컴파일 시간에 플레이어 연결 소유 범위에 기능을 추가할 수 없습니다.
다행히도 커피 스테인은 런타임에 플레이어 연결에 의해 소유되는 기능을 추가할 수 있는 시스템을 구현했습니다.
이 시스템은 원격 호출 객체
를 통해 구현됩니다.
원격 호출 객체(RCO)는 런타임에 각 플레이어 컨트롤러에 대해 개별적으로 생성됩니다. CSS의 코드는 생성, 복제, 소유권 이전을 처리합니다.
플레이어 컨트롤러를 소유한 클라이언트는 RCO의 클래스를 AFGPlayerController::GetRemoteCallObjectByClass
함수에 전달하여 RCO 인스턴스를 얻을 수 있습니다.
이 RCO 참조를 통해 클라이언트 측에서만 존재하는 GUI에서도 RCO의 RPC를 호출할 수 있습니다.