UE5多人MOBA+GAS 23、制作一个地面轰炸的技能

发布于:2025-07-19 ⋅ 阅读:(17) ⋅ 点赞:(0)


创建一个新的技能的流程

创建技能的流程
在这里插入图片描述
因为这个技能有两个阶段,一个是瞄准阶段,另一个是射击阶段,技能一开始触发的时候是属于瞄准阶段,不会直接提交消耗以及进入冷却,K2_CommitAbility()的调用将会在发射技能伤害的时候触发提交。

#pragma once

#include "CoreMinimal.h"
#include "GAS/Core/CGameplayAbility.h"
#include "GA_GroundBlast.generated.h"

/**
 * 
 */
UCLASS()
class CRUNCH_API UGA_GroundBlast : public UCGameplayAbility
{
	GENERATED_BODY()

public:
	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
private:
	// 瞄准动画
	UPROPERTY(EditDefaultsOnly, Category = "Animation")
	TObjectPtr<UAnimMontage> TargetingMontage;

	// 释放动画
	UPROPERTY(EditDefaultsOnly, Category = "Animation")
	TObjectPtr<UAnimMontage> CastMontage;
};
#include "GAS/Abilities/GA_GroundBlast.h"

#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"

void UGA_GroundBlast::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
                                      const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
                                      const FGameplayEventData* TriggerEventData)
{
	if (!HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)) return;

	UAbilityTask_PlayMontageAndWait* PlayGroundBlasAnimTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, TargetingMontage);
	PlayGroundBlasAnimTask->OnBlendOut.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnCancelled.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnCompleted.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnInterrupted.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->ReadyForActivation();
}

AM_GroundBlast_Targeting
在这里插入图片描述
AM_GroundBlast_Casting
在这里插入图片描述
继承GA创建蓝图并添加蒙太奇进去
在这里插入图片描述
将技能放入DT表中(做的新技能都要放进去让UI显示出来)
在这里插入图片描述
创建对应的按键输入
在这里插入图片描述

在这里插入图片描述
将技能添加到角色中
在这里插入图片描述

添加瞄准动画(身体随相机朝向旋转)

添加新的状态标签

	// 瞄准
	CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Stats_Aim)
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Stats_Aim, "Stats.Aim", "瞄准")

给技能添加构造函数,在构造函数中添加激活时给角色添加瞄准标签

UGA_GroundBlast::UGA_GroundBlast()
{
	// 技能激活时给角色添加瞄准标签
	ActivationOwnedTags.AddTag(TGameplayTags::Stats_Aim);
	// 阻断基础攻击技能
	BlockAbilitiesWithTag.AddTag(TGameplayTags::Ability_BasicAttack);
}

角色基类中响应瞄准Tag

	// 瞄准标签变化回调
	void AimTagUpdated(const FGameplayTag Tag, int32 NewCount);

	// 设置是否处于瞄准状态
	void SetIsAiming(bool bIsAiming);
	// 瞄准状态变化时回调
	virtual void OnAimStateChanged(bool bIsAiming);
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);
	}
}

void ACCharacter::AimTagUpdated(const FGameplayTag Tag, int32 NewCount)
{
	SetIsAiming(NewCount != 0);
}

void ACCharacter::SetIsAiming(bool bIsAiming)
{
	bUseControllerRotationYaw = bIsAiming;
	GetCharacterMovement()->bOrientRotationToMovement = !bIsAiming;
	OnAimStateChanged(bIsAiming);
}

void ACCharacter::OnAimStateChanged(bool bIsAiming)
{
	// 子类中重写
}

动画类中响应Tag

public:
	// 获取前进方向速度
	UFUNCTION(BlueprintCallable, meta=(BlueprintThreadSafe))
	FORCEINLINE float GetFwdSpeed() const { return FwdSpeed; }

	// 获取横向速度
	UFUNCTION(BlueprintCallable, meta=(BlueprintThreadSafe))
	FORCEINLINE float GetRightSpeed() const { return RightSpeed; }
	
	// 是否需要执行全身动画
	UFUNCTION(BlueprintCallable, meta=(BlueprintThreadSafe))
	bool ShouldDoFullBody() const;
private:
	// 前进方向速度
	float FwdSpeed;
	// 横向速度
	float RightSpeed;
	
	// 是否瞄准
	bool bIsAiming;

在初始化阶段绑定监听,并每帧更新FwdSpeedRightSpeed的值

void UCAnimInstance::NativeInitializeAnimation()
{
	// 获取Owner转换为角色
	OwnerCharacter = Cast<ACharacter>(TryGetPawnOwner());
	if (OwnerCharacter)
	{
		// 获取Owner的移动组件
		OwnerMovementComponent = OwnerCharacter->GetCharacterMovement();
	}

	UAbilitySystemComponent* OwnerASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TryGetPawnOwner());
	if (OwnerASC)
	{
		// 绑定标签监听
		OwnerASC->RegisterGameplayTagEvent(TGameplayTags::Stats_Aim).AddUObject(this, &UCAnimInstance::OwnerAimTagChanged);
	}
}

void UCAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	if (OwnerCharacter)
	{
		FVector Velocity = OwnerCharacter->GetVelocity();
		Speed = Velocity.Length();

		// 获取当前身体旋转角度与上一帧的差值,用于计算旋转变化率
		FRotator BodyRotator = OwnerCharacter->GetActorRotation();
		FRotator BodyRotatorDelta = UKismetMathLibrary::NormalizedDeltaRotator(BodyRotator, BodyPrevRotator);
		BodyPrevRotator = BodyRotator;

		// 计算 yaw 轴旋转速度(每秒旋转角度)
		YawSpeed = BodyRotatorDelta.Yaw / DeltaSeconds;

		// 如果偏航角速度为0,则使用特定的速度进行平滑插值
		float YawLerpSpeed = YawSpeedSmoothLerpSpeed;
		if (YawSpeed == 0)
		{
			YawLerpSpeed = YawSpeedLerpToZeroSpeed;
		}

		// 使用线性插值(FInterpTo)对 yaw 速度进行平滑处理,提高动画过渡质量
		SmoothedYawSpeed = UKismetMathLibrary::FInterpTo(SmoothedYawSpeed, YawSpeed, DeltaSeconds, YawLerpSpeed);

		// 获取拥有者角色的基础瞄准旋转
		FRotator ControlRotator = OwnerCharacter->GetBaseAimRotation();
		// 计算当前帧的旋转变化量,并将其归一化,以便在多个帧之间平滑旋转
		LookRotatorOffset = UKismetMathLibrary::NormalizedDeltaRotator(ControlRotator, BodyPrevRotator);

		// 获取当前帧的 fwd/right 速度
		FwdSpeed = Velocity.Dot(ControlRotator.Vector());
		RightSpeed = -Velocity.Dot(ControlRotator.Vector().Cross(FVector::UpVector));
	}

	if (OwnerMovementComponent)
	{
		bIsJumping = OwnerMovementComponent->IsFalling();
	}
}

bool UCAnimInstance::ShouldDoFullBody() const
{
	// 速度为0 并且 不处于瞄准状态
	return (GetSpeed() <= 0) && !(GetIsAiming());
}

void UCAnimInstance::OwnerAimTagChanged(const FGameplayTag Tag, int32 NewCount)
{
	bIsAiming = NewCount != 0;
}

创建一个混合空间
在这里插入图片描述
到动画蓝图中
在这里插入图片描述
在这里插入图片描述

设置瞄准状态下摄像机的位置

#pragma region Gameplay Ability
private:
	virtual void OnAimStateChanged(bool bIsAiming) override;
#pragma endregion

#pragma region 摄像机视角
private:
	// 瞄准时相机的本地偏移量
	UPROPERTY(EditDefaultsOnly, Category = view)
	FVector CameraAimLocalOffset;

	// 相机插值速度
	UPROPERTY(EditDefaultsOnly, Category = view)
	float CameraLerpSpeed = 20.f;
    
	// 相机插值定时器句柄
	FTimerHandle CameraLerpTimerHandle;

	// 插值相机到目标本地偏移位置
	void LerpCameraToLocalOffsetLocation(const FVector& Goal);

	// 相机插值Tick回调
	void TickCameraLocalOffsetLerp(FVector Goal);
#pragma endregion
void ACPlayerCharacter::OnAimStateChanged(bool bIsAiming)
{
	// 瞄准状态变化时,插值相机到瞄准或默认位置
	LerpCameraToLocalOffsetLocation(bIsAiming ? CameraAimLocalOffset : FVector{0.f});
}

void ACPlayerCharacter::LerpCameraToLocalOffsetLocation(const FVector& Goal)
{
	GetWorldTimerManager().ClearTimer(CameraLerpTimerHandle);

	// 下一帧执行,采取递归的方式
	CameraLerpTimerHandle = GetWorldTimerManager().SetTimerForNextTick(
		FTimerDelegate::CreateUObject(
			this,
			&ACPlayerCharacter::TickCameraLocalOffsetLerp,
			Goal));
}

void ACPlayerCharacter::TickCameraLocalOffsetLerp(FVector Goal)
{
	// 获取相机的位置
	FVector CurrentLocalOffset = ViewCamera->GetRelativeLocation();

	// 如果相机位置与目标位置的距离小于1,则直接设置相机位置
	if (FVector::Dist(CurrentLocalOffset, Goal) < 1.f)
	{
		ViewCamera->SetRelativeLocation(Goal);
		return;
	}
	// 计算插值系数,保证插值平滑且不超过1
	float LerpAlpha = FMath::Clamp(GetWorld()->GetDeltaSeconds() * CameraLerpSpeed, 0.f, 1.f);
	// 执行线性插值计算新位置
	FVector NewLocalOffset = FMath::Lerp(CurrentLocalOffset, Goal, LerpAlpha);
	// 设置相机位置
	ViewCamera->SetRelativeLocation(NewLocalOffset);
	// 继续下一帧插值,直到到达目标位置
	CameraLerpTimerHandle = GetWorldTimerManager().SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &ACPlayerCharacter::TickCameraLocalOffsetLerp, Goal));
}

去蓝图中调整进去瞄准状态相机的位置
在这里插入图片描述

添加新的检测通道

在这里插入图片描述
这里可以看见创建的检测通道,可以直接调用划红线的名称
在这里插入图片描述
但还是起个define更能清楚含义
在这里插入图片描述

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"

