UE5多人MOBA+GAS 43、制作激光技能 - 指南

文章目录

  • 创建激光GA
    • 添加技能标签
    • 初步实现激光技能
    • 给激光技能配置GE
    • 配置动画
    • 添加新的技能对应到角色以及资产的配置
  • 添加新的TA`TargetActor_Line`
  • 将激光TA添加到激光GA上
  • 修改一点杂事
  • 补充初始化金币的GE


创建激光GA

添加新技能激光技能GA_Laser(不用管下图的打错,我也是后知后觉)
在这里插入图片描述

添加技能标签

// 激光
CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Laser_Shoot)
CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Laser_Cooldown)
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Laser_Shoot, "Ability.Laser.Shoot", "激光技能")
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Laser_Cooldown, "Ability.Laser.Cooldown", "激光技能冷却")

初步实现激光技能

#pragma once
#include "CoreMinimal.h"
#include "GAS/Core/CGameplayAbility.h"
#include "GA_Laser.generated.h"
/**
* 激光类技能:
* - 持续施法技能,消耗法力值维持激光
* - 对路径上的目标造成持续伤害和击退效果
*/
UCLASS()
class CRUNCH_API UGA_Laser :
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 = "Targeting")
float TargetRange = 4000;
// 激光检测半径(厘米)
UPROPERTY(EditDefaultsOnly, Category = "Targeting")
float DetectionCylinderRadius = 30.f;
// 目标检测频率(秒)
UPROPERTY(EditDefaultsOnly, Category = "Targeting")
float TargetingInterval = 0.3f;
// 命中时应用的伤害效果
UPROPERTY(EditDefaultsOnly, Category = "Effects")
FGenericDamageEffectDef HitDamageEffect;
// 持续施法时的法力消耗效果
UPROPERTY(EditDefaultsOnly, Category = "Effects")
TSubclassOf<UGameplayEffect> OnGoingConsumptionEffect;
  // 持续消耗效果的激活句柄
  FActiveGameplayEffectHandle OnGoingConsumptionEffectHandle;
  // 激光技能动画蒙太奇
  UPROPERTY(EditDefaultsOnly, Category = "Anim")
  TObjectPtr<UAnimMontage> LaserMontage;
    // 目标Actor附加的骨骼名称
    UPROPERTY(EditDefaultsOnly, Category = "Targeting")
    FName TargetActorAttachSocketName = "Laser";
    // 动画事件触发的射击回调
    UFUNCTION()
    void ShootLaser(FGameplayEventData Payload);
    // 法力值更新回调(用于检测法力不足)
    void ManaUpdated(const FOnAttributeChangeData& ChangeData);
    };
