[UE4]网游中角色Pawn的移动位置同步以及RTS多角色同时移动的解决方案

下面方案的思路是:
每个Actor,为其定义一个代理(ActorProxy),真实的Actor放在服务端,代理ActorProxy放在客户端,移动Actor时,实际是移动服务端上的Actor,然后对客户端ActorProxy的位置进行同步。

摄像机绑定的是ActorProxy,服务端的真实Actor不用摄像机;而AIController实际控制的是服务端Actor,客户端其实没有AIController。

另外,下面例子的同步机制使用的UE4自身集成的Replication(也就是常用的UFUNCTION(Server, Reliable, WithValidation)),这个是非常耗费带宽的,因为是定时且频繁的同步数据。这个如果是做局域网服务端还可以,但是如果是大型高负载服务端,建议自己实现同步机制。另外,v4.4开始,shipping编译出来的版本会自动删掉UE4的server模式。原因肯定是官方也不希望这个方便测试的同步机制被开发者应用到生产环境中。所以下面例子中,参考下它的思路即可,具体的同步处理的细节问题可以忽略。

 

RTS游戏中多角色同时移动的方案:

为在每个自定义Pawn类定义个AIController,群体移动时,遍历所有Pawn,依次调用各自的AIController->MoveToLocation。

 

另外注意点:AIController->MoveToLocation无法像UNavigationSystem->SimpleMoveToLocation()那样可以主动绕开障碍物,一般建议在服务端通过UNavigationSystem获取移动路径的数据,然后将这些数据与客户端同步。

可参考:
Creating a Movement Component for an RTS in UE4 
http://www.gamedev.net/page/resources/_/technical/game-programming/creating-a-movement-component-for-an-rts-in-ue4-r4019

 

 

=============================================================================

=============================================================================

下面文章有点不足的问题是:服务端是通过AIController->MoveToLocation来寻路的,如果对于一个有障碍物的地图来啊,移动的时候会出现停滞。另外文章建议说服务端不要用UNavigationSystem>SimpleMoveToLocation,这个可能是误解,服务端是可以使用的。

正文:

[UE4] Getting Multiplayer Player Pawn AI Navigation to work (C++)

http://droneah.com/content/ue4-getting-multiplayer-player-pawn-ai-navigation-work-c

 

Unreal Engine is an awesome piece of technology making it easy to do almost anything you might want.

 

When using the Top Down view however, there is a hurdle to get over when trying to get multiplayer to work. This is a C++ project solution to this problem based on a BluePrints solution.

 

The basic problem stems from the fact that

 

"SimpleMoveToLocation was never intended to be used in a network environment. It's simple after all ;) Currently there's no dedicated engine way of making player pawn follow a path. " (from the same page)

 

To be able to get a working version of SimpleMoveToLocation, we need to do the following:

 

Create a proxy player class (BP_WarriorProxy is BP solution)

Set the proxy class as the default player controller class