#define ECC_TARGET ECC_GameTraceChannel1
#define ECC_SPRING_ARM ECC_GameTraceChannel2
ACCharacter::ACCharacter()
{
	PrimaryActorTick.bCanEverTick = true;
	// 禁用网格的碰撞功能
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	// 忽略弹簧臂的碰撞
	GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_SPRING_ARM, ECR_Ignore);
	// 忽略目标的碰撞
	GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_TARGET, ECR_Ignore);
}

设置弹簧臂的碰撞通道

ACPlayerCharacter::ACPlayerCharacter()
{
	// 创建并设置摄像机弹簧臂组件
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(GetRootComponent()); // 将弹簧臂组件附加到角色的根组件
	CameraBoom->bUsePawnControlRotation = true; // 使用Pawn控制旋转
	CameraBoom->ProbeChannel = ECC_SPRING_ARM;
}

瞄准目标用的Actor

创建一个继承GameplayAbilityTargetActorTargetActor_GroundPick

在这里插入图片描述
在这里插入图片描述

在本地客户端中获取摄像机的朝向以及位置,发出射线来确定目标的位置

// 幻雨喜欢小猫咪

#pragma once

#include "CoreMinimal.h"
#include "Abilities/GameplayAbilityTargetActor.h"
#include "TargetActor_GroundPick.generated.h"

/**
 * 
 */
UCLASS()
class ATargetActor_GroundPick : public AGameplayAbilityTargetActor
{
	GENERATED_BODY()
public:
	ATargetActor_GroundPick();
private:
	virtual void Tick(float DeltaTime) override;

	// 获取当前目标点(玩家视角射线检测地面)
	FVector GetTargetPoint() const;

	// 目标区域半径
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetAreaRadius = 300.f;

	// 目标检测最大距离
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetTraceRange = 2000.f;

	// 是否绘制调试信息
	bool bShouldDrawDebug = false;
};
// 幻雨喜欢小猫咪


#include "GAS/TA/TargetActor_GroundPick.h"

#include "Crunch/Crunch.h"

ATargetActor_GroundPick::ATargetActor_GroundPick()
{
	PrimaryActorTick.bCanEverTick = true;
}

void ATargetActor_GroundPick::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	// 检测是否是本地玩家控制(只在本地客户端中显示)
	if (PrimaryPC && PrimaryPC->IsLocalPlayerController())
	{
		// 设置目标点位置
		SetActorLocation(GetTargetPoint());
	}
}

FVector ATargetActor_GroundPick::GetTargetPoint() const
{
	if (!PrimaryPC || !PrimaryPC->IsLocalPlayerController())
		return GetActorLocation();

	FHitResult TraceResult;

	FVector ViewLoc;
	FRotator ViewRot;
	
	// 获取视角位置和朝向
	PrimaryPC->GetPlayerViewPoint(ViewLoc, ViewRot);

	// 获取射线终点位置
	FVector TraceEnd = ViewLoc + ViewRot.Vector() * TargetTraceRange;

	// 射线检测
	GetWorld()->LineTraceSingleByChannel(
		TraceResult,
		ViewLoc,
		TraceEnd,
		ECC_TARGET);

	// 如果没有命中,向下做一次射线检测,把结果点放地上
	if (!TraceResult.bBlockingHit)
	{
		GetWorld()->LineTraceSingleByChannel(TraceResult, TraceEnd, TraceEnd + FVector::DownVector * TNumericLimits<float>::Max(), ECC_TARGET);
	}

	// 如果依然没有命中,返回当前位置
	if (!TraceResult.bBlockingHit)
	{
		return GetActorLocation();
	}

	// 绘制调试球体
	if (bShouldDrawDebug)
	{
		DrawDebugSphere(GetWorld(), TraceResult.ImpactPoint, TargetAreaRadius, 32, FColor::Red);
	}

	return TraceResult.ImpactPoint;
}

GA_GroundBlast技能添加该能力目标Acotor,在技能触发的时候生成这个actor

private:
	// 地面选点目标Actor类
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	TSubclassOf<ATargetActor_GroundPick> TargetActorClass;

	
	// 瞄准动画
	UPROPERTY(EditDefaultsOnly, Category = "Animation")
	TObjectPtr<UAnimMontage> TargetingMontage;

	// 释放动画
	UPROPERTY(EditDefaultsOnly, Category = "Animation")
	TObjectPtr<UAnimMontage> CastMontage;
	
	// 目标确认回调
	UFUNCTION()
	void TargetConfirmed(const FGameplayAbilityTargetDataHandle& TargetDataHandle);

	// 目标取消回调
	UFUNCTION()
	void TargetCanceled(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
void UGA_GroundBlast::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
	const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
	const FGameplayEventData* TriggerEventData)
{
	if (!HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)) return;

	UAbilityTask_PlayMontageAndWait* PlayGroundBlasAnimTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, TargetingMontage);
	PlayGroundBlasAnimTask->OnBlendOut.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnCancelled.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnCompleted.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnInterrupted.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->ReadyForActivation();

	// 等待瞄准敌人
	UAbilityTask_WaitTargetData* WaitTargetDataTask = UAbilityTask_WaitTargetData::WaitTargetData(this, NAME_None, EGameplayTargetingConfirmation::UserConfirmed, TargetActorClass);
	// 确认技能
	WaitTargetDataTask->ValidData.AddDynamic(this, &UGA_GroundBlast::TargetConfirmed);
	// 技能取消
	WaitTargetDataTask->Cancelled.AddDynamic(this, &UGA_GroundBlast::TargetCanceled);
	WaitTargetDataTask->ReadyForActivation();

	// 生成目标Actor
	AGameplayAbilityTargetActor* TargetActor;
	WaitTargetDataTask->BeginSpawningActor(this, TargetActorClass, TargetActor);
	WaitTargetDataTask->FinishSpawningActor(this, TargetActor);
}