#include "GA_Laser.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
#include "Abilities/Tasks/AbilityTask_WaitCancel.h"
#include "Abilities/Tasks/AbilityTask_WaitTargetData.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "GAS/Core/CAttributeSet.h"
void UGA_Laser::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
if (!K2_CommitAbility() || !LaserMontage)
{
K2_EndAbility();
return;
}
// 仅在服务器或预测有效时执行
if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
{
// 播放激光蒙太奇
UAbilityTask_PlayMontageAndWait* PlayerLaserMontageTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, LaserMontage);
PlayerLaserMontageTask->OnBlendOut.AddDynamic(this, &UGA_Laser::K2_EndAbility);
PlayerLaserMontageTask->OnCancelled.AddDynamic(this, &UGA_Laser::K2_EndAbility);
PlayerLaserMontageTask->OnInterrupted.AddDynamic(this, &UGA_Laser::K2_EndAbility);
PlayerLaserMontageTask->OnCompleted.AddDynamic(this, &UGA_Laser::K2_EndAbility);
PlayerLaserMontageTask->
ReadyForActivation();
// 等待动画事件触发激光发射
UAbilityTask_WaitGameplayEvent* WaitShootEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_Laser_Shoot);
WaitShootEvent->EventReceived.AddDynamic(this, &UGA_Laser::ShootLaser);
WaitShootEvent->
ReadyForActivation();
// 设置技能取消监听
UAbilityTask_WaitCancel* WaitCancel = UAbilityTask_WaitCancel::WaitCancel(this);
WaitCancel->OnCancel.AddDynamic(this, &UGA_Laser::K2_EndAbility);
WaitCancel->
ReadyForActivation();
}
}
void UGA_Laser::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
// 移除持续消耗法力的效果
UAbilitySystemComponent* OwnerAbilitySystemComponent = GetAbilitySystemComponentFromActorInfo();
if (OwnerAbilitySystemComponent && OnGoingConsumptionEffectHandle.IsValid())
{
OwnerAbilitySystemComponent->
RemoveActiveGameplayEffect(OnGoingConsumptionEffectHandle);
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UGA_Laser::ShootLaser(FGameplayEventData Payload)
{
// --- 服务器端逻辑 ---
if (K2_HasAuthority())
{
// 1. 应用持续消耗法力的效果
OnGoingConsumptionEffectHandle = BP_ApplyGameplayEffectToOwner(OnGoingConsumptionEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));
// 2. 注册法力变化回调(检测法力不足)
UAbilitySystemComponent* OwnerAbilitySystemComponent = GetAbilitySystemComponentFromActorInfo();
if (OwnerAbilitySystemComponent)
{
OwnerAbilitySystemComponent->
GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetManaAttribute()).AddUObject(this, &UGA_Laser::ManaUpdated);
}
}
}
void UGA_Laser::ManaUpdated(const FOnAttributeChangeData& ChangeData)
{
// 当法力不足时自动结束技能
UAbilitySystemComponent* OwnerASC = GetAbilitySystemComponentFromActorInfo() ;
if (OwnerASC &&
!OwnerASC->
CanApplyAttributeModifiers(
OnGoingConsumptionEffect.GetDefaultObject(),
GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo),
MakeEffectContext(CurrentSpecHandle, CurrentActorInfo)))
{
K2_EndAbility();
}
}

给激光技能配置GE

创建多个GE
在这里插入图片描述
伤害GE
在这里插入图片描述
眩晕GE
在这里插入图片描述
持续射击的蓝耗GE
在这里插入图片描述
冷却GE(使用我之前的mmc的话)
在这里插入图片描述
不用的话就使用可浮动点数
在这里插入图片描述
首次提交的蓝耗
在这里插入图片描述
继承激光GA创建蓝图版本

在这里插入图片描述

配置动画

蒙太奇中添加激光的动画通知
在这里插入图片描述

添加新的技能对应到角色以及资产的配置

创建一个插槽Laser用来释放激光的起点
角色类中添加技能
在这里插入图片描述

UI数据中添加技能
在这里插入图片描述
在这里插入图片描述

添加新的TATargetActor_Line

用于设置激光以及检测敌人发送需要造成伤害的敌人
在这里插入图片描述
添加一下奶瓜粒子特效
在这里插入图片描述

