구현 사항
공격 판정을 별도의 어빌리티 클래스로 분리해 의존성 감소
애니메이션 몽타주의 노티파이를 활용해 원하는 타이밍에 공격을 판정하는 기능 추가
애니메이션 노티파이가 발동되면 판정을 위한 GA를 트리거해 발동
새로운 GA가 발동되면 공격 판정을 위한 AT를 실행
GAS에서 제공하는 타겟 액터를 활용해 물리 공격 판정을 수행
판정 결과를 시각적으로 확인할 수 있도록 드로우 디버그 기능 제공
GA의 Ability Trigger의 Trigger Tag를 지정하면 ASC가 게임플레이 이벤트를 보낼 때 어빌리티를 활성화시켜줌
애니메이션 노티파이로 어빌리티 실행 코드
void UAnimNotify_GASAttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
Super::Notify(MeshComp, Animation, EventReference);
if (MeshComp)
{
if (AActor* Owner = MeshComp->GetOwner())
{
FGameplayEventData PayloadData;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(Owner, TriggerGameplayTag, PayloadData);
}
}
}
게임플레이 타겟 액터(TA)
게임플레이 어빌리티에서 대상에 대한 판정(주로 물리 판정)을 구현할 때 사용하는 특수 액터
타겟 액터가 필요한 이유는 즉각적으로 타겟을 판정하는 것이 아닌 사용자의 최종 확인을 한번 더 거치거나(예, 원거리 범위 공격) 공격 범위 확인을 위한 추가 시각화(WorldReticle)이 필요할 수 있음
StartTargeting : 타겟팅을 시작
ConfirmTargetingAndContinue : 타겟팅을 확정하고 이후 남은 프로세스를 진행
ConfirmTargeting : 태스크 진행 없이 타겟팅만 확정
CancelTargeting : 타겟팅 취소
게임플레이 어빌리티 타겟 데이터
타겟 액터에서 판정한 결과를 담은 데이터
HitResult, 판정된 다수 액터 포인터, 시작 지점, 끝지점을 포함
타겟 데이터 핸들로 타겟 데이터를 여러 개 묶어 전송
AbilityTask와 TargetActor 사이의 흐름
FGameplayAbilityTargetData_SingleTargetHit을 생성할 때 DataHandle.Add를 통해 TSharedPtr을 사용해 넣고 있기 때문에 안전
타겟 액터로 공격 판정 구현 코드
void UABAT_Trace::Activate()
{
Super::Activate();
SpawnAndInitializeTargetActor();
FinalizeTargetActor();
SetWaitingOnAvatar();
}
void UABAT_Trace::OnDestroy(bool bInOwnerFinished)
{
if (SpawnedTargetActor)
{
SpawnedTargetActor->Destroy();
}
Super::OnDestroy(bInOwnerFinished);
}
UABAT_Trace* UABAT_Trace::CreateTask(UGameplayAbility* OwningAbility, TSubclassOf<AABTA_Trace> TargetActorClass)
{
UABAT_Trace* NewTask = NewAbilityTask<UABAT_Trace>(OwningAbility);
NewTask->TargetActorClass = TargetActorClass;
return NewTask;
}
void UABAT_Trace::SpawnAndInitializeTargetActor()
{
SpawnedTargetActor = Cast<AABTA_Trace>(Ability->GetWorld()->SpawnActorDeferred<AGameplayAbilityTargetActor>(TargetActorClass, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn));
if (SpawnedTargetActor)
{
SpawnedTargetActor->SetDrawDebug(true);
SpawnedTargetActor->TargetDataReadyDelegate.AddUObject(this, &ThisClass::OnTargetDataRedayCallback);
}
}
void UABAT_Trace::FinalizeTargetActor()
{
if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
{
const FTransform SpawnTransform = ASC->GetAvatarActor()->GetTransform();
SpawnedTargetActor->FinishSpawning(SpawnTransform);
ASC->SpawnedTargetActors.Push(SpawnedTargetActor);
SpawnedTargetActor->StartTargeting(Ability);
SpawnedTargetActor->ConfirmTargeting();
}
}
void UABAT_Trace::OnTargetDataRedayCallback(const FGameplayAbilityTargetDataHandle& DataHandle)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnComplete.Broadcast(DataHandle);
}
EndTask();
}
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;
// 현재 하드코딩이지만 Attribute를 통해 얻을 수 있음
const float AttackRange = 100.0f;
const float AttackRadius = 50.0f;
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;
bool bHitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
FGameplayAbilityTargetDataHandle DataHandle;
if (bHitDetected)
{
FGameplayAbilityTargetData_SingleTargetHit* TargetData = new FGameplayAbilityTargetData_SingleTargetHit(OutHitResult);
DataHandle.Add(TargetData);
}
#if ENABLE_DRAW_DEBUG
if (bDrawDebug)
{
const FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
const float CapsuleHalfHeight = AttackRange * 0.5f;
const FColor DrawColor = bHitDetected ? FColor::Green : FColor::Red;
DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(Forward).ToQuat(), DrawColor);
}
#endif
return DataHandle;
}
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);
UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
AttackTraceTask->OnComplete.AddDynamic(this, &ThisClass::OnTraceResultCallback);
AttackTraceTask->ReadyForActivation();
}
void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
bool bReplicateEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicateEndAbility, bWasCancelled);
}
'언리얼 > 게임플레이 어빌리티 시스템' 카테고리의 다른 글
언리얼5 게임플레이 이펙트를 활용한 어트리뷰트 변경 (2) | 2024.12.02 |
---|---|
언리얼5 캐릭터 어트리뷰트 설정 (0) | 2024.11.29 |
언리얼5 게임플레이 어빌리티와 태스크를 활용한 콤보 공격과 점프 (0) | 2024.11.26 |
언리얼5 캐릭터의 게임플레이 어빌리티 시스템 캐릭터 입력 처리 (0) | 2024.11.23 |
언리얼5 게임플레이 어빌리티 시스템 기본 설정 방법 (0) | 2024.11.22 |