void UGA_GroundBlast::TargetConfirmed(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	UE_LOG(LogTemp, Warning, TEXT("技能发射"));
	K2_EndAbility();
}

void UGA_GroundBlast::TargetCanceled(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	UE_LOG(LogTemp, Warning, TEXT("技能取消"));
	K2_EndAbility();
}

在这里插入图片描述
创建后 ,没什么需要做的

到技能中设置一下
在这里插入图片描述

到能力组件CAbilitySystemComponent中配置一下确认的输入ID和取消的输入ID

UCAbilitySystemComponent::UCAbilitySystemComponent()
{
	GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetHealthAttribute()).AddUObject(this, &UCAbilitySystemComponent::HealthUpdated);
	GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetManaAttribute()).AddUObject(this, &UCAbilitySystemComponent::ManaUpdated);

	GenericConfirmInputID = static_cast<int32>(ECAbilityInputID::Confirm);
	GenericCancelInputID = static_cast<int32>(ECAbilityInputID::Cancel);
}

在这里插入图片描述
在这里插入图片描述
左键发射,右键取消
在这里插入图片描述

让TA_GroundPick可以获取目标

在玩家控制器发送确认信息的时候会调用ConfirmTargetingAndContinue函数,在该函数中,生成一个球体检测该范围内的所有pawn,把数据通过广播传给GA,在GA中处理处理伤害的传递以及冲击等事情。

public:
	// 设置目标区域半径
	void SetTargetAreaRadius(float NewRadius);
	
	// 设置目标检测最大距离
	FORCEINLINE void SetTargetTraceRange(float NewRange) { TargetTraceRange = NewRange; }

	// 确认目标
	virtual void ConfirmTargetingAndContinue() override;

	// 设置目标筛选选项
	void SetTargetOptions(bool bTargetFriendly, bool bTargetEnenmy = true);
	
	// 设置是否绘制调试信息
	FORCEINLINE void SetShouldDrawDebug(bool bDrawDebug) { bShouldDrawDebug = bDrawDebug; }

private:
	// 是否可选敌方
	bool bShouldTargetEnemy = true;

	// 是否可选友方
	bool bShouldTargetFriendly = false;
// 幻雨喜欢小猫咪


#include "GAS/TA/TargetActor_GroundPick.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "GenericTeamAgentInterface.h"
#include "Abilities/GameplayAbility.h"
#include "Crunch/Crunch.h"
#include "Engine/OverlapResult.h"

ATargetActor_GroundPick::ATargetActor_GroundPick()
{
	PrimaryActorTick.bCanEverTick = true;
}

void ATargetActor_GroundPick::SetTargetAreaRadius(float NewRadius)
{
	TargetAreaRadius = NewRadius;
}

void ATargetActor_GroundPick::ConfirmTargetingAndContinue()
{
	// 检测目标
	TArray<FOverlapResult> OverlapResults;
	
	// 设置碰撞查询参数,仅检测Pawn
	FCollisionObjectQueryParams ObjectQueryParams;
	ObjectQueryParams.AddObjectTypesToQuery(ECC_Pawn);

	// 创建球形碰撞球体,设置半径和位置
	FCollisionShape CollisionShape;
	CollisionShape.SetSphere(TargetAreaRadius);

	GetWorld()->OverlapMultiByObjectType(
		OverlapResults,
		GetActorLocation(),
		FQuat::Identity,
		ObjectQueryParams,
		CollisionShape);

	TSet<AActor*> TargetActors;

	// 获取能力使用者的团队接口
	IGenericTeamAgentInterface* OwnerTeamInterface = nullptr;
	if (OwningAbility)
	{
		OwnerTeamInterface = Cast<IGenericTeamAgentInterface>(OwningAbility->GetAvatarActorFromActorInfo());
	}

	for (const FOverlapResult& OverlapResult : OverlapResults)
	{
		// 检测到友军,友军为false,不打友军跳过
		if (OwnerTeamInterface && OwnerTeamInterface->GetTeamAttitudeTowards(*OverlapResult.GetActor()) == ETeamAttitude::Friendly && !bShouldTargetFriendly)
			continue;
		// 检测到敌军,敌军为false,不打敌军跳过
		if (OwnerTeamInterface && OwnerTeamInterface->GetTeamAttitudeTowards(*OverlapResult.GetActor()) == ETeamAttitude::Hostile && !bShouldTargetEnemy)
			continue;

		// 添加目标
		TargetActors.Add(OverlapResult.GetActor());
	}

	// 创建目标数据
	FGameplayAbilityTargetDataHandle TargetData = UAbilitySystemBlueprintLibrary::AbilityTargetDataFromActorArray(TargetActors.Array(), false);

	// 触发目标数据已就绪委托
	TargetDataReadyDelegate.Broadcast(TargetData);
}

void ATargetActor_GroundPick::SetTargetOptions(bool bTargetFriendly, bool bTargetEnemy)
{
	bShouldTargetFriendly = bTargetFriendly;
	bShouldTargetEnemy = bTargetEnemy;
}

