文章目录
添加技能需要的东西
添加本地播放GC
在UCAbilitySystemStatics
中添加
cpp
/**
* 在本地触发指定的游戏提示效果(如技能特效、攻击反馈等)
*
* @param CueTargetActor 触发游戏提示的目标Actor(如角色、技能释放者)
* @param HitResult 包含命中位置、法线等碰撞信息的结果对象
* @param GameplayCueTag 标识游戏提示类型的GameplayTag(如"Ability.Attack.Basic")
*/
static void SendLocalGameplayCue(AActor* CueTargetActor, const FHitResult& HitResult, const FGameplayTag& GameplayCueTag);
cpp
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
cpp
// 冲刺
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)
cpp
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
cpp
#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);
};
cpp
#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); // 队伍ID
DOREPLIFETIME(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)
{
// 忽略无效Actor
if (!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
cpp
#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);
};
cpp
#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);
}
}
到角色中监听加速移动速度的回调
cpp
// 加速移动速度改变回调
void MoveSpeedAccelerationUpdated(const FOnAttributeChangeData& Data);
cpp
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吧
随后放进英雄资产里等他自己初始化吧
然后就可以开冲了