#pragma once
#include "CoreMinimal.h"
#include "GenericTeamAgentInterface.h"
#include "Abilities/GameplayAbilityTargetActor.h"
#include "Components/SphereComponent.h"
#include "TargetActor_Line.generated.h"
class UNiagaraComponent
;
/**
* 线性目标检测器
* 用于技能/能力的线性范围目标检测与反馈
*/
UCLASS()
class CRUNCH_API ATargetActor_Line :
public AGameplayAbilityTargetActor, public IGenericTeamAgentInterface
{
GENERATED_BODY()
public:
ATargetActor_Line();
/**
* 配置目标检测参数
* @param NewTargetRange 检测距离
* @param NewDetectionCylinderRadius 检测半径
* @param NewTargetingInterval 检测频率 /s
* @param OwnerTeamId 拥有者队伍ID
* @param bShouldDrawDebug 是否绘制调试信息
*/
void ConfigureTargetSetting(
float NewTargetRange,
float NewDetectionCylinderRadius,
float NewTargetingInterval,
FGenericTeamId OwnerTeamId,
bool bShouldDrawDebug
);
// 设置队伍ID
virtual void SetGenericTeamId(const FGenericTeamId& NewTeamID) override;
// 获取队伍ID
FORCEINLINE virtual FGenericTeamId GetGenericTeamId() const override {
return TeamId;
}
// 网络属性同步
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>
  & OutLifetimeProps) const override;
  // 开始目标检测
  virtual void StartTargeting(UGameplayAbility* Ability) override;
  // 每帧调用,处理目标检测和特效更新
  virtual void Tick(float DeltaTime) override;
  // 销毁时回调
  virtual void BeginDestroy() override;
  private:
  // 检测射程
  UPROPERTY(Replicated)
  float TargetRange;
  // 检测圆柱体半径
  UPROPERTY(Replicated)
  float DetectionCylinderRadius;
  // 检测间隔(秒)
  UPROPERTY()
  float TargetingInterval;
  // 所属队伍ID
  UPROPERTY(Replicated)
  FGenericTeamId TeamId;
  // 是否绘制调试信息
  UPROPERTY()
  bool bDrawDebug;
  // 技能拥有者Actor
  UPROPERTY(Replicated)
  const AActor* AvatarActor;
  // 激光特效长度参数名
  UPROPERTY(EditDefaultsOnly, Category = "VFX")
  FName LaserFXLengthParamName = "Length";
  // 根组件
  UPROPERTY(VisibleDefaultsOnly, Category = "Component")
  TObjectPtr<USceneComponent> RootComp;
    // 激光特效组件
    UPROPERTY(VisibleDefaultsOnly, Category = "Component")
    TObjectPtr<UNiagaraComponent> LaserVFX;
      // 目标检测球体组件
      UPROPERTY(VisibleDefaultsOnly, Category = "Component")
      TObjectPtr<USphereComponent> TargetEndDetectionSphere;
        // 定时检测句柄
        FTimerHandle PeriodicalTargetingTimerHandle;
        // 执行目标检测并报告结果
        void DoTargetCheckAndReport();
        // 更新激光特效和检测逻辑
        void UpdateTargetTrace();
        // 判断Actor是否应被报告为目标
        bool ShouldReportActorAsTarget(const AActor* ActorToCheck) const;
        };