这里的委托
在这里插入图片描述
传到这里
在这里插入图片描述

private:
	// 技能目标区域半径
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetAreaRadius = 300.f;

	UPROPERTY(EditDefaultsOnly, Category = "Damage")
	FGenericDamageEffectDef DamageEffectDef;
	// 目标检测最大距离
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetTraceRange = 2000.f;
// 配置一下参数
void UGA_GroundBlast::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
	const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
	const FGameplayEventData* TriggerEventData)
{
	// 生成目标Actor
	AGameplayAbilityTargetActor* TargetActor;
	WaitTargetDataTask->BeginSpawningActor(this, TargetActorClass, TargetActor);

	// 设置目标Actor参数
	ATargetActor_GroundPick* GroundPickActor = Cast<ATargetActor_GroundPick>(TargetActor);
	if (GroundPickActor)
	{
		GroundPickActor->SetShouldDrawDebug(ShouldDrawDebug());
		GroundPickActor->SetTargetAreaRadius(TargetAreaRadius);
		GroundPickActor->SetTargetTraceRange(TargetTraceRange);
	}
	WaitTargetDataTask->FinishSpawningActor(this, TargetActor);
}
void UGA_GroundBlast::TargetConfirmed(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	// 仅在服务器上执行伤害和击退
	if (K2_HasAuthority())
	{
		// 对目标应用伤害效果
		BP_ApplyGameplayEffectToTarget(TargetDataHandle, DamageEffectDef.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));
	}
	UE_LOG(LogTemp, Warning, TEXT("技能发射"));
	K2_EndAbility();
}

把升龙的伤害GE复制过来用一下
在这里插入图片描述
在这里插入图片描述

实现击飞多个目标在代码中调用GC生成特效

做击飞

CGameplayAbility中实现方便每个子类调用,传进来的循环遍历一下调用推目标的技能就好了

    // 推动多个目标
    void PushTargets(const TArray<AActor*>& Targets, const FVector& PushVel);
    // 推动TargetData中的所有目标
    void PushTargets(const FGameplayAbilityTargetDataHandle& TargetDataHandle, const FVector& PushVel);
void UCGameplayAbility::PushTargets(const TArray<AActor*>& Targets, const FVector& PushVel)
{
	for(AActor* Target : Targets)
	{
		PushTarget(Target, PushVel);
	}
}

void UCGameplayAbility::PushTargets(const FGameplayAbilityTargetDataHandle& TargetDataHandle, const FVector& PushVel)
{
	TArray<AActor*> Targets = UAbilitySystemBlueprintLibrary::GetAllActorsFromTargetData(TargetDataHandle);
	PushTargets(Targets, PushVel);
}

然后再伤害传递的地方调用一下

void UGA_GroundBlast::TargetConfirmed(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	// 仅在服务器上执行伤害和击退
	if (K2_HasAuthority())
	{
		// 对目标应用伤害效果
		BP_ApplyGameplayEffectToTarget(TargetDataHandle, DamageEffectDef.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));
		// 对目标施加推力
		PushTargets(TargetDataHandle, DamageEffectDef.PushVelocity);
	}
	UE_LOG(LogTemp, Warning, TEXT("技能发射"));
	K2_EndAbility();
}

添加GC、蓝耗和冷却的提交

添加cdtag

	// 大地爆炸cd
	CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_GroundBlast_Cooldown)
	UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_GroundBlast_Cooldown, "Ability.GroundBlast.Cooldown", "大地爆炸技能冷却")

添加GC显示特效,需要在TargetActor处将Actor的位置发送给能力

void ATargetActor_GroundPick::ConfirmTargetingAndContinue()
{
	// 创建目标数据
	FGameplayAbilityTargetDataHandle TargetData = UAbilitySystemBlueprintLibrary::AbilityTargetDataFromActorArray(TargetActors.Array(), false);

	// 添加命中点信息(用于特效等)
	FGameplayAbilityTargetData_SingleTargetHit* HitLocation = new FGameplayAbilityTargetData_SingleTargetHit;
	HitLocation->HitResult.ImpactPoint = GetActorLocation();
	TargetData.Add(HitLocation);
	// 触发目标数据已就绪委托
	TargetDataReadyDelegate.Broadcast(TargetData);
}

在技能中创建传GC的Tag,在发生伤害后面生成特效,再生成一下摄像机震动的GC,顺便播放一下技能的动画
CAbilitySystemStatics中写一个静态函数获取震动相机的标签

	// 获取摄像机震动游戏事件标签
	static FGameplayTag GetCameraShakeGameplayCueTag();
FGameplayTag UCAbilitySystemStatics::GetCameraShakeGameplayCueTag()
{
	return FGameplayTag::RequestGameplayTag("GameplayCue.CameraShake");
}

回到技能中添加特效并执行特效,顺便把冷却和CD一起提交了

private:
	// 冲击特效Cue标签
	UPROPERTY(EditDefaultsOnly, Category = "Cue")
	FGameplayTag BlastGameplayCueTag;
