게임플레이 이벤트를 사용해 게임플레이 어빌리티를 트리거하는 방법의 학습

타겟액터를 사용한 물리 판정의 구현

어빌리티 태스크와 타겟액터 사이세 발생하는 실행 흐름의 이해 

 

 

목차

     

     


     

     

    공격 판정 시스템의 구현 계획


     

     

    공격 판정을 위한 신규 기능 기획

     

    애니메이션 몽타주의 노티파이를 활용해 원하는 타이밍에 공격을 판정하는 기능 추가

    Anim Notify가 발동되면 판정을 위한 AT를 실행

    GAS에서 제공하는 타겟액터 를활용해 물리 공격 판정을 수행

    판정 결과를 시각적으로 확인할 수 있도록 드로우디버그 기능 제공   

     

     

     

    노란색 부분을 작업하였다.

    AbilityTask(AT)의 타겟액터를 사용해서 효율적인 물리 공격 판정을 구현


     

     

     

    애니메이션 노티파이의 설정


     

     

    AnimNotify_GASAttackHitCheck

     

    AnimNotify - AnimNotify_GASAttackHitCheck 생성

     

     

    AnimNotify_GASAttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Animation/AnimNotifies/AnimNotify.h"
    #include "GameplayTagContainer.h"
    #include "AnimNotify_GASAttackHitCheck.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API UAnimNotify_GASAttackHitCheck : public UAnimNotify
    {
    	GENERATED_BODY()
    	
    public:
    	UAnimNotify_GASAttackHitCheck();
    
    protected:
    	virtual FString GetNotifyName_Implementation() const override;
    	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
    
    protected:
    	UPROPERTY(EditAnywhere, Meta=(Categories=Event))
    	FGameplayTag TriggerGameplayTag;
    
    	UPROPERTY(EditAnywhere)
    	float ComboAttackLevel;
    };

     

     

     

     

    AnimNotify_GASAttackHitCheck.cpp

    더보기
    #include "Animation/AnimNotify_GASAttackHitCheck.h"
    #include "AbilitySystemBlueprintLibrary.h"
    
    UAnimNotify_GASAttackHitCheck::UAnimNotify_GASAttackHitCheck()
    {
    	ComboAttackLevel = 1.0f;
    }
    
    FString UAnimNotify_GASAttackHitCheck::GetNotifyName_Implementation() const
    {
    	return TEXT("GASAttackHitCheck");
    }
    
    void UAnimNotify_GASAttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
    {
    	Super::Notify(MeshComp, Animation, EventReference);
    
    	if (MeshComp)
    	{
    		AActor* OwnerActor = MeshComp->GetOwner();
    		if (OwnerActor)
    		{
    			FGameplayEventData PayloadData;
    			PayloadData.EventMagnitude = ComboAttackLevel;
    			UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(OwnerActor, TriggerGameplayTag, PayloadData);
    		}
    	}
    }

    void UAnimNotify_GASAttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)

    • UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(OwnerActor, TriggerGameplayTag, PayloadData);
      • 특정 액터(OwnerActor)에 Gamplay Tag(TriggerGameplayTag)를 넣어서 발동시키겠다.


     

     

    콤보공격 몽타주에 Gameplay Tag 지정하기

     

    Project Settings - Gameplay Tag

    • Gameplay Tag List에 Character.Action.AttackHitCheck 추가

     

    애니메이션 몽타주에 AnimNotify 추가

    • AnimNotify_GASAttackHitCheck 추가
      • Trigger Gamplay Tag에  Character.Action.AttackHitCheck  할당

     

     

    ABGA_GASAttackHitCheck 생성

     

    GameAbility - ABGA_GASAttackHitCheck 생성

     

     

    ABGA_GASAttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbility.h"
    #include "ABGA_AttackHitCheck.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API UABGA_AttackHitCheck : public UGameplayAbility
    {
    	GENERATED_BODY()
    	
    public:
    	UABGA_AttackHitCheck();
    
    	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
    
    protected:
    	UFUNCTION()
    	void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
    };

     

     

     

    ABGA_GASAttackHitCheck.cpp

    더보기
    #include "GA/ABGA_AttackHitCheck.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "GA/AT/ABAT_Trace.h"
    #include "GA/TA/ABTA_Trace.h"
    
    UABGA_AttackHitCheck::UABGA_AttackHitCheck()
    {
    	InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
    }
    
    void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
    {
    	ABGAS_LOG(LogABGAS, Log, TEXT("%s"), *TriggerEventData->EventTag.GetTagName().ToString());
    
    	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
    
    	UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, TargetActorClass);
    	AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
    	AttackTraceTask->ReadyForActivation();
    }
    
    void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
    {
    	// TargetDataHandle는 배열로 들어오기 때문에 0번 인덱스(=첫번째 데이터)에 이것이 있는지 확인
    	if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0)) // 있다면
    	{
    		FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
    		ABGAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *(HitResult.GetActor()->GetName()));
    	}
    
    	bool bReplicatedEndAbility = true;
    	bool bWasCancelled = false;
    	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
    }

     

     

    BPGA_AttackHitCheck 생성

    Details - Trigger

    • Ability Triggers
      • Trigger Tag에 Event.Character.Action.AttackHitCheck  태그 등록
        • 해당 태그를 Trigger로 지정해서 사용하면 시스템이 이것을 감지하고 ASC가 등록한 어빌리티 중에서 트리거가 이걸로 설정되어 있으면 게임플레이 이벤트 데이터를 ASC한테 보낼 때 자기가 자동으로 인식을 해서 이 어빌리티를 활성화 시켜준다.

     

     

    BP_ABGASCharacterPlayer에 Ability 할당

     

     


     

     

     

    ABAT_Trace 생성

     

    AbilityTask - ABAT_Trace 생성

     

     

    ABAT_Trace.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/Tasks/AbilityTask.h"
    #include "ABAT_Trace.generated.h"
    
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTraceResultDelegate, const FGameplayAbilityTargetDataHandle&, TargetDataHandle);
    
    UCLASS()
    class ARENABATTLEGAS_API UABAT_Trace : public UAbilityTask
    {
    	GENERATED_BODY()
    	
    public:
    	UABAT_Trace();
    
    	UFUNCTION(BlueprintCallable, Category = "Ability|Tasks", meta = (DisplayName = "WaitForTrace", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
    	static UABAT_Trace* CreateTask(UGameplayAbility* OwningAbility, TSubclassOf<class AABTA_Trace> TargetActorClass); 
    	virtual void Activate() override;					// 활성화될 때 호출되는 함수
    	virtual void OnDestroy(bool AbilityEnded) override; // 소멸될 때 호출되는 함수
    
    	void SpawnAndInitializeTargetActor();
    	void FinalizeTargetActor();
    
    protected:
    	void OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& DataHandle);
    
    public:
    	UPROPERTY(BlueprintAssignable)
    	FTraceResultDelegate OnComplete;
    
    protected:
    	UPROPERTY()
    	TSubclassOf<class AABTA_Trace> TargetActorClass;
    
    	UPROPERTY()
    	TObjectPtr<class AABTA_Trace> SpawnedTargetActor;
    };

     

     

     

    ABAT_Trace.cpp

    더보기
    #include "GA/AT/ABAT_Trace.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "AbilitySystemComponent.h"
    
    UABAT_Trace::UABAT_Trace()
    {
    }
    
    UABAT_Trace* UABAT_Trace::CreateTask(UGameplayAbility* OwningAbility, TSubclassOf<AABTA_Trace> TargetActorClass) // 일반적으로 static 함수로 만든다. OwningAbility: 자신을 호출한 어빌리티
    {
    	UABAT_Trace* NewTask = NewAbilityTask<UABAT_Trace>(OwningAbility);
    	NewTask->TargetActorClass = TargetActorClass;
    	return NewTask;
    }
    
    void UABAT_Trace::Activate() // 활성화될 때 호출되는 함수
    {
    	Super::Activate();
    
    	SpawnAndInitializeTargetActor();
    	FinalizeTargetActor();
    
    	SetWaitingOnAvatar();
    }
    
    void UABAT_Trace::OnDestroy(bool AbilityEnded) // 소멸될 때 호출되는 함수
    {
    	if (SpawnedTargetActor)
    	{
    		SpawnedTargetActor->Destroy();
    	}
    
    	Super::OnDestroy(AbilityEnded);
    }
    
    void UABAT_Trace::SpawnAndInitializeTargetActor()
    {
    	SpawnedTargetActor = Cast<AABTA_Trace>(Ability->GetWorld()->SpawnActorDeferred<AGameplayAbilityTargetActor>(TargetActorClass, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn));
    	if (SpawnedTargetActor)
    	{
    		SpawnedTargetActor->SetShowDebug(true);
    		SpawnedTargetActor->TargetDataReadyDelegate.AddUObject(this, &UABAT_Trace::OnTargetDataReadyCallback);
    	}
    }
    
    void UABAT_Trace::FinalizeTargetActor()
    {
    	UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
    	if (ASC)
    	{
    		const FTransform SpawnTransform = ASC->GetAvatarActor()->GetTransform();
    		SpawnedTargetActor->FinishSpawning(SpawnTransform); // 마무리
    
    		ASC->SpawnedTargetActors.Push(SpawnedTargetActor); // SpawnedTargetActor를 ASC의 SpawnedTargetActors 배열에 담음
    		SpawnedTargetActor->StartTargeting(Ability); // Ability정보를 넘겨 StartTargeting하라고 지시
    		SpawnedTargetActor->ConfirmTargeting(); // SpawnedTargetActor에 있는 ConfirmTargetingAndContinue함수가 실행됨 -> FGameplayAbilityTargetDataHandle 델리게이트 호출 -> OnTargetDataReadyCallback 호출. EndTask() 종료
    	}
    }
    
    void UABAT_Trace::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& DataHandle)
    {
    	if (ShouldBroadcastAbilityTaskDelegates())
    	{
    		OnComplete.Broadcast(DataHandle);
    	}
    
    	EndTask();
    }

     

     

     


     

     

     

     

     

    Target Actor (TA)


     

     

    게임플레이 어빌리티 타겟 액터 Gameplay Ability Target Actor

     

    Gameplay Ability Target Actor (TA)

    • 게임플레이 어빌리티에서 대상에 대한 판정(주로 물리 판정)을 구현할 때 사용하는 특수한 액터
    • 줄여서 TA라고 함
    • AGameplayAbilityTargetActor 클래스를 상속받아서 구현
    • 왜 타겟 액터(TA)가 필요한가?
      • 타겟을 설정하는 다양한 방법이 있음
      • Trace를 사용해 즉각적으로 타겟을 검출하는 방법  
      • 사용자의 최종 확인을 한번 더 거치는 방법이 있음.  ex. 원거리 범위 공격
      • 공격 범위 확인을 위한 추가 시각화 (시각화를 수행하는 액터를 월드레티클(WorldReticle)이라고함)
    • 주요 함수
      •  StartTargeting :  타겟팅을 시작 
      •  ConfirmTargetingAndContinue :   타겟팅을 확정하고 이후 남은 프로세스를 진행
      •  ConfirmTargeting :  태스크 진행 없이 타겟팅만 확정
      •  CancelTargeting타겟팅을 취소     

     

    예제에서는 즉각적인 타켓팅 기능만 구현함


     

     

    게임플레이 어빌리티 타겟 데이터 

     

    • 타겟 액터에서 판정한 결과를 담은 데이터
    • 다음의 속성을 가지고 있음
      • Trace 히트 결과 ( HitResult )
      • 판정된 다수의 액터 포인터
      • 시작 지점
      • 끝 지점
    • 타겟 데이터를 여러 개 묶어 전송하는 것이 일반적인데 이를 타겟 데이터 핸들이라고 함


     

     

    AT와 TA사이의 실행 흐름

     


     

     

    ABTA_Trace 생성

     

    GameplayAbiltyTargetActor - ABTA_Trace 생성

     

     

     

    ABTA_Trace.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbilityTargetActor.h"
    #include "ABTA_Trace.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API AABTA_Trace : public AGameplayAbilityTargetActor
    {
    	GENERATED_BODY()
    	
    public:
    	AABTA_Trace();
    
    	virtual void StartTargeting(UGameplayAbility* Ability) override;
    
    	virtual void ConfirmTargetingAndContinue() override;
    	void SetShowDebug(bool InShowDebug) { bShowDebug = InShowDebug; }
    
    protected:
    	virtual FGameplayAbilityTargetDataHandle MakeTargetData() const;
    
    	bool bShowDebug = false;
    };

     

     

     

    ABTA_Trace.cpp

    더보기
    #include "GA/TA/ABTA_Trace.h"
    #include "Abilities/GameplayAbility.h"
    #include "GameFramework/Character.h"
    #include "Components/CapsuleComponent.h"
    #include "Physics/ABCollision.h" // 물리 채널(CCHANNEL_ABACTION 매크로로 미리 지정해둠)
    #include "DrawDebugHelpers.h"
    
    AABTA_Trace::AABTA_Trace()
    {
    }
    
    void AABTA_Trace::StartTargeting(UGameplayAbility* Ability)
    {
    	Super::StartTargeting(Ability);
    
    	SourceActor = Ability->GetCurrentActorInfo()->AvatarActor.Get();
    }
    
    void AABTA_Trace::ConfirmTargetingAndContinue()
    {
    	if (SourceActor)
    	{
    		FGameplayAbilityTargetDataHandle DataHandle = MakeTargetData();
    		TargetDataReadyDelegate.Broadcast(DataHandle);
    	}
    }
    
    FGameplayAbilityTargetDataHandle AABTA_Trace::MakeTargetData() const
    {
    	ACharacter* Character = CastChecked<ACharacter>(SourceActor);
    
    	FHitResult OutHitResult;
    	const float AttackRange = 100.f; // 하드코딩. 추후에 AttributSet에서 받아오는 방식으로 변경
    	const float AttackRadius = 50.f;
    
    	FCollisionQueryParams Params(SCENE_QUERY_STAT(UABTA_Trace), false, Character); // 자기자신(=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, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
    
    	FGameplayAbilityTargetDataHandle DataHandle;
    	if (HitDetected) // 맞았다면
    	{
    		FGameplayAbilityTargetData_SingleTargetHit* TargetData = new FGameplayAbilityTargetData_SingleTargetHit(OutHitResult);
    		DataHandle.Add(TargetData);
    	}
    	
    #if ENABLE_DRAW_DEBUG // DrawDebug 
    
    	if (bShowDebug)
    	{
    		FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
    		float CapsuleHalfHeight = AttackRange * 0.5f;
    		FColor DrawColor = HitDetected ? FColor::Green : FColor::Red; // 맞은경우 녹색, 안 맞은경우 빨간색
    		DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(Forward).ToQuat(), DrawColor, false, 5.0f); // 디버깅용 그리기. FRotationMatrix::MakeFromZ(Forward).ToQuat로 캡슐 눕히기
    	}
    
    #endif
    
    	return DataHandle;
    }

     


     

     

    실행화면

     

     


     

     

    정리:  공격 판정 시스템의 구현 

     

    • 게임플레이 이벤트를 활용한 GA의 발동 구현 
    • 물리 판정 작업을 위한 AT의 작업
    • 물리 공격 판정 및 결과를 반환하는 TA의 구현
    • AT와 TA 사이의 실행 흐름 이해하기