#include "TargetActor_Line.h"
#include "NiagaraComponent.h"
#include "Abilities/GameplayAbility.h"
#include "Crunch/Crunch.h"
#include "Kismet/KismetMathLibrary.h"
#include "Net/UnrealNetwork.h"
// Sets default values
ATargetActor_Line::ATargetActor_Line()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
ShouldProduceTargetDataOnServer = true;
AvatarActor = nullptr;
// 创建根组件
RootComp = CreateDefaultSubobject<USceneComponent>("Root Comp");
  SetRootComponent(RootComp);
  // 创建目标检测球体组件(用于检测终点附近的目标)
  TargetEndDetectionSphere = CreateDefaultSubobject<USphereComponent>("Target End Detection Sphere");
    TargetEndDetectionSphere->
    SetupAttachment(GetRootComponent());
    TargetEndDetectionSphere->
    SetCollisionResponseToChannel(ECC_SPRING_ARM, ECR_Ignore);
    // 创建激光特效组件
    LaserVFX = CreateDefaultSubobject<UNiagaraComponent>("LaserVFX");
      LaserVFX->
      SetupAttachment(GetRootComponent());
      }
      void ATargetActor_Line::ConfigureTargetSetting(float NewTargetRange, float NewDetectionCylinderRadius,
      float NewTargetingInterval, FGenericTeamId OwnerTeamId, bool bShouldDrawDebug)
      {
      TargetRange = NewTargetRange;
      DetectionCylinderRadius = NewDetectionCylinderRadius;
      TargetingInterval = NewTargetingInterval;
      SetGenericTeamId(OwnerTeamId);
      bDrawDebug = bShouldDrawDebug;
      }
      void ATargetActor_Line::SetGenericTeamId(const FGenericTeamId& NewTeamID)
      {
      TeamId = NewTeamID;
      }
      void ATargetActor_Line::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>
        & OutLifetimeProps) const
        {
        Super::GetLifetimeReplicatedProps(OutLifetimeProps);
        DOREPLIFETIME(ATargetActor_Line, TeamId);
        DOREPLIFETIME(ATargetActor_Line, TargetRange);
        DOREPLIFETIME(ATargetActor_Line, DetectionCylinderRadius);
        DOREPLIFETIME(ATargetActor_Line, AvatarActor);
        }
        void ATargetActor_Line::StartTargeting(UGameplayAbility* Ability)
        {
        Super::StartTargeting(Ability);
        if (!OwningAbility)
        return;
        AvatarActor = OwningAbility->
        GetAvatarActorFromActorInfo();
        // 在服务器上开启定时器周期检测目标
        if (HasAuthority())
        {
        GetWorldTimerManager().SetTimer(PeriodicalTargetingTimerHandle, this, &ATargetActor_Line::DoTargetCheckAndReport, TargetingInterval, true);
        }
        }
        void ATargetActor_Line::Tick(float DeltaTime)
        {
        Super::Tick(DeltaTime);
        // 更新激光
        UpdateTargetTrace();
        }
        void ATargetActor_Line::BeginDestroy()
        {
        // 在去除Actor的时候停止定时器
        if (GetWorld() && PeriodicalTargetingTimerHandle.IsValid())
        {
        GetWorldTimerManager().ClearTimer(PeriodicalTargetingTimerHandle);
        }
        Super::BeginDestroy();
        }
        void ATargetActor_Line::DoTargetCheckAndReport()
        {
        // 服务器中执行
        if (!HasAuthority()) return;
        // 获取重叠的Actor
        TArray<AActor*> OverlappingActorSet;
          TargetEndDetectionSphere->
          GetOverlappingActors(OverlappingActorSet);
          // 筛选有效目标
          TArray<TWeakObjectPtr<AActor>> OverlappingActors;
            for (AActor* Actor : OverlappingActorSet)
            {
            if (ShouldReportActorAsTarget(Actor))
            {
            OverlappingActors.Add(Actor);
            }
            }
            // 构建目标数据
            FGameplayAbilityTargetDataHandle TargetDataHandle;
            FGameplayAbilityTargetData_ActorArray* ActorArray = new FGameplayAbilityTargetData_ActorArray;
            ActorArray->
            SetActors(OverlappingActors);
            TargetDataHandle.Add(ActorArray);
            // 广播目标数据就绪事件
            TargetDataReadyDelegate.Broadcast(TargetDataHandle);
            }
            void ATargetActor_Line::UpdateTargetTrace()
            {
            // 设置激光起始点
            FVector ViewLocation = GetActorLocation();
            FRotator ViewRotation = GetActorRotation();
            if (AvatarActor)
            {
            // 获取摄像机位置和瞄准的旋转
            AvatarActor->
            GetActorEyesViewPoint(ViewLocation, ViewRotation);
            }
            // 计算激光方向和终点
            FVector LookEndPoint = ViewLocation + ViewRotation.Vector() * 100000;
            // 根据角色位置以及相机方向计算出激光方向
            FRotator LookRotation = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), LookEndPoint);
            SetActorRotation(LookRotation);
            // 获取激光终点 : 由激光发射位置,由角色朝向,和激光长度设置终点位置
            FVector SweepEndLocation = GetActorLocation() + LookRotation.Vector() * TargetRange;
            TArray<FHitResult> HitResults;
              // 配置碰撞参数,忽略自身和拥有者
              FCollisionQueryParams QueryParams;
              QueryParams.AddIgnoredActor(AvatarActor);
              QueryParams.AddIgnoredActor(this);
              FCollisionResponseParams CollisionResponseParams(ECR_Overlap);
              // 球形检测,查找路径上的目标
              GetWorld()->
              SweepMultiByChannel(HitResults, GetActorLocation(), SweepEndLocation, FQuat::Identity, ECC_WorldDynamic, FCollisionShape::MakeSphere(DetectionCylinderRadius), QueryParams, CollisionResponseParams);
              FVector LineEndLocation = SweepEndLocation;
              float LineLength = TargetRange;
              // 命中第一个非友方目标则截断激光
              for (FHitResult& HitResult : HitResults)
              {
              if (HitResult.GetActor())
              {
              if (GetTeamAttitudeTowards(*HitResult.GetActor()) != ETeamAttitude::Friendly)
              {
              LineEndLocation = HitResult.ImpactPoint;
              LineLength = FVector::Distance(GetActorLocation() , LineEndLocation);
              break;
              }
              }
              }
              // 更新终点检测球体和激光长度
              TargetEndDetectionSphere->
              SetWorldLocation(LineEndLocation);
              if (LaserVFX)
              {
              LaserVFX->
              SetVariableFloat(LaserFXLengthParamName, LineLength/100.f);
              }
              }
              bool ATargetActor_Line::ShouldReportActorAsTarget(const AActor* ActorToCheck) const
              {
              // 基本无效检查
              if (!ActorToCheck) return false;
              if (ActorToCheck == AvatarActor) return false;
              // 忽略自身
              if (ActorToCheck == this) return false;
              // 忽略自身
              // 团队关系检查(只报告敌对目标)
              if (GetTeamAttitudeTowards(*ActorToCheck) != ETeamAttitude::Hostile)
              return false;
              return true;
              }