void UGA_GroundBlast::TargetConfirmed(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	if (!K2_CommitAbility())
	{
		K2_EndAbility();
		return;
	}
	// 仅在服务器上执行伤害和击退
	if (K2_HasAuthority())
	{
		// 对目标应用伤害效果
		BP_ApplyGameplayEffectToTarget(TargetDataHandle, DamageEffectDef.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));
		// 对目标施加推力
		PushTargets(TargetDataHandle, DamageEffectDef.PushVelocity);
	}

	FGameplayCueParameters BlastingGameplayCueParameters;
	// 设置特效的位置
	BlastingGameplayCueParameters.Location = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 1).ImpactPoint;
	// 设置特效的大小
	BlastingGameplayCueParameters.RawMagnitude = TargetAreaRadius;

	// 播放冲击特效和摄像机震动
	GetAbilitySystemComponentFromActorInfo()->ExecuteGameplayCue(BlastGameplayCueTag, BlastingGameplayCueParameters);
	GetAbilitySystemComponentFromActorInfo()->ExecuteGameplayCue(UCAbilitySystemStatics::GetCameraShakeGameplayCueTag(), BlastingGameplayCueParameters);

	// 播放释放动画
	UAnimInstance* OwnerAnimInst = GetOwnerAnimInstance();
	if (OwnerAnimInst)
	{
		OwnerAnimInst->Montage_Play(CastMontage);
	}
	UE_LOG(LogTemp, Warning, TEXT("技能发射"));
	K2_EndAbility();
}

添加GC
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
创建两个GE,代表CD和蓝耗
在这里插入图片描述
在这里插入图片描述
CD的GE要记得传一下对应的CDTag
在这里插入图片描述
在这里插入图片描述

完善TargetActor_GroundPick的外观

private:
	// 贴花(用于显示技能范围)
	UPROPERTY(VisibleDefaultsOnly, Category = "Visual")
	TObjectPtr<UDecalComponent> DecalComp;
ATargetActor_GroundPick::ATargetActor_GroundPick()
{
	PrimaryActorTick.bCanEverTick = true;
	// 创建根组件
	SetRootComponent(CreateDefaultSubobject<USceneComponent>("Root Comp"));

	// 创建贴花组件用于显示范围
	DecalComp = CreateDefaultSubobject<UDecalComponent>("Decal Comp");
	DecalComp->SetupAttachment(GetRootComponent());
}

void ATargetActor_GroundPick::SetTargetAreaRadius(float NewRadius)
{
	TargetAreaRadius = NewRadius;
	// 同步贴花的显示大小
	DecalComp->DecalSize = FVector{NewRadius};
}

创建一个材质,改成贴花还有半透明
在这里插入图片描述

if(gradient == 0)
{
 return 0;
}
if(gradient < thickness)
{
 return 1;
}
return 0;

在这里插入图片描述

float2 coord = (float2(uvcoord) - float2(0.5, 0.5)) * 2;

if(abs(coord.x) < thickness || abs(coord.y) < thickness)
{
 return 0;
}

return 1;

在这里插入图片描述
回到TA_GroundPick让红色的箭头朝上,把刚做好的贴花材质弄进去
在这里插入图片描述
在这里插入图片描述

完整代码

TargetActor_GroundPick的完整代码

// 幻雨喜欢小猫咪

#pragma once

#include "CoreMinimal.h"
#include "Abilities/GameplayAbilityTargetActor.h"
#include "Components/DecalComponent.h"
#include "TargetActor_GroundPick.generated.h"

/**
 * 
 */
UCLASS()
class ATargetActor_GroundPick : public AGameplayAbilityTargetActor
{
	GENERATED_BODY()
public:
	ATargetActor_GroundPick();

	// 设置目标区域半径
	void SetTargetAreaRadius(float NewRadius);
	
	// 设置目标检测最大距离
	FORCEINLINE void SetTargetTraceRange(float NewRange) { TargetTraceRange = NewRange; }

	// 确认目标
	virtual void ConfirmTargetingAndContinue() override;

	// 设置目标筛选选项
	void SetTargetOptions(bool bTargetFriendly, bool bTargetEnemy = true);

	// 设置是否绘制调试信息
	FORCEINLINE void SetShouldDrawDebug(bool bDrawDebug) { bShouldDrawDebug = bDrawDebug; }
private:
	// 贴花(用于显示技能范围)
	UPROPERTY(VisibleDefaultsOnly, Category = "Visual")
	TObjectPtr<UDecalComponent> DecalComp;
	
	// 是否可选敌方
	bool bShouldTargetEnemy = true;

	// 是否可选友方
	bool bShouldTargetFriendly = false;
	
	virtual void Tick(float DeltaTime) override;

	// 获取当前目标点(玩家视角射线检测地面)
	FVector GetTargetPoint() const;

	// 目标区域半径
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetAreaRadius = 300.f;

	// 目标检测最大距离
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetTraceRange = 2000.f;

	// 是否绘制调试信息
	bool bShouldDrawDebug = true;
};
// 幻雨喜欢小猫咪


#include "GAS/TA/TargetActor_GroundPick.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "GenericTeamAgentInterface.h"
#include "Abilities/GameplayAbility.h"
#include "Crunch/Crunch.h"
#include "Engine/OverlapResult.h"

ATargetActor_GroundPick::ATargetActor_GroundPick()
{
	PrimaryActorTick.bCanEverTick = true;
	// 创建根组件
	SetRootComponent(CreateDefaultSubobject<USceneComponent>("Root Comp"));

	// 创建贴花组件用于显示范围
	DecalComp = CreateDefaultSubobject<UDecalComponent>("Decal Comp");
	DecalComp->SetupAttachment(GetRootComponent());
}

