UE5 GAS Gameplay Ability 구현
Gameplay Ability란
섹션 제목: “Gameplay Ability란”UGameplayAbility는 GAS(Gameplay Ability System)에서 캐릭터가 수행할 수 있는 하나의 능력(스킬, 공격, 구르기 등) 을 표현하는 클래스입니다. 활성화 조건, 비용, 쿨다운, 실행 로직을 하나의 객체로 캡슐화합니다.
프로젝트 설정
섹션 제목: “프로젝트 설정”Build.cs에 GAS 모듈을 추가합니다.
PublicDependencyModuleNames.AddRange(new string[]{ "Core", "CoreUObject", "Engine", "GameplayAbilities", "GameplayTasks", "GameplayTags"});기본 Gameplay Ability 구현
섹션 제목: “기본 Gameplay Ability 구현”#pragma once#include "Abilities/GameplayAbility.h"#include "MyGameplayAbility.generated.h"
UCLASS()class MYGAME_API UMyGameplayAbility : public UGameplayAbility{ GENERATED_BODY()
public: UMyGameplayAbility();
// 활성화: 어빌리티 실행 진입점 virtual void ActivateAbility( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
// 취소 처리 virtual void CancelAbility( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility) override;
protected: // 어빌리티 종료 (반드시 호출해야 함) void EndAbilityCleanup( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bWasCancelled);};#include "MyGameplayAbility.h"#include "AbilitySystemComponent.h"
UMyGameplayAbility::UMyGameplayAbility(){ // 인스턴스 정책: 실행마다 새 인스턴스 생성 InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerExecution;
// 네트워크 실행 정책 NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;}
void UMyGameplayAbility::ActivateAbility( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData){ // 비용·쿨다운 확인 후 커밋 (실패 시 자동으로 어빌리티 종료) if (!CommitAbility(Handle, ActorInfo, ActivationInfo)) { EndAbility(Handle, ActorInfo, ActivationInfo, true, true); return; }
// 실제 능력 로직 실행 // ...
// 즉시 종료 (단순 능력) EndAbility(Handle, ActorInfo, ActivationInfo, true, false);}
void UMyGameplayAbility::CancelAbility( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility){ Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility); // 취소 시 정리 로직}근접 공격 어빌리티 예시
섹션 제목: “근접 공격 어빌리티 예시”UCLASS()class UMeleeAttackAbility : public UMyGameplayAbility{ GENERATED_BODY()
public: UMeleeAttackAbility();
void ActivateAbility( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
protected: UPROPERTY(EditDefaultsOnly, Category = "Attack") float _attackRange = 150.f;
UPROPERTY(EditDefaultsOnly, Category = "Attack") float _attackDamage = 30.f;
// 히트 이벤트 태그 UPROPERTY(EditDefaultsOnly, Category = "Attack") FGameplayTag _hitEventTag;
// 애니메이션 몽타주 UPROPERTY(EditDefaultsOnly, Category = "Animation") UAnimMontage* _attackMontage;
private: // 몽타주 이벤트 핸들 FDelegateHandle _montageEventHandle;};#include "MeleeAttackAbility.h"#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
UMeleeAttackAbility::UMeleeAttackAbility(){ InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerExecution;}
void UMeleeAttackAbility::ActivateAbility( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData){ if (!CommitAbility(Handle, ActorInfo, ActivationInfo)) { EndAbility(Handle, ActorInfo, ActivationInfo, true, true); return; }
// 몽타주 재생 태스크 auto* MontageTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy( this, NAME_None, _attackMontage, 1.0f, NAME_None, true);
MontageTask->OnCompleted.AddDynamic(this, &UMeleeAttackAbility::OnMontageCompleted); MontageTask->OnCancelled.AddDynamic(this, &UMeleeAttackAbility::OnMontageCancelled); MontageTask->OnInterrupted.AddDynamic(this, &UMeleeAttackAbility::OnMontageCancelled); MontageTask->ReadyForActivation();
// 히트 이벤트 대기 태스크 (애니메이션 Notify에서 발생) auto* HitEventTask = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent( this, _hitEventTag);
HitEventTask->EventReceived.AddDynamic(this, &UMeleeAttackAbility::OnHitEvent); HitEventTask->ReadyForActivation();}
void UMeleeAttackAbility::OnHitEvent(FGameplayEventData Payload){ // 공격 범위 내 적 탐지 AActor* Owner = GetOwningActorFromActorInfo(); if (!Owner) return;
TArray<FHitResult> Hits; FCollisionShape Sphere = FCollisionShape::MakeSphere(_attackRange);
Owner->GetWorld()->SweepMultiByChannel( Hits, Owner->GetActorLocation(), Owner->GetActorLocation() + Owner->GetActorForwardVector() * _attackRange, FQuat::Identity, ECC_Pawn, Sphere );
for (const auto& Hit : Hits) { AActor* HitActor = Hit.GetActor(); if (HitActor && HitActor != Owner) { // GameplayEffect로 데미지 적용 FGameplayEffectSpecHandle DamageSpec = MakeOutgoingGameplayEffectSpec( DamageEffectClass, GetAbilityLevel());
if (DamageSpec.IsValid()) { DamageSpec.Data->SetSetByCallerMagnitude( FGameplayTag::RequestGameplayTag("Data.Damage"), _attackDamage );
ApplyGameplayEffectSpecToTarget(Handle, CurrentActorInfo, CurrentActivationInfo, DamageSpec, UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(HitActor)); } } }}
void UMeleeAttackAbility::OnMontageCompleted(){ EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, false);}
void UMeleeAttackAbility::OnMontageCancelled(){ EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, true);}쿨다운 설정
섹션 제목: “쿨다운 설정”쿨다운은 GameplayEffect로 구현합니다.
// 어빌리티 생성자에서 쿨다운 GameplayEffect 지정UMeleeAttackAbility::UMeleeAttackAbility(){ // CooldownGameplayEffectClass: 에디터에서 GE_Cooldown_MeleeAttack 설정}GE_Cooldown_MeleeAttack GameplayEffect 설정:
- Duration Policy:
Has Duration - Duration Magnitude: 1.5 (초)
- Granted Tags:
Ability.Cooldown.MeleeAttack
// 쿨다운 남은 시간 조회float GetCooldownRemaining(){ UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo(); if (!ASC) return 0.f;
FGameplayTagContainer CooldownTags; GetCooldownGameplayEffect()->InheritableOwnedTagsContainer.GetAllGameplayTags(CooldownTags);
FGameplayEffectQuery Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(CooldownTags); TArray<float> Durations = ASC->GetActiveEffectsTimeRemaining(Query);
return Durations.Num() > 0 ? Durations.Max() : 0.f;}어빌리티 부여 및 활성화
섹션 제목: “어빌리티 부여 및 활성화”// 캐릭터에 어빌리티 부여void AMyCharacter::GrantAbility(TSubclassOf<UGameplayAbility> AbilityClass, int32 Level){ if (!AbilitySystemComponent || !HasAuthority()) return;
FGameplayAbilitySpec Spec(AbilityClass, Level, INDEX_NONE, this); AbilitySystemComponent->GiveAbility(Spec);}
// 입력 액션으로 어빌리티 활성화void AMyCharacter::OnAttackInput(){ if (!AbilitySystemComponent) return;
FGameplayTagContainer AttackTags; AttackTags.AddTag(FGameplayTag::RequestGameplayTag("Ability.Attack.Melee"));
AbilitySystemComponent->TryActivateAbilitiesByTag(AttackTags);}| 개념 | 역할 |
|---|---|
UGameplayAbility | 능력 로직 정의 |
CommitAbility | 비용·쿨다운 소모 |
EndAbility | 어빌리티 종료 (반드시 호출) |
AbilityTask | 비동기 작업 (몽타주, 이벤트 대기) |
GameplayEffect | 쿨다운·비용·데미지 적용 |
GiveAbility | ASC에 어빌리티 등록 |
GAS Ability는 CommitAbility → 로직 실행 → EndAbility 흐름이 핵심입니다. EndAbility를 누락하면 어빌리티가 종료되지 않아 심각한 버그로 이어집니다.