GAS를 활용하여 범위 공격 스킬을 구현해보자.
#include "GA/ABGA_Skill.h"
#include "Character/ABGASCharacterPlayer.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
UABGA_Skill::UABGA_Skill()
{
}
void UABGA_Skill::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
AABGASCharacterPlayer* TargetCharacter = Cast<AABGASCharacterPlayer>(ActorInfo->AvatarActor.Get());
if (!TargetCharacter) return;
ActiveSkillActionMontage = TargetCharacter->GetSkillActionMontage();
if (!ActiveSkillActionMontage) return;
TargetCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
UAbilityTask_PlayMontageAndWait* PlayMontageTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("SkillMontage"), ActiveSkillActionMontage, 1.0f);
PlayMontageTask->OnCompleted.AddDynamic(this, &UABGA_Skill::OnCompleteCallback);
PlayMontageTask->OnInterrupted.AddDynamic(this, &UABGA_Skill::OnInterruptedCallback);
PlayMontageTask->ReadyForActivation();
}
void UABGA_Skill::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
AABGASCharacterPlayer* TargetCharacter = Cast<AABGASCharacterPlayer>(ActorInfo->AvatarActor.Get());
if (TargetCharacter)
{
TargetCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UABGA_Skill::OnCompleteCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
void UABGA_Skill::OnInterruptedCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = true;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
위 코드는 길지만 간단하다.
이 Ability가 실행되고 Montage를 재생한 후 Montage가 끝나면 Ability가 끝난다는 것이다.
그럼 이 Ability를 Character가 받아야한다. 해당 Ability는 무기를 집을 때 사용 가능하다고 가정하자.
void AABGASCharacterPlayer::EquipWeapon(const FGameplayEventData* EventData)
{
if (Weapon)
{
Weapon->SetSkeletalMesh(WeaponMesh);
FGameplayAbilitySpec NewSkillSpec(SkillAbilityClass);
NewSkillSpec.InputID = 3;
//해당 어빌리티가 없으면 GiveAbility를 한다.
if (ASC->FindAbilitySpecFromClass(SkillAbilityClass))
{
ASC->GiveAbility(NewSkillSpec);
}
}
}
void AABGASCharacterPlayer::UnequipWeapon(const FGameplayEventData* EventData)
{
if (Weapon)
{
Weapon->SetSkeletalMesh(nullptr);
FGameplayAbilitySpec* SkillAbilitySpec = ASC->FindAbilitySpecFromClass(SkillAbilityClass);
if (SkillAbilitySpec)
{
//어빌리티 제거
ASC->ClearAbility(SkillAbilitySpec->Handle);
}
}
}
void AABGASCharacterPlayer::GASInputPressed(int32 InputId)
{
FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
if (Spec)
{
Spec->InputPressed = true;
if (Spec->IsActive()) //Ability가 이미 발동이 되었는지 확인한다.
{
//다시 눌러졌음을 알리기만한다.
ASC->AbilitySpecInputPressed(*Spec);
}
else
{
//어빌리티를 실행하고 핸들을 사용해서 제어를 해야하기 때문에 Spec에 있는 Handle 값을 넣어준다.
ASC->TryActivateAbility(Spec->Handle);
}
}
}
이것 또한 지금까지 따라왔으면 간단하다.
넣어둔 SkillAbilityClass를 찾아 없다면 GiveAbility를 하고 무기를 해제할 때 해당 Ability도 해제시켜준다.
그리고 해당 ID에 맞는 값이 눌려졌을 때 TryActivateAbility를 한다.
그렇다면 해당 Ability가 발동하면 현재까진 AnimMontage가 재생이 될 것이다.
AnimMontage에 이전에 제작했던 AnimNotify_GASAttackHitCheck를 추가하면
캐릭터에게 GiveAbility가 되어있는 Ability 중 Tag에 맞는 Ability가 발동할 것이다.

Tag는 다음과 같이 설정했다.
이 Skill Ability도 이전에 제작했던 ABGA_AttackHitCheck를 실행할 것이다.
여기서 문제가 생긴다.
이전에 작성했던 ABGA_AttackHitCheck Trace를 통해서 1명의 Target만 뽑았다.
하지만 범위 공격은 1명의 Target만 뽑는 동작이 아니다.
그렇다면 Trace를 여러명 진행해야한다.
//UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, TargetActorClass);
일단 이전에 CreateTask를 StaticClass()로 하던 것을 TSubclassOf로 Blueprint에서 받은 Class를 사용한다.
그리고 이 AABTA_Trace를 상속받은 클래스를 하나 만든다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/TA/ABTA_SphereMultiTrace.h"
#include "ArenaBattleGAS.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "Abilities/GameplayAbility.h"
#include "GameFramework/Character.h"
#include "Engine/OverlapResult.h"
#include "Physics/ABCollision.h"
#include "DrawDebugHelpers.h"
AABTA_SphereMultiTrace::AABTA_SphereMultiTrace()
{
}
FGameplayAbilityTargetDataHandle AABTA_SphereMultiTrace::MakeTargetData() const
{
ACharacter* Character = CastChecked<ACharacter>(SourceActor);
UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(SourceActor);
if (!ASC)
{
GAS_LOG(LogABGAS, Error, TEXT("ASC Not Found"));
return FGameplayAbilityTargetDataHandle();
}
//겹침 정보를 TArray로 받는다.
TArray<FOverlapResult> Overlaps;
const float SkillRadius = 600.0f;
FVector Origin = Character->GetActorLocation();
FCollisionQueryParams Params(SCENE_QUERY_STAT(AABTA_SphereMultiTrace), false, Character);
//겹친 정보를 Overlaps에 넣는다.
GetWorld()->OverlapMultiByChannel(Overlaps, Origin, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(SkillRadius), Params);
//아래의 FGameplayAbilityTargetData_ActorArray에 TWeakObjectPtr이 들어가기에 따로 배열을 만들어서 넣어준다.
TArray <TWeakObjectPtr<AActor>> HitActors;
for (const FOverlapResult& Overlap : Overlaps)
{
AActor* HitActor = Overlap.OverlapObjectHandle.FetchActor<AActor>();
if (HitActor && !HitActors.Contains(HitActor))
{
HitActors.Add(HitActor);
}
}
//다수의 Actor를 보관하는 타입이다.
FGameplayAbilityTargetData_ActorArray* ActorsDatas = new FGameplayAbilityTargetData_ActorArray();
//SetActors에 WeakPtr이 들어간다.
ActorsDatas->SetActors(HitActors);
#if ENABLE_DRAW_DEBUG
if (bShowDebug)
{
FColor DrawColor = HitActors.Num() > 0 ? FColor::Green : FColor::Red;
DrawDebugSphere(GetWorld(), Origin, SkillRadius, 16, DrawColor, false, 5.f);
}
#endif
return FGameplayAbilityTargetDataHandle(ActorsDatas);
}
다음 코드와 같이 여러 Actor를 감지할 수 있는 Trace를 제작하면 된다.
특이사항은 FGammeplayAbilityTargetData_ActorArray형의 SetActors가 TWeakObjectPtr이 들어간다는 점이다.
그렇기에 위 코드와 같이 TArray로 받은 값을 다시 TWeakObjectPtr<AActor>형 배열에 넣는 것을 확인할 수 있다.
'Unreal 게임 개발 > Unreal 강의 개인 정리' 카테고리의 다른 글
| 스킬 구현 (Cost & CoolTime / 최종 데미지 수동 계산) - GAS (2) | 2025.08.28 |
|---|---|
| 스킬 구현 (데미지 & Cue 부여 / Skill Attribute) - GAS (1) | 2025.08.28 |
| 아이템 상자 제작 - GAS (0) | 2025.08.27 |
| 게임플레이 큐 & 공격 파티클 재생 - GAS (2) | 2025.08.27 |
| 기간형 게임플레이 이펙트 & 무적 구현 & 스택형 버프 - GAS (3) | 2025.08.26 |