void ATargetActor_GroundPick::SetTargetAreaRadius(float NewRadius)
{
	TargetAreaRadius = NewRadius;
	// 同步贴花的显示大小
	DecalComp->DecalSize = FVector{NewRadius};
}

void ATargetActor_GroundPick::ConfirmTargetingAndContinue()
{
	// 检测目标
	TArray<FOverlapResult> OverlapResults;
	
	// 设置碰撞查询参数,仅检测Pawn
	FCollisionObjectQueryParams ObjectQueryParams;
	ObjectQueryParams.AddObjectTypesToQuery(ECC_Pawn);

	// 创建球形碰撞球体,设置半径和位置
	FCollisionShape CollisionShape;
	CollisionShape.SetSphere(TargetAreaRadius);

	// 检测碰撞
	GetWorld()->OverlapMultiByObjectType(
		OverlapResults,
		GetActorLocation(),
		FQuat::Identity,
		ObjectQueryParams,
		CollisionShape
		);

	TSet<AActor*> TargetActors;

	// 获取能力使用者的团队接口
	IGenericTeamAgentInterface* OwnerTeamInterface = nullptr;
	if (OwningAbility)
	{
		OwnerTeamInterface = Cast<IGenericTeamAgentInterface>(OwningAbility->GetAvatarActorFromActorInfo());
	}

	for (const FOverlapResult& OverlapResult : OverlapResults)
	{
		// 检测到友军,友军为false,不打友军跳过
		if (OwnerTeamInterface && OwnerTeamInterface->GetTeamAttitudeTowards(*OverlapResult.GetActor()) == ETeamAttitude::Friendly && !bShouldTargetFriendly)
			continue;
		// 检测到敌军,敌军为false,不打敌军跳过
		if (OwnerTeamInterface && OwnerTeamInterface->GetTeamAttitudeTowards(*OverlapResult.GetActor()) == ETeamAttitude::Hostile && !bShouldTargetEnemy)
			continue;

		// 添加目标
		TargetActors.Add(OverlapResult.GetActor());
	}

	// 创建目标数据
	FGameplayAbilityTargetDataHandle TargetData = UAbilitySystemBlueprintLibrary::AbilityTargetDataFromActorArray(TargetActors.Array(), false);

	// 添加命中点信息(用于特效等)
	FGameplayAbilityTargetData_SingleTargetHit* HitLocation = new FGameplayAbilityTargetData_SingleTargetHit;
	HitLocation->HitResult.ImpactPoint = GetActorLocation();
	TargetData.Add(HitLocation);
	// 触发目标数据已就绪委托
	TargetDataReadyDelegate.Broadcast(TargetData);
}

void ATargetActor_GroundPick::SetTargetOptions(bool bTargetFriendly, bool bTargetEnemy)
{
	bShouldTargetFriendly = bTargetFriendly;
	bShouldTargetEnemy = bTargetEnemy;
}

void ATargetActor_GroundPick::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	// 检测是否是本地玩家控制(只在本地客户端中显示)
	if (PrimaryPC && PrimaryPC->IsLocalPlayerController())
	{
		// 设置目标点位置
		SetActorLocation(GetTargetPoint());
	}
}

FVector ATargetActor_GroundPick::GetTargetPoint() const
{
	if (!PrimaryPC || !PrimaryPC->IsLocalPlayerController())
		return GetActorLocation();

	FHitResult TraceResult;

	FVector ViewLoc;
	FRotator ViewRot;
	
	// 获取视角位置和朝向
	PrimaryPC->GetPlayerViewPoint(ViewLoc, ViewRot);

	// 获取射线终点位置
	FVector TraceEnd = ViewLoc + ViewRot.Vector() * TargetTraceRange;

	// 射线检测
	GetWorld()->LineTraceSingleByChannel(
		TraceResult,
		ViewLoc,
		TraceEnd,
		ECC_TARGET);

	// 如果没有命中,向下做一次射线检测,把结果点放地上
	if (!TraceResult.bBlockingHit)
	{
		GetWorld()->LineTraceSingleByChannel(TraceResult, TraceEnd, TraceEnd + FVector::DownVector * TNumericLimits<float>::Max(), ECC_TARGET);
	}

	// 如果依然没有命中,返回当前位置
	if (!TraceResult.bBlockingHit)
	{
		return GetActorLocation();
	}

	// 绘制调试球体
	if (bShouldDrawDebug)
	{
		DrawDebugSphere(GetWorld(), TraceResult.ImpactPoint, TargetAreaRadius, 32, FColor::Red);
	}

	return TraceResult.ImpactPoint;
}

GA_GroundBlast的完整代码

// 幻雨喜欢小猫咪

#pragma once

#include "CoreMinimal.h"
#include "GAS/Core/CGameplayAbility.h"
#include "GAS/Core/CGameplayAbilityTypes.h"
#include "GAS/TA/TargetActor_GroundPick.h"
#include "GA_GroundBlast.generated.h"

/**
 * 
 */
UCLASS()
class CRUNCH_API UGA_GroundBlast : public UCGameplayAbility
{
	GENERATED_BODY()

public:
	UGA_GroundBlast();
	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
private:
	// 冲击特效Cue标签
	UPROPERTY(EditDefaultsOnly, Category = "Cue")
	FGameplayTag BlastGameplayCueTag;
	
