게임플레이 어빌리티의 인스턴싱 옵션
- 상황에 따라 다양한 인스턴스 정책을 지정 가능하다.
- NonInstanced : 인스턴싱 없이 CDO에서 일괄처리
가장 가볍지만 어떤 상태가 부여되는 게임플레이 어빌리티를 만들고 싶을 땐 적합하지 않음.
즉, 즉시 발동되고 상태 추적이 필요없는 간단한 어빌리티(점프)등에 어울리고
지속시간이나 충전형, 실행중인지 판단해야하는 어빌리티에는 적합하지 않다.
이 NonInstanced에 상태를 부착하려고 하면 Error가 난다. - InstancedPerActor : 액터의 ASC마다 하나의 어빌리티 인스턴스를 만들어서 처리함.
이 인스턴스는 Primary Instance이다.
대부분의 경우에서 InstancedPerActor가 가장 무난한 선택이다. - InstancedPerExecution : 발동할 때 마다 인스턴스 생산 (가장 먼저 생성된 것 : Primary Instance)
같은 어빌리티를 동시에 여러 번 돌려야하고 각 실행마다 완전히 독립적인 상태가 필요한 경우에 사용한다.
기본값은 InstancedPerExecution이다.
어빌리티 태스크(AT)의 활용
- 게임플레이 어빌리티(GA)의 실행(Activation)은 한 프레임에서 이루어진다.
- 게임플레이 어빌리티(GA)가 시작되면 Cancel or EndAbility함수가 호출되기까지는 발동이 되어있는 상태로 남아있음.
- 애니매이션 재생 같이 시간이 소요되고 상태를 관리해야하는 어빌리티의 구현 방법
- 비동기적으로 작업을 수행하고 끝나면 결과를 통보받는 형태
- 이를 위해 GAS는 AT를 제공하고 있음. - GA는 필요에 따라 다수의 AT를 사용해 복잡한 로직을 설계할 수 있음.
- UE5 - Plugins - Runtime - GameplayAbilities - Source - GameplayAbilities - Private - Abilities - Task
로 들어가면 다양한 Task가 있는 것을 확인할 수 있다.
어빌리티 태스크 활용 패턴
- 어빌리티 태스크에 작업이 끝나면 브로드캐스팅되는 종료 델리게이트를 선언함.
- GA는 AT를 생성한 후 바로 종료 델리게이트를 구독함.
- GA의 구독 설정이 완료되면 AT를 구동 : AT의 ReadyForActivation 함수 호출
- AT의 작업이 끝나면 델리게이트를 구독한 GA의 콜백 함수가 호출된다.
- GA의 콜백함수가 호출되면 GA의 EndAbiltiy 함수를 호출해 GA를 종료.
이 Task를 활용해서 공격을 제작해보자.
#include "GA/ABGA_Attack.h"
#include "Character/ABCharacterBase.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "ArenaBattleGAS.h"
UABGA_Attack::UABGA_Attack()
{
//인스턴싱 옵션을 InstancedPerActor로 설정
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}
void UABGA_Attack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
AABCharacterBase* ABCharacter = CastChecked<AABCharacterBase>(ActorInfo->AvatarActor.Get());
//공격 중 움직이지 못하도록 설정.
ABCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
//이 PlayMontageAndWait Task는 Montage를 재생하고 끝날 때 까지 기다리는 Task이다.
// 아래처럼 작성하면 Montage를 재생하고 끝날 때 까지 기다리는 Task를 생성한 것이다.
//파라미터는 Task를 소유하고 있는 어빌리티에 대한 정보, Task를 지정할 수 있는 고유 이름, 재생할 Montage이다.
UAbilityTask_PlayMontageAndWait* PlayAttackTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayAttack"), ABCharacter->GetComboActionMontage());
//Completed, Interrupted Delegate에 함수 bind
PlayAttackTask->OnCompleted.AddDynamic(this, &ThisClass::OnCompleteCallback);
PlayAttackTask->OnInterrupted.AddDynamic(this, &ThisClass::OnInterruptedCallback);
//Task를 활성화 시킨다. 활성화 시키지 않으면 Task는 동작하지 않는다.
PlayAttackTask->ReadyForActivation();
}
void UABGA_Attack::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
Super::InputPressed(Handle, ActorInfo, ActivationInfo);
GAS_LOG(LogABGAS, Log, TEXT("Begin"));
}
void UABGA_Attack::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);
}
void UABGA_Attack::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
AABCharacterBase* ABCharacter = CastChecked<AABCharacterBase>(ActorInfo->AvatarActor.Get());
//다시 움직일 수 있게 설정
ABCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}
void UABGA_Attack::OnCompleteCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
void UABGA_Attack::OnInterruptedCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = true;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
위와 같이 Attack GA를 만들어준다.
ActivateAbility에서 UAbilityTask_PlayMontageAndWait를 생성하여 Montage 재생이 끝나고 OnCompleteCallback를 실행할 수 있도록 한다.
주의할 점은 PlayAttackTask->ReadyForActivation()을 하지 않으면 Task는 동작하지 않는다.
AddDynamic에 Bind한 Delegate는 Task에 다음과 같이 있다.
UCLASS(MinimalAPI)
class UAbilityTask_PlayMontageAndWait : public UAbilityTask
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FMontageWaitSimpleDelegate OnCompleted;
UPROPERTY(BlueprintAssignable)
FMontageWaitSimpleDelegate OnBlendedIn;
UPROPERTY(BlueprintAssignable)
FMontageWaitSimpleDelegate OnBlendOut;
UPROPERTY(BlueprintAssignable)
FMontageWaitSimpleDelegate OnInterrupted;
UPROPERTY(BlueprintAssignable)
FMontageWaitSimpleDelegate OnCancelled;
...
이제

