// Copyright (c) 2026 Reignance Studios. MIT License, see LICENSE. #pragma once #include "CoreMinimal.h" #include "Engine/DataAsset.h" #include "EnhancedInputComponent.h" #include "GameplayTagContainer.h" #include "RSAbilityInputConfig.generated.h" class UInputAction; USTRUCT(BlueprintType) struct FRSAbilityInputBinding { GENERATED_BODY() UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr InputAction = nullptr; /** Abilities whose asset tags include this tag start on press, e.g. Ability.Dash. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input", meta = (Categories = "Ability")) FGameplayTag AbilityTag; }; /** * Maps Enhanced Input actions to ability tags, so abilities are bound by tag instead of by * input ID. Press activates matching abilities (or forwards the press to an active one), * release forwards to WaitInputRelease. See URSAbilitySystemLibrary::PressAbilityInputByTag. * * In C++, from SetupPlayerInputComponent: * * InputConfig->BindAbilityActions(CastChecked(PlayerInputComponent), * this, &AMyCharacter::AbilityInputPressed, &AMyCharacter::AbilityInputReleased); * * void AMyCharacter::AbilityInputPressed(FGameplayTag Tag) { URSAbilitySystemLibrary::PressAbilityInputByTag(this, Tag); } * void AMyCharacter::AbilityInputReleased(FGameplayTag Tag) { URSAbilitySystemLibrary::ReleaseAbilityInputByTag(this, Tag); } * * In Blueprint, call Press/Release Ability Input By Tag from the Started and Completed pins. */ UCLASS(BlueprintType, Const) class RSGASHELPERS_API URSAbilityInputConfig : public UDataAsset { GENERATED_BODY() public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input", meta = (TitleProperty = "AbilityTag")) TArray AbilityInputs; /** The tag bound to an action, or an empty tag. */ UFUNCTION(BlueprintPure, Category = "RS|GAS|Input") FGameplayTag FindAbilityTagForAction(const UInputAction* InputAction) const; /** * Binds every entry: Started calls Pressed(Tag), Completed and Canceled call Released(Tag). * Started fires once per press, so a held key does not retrigger the ability every frame. */ template void BindAbilityActions(UEnhancedInputComponent* Input, UserClass* Object, PressedFunc Pressed, ReleasedFunc Released) const { check(Input); for (const FRSAbilityInputBinding& Binding : AbilityInputs) { if (!Binding.InputAction || !Binding.AbilityTag.IsValid()) { continue; } Input->BindAction(Binding.InputAction, ETriggerEvent::Started, Object, Pressed, Binding.AbilityTag); Input->BindAction(Binding.InputAction, ETriggerEvent::Completed, Object, Released, Binding.AbilityTag); Input->BindAction(Binding.InputAction, ETriggerEvent::Canceled, Object, Released, Binding.AbilityTag); } } };