[UE5/GAS] AnimNotify와 Trigger Tag로 공격 판정하기
인프런 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 공부 노트
강의를 들으며 개념 정리 후, 다른 프로젝트에 실습하고 있습니다.
1. Trigger Tag를 등록하여 GA 활성화하기
1) AnimNotify를 상속받은 cpp 클래스 생성
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "GameplayTagContainer.h"
#include "VSAnimNotify_AttackHitCheck.generated.h"
/**
*
*/
UCLASS()
class PROJECTLOL_API UVSAnimNotify_AttackHitCheck : public UAnimNotify
{
GENERATED_BODY()
public:
UVSAnimNotify_AttackHitCheck();
protected:
virtual FString GetNotifyName_Implementation() const override;
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
protected:
UPROPERTY(EditAnywhere)
FGameplayTag TriggerGameplayTag;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Animation/LOLVampireSurvivors/Champion/VSAnimNotify_AttackHitCheck.h"
#include "AbilitySystemBlueprintLibrary.h"
UVSAnimNotify_AttackHitCheck::UVSAnimNotify_AttackHitCheck()
{
}
FString UVSAnimNotify_AttackHitCheck::GetNotifyName_Implementation() const
{
// 에디터에 표시 할 텍스트.
return TEXT("VSSkillAttackHitCheck");
}
void UVSAnimNotify_AttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
Super::Notify(MeshComp, Animation, EventReference);
// 캐릭터인지 확인.
if (MeshComp)
{
AActor* OwnerActor = MeshComp->GetOwner();
if (OwnerActor)
{
// ASC를 가진 지정한 액터에 지정한 태그를 넣어 이벤트 발생시키기 위한 기능.
FGameplayEventData PayloadData;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(OwnerActor, TriggerGameplayTag, PayloadData);
}
}
}
2) AnimationMontage에 생성한 Notify 지정
3) Tag 추가
4) 생성한 Tag 지정
5) 생성한 GA를 상속받은 블루프린트 생성, 캐릭터에 추가하기.
6) 생성한 GA 블루프린트에 Ability Trigger Tag 추가하기.
→ ASC가 등록한 어빌리티 중 해당 Trigger Tag가 있으면 어빌리티 활성화.
어빌리티 활성화 확인.
2. 공격 판정 GA > AT > TA 생성
TA (TargetActor)
GA에서 대상에 대한 판정(주로 물리 판정)을 구현할 때 사용하는 특수한 Actor.
- AGameplayAbilityTargetActor 클래스를 상속받아서 구현
타겟을 검출하는 방법
- Trace를 사용하여 즉각적으로 타겟을 검출하는 방법 → TA를 사용하지 않아도 가능하지만, 확장 가능성을 위해 사용
- 사용자의 최종 확인을 한번 더 거쳐 타겟을 검출하는 방법 (범위 공격 등)
- 공격 범위 확인을 위한 추가 시각화 (시각화 수행 액터 : WorldReticle)
주요 함수
StartTargeting : 타겟팅을 시작하여 타겟 검출
→ ConfirmTargetingAndContinue : 타겟팅을 확실히 지정하고 해당 AT 수행
→ ConfirmTargeting : 타겟팅 확정만
→ CancelTargeting : 타겟팅 취소(타겟 액터 삭제)
GameAbilityTargetData
타겟팅 전송 목적의 데이터.
타겟 액터에서 판정한 결과를 담은 데이터.
TargetDataHandle이 TargetData 여러 개를 묶어 AT에 전송
속성
- Trace HitResult
- 판정된 다수의 액터 포인터
- 판정 시작 지점
- 판정 끝 지점
3. 구현 코드
TA 생성
VSTA_Trace.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbilityTargetActor.h"
#include "VSTA_Trace.generated.h"
/**
*
*/
UCLASS()
class PROJECTLOL_API AVSTA_Trace : public AGameplayAbilityTargetActor
{
GENERATED_BODY()
public:
explicit AVSTA_Trace();
// 타겟팅 시작.
virtual void StartTargeting(UGameplayAbility* Ability) override;
// 타겟팅 확정 후 AT 발동.
virtual void ConfirmTargetingAndContinue() override;
void SetShowDebug(bool InShowDebug) { bShowDebug = InShowDebug; }
protected:
virtual FGameplayAbilityTargetDataHandle MakeTargetData() const;
bool bShowDebug = false;
};
VSTA_Trace.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/TA/VSTA_Trace.h"
#include "VSTA_Trace.h"
#include "Abilities/GameplayAbility.h"
#include "GA/AT/VSAT_Trace.h"
#include "GameFramework/Character.h"
#include "Components/CapsuleComponent.h"
#include "Physics/LCCollisiion.h"
#include "CollisionShape.h"
#include "DrawDebugHelpers.h"
AVSTA_Trace::AVSTA_Trace()
{
}
void AVSTA_Trace::StartTargeting(UGameplayAbility* Ability)
{
Super::StartTargeting(Ability);
SourceActor = Ability->GetCurrentActorInfo()->AvatarActor.Get();
}
void AVSTA_Trace::ConfirmTargetingAndContinue()
{
if (SourceActor)
{
FGameplayAbilityTargetDataHandle DataHandle = MakeTargetData();
TargetDataReadyDelegate.Broadcast(DataHandle);
}
}
FGameplayAbilityTargetDataHandle AVSTA_Trace::MakeTargetData() const
{
ACharacter* Character = CastChecked<ACharacter>(SourceActor);
FHitResult OutHitResult;
// 임시 공격 판정 범위. 후에 어트리뷰트로 공급받기.
const float AttackRange = 100.f;
const float AttackRadius = 50.f;
FCollisionQueryParams Params(SCENE_QUERY_STAT(UVSTA_Trace), false, Character);
const FVector Forward = Character->GetActorForwardVector();
// 충돌 검출 시작 지점.
const FVector Start = Character->GetActorLocation() + Forward * Character->GetCapsuleComponent()->GetScaledCapsuleRadius();
// 충돌 검출 종료 지점.
const FVector End = Start + Forward * AttackRange;
// 충돌 판정.
bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, TCC_CHARACTER, FCollisionShape::MakeSphere(AttackRadius), Params);
// 충돌한 타겟 데이터 검출.
FGameplayAbilityTargetDataHandle DataHandle;
if (HitDetected)
{
FGameplayAbilityTargetData_SingleTargetHit* TargetData = new FGameplayAbilityTargetData_SingleTargetHit(OutHitResult);
DataHandle.Add(TargetData); // shared_ptr을 사용하기 때문에 레퍼런스 유지 동안 객체 유지.
}
#if ENABLE_DRAW_DEBUG
if (bShowDebug)
{
FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
float CapsuleHalfHeight = AttackRadius * 0.5f;
FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;
DrawDebugCapsule(
GetWorld(),
CapsuleOrigin,
CapsuleHalfHeight,
AttackRadius,
FRotationMatrix::MakeFromZ(Forward).ToQuat(),
DrawColor,
false,
5.f);
}
#endif
return DataHandle;
}
타겟팅 검출 AT
VSAT_Trace.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "VSAT_Trace.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTraceResultDelegate, const FGameplayAbilityTargetDataHandle&, TargetDataHandle);
/**
*
*/
UCLASS()
class PROJECTLOL_API UVSAT_Trace : public UAbilityTask
{
GENERATED_BODY()
public:
explicit UVSAT_Trace();
// AT 인스턴스 생성 반환 함수.
UFUNCTION(BlueprintCallable, Category = "Ability|Tasks", meta = (DisplayName = "WaitForTrace", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
static UVSAT_Trace* CreateTask(UGameplayAbility* OwningAbility, TSubclassOf<class AVSTA_Trace> TargetActorClass);
virtual void Activate() override;
virtual void OnDestroy(bool AbilityEnded) override;
// 타겟 액터 생성.
void SpawnAndInitializeTargetActor();
// 타겟 액터 확정.
void FinalizeTargetActor();
protected:
void OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& DataHandel);
public:
UPROPERTY(BlueprintAssignable)
FTraceResultDelegate OnComplete;
protected:
UPROPERTY()
TSubclassOf<class AVSTA_Trace> TargetActorClass;
UPROPERTY()
TObjectPtr<class AVSTA_Trace> SpawnedTargetActor;
};
VSAT_Trace.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/AT/VSAT_Trace.h"
#include "Abilities/GameplayAbilityTargetActor.h"
#include "GA/TA/VSTA_Trace.h"
#include "AbilitySystemComponent.h"
UVSAT_Trace::UVSAT_Trace()
{
}
UVSAT_Trace* UVSAT_Trace::CreateTask(UGameplayAbility* OwningAbility, TSubclassOf<AVSTA_Trace> InTargetActorClass)
{
// 태스크의 생성과 준비.
UVSAT_Trace* NewTask = NewAbilityTask<UVSAT_Trace>(OwningAbility);
NewTask->TargetActorClass = InTargetActorClass;
return NewTask;
}
void UVSAT_Trace::Activate()
{
Super::Activate();
// 타겟 액터 생성.
SpawnAndInitializeTargetActor();
// 타겟 액터 확정.
FinalizeTargetActor();
SetWaitingOnAvatar();
}
void UVSAT_Trace::OnDestroy(bool AbilityEnded)
{
Super::OnDestroy(AbilityEnded);
// Task 종료 시 TargetActor가 있다면, 삭제하기.
if (SpawnedTargetActor)
{
SpawnedTargetActor->Destroy();
}
}
void UVSAT_Trace::SpawnAndInitializeTargetActor()
{
SpawnedTargetActor = Cast<AVSTA_Trace>(Ability->GetWorld()->SpawnActorDeferred<AGameplayAbilityTargetActor>(TargetActorClass, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn));
if (SpawnedTargetActor)
{
SpawnedTargetActor->SetShowDebug(true);
// 델리게이트에 함수 등록.
SpawnedTargetActor->TargetDataReadyDelegate.AddUObject(this, &UVSAT_Trace::OnTargetDataReadyCallback);
}
}
void UVSAT_Trace::FinalizeTargetActor()
{
// 최종 타겟 액터 검증.
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (ASC)
{
const FTransform SpawnTransform = ASC->GetAvatarActor()->GetTransform();
SpawnedTargetActor->FinishSpawning(SpawnTransform);
ASC->SpawnedTargetActors.Add(SpawnedTargetActor);
SpawnedTargetActor->StartTargeting(Ability);
// 타겟팅 확정.
SpawnedTargetActor->ConfirmTargeting();
}
}
void UVSAT_Trace::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& DataHandle)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnComplete.Broadcast(DataHandle);
}
EndTask();
}
공격 판정 GA
VSGA_SkillAttackHitCheck.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "VSGA_SkillAttackHitCheck.generated.h"
/**
*
*/
UCLASS()
class PROJECTLOL_API UVSGA_SkillAttackHitCheck : public UGameplayAbility
{
GENERATED_BODY()
public:
explicit UVSGA_SkillAttackHitCheck();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
protected:
// Trace AT 종료 시 콜백.
UFUNCTION()
void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
};
VSGA_SkillAttackHitCheck.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/Attack/VSGA_SkillAttackHitCheck.h"
#include "VSGA_SkillAttackHitCheck.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "ProjectLOL.h"
#include "GA/AT/VSAT_Trace.h"
#include "GA/TA/VSTA_Trace.h"
UVSGA_SkillAttackHitCheck::UVSGA_SkillAttackHitCheck()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}
void UVSGA_SkillAttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
//LOL_LOG(LogProjectLOL, Log, TEXT("Begin"));
// TA를 가진 AT 생성.
UVSAT_Trace* AttackTrackTask = UVSAT_Trace::CreateTask(this, AVSTA_Trace::StaticClass());
// Task 완료 이벤트 구독.
AttackTrackTask->OnComplete.AddDynamic(this, &UVSGA_SkillAttackHitCheck::OnTraceResultCallback);
AttackTrackTask->ReadyForActivation();
}
void UVSGA_SkillAttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
// 타겟 데이터 핸들의 첫번째 데이터가 있는지 확인.
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
{
FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
LOL_LOG(LogProjectLOL, Log, TEXT("Target %s Detected"), *(HitResult.GetActor()->GetName()));
}
// 어빌리티 종료.
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
결과 화면
최종적으로,
1) 공격 애니메이션이 재생되면, AnimNotify에 등록된 TriggerTag를 가진 공격 판정 GA가 호출됨.
2) GA에서 TA를 가진 타겟 검출 AT를 생성하고, 검출 완료 델리게이트를 구독.
3) AT에서 TA의 타겟팅 검출 완료 델리게이트를 구독
4) TA에서 확정된 Actor를 GameplayAbilityTargetDataHandle에 담아 전달, 검출 완료 델리게이트 호출.
5) AT에서 최종 확정을 진행하고, GA에 검출 완료 델리게이트 호출.
6) 검출된 액터가 있다면 진행. (현재는 로그 출력)
으로 진행되는 과정이다.