在这里插入图片描述

将激光TA添加到激光GA上

到CGameplayAbility中添加一个函数用来应用伤害

// 将伤害应用到TargetDataHandle中的所有目标
void ApplyDamageToTargetDataHandle(const FGameplayAbilityTargetDataHandle& TargetDataHandle, const FGenericDamageEffectDef& Damage, int Level = 1);
void UCGameplayAbility::ApplyDamageToTargetDataHandle(const FGameplayAbilityTargetDataHandle& TargetDataHandle,
const FGenericDamageEffectDef& Damage, int Level)
{
const UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
AActor* AvatarActor = GetAvatarActorFromActorInfo();
// 创建效果上下文, 设置能力 、源对象 和 施加者
FGameplayEffectContextHandle ContextHandle = ASC->
MakeEffectContext();
ContextHandle.SetAbility(this);
ContextHandle.AddSourceObject(AvatarActor);
ContextHandle.AddInstigator(AvatarActor, AvatarActor);
for (const auto& TypePair : Damage.DamageTypeDefinitions)
{
// 创建效果Spec句柄,指定效果类、能力等级和上下文
FGameplayEffectSpecHandle EffectSpecHandle = ASC->
MakeOutgoingSpec(Damage.DamageEffect, Level, ContextHandle);
float TotalModifier = TypePair.Value.BaseDamage.GetValueAtLevel(Level);
for (const auto& Modifier : TypePair.Value.AttributeDamageModifiers)
{
UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(EffectSpecHandle, Modifier.Key, Modifier.Value);
}
UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(EffectSpecHandle, TypePair.Key, TotalModifier);
// 在目标上应用游戏效果规范
K2_ApplyGameplayEffectSpecToTarget(EffectSpecHandle,TargetDataHandle);
}
}

发射激光技能中添加这个TA

