UE5多人MOBA+GAS 45、制作冲刺技能
文章目录
- 添加技能需要的东西
- 添加本地播放GC
- 添加冲刺tag
- 添加一个新的TA用于检测敌方单位
- 添加冲刺GA
- 到角色中监听加速移动速度的回调
- 创建蒙太奇
- 添加GE
- 添加到数据表中
- 添加到角色中
- 纠错
添加技能需要的东西
添加本地播放GC
在UCAbilitySystemStatics
中添加
/*** 在本地触发指定的游戏提示效果(如技能特效、攻击反馈等)* * @param CueTargetActor 触发游戏提示的目标Actor(如角色、技能释放者)* @param HitResult 包含命中位置、法线等碰撞信息的结果对象* @param GameplayCueTag 标识游戏提示类型的GameplayTag(如"Ability.Attack.Basic")*/static void SendLocalGameplayCue(AActor* CueTargetActor, const FHitResult& HitResult, const FGameplayTag& GameplayCueTag);
void UCAbilitySystemStatics::SendLocalGameplayCue(AActor* CueTargetActor, const FHitResult& HitResult,const FGameplayTag& GameplayCueTag)
{FGameplayCueParameters CueParams;CueParams.Location = HitResult.ImpactPoint;CueParams.Normal = HitResult.ImpactNormal;UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(CueTargetActor, GameplayCueTag, EGameplayCueEvent::Executed, CueParams);
}
添加冲刺tag
// 冲刺CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Dash)CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Dash_Start)CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Dash_Cooldown)
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Dash, "Ability.Dash", "冲刺技能")UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Dash_Start, "Ability.Dash.Start", "冲刺技能开始")UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Dash_Cooldown, "Ability.Dash.Cooldown", "冲刺技能冷却")
添加一个新的TA用于检测敌方单位
TargetActor_Around
#pragma once#include "CoreMinimal.h"
#include "GenericTeamAgentInterface.h"
#include "Abilities/GameplayAbilityTargetActor.h"
#include "TargetActor_Around.generated.h"class USphereComponent;
/*** 圆形范围目标检测器,用于检测角色周围的敌对目标* 实现队伍关系接口(IGenericTeamAgentInterface)用于敌我识别*/
UCLASS()
class ATargetActor_Around : public AGameplayAbilityTargetActor, public IGenericTeamAgentInterface
{GENERATED_BODY()
public:ATargetActor_Around();// 配置检测参数:检测半径、队伍ID和本地视觉提示标签void ConfigureDetection(float DetectionRadius, const FGenericTeamId& InTeamId, const FGameplayTag& InLocalGameplayCueTag);/** 实现IGenericTeamAgentInterface接口 - 设置队伍ID */virtual void SetGenericTeamId(const FGenericTeamId& NewTeamID) override;/** 实现IGenericTeamAgentInterface接口 - 获取队伍ID */FORCEINLINE virtual FGenericTeamId GetGenericTeamId() const override { return TeamId; }/** 网络属性复制 */virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;private:UPROPERTY(Replicated)FGenericTeamId TeamId; // 当前检测器的队伍ID(用于敌我识别)// 根组件(用于位置定位)UPROPERTY(VisibleDefaultsOnly, Category = "Comp")TObjectPtr<USceneComponent> RootComp;// 球形碰撞体(用于范围检测)UPROPERTY(VisibleDefaultsOnly, Category = "Targeting")TObjectPtr<USphereComponent> DetectionSphere;// 检测半径(网络同步)UPROPERTY(ReplicatedUsing = OnRep_TargetDetectionRadiusReplicated)float TargetDetectionRadius;// 检测半径复制回调(客户端同步时更新碰撞体大小)UFUNCTION()void OnRep_TargetDetectionRadiusReplicated();// 本地视觉提示标签(命中目标时播放的视觉效果)UPROPERTY(Replicated)FGameplayTag LocalGameplayCueTag;// 碰撞体进入检测范围时的回调UFUNCTION()void ActorInDetectionRange(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
};
#include "TargetActor_Around.h"#include "Abilities/GameplayAbility.h"
#include "Components/SphereComponent.h"
#include "GAS/Core/CAbilitySystemStatics.h"
#include "Net/UnrealNetwork.h"// Sets default values
ATargetActor_Around::ATargetActor_Around()
{PrimaryActorTick.bCanEverTick = true;// 网络设置:在服务器和客户端同步bReplicates = true;// 重要:确保服务器能产生目标数据ShouldProduceTargetDataOnServer = true;// 创建根组件RootComp = CreateDefaultSubobject<USceneComponent>("Root Comp");SetRootComponent(RootComp);// 创建球形碰撞体用于范围检测DetectionSphere = CreateDefaultSubobject<USphereComponent>("Detection Sphere");DetectionSphere->SetupAttachment(GetRootComponent());// 配置碰撞设置:只检测Pawn类型的重叠DetectionSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);DetectionSphere->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);// 绑定重叠开始事件回调DetectionSphere->OnComponentBeginOverlap.AddDynamic(this, &ATargetActor_Around::ActorInDetectionRange);}void ATargetActor_Around::ConfigureDetection(float DetectionRadius, const FGenericTeamId& InTeamId,const FGameplayTag& InLocalGameplayCueTag)
{// 设置队伍关系SetGenericTeamId(InTeamId);// 更新碰撞体大小DetectionSphere->SetSphereRadius(DetectionRadius);// 同步到网络变量TargetDetectionRadius = DetectionRadius;// 设置视觉提示标签LocalGameplayCueTag = InLocalGameplayCueTag;
}void ATargetActor_Around::SetGenericTeamId(const FGenericTeamId& NewTeamID)
{TeamId = NewTeamID;
}void ATargetActor_Around::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{Super::GetLifetimeReplicatedProps(OutLifetimeProps);// 注册需要网络同步的属性DOREPLIFETIME(ATargetActor_Around, TeamId); // 队伍IDDOREPLIFETIME(ATargetActor_Around, LocalGameplayCueTag); // 视觉标签DOREPLIFETIME(ATargetActor_Around, TargetDetectionRadius); // 检测半径
}void ATargetActor_Around::OnRep_TargetDetectionRadiusReplicated()
{// 客户端收到半径更新时,同步更新碰撞体大小DetectionSphere->SetSphereRadius(TargetDetectionRadius);
}void ATargetActor_Around::ActorInDetectionRange(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{// 忽略无效Actorif (!OtherActor) return;// 获取能力拥有者(避免检测到自己)AActor* AvatarActor = nullptr;if (OwningAbility){AvatarActor = OwningAbility->GetAvatarActorFromActorInfo();}// 忽略自身和拥有者if (OtherActor == AvatarActor) return;if (OtherActor == this) return;// 队伍关系检查:只处理敌对目标if (GetTeamAttitudeTowards(*OtherActor) != ETeamAttitude::Hostile) return;// 服务器处理目标数据if (HasAuthority()){// 构建目标数据FGameplayAbilityTargetDataHandle TargetDataHandle;FGameplayAbilityTargetData_ActorArray* ActorArray = new FGameplayAbilityTargetData_ActorArray;ActorArray->SetActors(TArray<TWeakObjectPtr<AActor>>{OtherActor});TargetDataHandle.Add(ActorArray);// 通知能力系统目标已就绪TargetDataReadyDelegate.Broadcast(TargetDataHandle);}// 播放GC特效FHitResult HitResult;HitResult.ImpactPoint = OtherActor->GetActorLocation(); // 命中点为目标位置HitResult.ImpactNormal = (OtherActor->GetActorLocation() - GetActorLocation()).GetSafeNormal(); // 命中方向UCAbilitySystemStatics::SendLocalGameplayCue(OtherActor, HitResult, LocalGameplayCueTag);
}
添加冲刺GA
GA_Dash
#pragma once#include "CoreMinimal.h"
#include "GAS/Core/CGameplayAbility.h"
#include "GA_Dash.generated.h"class UCharacterMovementComponent;
/*** 冲刺能力类,使角色能够向目标方向冲刺并对路径上的敌人造成伤害*/
UCLASS()
class CRUNCH_API UGA_Dash : public UCGameplayAbility
{GENERATED_BODY()
public:// 激活能力时调用virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;// 结束能力时调用virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;private:// 冲刺动作的动画蒙太奇UPROPERTY(EditDefaultsOnly, Category = "Anim")TObjectPtr<UAnimMontage> DashMontage;// 目标检测半径(单位:厘米)UPROPERTY(EditDefaultsOnly, Category = "Targeting")float TargetDetectionRadius = 300.f;// 本地游戏提示标签(用于视觉效果)UPROPERTY(EditDefaultsOnly, Category = "GameplayCue")FGameplayTag LocalGameplayCueTag;// 目标检测器附加的骨骼名称UPROPERTY(EditDefaultsOnly, Category = "Targeting")FName TargetActorAttachSocketName = "TargetDashCenter";// 目标检测器类(圆形范围检测)UPROPERTY(EditDefaultsOnly, Category = "Targeting")TSubclassOf<class ATargetActor_Around> TargetActorClass;// 命中目标后的击退速度UPROPERTY(EditDefaultsOnly, Category = "Effects")float TargetHitPushSpeed = 3000.f;// 命中目标时应用的伤害效果UPROPERTY(EditDefaultsOnly, Category = "Effects")FGenericDamageEffectDef DamageEffect;// 冲刺过程中应用的持续效果UPROPERTY(EditDefaultsOnly, Category = "Effects")TSubclassOf<UGameplayEffect> DashEffect;// 当前激活的冲刺效果句柄FActiveGameplayEffectHandle DashEffectHandle;// 推动角色前进的定时器句柄FTimerHandle PushForwardInputTimerHandle;// 推动角色沿当前方向前进void PushForward();// 缓存角色移动组件UPROPERTY()TObjectPtr<UCharacterMovementComponent> OwnerCharacterMovementComponent;// 动画事件触发时开始冲刺逻辑UFUNCTION()void StartDash(FGameplayEventData Payload);// 目标检测完成回调UFUNCTION()void TargetReceived(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
};
#include "GA_Dash.h"#include "AbilitySystemComponent.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
#include "Abilities/Tasks/AbilityTask_WaitTargetData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GAS/TA/TargetActor_Around.h"void UGA_Dash::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{// 检查能力是否可提交(资源消耗等)和动画是否有效if (!K2_CommitAbility() || !DashMontage){// 条件不满足则立即结束能力K2_EndAbility();return;}// 确保在服务端或预测有效时执行if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)){// 创建并播放冲刺动画蒙太奇UAbilityTask_PlayMontageAndWait* PlayDashMontage = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, DashMontage);// 绑定动画结束/中断事件到能力结束PlayDashMontage->OnBlendOut.AddDynamic(this, &UGA_Dash::K2_EndAbility);PlayDashMontage->OnCancelled.AddDynamic(this, &UGA_Dash::K2_EndAbility);PlayDashMontage->OnInterrupted.AddDynamic(this, &UGA_Dash::K2_EndAbility);PlayDashMontage->OnCompleted.AddDynamic(this, &UGA_Dash::K2_EndAbility);PlayDashMontage->ReadyForActivation();// 等待动画中的冲刺开始事件UAbilityTask_WaitGameplayEvent* WaitDashStartEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_Dash_Start);WaitDashStartEvent->EventReceived.AddDynamic(this, &UGA_Dash::StartDash);WaitDashStartEvent->ReadyForActivation();}
}void UGA_Dash::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{// 获取能力系统组件UAbilitySystemComponent* OwnerAbilitySystemComponent = GetAbilitySystemComponentFromActorInfo();// 移除冲刺效果if (OwnerAbilitySystemComponent && DashEffectHandle.IsValid()){OwnerAbilitySystemComponent->RemoveActiveGameplayEffect(DashEffectHandle);}// 清除推进定时器if (PushForwardInputTimerHandle.IsValid()){GetWorld()->GetTimerManager().ClearTimer(PushForwardInputTimerHandle);}Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}void UGA_Dash::PushForward()
{// 如果存在移动组件,则沿角色前方持续推动if (OwnerCharacterMovementComponent){// 获取角色前方向量FVector ForwardActor = GetAvatarActorFromActorInfo()->GetActorForwardVector();// 添加移动输入OwnerCharacterMovementComponent->AddInputVector(ForwardActor);// 设置下一帧继续推动(循环递归调用)PushForwardInputTimerHandle = GetWorld()->GetTimerManager().SetTimerForNextTick(this, &UGA_Dash::PushForward);}
}void UGA_Dash::StartDash(FGameplayEventData Payload)
{// 在服务端应用冲刺效果if (K2_HasAuthority()){if (DashEffect){DashEffectHandle = BP_ApplyGameplayEffectToOwner(DashEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));}}// 本地控制角色:启动连续推进if (IsLocallyControlled()){// 缓存移动组件OwnerCharacterMovementComponent = GetAvatarActorFromActorInfo()->GetComponentByClass<UCharacterMovementComponent>();// 启动推进循环PushForwardInputTimerHandle = GetWorld()->GetTimerManager().SetTimerForNextTick(this, &UGA_Dash::PushForward);}// 创建目标检测任务UAbilityTask_WaitTargetData* WaitTargetData = UAbilityTask_WaitTargetData::WaitTargetData(this, NAME_None, EGameplayTargetingConfirmation::CustomMulti, // 自定义确认方式TargetActorClass);// 绑定目标检测完成回调WaitTargetData->ValidData.AddDynamic(this, &UGA_Dash::TargetReceived);WaitTargetData->ReadyForActivation();// 生成目标检测器AGameplayAbilityTargetActor* TargetActor;WaitTargetData->BeginSpawningActor(this, TargetActorClass, TargetActor);// 配置目标检测器ATargetActor_Around* TargetActorAround = Cast<ATargetActor_Around>(TargetActor);if (TargetActorAround){// 设置检测半径、队伍过滤和视觉提示TargetActorAround->ConfigureDetection(TargetDetectionRadius, GetOwnerTeamId(), LocalGameplayCueTag);}// 完成生成WaitTargetData->FinishSpawningActor(this, TargetActor);// 将检测器附加到角色骨骼if (TargetActorAround){TargetActorAround->AttachToComponent(GetOwningComponentFromActorInfo(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, TargetActorAttachSocketName);}
}void UGA_Dash::TargetReceived(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{// 服务端处理:对目标应用效果if (K2_HasAuthority()){// 应用伤害效果ApplyDamageToTargetDataHandle(TargetDataHandle, DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));// 击退目标PushTargetsFromOwnerLocation(TargetDataHandle, TargetHitPushSpeed);}
}
到角色中监听加速移动速度的回调
// 加速移动速度改变回调void MoveSpeedAccelerationUpdated(const FOnAttributeChangeData& Data);
void ACCharacter::BindGASChangeDelegates()
{if (CAbilitySystemComponent){CAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).AddUObject(this, &ACCharacter::DeathTagUpdated);CAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Stun).AddUObject(this, &ACCharacter::StunTagUpdated);CAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Aim).AddUObject(this, &ACCharacter::AimTagUpdated);CAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Focus).AddUObject(this, &ACCharacter::FocusTagUpdated);CAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(CAttributeSet->GetMoveSpeedAttribute()).AddUObject(this, &ACCharacter::MoveSpeedUpdated);CAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetMaxHealthAttribute()).AddUObject(this, &ACCharacter::MaxHealthUpdated);CAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetMaxManaAttribute()).AddUObject(this, &ACCharacter::MaxManaUpdated);CAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetMoveAccelerationAttribute()).AddUObject(this, &ACCharacter::MoveSpeedAccelerationUpdated);}
}void ACCharacter::MoveSpeedAccelerationUpdated(const FOnAttributeChangeData& Data)
{GetCharacterMovement()->MaxAcceleration = Data.NewValue;
}
创建蒙太奇
为其添加一个插槽,用来塞一个TA进去
添加GE
冲刺的GE
冷却GE
伤害GE
成本GE
再创建一个TA塞进去
添加到数据表中
添加到角色中
纠错
这个时候就会又bro发出疑问了,为什么放了冲刺技能后我不能动了呢
因为我们一开始的时候那个加速度没有给他初始化导致的
那只能创建一个GE来应用一个初始的Value,免得这个无限效果消失的时候变成了默认的0蛋
角色中的加速度是2048,那我直接应用一个2048吧
随后放进英雄资产里等他自己初始化吧
然后就可以开冲了