물리 판정을 위해 제작한 GA와 AT 그리고 TA를 순서대로 살펴보자.
GA의 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)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
//AbilityTask를 생성함.
UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
//AbilityTask가 끝났을 때 OnTraceResultCallback 수행.
AttackTraceTask->OnComplete.AddDynamic(this, &ThisClass::OnTraceResultCallback);
//Task를 실행시킨다.
AttackTraceTask->ReadyForActivation();
}
void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
//FGameplayAbilityTargetDataHandle는 내부적으로 TArray<TSharedPtr<FGameplayAbilityTargetData>> 컨테이너를 들고 있다.
//TagetDataHasHitResult는 TargetDataHandle에서 0번째 요소를 뽑아 해당 요소에 HitResult가 존재하는지 확인하는 함수이다.
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
{
//TargetDataHandle의 0번째 요소에서 HitResult를 Get하여 저장.
FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
//어떤 Target이 맞았는지 출력
GAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *HitResult.GetActor()->GetName());
}
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
여기서는 ABAT_Trace를 생성하고 OnComplete구독을 하며 Task를 실행시킨다.
그럼 이제 ABAT_Trace Task를 보자.
// Fill out your copyright notice in the Description page of Project Settings.
#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)
{
//UABAT_Trace Task를 생성하여 Return한다.
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를 생성할 때 SpawnActorDeferred로 초기화할 시간을 주는 상태로 생성합니다.
SpawnedTargetActor = Cast<AABTA_Trace>(Ability->GetWorld()->SpawnActorDeferred<AGameplayAbilityTargetActor>(TargetActorClass, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn));
if (SpawnedTargetActor)
{
//SpawnedTargetActor의 변수값과 Delegate에 함수를 Bind합니다.
SpawnedTargetActor->SetShowDebug(true);
//아래 Delegate는 ConfirmTargeting()할 때 호출됩니다.
SpawnedTargetActor->TargetDataReadyDelegate.AddUObject(this, &UABAT_Trace::OnTargetDataReadyCallback);
}
}
void UABAT_Trace::FinalizeTargetActor()
{
//ASC는 CreateTask의 NewAbilityTask과정에서 해당 Task가 어떤 Ability와 연결될지 알려주는데
//이때 ASC도 같이 세팅하여 ASC를 Task에서 들고올 수 있다.
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (ASC)
{
//ASC에서 아바타 Actor의 Transform을 가져온다.
//그리고 아래 FinishSpawning에 해당 값을 넣어 액터를 해당 위치에 배치시킨다.
const FTransform SpawnTransform = ASC->GetAvatarActor()->GetTransform();
//여기서 Deferred로 지연된 생성이 완료됩니다.
SpawnedTargetActor->FinishSpawning(SpawnTransform);
//ASC에는 자신이 스폰한 TargetActor를 추적하는 배열인 SpawnedTargetActors가 있다.
//Push를 한다는 의미는 ASC가 해당 TargetActor를 관리 리스트에 등록한다는 의미이다.
ASC->SpawnedTargetActors.Push(SpawnedTargetActor);
//TargetActor가 어떤 Ability를 위한 Targeting인지 파라미터를 통해 알 수 있으면 Targeting을 시작하는 함수이다.
SpawnedTargetActor->StartTargeting(Ability);
//Trace기반 TargetActor는 즉시 결과를 낼 수 있어서 바로 결과를 낸다.
//혹시 TargetActor를 모아야하는 다른 Targeting의 경우에 바로 호출하지 않고 TargetActorHandleData를 모은 뒤에 호출한다.
SpawnedTargetActor->ConfirmTargeting();
}
}
void UABAT_Trace::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& DataHandle)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnComplete.Broadcast(DataHandle);
}
EndTask();
}
해당 GA가 Activate되면
SpawnAndInitializeTargetActor에서 TargetActor를 지연 생성(SpawnActorDeferred)를 하고 값 초기화 및 Delegate Bind한다.
그리고 FinalizeTargetActor에서 TargetActor를 생성 완료하고 ASC가 해당 TargetActor를 관리 리스트에 등록한다.
그리고 Targeting을 시작하고 아래 코드를 보면 알겠지만 해당 Targeting은 Trace라서 아래에 ConfirmTargeting()을 한다.
그리고 ABTA_Trace이다.
#include "GA/TA/ABTA_Trace.h"
#include "Abilities/GameplayAbility.h"
#include "GameFramework/Character.h"
#include "Components/CapsuleComponent.h"
#include "Physics/ABCollision.h"
#include "DrawDebugHelpers.h"
AABTA_Trace::AABTA_Trace()
{
}
void AABTA_Trace::StartTargeting(UGameplayAbility* Ability)
{
Super::StartTargeting(Ability);
//SourceActor는 AGameplayAbilityTargetActor에 선언되어있다.
SourceActor = Ability->GetCurrentActorInfo()->AvatarActor.Get();
}
void AABTA_Trace::ConfirmTargetingAndContinue()
{
if (SourceActor)
{
//Trace 기반이기에 여기서 DataHandle을 만들고 반환한다.
FGameplayAbilityTargetDataHandle DataHandle = MakeTargetData();
//TargetDataReadyDelegate는 AGameplayAbilityTargetActor에 선언되어 있으며 FGameplayAbilityTargetDataHandle대한 인자를 받는다.
TargetDataReadyDelegate.Broadcast(DataHandle);
}
}
FGameplayAbilityTargetDataHandle AABTA_Trace::MakeTargetData() const
{
ACharacter* Character = CastChecked<ACharacter>(SourceActor);
FHitResult OutHitResult;
//임시 하드코딩
const float AttackRange = 100.0f;
const float AttackRadius = 50.0f;
// Sweep 충돌 검사 파라미터 (Trace를 수행한 주체를 무시하도록 설정)
//SCENE_QUERY_STAT은 해당 트레이스의 이름이다.
FCollisionQueryParams Params(SCENE_QUERY_STAT(UABTA_Trace), false, Character);
const FVector Forward = Character->GetActorForwardVector();
const FVector Start = Character->GetActorLocation() + Forward * Character->GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector End = Start + Forward * AttackRange;
// 구체 형태로 Sweep Trace 수행
// Start ~ End 구간을 AttackRadius만큼의 Sphere로 쓸어보면서 CCHANNEL_ABACTION을 가진 Actor 충돌 탐색
//가장 먼저 탐지된 한개 충돌 결과만 반환
bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
FGameplayAbilityTargetDataHandle DataHandle;
//맞은게 있다면
if (HitDetected)
{
// HitResult를 담은 TargetData 생성한다.
//OutHitResult는 SweepSingleByChannel상 하나만 채워지기 때문에 하나의 TargetData가 만들어진다.
FGameplayAbilityTargetData_SingleTargetHit* TargetData = new FGameplayAbilityTargetData_SingleTargetHit(OutHitResult);
// Handle에 추가
DataHandle.Add(TargetData);
}
#if ENABLE_DRAW_DEBUG
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);
}
#endif
return DataHandle;
}
여기서는 하나의 TargetActor를 Sphere Trace로 받아서 Handle에 저장한 후 Delegate로 Broadcast한다.
Trace로 하는 동작이기에 바로 결과를 도출할 수 있어서 ConfirmTargeting에 의해 호출되는 ConfirmTargetAndContinue에서 Data를 수집하였지만 기다려야하는 경우에는 다른 곳에서 작성해야한다.
이제 다음 동작은 Broadcast되면 AT에서 해당 신호를 받아
SpawnedTargetActor->TargetDataReadyDelegate.AddUObject(this, &UABAT_Trace::OnTargetDataReadyCallback);
에 의해 OnTargetDataReadyCallback을 실행한다.
OnTargetDataReadyCallback함수 내부의 OnComplete.Broadcast(DataHandle);를 통해
GA에 DataHandle을 넘기게 되고 어떤 Actor가 맞았는지 GA에서 출력하게 된다.
'Unreal 게임 개발 > Unreal 강의 개인 정리' 카테고리의 다른 글
| 게임플레이 이펙트 & Modifier (+예시) - GAS (0) | 2025.08.24 |
|---|---|
| 캐릭터 어트리뷰트 & 데미지 입히기(Attribute 사용) - GAS (0) | 2025.08.23 |
| Notify로 GA실행 & GameplayAbilityTargetActor - GAS (0) | 2025.08.22 |
| GA에서 AT 사용 방법 (+BP) - GAS (0) | 2025.08.20 |
| 공격 입력처리 & 인스턴싱 옵션 & 어빌리티 태스크 - GAS (3) | 2025.08.20 |