// 激光目标Actor类(直线检测)
UPROPERTY(EditDefaultsOnly, Category = "Targeting")
TSubclassOf<
class ATargetActor_Line
> LaserTargetActorClass;
// 目标数据接收回调(处理命中目标)
UFUNCTION()
void TargetReceived(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
void UGA_Laser::ShootLaser(FGameplayEventData Payload)
{
// --- 服务器端逻辑 ---
if (K2_HasAuthority())
{
// 应用持续消耗法力的效果
OnGoingConsumptionEffectHandle = BP_ApplyGameplayEffectToOwner(OnGoingConsumptionEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));
// 注册法力变化回调(检测法力不足)
UAbilitySystemComponent* OwnerAbilitySystemComponent = GetAbilitySystemComponentFromActorInfo();
if (OwnerAbilitySystemComponent)
{
OwnerAbilitySystemComponent->
GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetManaAttribute()).AddUObject(this, &UGA_Laser::ManaUpdated);
}
}
// 创建目标检测任务
UAbilityTask_WaitTargetData* WaitDamageTargetTask = UAbilityTask_WaitTargetData::WaitTargetData(
this,
NAME_None,
EGameplayTargetingConfirmation::CustomMulti, // 持续检测模式
LaserTargetActorClass
);
// 绑定委托
WaitDamageTargetTask->ValidData.AddDynamic(this, &UGA_Laser::TargetReceived);
// 执行任务
WaitDamageTargetTask->
ReadyForActivation();
// 配置并生成目标检测Actor
AGameplayAbilityTargetActor* TargetActor;
WaitDamageTargetTask->
BeginSpawningActor(this, LaserTargetActorClass, TargetActor);
if (ATargetActor_Line* LineTargetActor = Cast<ATargetActor_Line>(TargetActor))
  {
  // 设置检测参数:范围/半径/频率/队伍/调试显示
  LineTargetActor->
  ConfigureTargetSetting(
  TargetRange,
  DetectionCylinderRadius,
  TargetingInterval,
  GetOwnerTeamId(),
  ShouldDrawDebug()
  );
  }
  WaitDamageTargetTask->
  FinishSpawningActor(this, TargetActor);
  // 将Actor附加到骨骼中
  if (ATargetActor_Line* LineTargetActor = Cast<ATargetActor_Line>(TargetActor))
    {
    LineTargetActor->
    AttachToComponent(
    GetOwningComponentFromActorInfo(),
    FAttachmentTransformRules::SnapToTargetNotIncludingScale,
    TargetActorAttachSocketName
    );
    }
    }
    void UGA_Laser::TargetReceived(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
    {
    // --- 服务器端逻辑 ---
    if (K2_HasAuthority())
    {
    // 1. 对目标应用伤害效果
    // BP_ApplyGameplayEffectToTarget(
    // TargetDataHandle, 
    // HitDamageEffect, 
    // GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo)
    // );
    // K2_ApplyGameplayEffectSpecToTarget(,TargetDataHandle);
    ApplyDamageToTargetDataHandle(TargetDataHandle, HitDamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));
    }
    // 2. 对目标施加击退力
    PushTargets(
    TargetDataHandle,
    GetAvatarActorFromActorInfo()->
    GetActorForwardVector() * HitDamageEffect.PushVelocity // 基于发射者方向的击退
    );
    }

编译后在蓝图中把这个TA塞进来
在这里插入图片描述
在这里插入图片描述
再把瞄准偏移移动一下
在这里插入图片描述
在这里插入图片描述

修改一点杂事

把风暴核心的碰撞修改一下
在这里插入图片描述
顺便把风暴核心的胶囊体弄大点,可能太小不好爬楼梯
在这里插入图片描述

把地图的两个目标的碰撞类型改为Pawn
在这里插入图片描述

补充初始化金币的GE

在这里插入图片描述
到英雄固定资产中添加
在这里插入图片描述
在这里插入图片描述

posted @ 2025-08-16 21:49  yjbjingcha  阅读(1)  评论(0)    收藏  举报