If you haven’t used the Enhanced Input Plugin, I highly recommend that you do give it a try before reading this article.
This post isn’t a tutorial about the Enhanced Input Plugin, so I encourage readers to explore further on how its interface works. However, for the sake of completion I’ll highlight various features from the Plugin that are relevant for this post:
Input Action: Asset that defines an action in our game and interacts with the input system through Input Mapping Contexts.
BindAction: Binds an Input Action to a function and returns a handle.
RemoveBindingByHandle: Removes the binding associated to the passed handle.
Input binding with GAS
Besides having the Ability System Component in the PlayerState, it is common for each Avatar Actor pawn to have different inputs and skills. Therefore, the responsibility for binding and granting abilities should belong to the Avatar Pawn (or to a component in the Avatar).
Input should always be granted in the local client. However, as mentioned in our post about GAS network optimizations, abilities must be granted to our ASC on the server. Avatar Pawns generally grant abilities in the Pawn’s server PossessedBy(AController* NewController) function, however, they don’t reach the local client until the ASC gets a chance to replicate them, as displayed in the following flowchart of GiveAbility:
As we can see above, OnGiveAbility is called when the local client receives the ability, so we can start manipulating it locally and therefore binding it to an InputAction.
Receiving abilities in the client
To bind an ability right when it’s received in the client, we can do the following:
Override OnGiveAbility to call the interface function when the ability is received.
Implement IAbilityBindingInterface in every pawn that should bind abilities to input actions.
Let’s first create IAbilityBindingInterface:
Now, let’s override OnGiveAbility to add the interface call:
Finally, let’s implement IAbilityBindingInterface in every pawn that should bind abilities to input actions:
At this stage, we can bind Input Actions to Gameplay Abilities. As you can see above, I am using a custom AbilitySet and a component that deals with the binding of the abilities.
AbilitySet to bind abilities
This Section provides a detailed description on how to do a binding once we receive an ability in the client.
The first thing to consider is our AbilitySet, which holds an array of FGameplayAbilityInfo, which is what we’ll configure to determine the Input Actions and Abilities granted to our Pawn:
This is the implementation of the data asset:
This AbilitySet is employed to grant abilities and bind input, however, in this post we are only going to focus on the BindAbility and UnbindAbility functions called in the IAbilityBindingInterface functions implementation in our pawns.
Following next, we provide the implementation of both relevant methods:
As we can see FGameplayAbilitySpec contains information about the ability class, which is all we need to resolve the mapping in the client. The next Section showcases how the input component handles the Ability Binding internally to interact with the Enhanced Input Plugin.
Our custom InputComponent handles the Enhanced Input Plugin internal details and reacts to input presses and releases:
Binding an Ability can be decomposed in the following steps:
Use an increasing ID to assign an Input ID to the incoming AbilitySpec.
Store the InputAction and the AbilitySpec Handle in the AbilityInputBinding map.
Bind the press and release actions and store the handles in the current map entry.
The pressed and the released event are hooked to two functions also present in the Input Component:
As we can see, the assigned InputID will be used to trigger the ability using AbilityLocalInputPressed and AbilityLocalInputReleased.
To unbind abilities the process goes as follows:
Which can be summarised in the following steps:
Find all the entries that should be removed.
Remove the Handle from the bound abilities array from the Ability binding map.
If an entry of our map ends up with 0 bound abilities, we remove said entry of the map.
And with that, you should have a working Input system!
I encourage all my readers to take a look at the custom InputComponent implementation in the Valley of the Ancient Sample by Epic Games, since this article was heavily inspired by how they deal with input in that project.
Well… that was it from my side! I hope you learned something with this article!
Also! This is not the only way to do Input Binding with GAS, I always recommend to do your own research before blindly following any random internet post, so please, do so!
Feel free to ask any question or provide feedback for these posts, you can find me on twitter, DM’s are open! And by the way, there are more articles to come! I won’t spoil anything this time, but you guys will probably predict what it will be about!
Please let me know if I have forgotten something, there’s a substantial amount of code and information and I may have overlooked something.
As a final remark, I would like to highlight the invaluable work my friend Mirror did proofreading this article. If you are interested in literature and art, go support her stuff!