GameplayTag를 활용한 InputAction 바인드

김지윤·2025년 5월 25일
0

UE5_GAS

목록 보기
8/22

기존의 바인드 방식과는 조금 다르다.
키 입력이 들어왔을 때 GameplayTag를 활용해서 처리하는 방식으로, 런타임 중에도 키 바인드를 쉽게 바꿀 수 있다.
강사의 말로는 Lyra에서 사용하는 방식이라고 한다.(확실하지 않음)

먼저 태그부터 추가하자.

// AuraGameplayTag.cpp
GameplayTags.InputTag_LMB = UGameplayTagsManager::Get().AddNativeGameplayTag(
	FName("InputTag.LMB"),FString("Input Tag for Left Mouse Button"));
GameplayTags.InputTag_RMB = UGameplayTagsManager::Get().AddNativeGameplayTag(
	FName("InputTag.RMB"),FString("Input Tag for Right Mouse Button"));
GameplayTags.InputTag_1 = UGameplayTagsManager::Get().AddNativeGameplayTag(
	FName("InputTag.1"),FString("Input Tag for 1 key"));
GameplayTags.InputTag_2 = UGameplayTagsManager::Get().AddNativeGameplayTag(
	FName("InputTag.2"),FString("Input Tag for 2 key"));
GameplayTags.InputTag_3 = UGameplayTagsManager::Get().AddNativeGameplayTag(
	FName("InputTag.3"),FString("Input Tag for 3 key"));
GameplayTags.InputTag_4 = UGameplayTagsManager::Get().AddNativeGameplayTag(
	FName("InputTag.4"),FString("Input Tag for 4 key"));

키 입력이 들어왔을 때 어떤 키인지 구분하기 위해 사용하는 태그가 될 것이다.
그 다음은 InputConfig다.

// AuraInputConfig.h
USTRUCT(BlueprintType)
struct FAuraInputAction
{
	GENERATED_BODY()

	UPROPERTY(EditDefaultsOnly)
	const UInputAction* InputAction = nullptr;

	UPROPERTY(EditDefaultsOnly)
	FGameplayTag InputTag = FGameplayTag();
};

UCLASS()
class AURA_API UAuraInputConfig : public UDataAsset
{
	GENERATED_BODY()

public:
	// Tag를 매개변수로 받아 해당하는 InputAction을 아래 구조체 배열에서 탐색, 반환합니다.
	const UInputAction* FindAbilityInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFount);

	// 블루프린트로 확장한 DataAsset에서 이 배열의 값을 초기화합니다.
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TArray<FAuraInputAction> AbilityInputActions;
};

GameplayTag와 InputAction을 구조체로 묶고 배열로 선언한 DataAsset 클래스다.

미리 선언해둔 InputAction을 Tag와 묶어준다.
다음은 InputComponent를 만들어야 한다.

// AuraInputComponent.h
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class AURA_API UAuraInputComponent : public UEnhancedInputComponent
{
	GENERATED_BODY()

public:
	template<class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
	void BindAbilityActions(const UAuraInputConfig* InputConfig, UserClass* Object,
		PressedFuncType PressedFunc, HeldFuncType HeldFunc, ReleasedFuncType ReleasedFunc);

};

template <class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
void UAuraInputComponent::BindAbilityActions(const UAuraInputConfig* InputConfig, UserClass* Object,
	PressedFuncType PressedFunc, HeldFuncType HeldFunc, ReleasedFuncType ReleasedFunc)
{
	check(InputConfig);

	// DataAsset이 갖고 있는 구조체 배열을 통해 InputAction들을 가져온다.
	for (const FAuraInputAction& Action : InputConfig->AbilityInputActions)
	{
		if (Action.InputAction && Action.InputTag.IsValid())
		{
			// InputAction마다 총 3개의 함수를 바인드하는데, 매개변수로 Action.InputTag를 함께 전달한다. 
			// 이 경우 InputAction에 해당하는 함수들(Func들)은 호출될 때 자동으로 Action.InputTag가 매개변수로 들어간다.
			if (PressedFunc)
			{
				BindAction(Action.InputAction, ETriggerEvent::Started, Object, PressedFunc, Action.InputTag);
			}
			
			if (HeldFunc)
			{
				BindAction(Action.InputAction, ETriggerEvent::Triggered, Object, HeldFunc, Action.InputTag);
			}
			
			if (ReleasedFunc)
			{
				BindAction(Action.InputAction, ETriggerEvent::Completed, Object, ReleasedFunc, Action.InputTag);
			}
		}
	}
}

