UE5多人MOBA+GAS 45、制作冲刺技能

文章目录


添加技能需要的东西

添加本地播放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吧

随后放进英雄资产里等他自己初始化吧

然后就可以开冲了

相关推荐
Jerry说前后端4 小时前
Android 数据可视化开发:从技术选型到性能优化
android·信息可视化·性能优化
Meteors.5 小时前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton6 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw10 小时前
安卓图片性能优化技巧
android
风往哪边走10 小时前
自定义底部筛选弹框
android
Yyyy48211 小时前
MyCAT基础概念
android
Android轮子哥11 小时前
尝试解决 Android 适配最后一公里
android
雨白12 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
风往哪边走13 小时前
自定义仿日历组件弹框
android