Move the camera to the proxy (Since the actual player class is on the server, we can't put a camera on it to display on the client)

The BP solution talks about four classes - our counterparts are as follows:

 

BP_WarriorProxy: ADemoPlayerProxy

BP_WarriorController: ADemoPlayerController (Auto-created when creating a c++ top down project)

BP_Warrior: ADemoCharacter (Auto-created when creating a C++ top down project)

BP_WarriorAI: AAIController - we have no reason to subclass it.

So, from a standard c++ top down project, the only class we need to add is the ADemoPlayerProxy - so go ahead and do that first.

 

The first thing we'll do is rewire the ADemoGameMode class to initialise the proxy class instead of the default MyCharacter Blueprint.

Cpp代码   收藏代码
  1. ADemoGameMode::ADemoGameMode(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)   
  2. {   
  3.     // use our custom PlayerController class   
  4.     PlayerControllerClass = ADemoPlayerController::StaticClass();   
  5.    
  6.     // set default pawn class to our Blueprinted character   
  7.     /* static ConstructorHelpers::FClassFinder<apawn> PlayerPawnBPClass(TEXT("/Game/Blueprints/MyCharacter"));  
  8.     if (PlayerPawnBPClass.Class != NULL)  
  9.     {  
  10.         DefaultPawnClass = PlayerPawnBPClass.Class;  
  11.     }*/   
  12.    
  13.     DefaultPawnClass = ADemoPlayerProxy::StaticClass();   
  14. }   

 

 

Our Player Proxy class declaration

Cpp代码   收藏代码
  1. /* This class will work as a proxy on the client - tracking the movements of the  
  2.  * real Character on the server side and sending back controls. */   
  3. UCLASS() class Demo_API ADemoPlayerProxy : public APawn   
  4. {   
  5.     GENERATED_UCLASS_BODY()   
  6.     /** Top down camera */   
  7.     UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera) TSubobjectPtr<class ucameracomponent> TopDownCameraComponent;   
  8.    
  9.     /** Camera boom positioning the camera above the character */   
  10.     UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera) TSubobjectPtr<class uspringarmcomponent> CameraBoom;   
  11.    
  12.     // Needed so we can pick up the class in the constructor and spawn it elsewhere   
  13.     TSubclassOf<aactor> CharacterClass;   
  14.    
  15.     // Pointer to the actual character. We replicate it so we know its location for the camera on the client   
  16.     UPROPERTY(Replicated) ADemoCharacter* Character;   
  17.    
  18.     // The AI Controller we will use to auto-navigate the player   
  19.     AAIController* PlayerAI;   
  20.    
  21.     // We spawn the real player character and other such elements here   
  22.     virtual void BeginPlay() override;   
  23.    
  24.     // Use do keep this actor in sync with the real one   
  25.     void Tick(float DeltaTime);   
  26.    
  27.     // Used by the controller to get moving to work   
  28.     void MoveToLocation(const FVector& vector);   
  29. };   

 

 

and the definition:

Cpp代码   收藏代码
  1. #include "Demo.h"  
  2. #include "DemoCharacter.h"  
  3. #include "AIController.h"  
  4. #include "DemoPlayerProxy.h"  
  5. #include "UnrealNetwork.h"  
  6.    
  7.    
  8. ADemoPlayerProxy::ADemoPlayerProxy(const class FPostConstructInitializeProperties& PCIP)  
  9. : Super(PCIP)  
  10. {  
  11.     // Don't rotate character to camera direction  
  12.     bUseControllerRotationPitch = false;  
  13.     bUseControllerRotationYaw = false;  
  14.     bUseControllerRotationRoll = false;  
  15.    
  16.     // It seems that without a RootComponent, we can't place the Actual Character easily  
  17.     TSubobjectPtr<UCapsuleComponent> TouchCapsule = PCIP.CreateDefaultSubobject<ucapsulecomponent>(this, TEXT("dummy"));  
  18.     TouchCapsule->InitCapsuleSize(1.0f, 1.0f);  
  19.     TouchCapsule->SetCollisionEnabled(ECollisionEnabled::NoCollision);  
  20.     TouchCapsule->SetCollisionResponseToAllChannels(ECR_Ignore);  
  21.     RootComponent = TouchCapsule;  
  22.    
  23.     // Create a camera boom...  
  24.     CameraBoom = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));  
  25.     CameraBoom->AttachTo(RootComponent);  
  26.     CameraBoom->bAbsoluteRotation = true// Don't want arm to rotate when character does  
  27.     CameraBoom->TargetArmLength = 800.f;  
  28.     CameraBoom->RelativeRotation = FRotator(-60.f, 0.f, 0.f);  
  29.     CameraBoom->bDoCollisionTest = false// Don't want to pull camera in when it collides with level  
  30.    
  31.     // Create a camera...  
  32.     TopDownCameraComponent = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("TopDownCamera"));  
  33.     TopDownCameraComponent->AttachTo(CameraBoom, USpringArmComponent::SocketName);  
  34.     TopDownCameraComponent->bUseControllerViewRotation = false// Camera does not rotate relative to arm  
  35.    
  36.     if (Role == ROLE_Authority)  
  37.     {  
  38.         static ConstructorHelpers::FObjectFinder<UClass> PlayerPawnBPClass(TEXT("/Game/Blueprints/MyCharacter.MyCharacter_C"));  
  39.         CharacterClass = PlayerPawnBPClass.Object;  
  40.     }  
  41.    
  42. }  
  43.    
  44. void ADemoPlayerProxy::BeginPlay()  
  45. {  
  46.     Super::BeginPlay();  
  47.     if (Role == ROLE_Authority)  
  48.     {  
  49.         // Get current location of the Player Proxy  
  50.         FVector Location =  GetActorLocation();  
  51.         FRotator Rotation = GetActorRotation();  
  52.    
  53.         FActorSpawnParameters SpawnParams;  
  54.         SpawnParams.Owner = this;  
  55.         SpawnParams.Instigator = Instigator;  
  56.         SpawnParams.bNoCollisionFail = true;  
  57.    
  58.         // Spawn the actual player character at the same location as the Proxy  
  59.         Character = Cast<ADemoCharacter>(GetWorld()->SpawnActor(CharacterClass, &Location, &Rotation, SpawnParams));  
  60.    
  61.         // We use the PlayerAI to control the Player Character for Navigation  
  62.         PlayerAI = GetWorld()->SpawnActor<AAIController>(GetActorLocation(), GetActorRotation());  
  63.         PlayerAI->Possess(Character);  
  64.     }  
  65.    
  66. }  
  67.    
  68. void ADemoPlayerProxy::Tick(float DeltaTime)  
  69. {  
  70.    
  71.     Super::Tick(DeltaTime);  
  72.     if (Character)  
  73.     {  
  74.         // Keep the Proxy in sync with the real character  
  75.         FTransform CharTransform = Character->GetTransform();  
  76.         FTransform MyTransform = GetTransform();  
  77.    
  78.         FTransform Transform;  
  79.         Transform.LerpTranslationScale3D(CharTransform, MyTransform, ScalarRegister(0.5f));  
  80.    
  81.         SetActorTransform(Transform);  
  82.    
  83.     }  
  84. }  
  85.    
  86. void ADemoPlayerProxy::MoveToLocation(const FVector& DestLocation)  
  87. {  
  88.     /** Looks easy - doesn't it. 
  89.      *  What this does is to engage the AI to pathfind. 
  90.      *  The AI will then "route" the character correctly. 
  91.      *  The Proxy (and with it the camera), on each tick, moves to the location of the real character 
  92.      * 
  93.      *  And thus, we get the illusion of moving with the Player Character 
  94.      */  
  95.     PlayerAI->MoveToLocation(DestLocation);  
  96. }  
  97.    
  98. void ADemoPlayerProxy::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const  
  99. {  
  100.    
  101.     Super::GetLifetimeReplicatedProps(OutLifetimeProps);  
  102.    
  103.     // Replicate to Everyone  
  104.     DOREPLIFETIME(ADemoPlayerProxy, Character);  
  105. }  

 

 