Tag에 공격중인지 혹은 점프중인지 확인하는 Tag를 가져오고

Blueprint에서 지정해주면 된다.
이러한 Tag를 활용하여 현재 어떤 어빌리티가 발동되었는지 확인할 수 있다.
이건 Blueprint에서 해주는 것이 좋은데 이후에 Tag가 변경되거나 수정되었을 때 C++에서 하면 컴파일하는 과정이 필요하기에
번거로워 Blueprint에서 하는 것이 편리하다.
Jump를 초기에 Non Instanced로 해서
LogBlueprint: Error: [AssetLog] D:\GAS_Tuto\UnrealGAS1-main\Content\ArenaBattleGAS\Blueprint\GA\BPGA_CharacterJump.uasset: [Compiler] Gameplay Ability Instancing Policy is NonInstanced which is deprecated. Use InstancedPerActor. Use CVar AbilitySystem.Fix.AllowNonInstancedAbilities to temporarily allow this during fixup.
다음과 같은 에러가 났는데

Instancing Policy를 Instanced Per Actor로 지정하면 에러가 사라진다.
그럼 Tag가 부착되었는지 어떻게 확인 가능할까?
Tag 부착 확인 방법
`를 누르고 showdebug abilitysystem 작성하고 Enter를 하면

다음과 같이 GAS 프레임워크의 상태를 실시간으로 보여주며
Owned Tags도 나오는 것을 확인할 수 있다.
APlayerController* PlayerController = CastChecked<APlayerController>(NewController);
PlayerController->ConsoleCommand(TEXT("showdebug abilitysystem"));
또는 C++에서 다음과 같이 필요한 곳에 입력해주면 ConsoleCommand가 들어가서 매번 치지 않아도 나타난다.
showdebug abilitysystem Debug Tip
해당 상태에서 PageUp, PageDown을 하면 어떤 캐릭터의 GAS를 보여줄 지 정할 수 있는데
PageUp, Down을 해도 OwnedTag는 플레이어에게 고정되어있다.
이를 바꾸기 위해
DefaultGame.ini에 해당 코드를 추가하면
PageUp, Down을 했을 때 다른 캐릭터의 상태를 Debug하게 된다.
[/Script/GameplayAbilities.AbilitySystemGlobals]
bUseDebugTargetFromHud = True
연속 공격 구현

연속 공격에 사용할 Data는 다음과 같이 되어있다.
void UABGA_Attack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
...
// 1.0f는 속도 배율, GetNextSection을 통해 다음 Section을 불러들인다. (연속공격)
UAbilityTask_PlayMontageAndWait* PlayAttackTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayAttack"), ABCharacter->GetComboActionMontage(), 1.0f, GetNextSection());
...
StartComboTimer();
}
void UABGA_Attack::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
Super::InputPressed(Handle, ActorInfo, ActivationInfo);
GAS_LOG(LogABGAS, Log, TEXT("Begin"));
if (!ComboTimer.IsValid())
{
HasNextComboInput = false;
}
else
{
HasNextComboInput = true;
}
}
...
FName UABGA_Attack::GetNextSection()
{
CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, CurrentComboData->MaxComboCount);
FName NextScection = *FString::Printf(TEXT("%s%d"), *CurrentComboData->MontageSectionNamePrefix, CurrentCombo);
return NextScection;
}
void UABGA_Attack::StartComboTimer()
{
int32 ComboIndex = CurrentCombo - 1;
ensure(CurrentComboData->EffectiveFrameCount.IsValidIndex(ComboIndex));
const float ComboEffectiveTime = CurrentComboData->EffectiveFrameCount[ComboIndex] / CurrentComboData->FrameRate;
if (ComboEffectiveTime > 0.0f)
{
GetWorld()->GetTimerManager().SetTimer(ComboTimer, this, &UABGA_Attack::CheckComboInput, ComboEffectiveTime, false);
}
}
void UABGA_Attack::CheckComboInput()
{
ComboTimer.Invalidate();
if (HasNextComboInput)
{
//아래 함수는 GA에 있기 때문에 Animation Instance를 가져올 필요가 없다.
MontageJumpToSection(GetNextSection());
StartComboTimer();
HasNextComboInput = false;
}
}
ActivateAbility의 Task를 만드는 곳에서 Rate와 StartSection을 추가한다.
StartComboTimer는 공격을 시작하고 Data에 있는 EffectiveFrameCount Frame안에 입력이 들어오는지 체크하는
CheckComboInput을 일정 시간 뒤에 실행한다.
이 CheckComboInput이 실행되기 이전에 Input이 들어와서 InputPressed가 실행되면 HasNextComboInput = true로 하여
Input이 들어옴을 체크한다.
그리고 CheckComboInput()이 실행되면 다음 Section으로 Jump하여 다음 Section을 실행한다.
UAbilityTask_PlayMontageAndWait는 지정한 Montage를 한 번만 재생(start)하며, 시작 Section부터 재생을 시작합니다.
이후 Montage_JumpToSection()을 통해 다른 Section으로 이동하더라도, 이는 같은 Montage의 재생 흐름 안에서의 위치 이동일 뿐이므로 Montage 전체가 끝나야만 OnCompleted Delegate가 Broadcast됩니다.
따라서, Jump한 Section이 끝나면서 Montage가 종료되어야 OnCompleted가 호출됩니다.
이렇게 연속 공격도 구현이 가능하다.
'Unreal 게임 개발 > Unreal 강의 개인 정리' 카테고리의 다른 글
| Notify로 GA실행 & GameplayAbilityTargetActor - GAS (0) | 2025.08.22 |
|---|---|
| GA에서 AT 사용 방법 (+BP) - GAS (0) | 2025.08.20 |
| GA Spec & 점프 입력처리 - GAS (0) | 2025.08.19 |
| Gameplay Tag - GAS (1) | 2025.08.19 |
| GAS 및 GA - GAS (0) | 2025.08.18 |