目标:使用UnrealEngine引擎,通过GAS系统,实现鼠标点击后,角色自动移动到鼠标点击位置,要求:必须实现服务器、客户端都能正常运行
1. 创建Gameplay Ability
首先创建鼠标移动的Gameplay Ability:
cpp
// ClickMoveAbility.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "ClickMoveAbility.generated.h"
UCLASS()
class YOURGAME_API UClickMoveAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
UClickMoveAbility();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData) override;
protected:
UFUNCTION()
void OnMoveCompleted(bool bSuccess);
private:
FVector TargetLocation;
};
cpp
// ClickMoveAbility.cpp
#include "ClickMoveAbility.h"
#include "AbilitySystemComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "AIController.h"
UClickMoveAbility::UClickMoveAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
}
void UClickMoveAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
if (!HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
{
return;
}
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
return;
}
// 从EventData获取目标位置
if (TriggerEventData && TriggerEventData->TargetData.Num() > 0)
{
FGameplayAbilityTargetDataHandle TargetData = TriggerEventData->TargetData;
if (const FGameplayAbilityTargetData_LocationInfo* LocationData =
static_cast<const FGameplayAbilityTargetData_LocationInfo*>(TargetData.Get(0)))
{
TargetLocation = LocationData->TargetLocation;
ACharacter* Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
if (Character && Character->GetLocalRole() != ROLE_SimulatedProxy)
{
AAIController* AIController = Cast<AAIController>(Character->GetController());
if (!AIController)
{
// 如果没有AI控制器,使用玩家控制器
APlayerController* PlayerController = Cast<APlayerController>(Character->GetController());
if (PlayerController)
{
// 对于玩家角色,直接设置移动
Character->GetCharacterMovement()->SetMovementMode(MOVE_Walking);
// 服务器端执行移动
if (HasAuthority(&ActivationInfo))
{
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetCurrent(Character->GetWorld());
if (NavSystem)
{
FNavLocation NavLocation;
if (NavSystem->ProjectPointToNavigation(TargetLocation, NavLocation))
{
// 使用AI控制器或直接设置位置
if (AIController = Character->GetWorld()->SpawnActor<AAIController>())
{
AIController->Possess(Character);
}
}
}
}
// 客户端预测移动
FVector MoveDirection = (TargetLocation - Character->GetActorLocation()).GetSafeNormal();
Character->AddMovementInput(MoveDirection, 1.0f);
// 设置完成回调
FTimerHandle TimerHandle;
Character->GetWorldTimerManager().SetTimer(TimerHandle,
FTimerDelegate::CreateUObject(this, &UClickMoveAbility::OnMoveCompleted, true),
2.0f, false);
}
}
else
{
// AI控制器移动
FAIMoveRequest MoveRequest;
MoveRequest.SetGoalLocation(TargetLocation);
MoveRequest.SetAcceptanceRadius(50.0f);
FPathFollowingRequestResult MoveResult = AIController->MoveTo(MoveRequest);
if (MoveResult.Code == EPathFollowingRequestResult::RequestSuccessful)
{
// 移动开始成功
AIController->ReceiveMoveCompleted.AddDynamic(this, &UClickMoveAbility::OnMoveCompleted);
}
else
{
OnMoveCompleted(false);
}
}
}
}
}
EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
}
void UClickMoveAbility::OnMoveCompleted(bool bSuccess)
{
// 清理资源
if (GetCurrentActorInfo())
{
ACharacter* Character = Cast<ACharacter>(GetCurrentActorInfo()->AvatarActor.Get());
if (Character)
{
AAIController* AIController = Cast<AAIController>(Character->GetController());
if (AIController)
{
AIController->ReceiveMoveCompleted.RemoveDynamic(this, &UClickMoveAbility::OnMoveCompleted);
}
}
}
}
2. 创建Ability Task用于鼠标点击
cpp
// AbilityTask_ClickMove.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "AbilityTask_ClickMove.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FClickMoveDelegate, const FVector&, TargetLocation);
UCLASS()
class YOURGAME_API UAbilityTask_ClickMove : public UAbilityTask
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FClickMoveDelegate OnClickMove;
UFUNCTION(BlueprintCallable, Category = "Ability|Tasks",
meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
static UAbilityTask_ClickMove* ClickMove(UGameplayAbility* OwningAbility);
virtual void Activate() override;
private:
void OnClickHandler(const FVector& Location);
};
cpp
// AbilityTask_ClickMove.cpp
#include "AbilityTask_ClickMove.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/Pawn.h"
UAbilityTask_ClickMove* UAbilityTask_ClickMove::ClickMove(UGameplayAbility* OwningAbility)
{
UAbilityTask_ClickMove* MyObj = NewAbilityTask<UAbilityTask_ClickMove>(OwningAbility);
return MyObj;
}
void UAbilityTask_ClickMove::Activate()
{
Super::Activate();
APlayerController* PC = Cast<APlayerController>(Ability->GetCurrentActorInfo()->PlayerController);
if (PC && PC->IsLocalController())
{
// 设置鼠标点击事件
PC->bShowMouseCursor = true;
PC->bEnableClickEvents = true;
PC->bEnableMouseOverEvents = true;
// 绑定点击事件(这里需要你在PlayerController中实现点击处理)
}
}
void UAbilityTask_ClickMove::OnClickHandler(const FVector& Location)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnClickMove.Broadcast(Location);
}
EndTask();
}
3. 在角色类中设置GAS组件
cpp
// YourCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "AbilitySystemComponent.h"
#include "YourCharacter.generated.h"
UCLASS()
class YOURGAME_API AYourCharacter : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
AYourCharacter();
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
UFUNCTION(Server, Reliable, WithValidation)
void Server_TriggerClickMove(const FVector& TargetLocation);
protected:
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GAS")
UAbilitySystemComponent* AbilitySystemComponent;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GAS")
TSubclassOf<class UGameplayAbility> ClickMoveAbility;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
private:
void OnInputClick();
};
cpp
// YourCharacter.cpp
#include "YourCharacter.h"
#include "ClickMoveAbility.h"
#include "GameFramework/PlayerController.h"
AYourCharacter::AYourCharacter()
{
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>("AbilitySystemComponent");
}
void AYourCharacter::BeginPlay()
{
Super::BeginPlay();
if (AbilitySystemComponent)
{
if (HasAuthority() && ClickMoveAbility)
{
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(ClickMoveAbility.GetDefaultObject(), 1, 0));
}
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
}
UAbilitySystemComponent* AYourCharacter::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void AYourCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAction("ClickMove", IE_Pressed, this, &AYourCharacter::OnInputClick);
}
void AYourCharacter::OnInputClick()
{
APlayerController* PC = Cast<APlayerController>(GetController());
if (PC && PC->IsLocalController())
{
FHitResult HitResult;
if (PC->GetHitResultUnderCursor(ECC_Visibility, false, HitResult))
{
Server_TriggerClickMove(HitResult.Location);
}
}
}
void AYourCharacter::Server_TriggerClickMove_Implementation(const FVector& TargetLocation)
{
if (AbilitySystemComponent)
{
// 创建目标数据
FGameplayAbilityTargetData_LocationInfo* TargetData = new FGameplayAbilityTargetData_LocationInfo();
TargetData->TargetLocation.LocationType = EGameplayAbilityTargetingLocationType::LiteralTransform;
TargetData->TargetLocation.LiteralTransform = FTransform(TargetLocation);
FGameplayAbilityTargetDataHandle TargetDataHandle;
TargetDataHandle.Add(TargetData);
// 触发能力
FGameplayEventData EventData;
EventData.TargetData = TargetDataHandle;
FGameplayTag Tag = FGameplayTag::RequestGameplayTag("Ability.ClickMove");
AbilitySystemComponent->HandleGameplayEvent(Tag, &EventData);
}
}
bool AYourCharacter::Server_TriggerClickMove_Validate(const FVector& TargetLocation)
{
// 添加验证逻辑,防止作弊
return FMath::IsFinite(TargetLocation.X) && FMath::IsFinite(TargetLocation.Y) && FMath::IsFinite(TargetLocation.Z);
}
4. 玩家控制器处理鼠标点击
cpp
// YourPlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "YourPlayerController.generated.h"
UCLASS()
class YOURGAME_API AYourPlayerController : public APlayerController
{
GENERATED_BODY()
public:
virtual void SetupInputComponent() override;
virtual void PlayerTick(float DeltaTime) override;
protected:
UFUNCTION(Server, Reliable, WithValidation)
void Server_SendClickLocation(const FVector& Location);
private:
void OnClickPressed();
};
cpp
// YourPlayerController.cpp
#include "YourPlayerController.h"
#include "YourCharacter.h"
void AYourPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("ClickMove", IE_Pressed, this, &AYourPlayerController::OnClickPressed);
}
void AYourPlayerController::PlayerTick(float DeltaTime)
{
Super::PlayerTick(DeltaTime);
// 可选:显示鼠标位置预览
}
void AYourPlayerController::OnClickPressed()
{
FHitResult HitResult;
if (GetHitResultUnderCursor(ECC_Visibility, false, HitResult))
{
AYourCharacter* Character = Cast<AYourCharacter>(GetPawn());
if (Character)
{
Server_SendClickLocation(HitResult.Location);
}
}
}
void AYourPlayerController::Server_SendClickLocation_Implementation(const FVector& Location)
{
AYourCharacter* Character = Cast<AYourCharacter>(GetPawn());
if (Character)
{
Character->Server_TriggerClickMove(Location);
}
}
bool AYourPlayerController::Server_SendClickLocation_Validate(const FVector& Location)
{
// 验证位置是否合理
return true;
}
5. 游戏能力集配置
在项目的Gameplay Ability Set配置中:
cpp
// 在项目设置或单独的配置类中
UCLASS()
class YOURGAME_API UYourAbilitySet : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Abilities")
TArray<FGameplayAbilitySetup> Abilities;
void GiveToAbilitySystem(UAbilitySystemComponent* ASC) const;
};
关键要点
-
网络复制 :使用
NetExecutionPolicy = LocalPredicted确保客户端预测 -
服务器验证:所有关键操作都有服务器RPC和验证函数
-
权限检查 :在Ability中检查
HasAuthorityOrPredictionKey -
移动同步:通过CharacterMovementComponent确保移动在网络间同步
-
输入处理:本地控制器处理输入,通过RPC传递给服务器
这个实现确保了在多人游戏中,无论是服务器还是客户端,角色都能正确响应鼠标点击移动,并且移动会在所有客户端间同步显示。