방금 만든 InputConfig와 함수 포인터 3개를 매개변수로 받아 모든 InputAction에게 3개의 함수를 바인드하는 함수를 InputComponent에서 선언한다.
이 때 BindAction 함수를 보면 매개변수로 Tag가 들어가는데, 바인드된 콜백함수가 호출될 때 자동으로 매개변수에 Tag가 들어가게 된다.

그럼 이제 PlayerController로 가서 이 함수를 호출해주자.

// AuraPlayerController.cpp
void AAuraPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	// InputComponent는 ProjectSettings의 Input 탭에서 지정한다.
	UAuraInputComponent* AuraInputComponent = CastChecked<UAuraInputComponent>(InputComponent);

	// InputComponent에게 InputConfig(DataAsset)과 함수 포인터들을 전달
	AuraInputComponent->BindAbilityActions(InputConfig, this,
		&ThisClass::AbilityInputTagPressed, &ThisClass::AbilityInputTagHeld, &ThisClass::AbilityInputTagReleased);
}

InputConfig와 InputComponent를 모두 갖고 있는 PlayerController의 함수다.
InputConfig는 직접 선언해 EditDefaultOnly 태그를 붙여 블루프린트에서 할당해줬다.
그럼 InputComponent는 어떻게 갖고 있는 걸까?

Project Settings의 Input 카테고리를 보면 Default Classes 탭이 있다.
여기에서 직접 넣어준다.
프로젝트 파일 경로에 Config의 DefaultInput에서 할당해주는 거다.
그럼 이제 바인드할 함수 3개를 정의해주자.

// AuraPlayerController.cpp
void AAuraPlayerController::AbilityInputTagPressed(FGameplayTag InputTag)
{
	...
}

void AAuraPlayerController::AbilityInputTagHeld(FGameplayTag InputTag)
{
	...
}

void AAuraPlayerController::AbilityInputTagReleased(FGameplayTag InputTag)
{
	...
}

내부 구현은 이 게시글에선 중요하지 않으니 생략했다.
InputTag를 매개변수로 받아 그에 따른 처리를 하도록 구현되는 함수다.
기존엔 InputAction을 모두 멤버변수로 선언, 해당 InputAction마다 바인드할 함수를 하나씩 선언해 구현하고 붙여줬다.
심지어 이렇게 구현하는 경우 Pressed, Held, Released 이벤트를 모두 따로 바인드해야 돼서 코드 줄 수만 길어지는 게 아니라 함수의 개수 자체도 상당히 많았다.
그러나 이 방식은 같은 함수로 들어와서 InputTag에 따라 다른 처리를 하게 되므로 함수 개수가 많아질 일이 없으며, 읽기도 어렵지 않다.
함수의 코드 줄 수 자체가 길어질 순 있겠으나, IDE는 코드를 접는 기능도 지원하기 때문에

읽는 데에 큰 문제는 없을 것이다.
사실 구현부도 '조건이 만족되면 Ability 사용' 정도만 다루기 때문에, 코드 줄 수가 아무리 길어져봐야 심각한 정도는 아닐 거다.
실제 로직 자체는 Ability가 구현하기 때문이다.

profile
공부한 거 시간 날 때 작성하는 곳

0개의 댓글