Input Config / Aura Input Component / Callbacks for Ability Input / Activating Abilities
GameplayAbility를 커스터마이징 가능한 키 입력과 연결해서 사용하기 위한 설계
전체적인 구조는 다음과 같음.
키 입력을 통해 GameplayAbility를 작동시키되, 키 설정을 바꿀 수 있도록 하기 위한 구조임.

AuraGameplayAbility
- GameplayAbility가 스스로 어떤 키에 바인딩 되었는지 알 수 있어야 하므로, FGameplayTag형의 변수 StartupInputTag를 생성해둠.
(최초의 키값이 존재해야 하기 때문에 생성하는 것)
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "AuraGameplayAbility.generated.h"
UCLASS()
class AURA_API UAuraGameplayAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "Input")
FGameplayTag StartupInputTag;
};
Input Config
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "AuraInputConfig.generated.h"
USTRUCT(BlueprintType)
struct FAuraInputAction
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly)
const class UInputAction* InputAction = nullptr;
UPROPERTY(EditDefaultsOnly)
FGameplayTag InputTag = FGameplayTag::EmptyTag;
};
UCLASS()
class AURA_API UAuraInputConfig : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<FAuraInputAction> AbilityInputActions;
const UInputAction* FindAbilityInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound) const;
};
// AuraInputConfig.h
#include "Input/AuraInputConfig.h"
#include "InputAction.h"
const UInputAction* UAuraInputConfig::FindAbilityInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound) const
{
for (auto& AbilityInputAction : AbilityInputActions)
{
if (AbilityInputAction.InputTag == InputTag)
{
return AbilityInputAction.InputAction;
}
}
if (bLogNotFound)
{
UE_LOG(LogTemp, Error, TEXT("UAuraInputConfig::FindAbilityInputActionForTag: No input action found for tag %s"), *InputTag.ToString());
}
return nullptr;
}struct FAuraGameplayTags
{
public:
static const FAuraGameplayTags& Get() { return GameplayTags; }
static void InitializeNativeGameplayTags();
// ...
/* Input Tags */
FGameplayTag InputTag_LMB;
FGameplayTag InputTag_RMB;
FGameplayTag InputTag_1;
FGameplayTag InputTag_2;
FGameplayTag InputTag_3;
FGameplayTag InputTag_4;
private:
static FAuraGameplayTags GameplayTags;
};
EnhancedInputComponent를 상속하는 AuraInputComponent 클래스를 생성.
AuraInputComponent는 기존의 EnhancedInputComponent의 기능을 그대로 사용하되, 템플릿을 통해 GameplayTag를 통해 GameplayAbility를 사용하기 위한 함수 바인딩을 실시하는 함수 BindAbilityAction()을 추가한 클래스임.
#include "CoreMinimal.h"
#include "EnhancedInputComponent.h"
#include "Input/AuraInputConfig.h"
#include "AuraInputComponent.generated.h"
UCLASS()
class AURA_API UAuraInputComponent : public UEnhancedInputComponent
{
GENERATED_BODY()
public:
template<class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
void BindAbilityAction(const UAuraInputConfig* InputConfig, UserClass* Object, PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, HeldFuncType HeldFunc);
};
// PressedFuncType, ReleasedFuncType, HeldFuncType은 바인딩하는 함수
template<class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
inline void UAuraInputComponent::BindAbilityAction(const UAuraInputConfig* InputConfig, UserClass* Object, PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, HeldFuncType HeldFunc)
{
check(InputConfig);
// AuraInputConfig 데이터 에셋에 설정한 FAuraInputAction들을 순회하며
// FAuraInputAction의 UInputAction에 입력 함수들(PressedFuncType, ReleasedFuncType, HeldFuncType)을 바인딩함.
// BindAction() 함수는 마지막 매개변수로 임의 개수의 바인딩 함수 매개변수를 받으므로, FAuraInputAction의 FGameplayTag를 넘겨줌.
// 이를 통해, UInputAction에 매핑된 키가 입력되면, 바인딩 함수에 FGameplayTag를 매개변수로 넘겨 호출되게끔 할 수 있음.
for (const FAuraInputAction& Action : InputConfig->AbilityInputActions)
{
if (Action.InputAction && Action.InputTag.IsValid())
{
if (PressedFunc)
{
BindAction(Action.InputAction, ETriggerEvent::Started, Object, PressedFunc, Action.InputTag);
}
if (ReleasedFunc)
{
BindAction(Action.InputAction, ETriggerEvent::Completed, Object, ReleasedFunc, Action.InputTag);
}
if (HeldFunc)
{
BindAction(Action.InputAction, ETriggerEvent::Triggered, Object, HeldFunc, Action.InputTag);
}
}
}
};
// EnhancedInputComponent.h - BindAction
template<class UserClass, typename... VarTypes> \
FEnhancedInputActionEventBinding& BindAction(const UInputAction* Action, ETriggerEvent TriggerEvent, UserClass* Object, typename HANDLER_SIG::template TMethodPtr< UserClass, VarTypes... > Func, VarTypes... Vars) \
{ \
TUniquePtr<FEnhancedInputActionEventDelegateBinding<HANDLER_SIG>> AB = MakeUnique<FEnhancedInputActionEventDelegateBinding<HANDLER_SIG>>(Action, TriggerEvent); \
AB->Delegate.BindDelegate<UserClass>(Object, Func, Vars...); \
AB->Delegate.SetShouldFireWithEditorScriptGuard(bShouldFireDelegatesInEditor); \
return *EnhancedActionEventBindings.Add_GetRef(MoveTemp(AB)); \
}
AuraInputComponent를 기존의 EnhancedInputComponent 대신 사용해야 하기 때문에, 프로젝트 설정에서 Input Component를 AuraInputComponent로 설정하는 것을 잊어선 안 됨.
키 입력을 통해 GameplayAbility가 실제로 작동하도록 함.
따라서, AuraPlayerController는 AuraAbilitySystemComponent에 대한 참조, AuraInputConfig에 대한 참조 (에디터 내에서 설정 가능하도록)를 지녀야 하며, 입력 액션 바인딩을 통해 AbilitySystemComponent가 입력 액션과 쌍을 이루는 FGameplayTag을 지닌 GameplayAbility를 사용하는 함수를 정의해야 함.
#include "Player/AuraPlayerController.h"
#include "Interaction/EnemyInterface.h"
#include "Input/AuraInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystem/AuraAbilitySystemComponent.h"
// AuraInputComponent에 정의한 InputAction 바인딩용 함수를 호출함.
void AAuraPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
UAuraInputComponent* AuraInputComponent = CastChecked<UAuraInputComponent>(InputComponent);
AuraInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AAuraPlayerController::Move);
AuraInputComponent->BindAbilityAction(InputConfig, this, &ThisClass::AbilityInputTagPressed, &ThisClass::AbilityInputTagReleased, &ThisClass::AbilityInputTagHeld);
}
UAuraAbilitySystemComponent* AAuraPlayerController::GetASC()
{
if (AuraAbilitySystemComponent == nullptr)
{
AuraAbilitySystemComponent = Cast<UAuraAbilitySystemComponent>(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetPawn<APawn>()));
}
return AuraAbilitySystemComponent;
}
// InputAction 바인딩용 함수들
// AuraAbilitySystemComponent의 GameplayAbility를 탐색 및 작동시키는 함수로 연결하는 역할
void AAuraPlayerController::AbilityInputTagPressed(FGameplayTag InputTag)
{
// GEngine->AddOnScreenDebugMessage(1, 5.f, FColor::Red, FString::Printf(TEXT("Pressed Tag: %s"), *InputTag.ToString()));
}
void AAuraPlayerController::AbilityInputTagReleased(FGameplayTag InputTag)
{
if (GetASC() == nullptr) return;
GetASC()->AbilityInputTagReleased(InputTag);
}
void AAuraPlayerController::AbilityInputTagHeld(FGameplayTag InputTag)
{
if (GetASC() == nullptr) return;
GetASC()->AbilityInputTagHeld(InputTag);
}
AuraAbilitySystemComponent는 AuraPlayerController가 호출하는 함수를 통해 실제 GameplayAbility를 작동시켜야 함.
AbilitySystemComponent는 현재 보유한 GameplayAbility들을 보유하는 변수 FGameplayAbilityContainer를 반환하는 GetActivatableAbilities() 함수를 제공함.
또한 GameplayAbility를 작동하도록 시도하는 함수 TryActivateAbility()도 제공함.
따라서 AuraAbilitySystemComponent는 AuraPlayerController로부터 전달받을 수 있는 FGameplayTag를 통해 현재 보유한 AuraGameplayAbility 중 전달받은 Tag와 일치하는 StartupTag(FGameplayTag)를 지닌 GameplayAbility를 작동시키면 됨.
// AuraAbilitySystemComponent.cpp
// 입력이 유지될 때 호출되는 함수. 입력 시작을 포함함.
void UAuraAbilitySystemComponent::AbilityInputTagHeld(const FGameplayTag& InputTag)
{
if (!InputTag.IsValid()) return;
for (FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
{
if (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
{
AbilitySpecInputPressed(AbilitySpec);
if (!AbilitySpec.IsActive())
{
TryActivateAbility(AbilitySpec.Handle);
}
}
}
}
// 입력이 중단(Release)될 때 호출되는 함수
void UAuraAbilitySystemComponent::AbilityInputTagReleased(const FGameplayTag& InputTag)
{
if (!InputTag.IsValid()) return;
for (FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
{
if (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
{
AbilitySpecInputReleased(AbilitySpec);
}
}
}