	// 技能目标区域半径
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetAreaRadius = 300.f;

	UPROPERTY(EditDefaultsOnly, Category = "Damage")
	FGenericDamageEffectDef DamageEffectDef;
	// 目标检测最大距离
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	float TargetTraceRange = 2000.f;
	
	// 地面选点目标Actor类
	UPROPERTY(EditDefaultsOnly, Category = "Targeting")
	TSubclassOf<ATargetActor_GroundPick> TargetActorClass;
	
	// 瞄准动画
	UPROPERTY(EditDefaultsOnly, Category = "Animation")
	TObjectPtr<UAnimMontage> TargetingMontage;

	// 释放动画
	UPROPERTY(EditDefaultsOnly, Category = "Animation")
	TObjectPtr<UAnimMontage> CastMontage;
	
	// 目标确认回调
	UFUNCTION()
	void TargetConfirmed(const FGameplayAbilityTargetDataHandle& TargetDataHandle);

	// 目标取消回调
	UFUNCTION()
	void TargetCanceled(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
};

// 幻雨喜欢小猫咪


#include "GAS/Abilities/GA_GroundBlast.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "GAS/Core/TGameplayTags.h"
#include "Abilities/Tasks/AbilityTask_WaitTargetData.h"
#include "GAS/Core/CAbilitySystemStatics.h"

UGA_GroundBlast::UGA_GroundBlast()
{
	// 技能激活时给角色添加瞄准标签
	ActivationOwnedTags.AddTag(TGameplayTags::Stats_Aim);
	// 阻断基础攻击技能
	BlockAbilitiesWithTag.AddTag(TGameplayTags::Ability_BasicAttack);
}

void UGA_GroundBlast::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
	const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
	const FGameplayEventData* TriggerEventData)
{
	if (!HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)) return;

	UAbilityTask_PlayMontageAndWait* PlayGroundBlasAnimTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, TargetingMontage);
	PlayGroundBlasAnimTask->OnBlendOut.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnCancelled.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnCompleted.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->OnInterrupted.AddDynamic(this, &UGA_GroundBlast::K2_EndAbility);
	PlayGroundBlasAnimTask->ReadyForActivation();

	// 等待瞄准敌人
	UAbilityTask_WaitTargetData* WaitTargetDataTask = UAbilityTask_WaitTargetData::WaitTargetData(this, NAME_None, EGameplayTargetingConfirmation::UserConfirmed, TargetActorClass);
	// 确认技能
	WaitTargetDataTask->ValidData.AddDynamic(this, &UGA_GroundBlast::TargetConfirmed);
	// 技能取消
	WaitTargetDataTask->Cancelled.AddDynamic(this, &UGA_GroundBlast::TargetCanceled);
	WaitTargetDataTask->ReadyForActivation();

	// 生成目标Actor
	AGameplayAbilityTargetActor* TargetActor;
	WaitTargetDataTask->BeginSpawningActor(this, TargetActorClass, TargetActor);

	// 设置目标Actor参数
	ATargetActor_GroundPick* GroundPickActor = Cast<ATargetActor_GroundPick>(TargetActor);
	if (GroundPickActor)
	{
		GroundPickActor->SetShouldDrawDebug(ShouldDrawDebug());
		GroundPickActor->SetTargetAreaRadius(TargetAreaRadius);
		GroundPickActor->SetTargetTraceRange(TargetTraceRange);
	}
	WaitTargetDataTask->FinishSpawningActor(this, TargetActor);
}

void UGA_GroundBlast::TargetConfirmed(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	if (!K2_CommitAbility())
	{
		K2_EndAbility();
		return;
	}
	// 仅在服务器上执行伤害和击退
	if (K2_HasAuthority())
	{
		// 对目标应用伤害效果
		BP_ApplyGameplayEffectToTarget(TargetDataHandle, DamageEffectDef.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo));
		// 对目标施加推力
		PushTargets(TargetDataHandle, DamageEffectDef.PushVelocity);
	}

	FGameplayCueParameters BlastingGameplayCueParameters;
	// 设置特效的位置
	BlastingGameplayCueParameters.Location = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 1).ImpactPoint;
	// 设置特效的大小
	BlastingGameplayCueParameters.RawMagnitude = TargetAreaRadius;

	// 播放冲击特效和摄像机震动
	GetAbilitySystemComponentFromActorInfo()->ExecuteGameplayCue(BlastGameplayCueTag, BlastingGameplayCueParameters);
	GetAbilitySystemComponentFromActorInfo()->ExecuteGameplayCue(UCAbilitySystemStatics::GetCameraShakeGameplayCueTag(), BlastingGameplayCueParameters);

	// 播放释放动画
	UAnimInstance* OwnerAnimInst = GetOwnerAnimInstance();
	if (OwnerAnimInst)
	{
		OwnerAnimInst->Montage_Play(CastMontage);
	}
	UE_LOG(LogTemp, Warning, TEXT("技能发射"));
	K2_EndAbility();
}

void UGA_GroundBlast::TargetCanceled(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	UE_LOG(LogTemp, Warning, TEXT("技能取消"));
	K2_EndAbility();
}


网站公告

今日签到

点亮在社区的每一天
去签到