文章目录
创建一个新的技能的流程
创建技能的流程
因为这个技能有两个阶段,一个是瞄准阶段,另一个是射击阶段,技能一开始触发的时候是属于瞄准阶段,不会直接提交消耗以及进入冷却,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;
在初始化阶段绑定监听,并每帧更新FwdSpeed
和 RightSpeed
的值
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
创建一个继承GameplayAbilityTargetActor
的TargetActor_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();
}