使用UnrealEngine引擎,实现鼠标点击移动

目标:使用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;
};

关键要点

  1. 网络复制 :使用 NetExecutionPolicy = LocalPredicted 确保客户端预测

  2. 服务器验证:所有关键操作都有服务器RPC和验证函数

  3. 权限检查 :在Ability中检查 HasAuthorityOrPredictionKey

  4. 移动同步:通过CharacterMovementComponent确保移动在网络间同步

  5. 输入处理:本地控制器处理输入,通过RPC传递给服务器

这个实现确保了在多人游戏中,无论是服务器还是客户端,角色都能正确响应鼠标点击移动,并且移动会在所有客户端间同步显示。

相关推荐
xlq223222 小时前
15.list(上)
数据结构·c++·list
云帆小二2 小时前
从开发语言出发如何选择学习考试系统
开发语言·学习
Elias不吃糖3 小时前
总结我的小项目里现在用到的Redis
c++·redis·学习
BullSmall3 小时前
《道德经》第六十三章
学习
BullSmall3 小时前
《道德经》第六十二章
学习
No0d1es4 小时前
电子学会青少年软件编程(C/C++)六级等级考试真题试卷(2025年9月)
c语言·c++·算法·青少年编程·图形化编程·六级
Knox_Lai4 小时前
数据结构与算法学习(0)-常见数据结构和算法
c语言·数据结构·学习·算法
不会c嘎嘎4 小时前
每日一练 -- day1
c++·算法
yy_xzz4 小时前
VCPKG && Tesseract OCR
c++·图像处理·opencv