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

文章目录

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

创建激光GA

添加新技能激光技能GA_Laser(不用管下图的打错,我也是后知后觉)

添加技能标签

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

初步实现激光技能

cpp 复制代码
#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);
};
cpp 复制代码
#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

用于设置激光以及检测敌人发送需要造成伤害的敌人

添加一下奶瓜粒子特效

cpp 复制代码
#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;
};
cpp 复制代码
#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中添加一个函数用来应用伤害

cpp 复制代码
	// 将伤害应用到TargetDataHandle中的所有目标
	void ApplyDamageToTargetDataHandle(const FGameplayAbilityTargetDataHandle& TargetDataHandle, const FGenericDamageEffectDef& Damage, int Level = 1);
cpp 复制代码
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

cpp 复制代码
	// 激光目标Actor类(直线检测)
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	TSubclassOf<class ATargetActor_Line> LaserTargetActorClass;

	// 目标数据接收回调(处理命中目标)
	UFUNCTION()
	void TargetReceived(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
cpp 复制代码
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

到英雄固定资产中添加

相关推荐
幻雨様3 小时前
UE5多人MOBA+GAS 48、制作闪现技能
ue5
ue星空3 天前
UE5配置MRQ编解码器输出MP4视频
ue5·音视频
吴梓穆9 天前
UE5 图片9宫格切割
ue5
Kingsdesigner10 天前
游戏开发流程革命:我用Substance插件,在UE5内实现材质的实时“创世纪”
游戏·adobe·ue5·游戏引擎·游戏开发·设计师·substance 3d
幻雨様12 天前
UE5多人MOBA+GAS 37、库存系统(四)
ue5
DoomGT12 天前
Physics Simulation - UE中Projectile相关事项
ue5·游戏引擎·虚幻·虚幻引擎·unreal engine
右弦GISer14 天前
【UE5医学影像可视化】读取本地Dicom生成VolumeTexture,实现2D显示和自动翻页
ue5·dicom·医学图像
小梦白14 天前
RPG增容3:尝试使用MVC结构搭建玩家升级UI(一)
游戏·ui·ue5·mvc
AgilityBaby14 天前
解决「CPU Virtualization Technology 未开启或被占用」弹窗问题
ue5·游戏引擎·无畏契约·cpu 虚拟化技术