We'll now cover changes to the Player Controller. We'll rewire it here to ask the proxy to move, which will in turn ask the AIController to find a path and move the real player character. 

 

This involves changing the SetMoveDestination method to call a server side method to move the character. When the character moves, the player Proxy will automatically mirror the movements.

 

In the header file, add the following function

Cpp代码   收藏代码
  1. /** Navigate player to the given world location (Server Version) */  
  2. UFUNCTION(reliable, server, WithValidation)  
  3. void ServerSetNewMoveDestination(const FVector DestLocation);  

 

 

 

Now let's implement it (DemoPlayerController.cpp):

Cpp代码   收藏代码
  1. bool ADemoPlayerController::ServerSetNewMoveDestination_Validate(const FVector DestLocation)  
  2. {  
  3.     return true;  
  4. }  
  5.    
  6. /* Actual implementation of the ServerSetMoveDestination method */  
  7. void ADemoPlayerController::ServerSetNewMoveDestination_Implementation(const FVector DestLocation)  
  8. {  
  9.     ADemoPlayerProxy* Pawn = Cast<ademoplayerproxy>(GetPawn());  
  10.     if (Pawn)  
  11.     {  
  12.         UNavigationSystem* const NaDemoys = GetWorld()->GetNavigationSystem();  
  13.         float const Distance = FVector::Dist(DestLocation, Pawn->GetActorLocation());  
  14.    
  15.         // We need to issue move command only if far enough in order for walk animation to play correctly  
  16.         if (NaDemoys && (Distance > 120.0f))  
  17.         {  
  18.             //NaDemoys->SimpleMoveToLocation(this, DestLocation);  
  19.             Pawn->MoveToLocation(DestLocation);  
  20.         }  
  21.     }  
  22.    
  23. }  

 

 

And finally, the rewiring of the original method:

Cpp代码   收藏代码
  1. void ADemoPlayerController::SetNewMoveDestination(const FVector DestLocation)  
  2. {  
  3.     ServerSetNewMoveDestination(DestLocation);  
  4. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值