Gameplay Ability System - Enhanced Input Plugin for Multiplayer Games
In this post we’ll see how to employ the Enhanced Input Plugin alongside the Gameplay Ability System Plugin in multiplayer games.
Introduction
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:
- Create
IAbilityBindingInterface
. - 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.
Input component
Our custom InputComponent
handles the Enhanced Input Plugin internal details and reacts to input presses and releases:
This implementation follows partially the design principles about input binding explored in the Valley of the Ancient Sample by Epic Games.
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 theAbilityInputBinding
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.
Conclusion
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!
Enjoy, vori.