GAS Ability Activation

Often times, you want to pass in some parameters to an ability. This is generally made easy by activating an ability via a FGameplayEvent data, which is flexible enough.

Activating abilities via one of the other mechanisms TryActivateAbilities(ByTag, ByClass) gives you no real parameterization at all. For this use case I have added a project level extension I call Preactivation events. If provides a function that occurs even prior to CanActivateAbility that transforms a non event activated ability into an event activated one, providing the ability an opportunity to do whatever logic it needs to populate the event with dependent data. This has an added benefit that the event will be replicated to the server. GAS doesn’t provide this out of the box. You can’t make encapsulated abilities that depend on external data.

Some use cases we use this for, Ledge grab and ladder climb, that depend on complex client side traces to determine suitability. It’s not just a boolean about whether it CanActivate, but there are a bunch of transform, positional data that it calculates as part of even determining this boolean state we want to use during the ability execution. And it certainly makes no sense doing it multiple times, in CanActivate, as well as in the ActivateAbility.

Here’s the meat of this added feature for the Preactivation event.

[Image Removed]

A third use case I am finding to be inadequately resolvable in GAS. This is for an ability that I need to provide parameterization to(suggesting an initial activation by event), but the ability also needs to perform some validation/setup logic before it can be activated fully.

Sometimes abilities need to gather a decent chunk of information or do some involved work that the execution of the ability depends on. You can’t do this in CanActivate, it’s const. I can’t do this in my PreactivationEvent, because I’m already activating by event. What I’m looking for is a pathway to mutate the incoming event, to allow the ability to fill out more data that will be used during execution(or fail the activate altogether if it is unsuccessful).

The normal use case seems to be to do this during the ability activation, and just don’t commit the ability and call EndAbility as a failure. TriggerAbilityFromGameplayEvent will return true that an ability activated, even if the ability ended within the scope of that call. This means that AI logic assumes an ability executed, and if it effectively didn’t, this can cause problems with the state tree logic. This basically makes the return value of TriggerAbilityFromGameplayEvent an inadequate representation about whether an ability actually activated, because although the ability might have activated, it also could have bailed out immediately before it was commited.

I don’t want to depend on ability commit(AbilityCommittedCallbacks) because that wouldn’t facilitate a mutated event that would be preserved as part of the activation RPC to the server. Really what I am after is a way to truly keep all the validation work as part of the CanActivate validation. It’s messy to have an ability activate and fail immediately, on conditions that it should be able to validate in the CanActivateAbility. And not only validate, but remember for usage during the activation.

My second idea is to put an mutator function into the process, like a version of CanActivateAbility that can mutate the event data, via UPARAM(ref) FGameplayEventData& EventData, rather than a const parameter.

My questions are

  • What does Epic do for abilities that otherwise would individually do some of its validation and parameter gathering during the ability activation? Do you use the pattern of ActivateAbility -> gather info -> if failed, EndAbility, if succeeded, Commit, etc
  • How does Epic account for the blurred meaning of “an ability activated”, in AI for instance, when the system tells you something activated, and you might have logic that depends on something real having activated, and not a false start sort of activation.
  • Have yall wrestled with the inability to share validation parameter gathering with the execution of the ability? It seems counterproductive for them to be partitioned.

Edit: I just realized that the passing of the gameplay event into CanActivateAbility appears to be one of our engine modifications, related to the preactivate functionality, so really making that mutable is an extension to a modification of our own, but still I would appreciate the above questions answered.

[Attachment Removed]

To start, parameterization of abilities is something we’re actively working on. Our goal is to provide something that “just works” without being overly complicated or cumbersome.

That said, we’ve got a couple of different methods we use internally. For simple things that aren’t used often, simply re-querying the data is pretty straightforward and understandable. Second for slightly more complicated abilities we might activate them through a GE and pass relevant data along in the EventData.

By far the most robust we have though is a custom struct that gets passed around with a custom child ability. This struct has all the information the ability may need and can do a lot of the extra processing itself, so the ability acts like the logic and the struct decides effects, targets, etc… and can store cached data the ability may need.

I’m happy to discuss further options and goals to help address your needs and potentially help guide future design, otherwise I’ll consider this resolved.

[Attachment Removed]

Absolutely, for instances like you’ve described we generally either have that child struct with containers for the various data the ability will need and fill it out in the CanActivateAbility(). There are other places where a separate component is used for this instead, usually when the data needs to be replicated to simulated proxies. Unfortunately with the way abilities currently work this has simply been the default for a while. While we do have a couple structs that can work across a selection of abilities and their dynamic data needs this system does mean that many abilities have a bespoke system set up with it to hold any cached or other dynamic data for the ability.

We are currently working on improvements to this structure as this and similar questions regularly come up regarding data used by abilities in CanActivateAbility, dynamic values needed by GameplayCues on simulated proxies, and more. As you’ve noted this isn’t a simple problem to solve, but the alternative we have of managing the state of a separate object alongside the ability is definitely cumbersome and a potential failure point if things desync.

[Attachment Removed]

Hey [mention removed]​

Thanks for your response.

If possible I’d love to get a bit more detail.

When you say a custom struct in a child ability, I assume you’re still talking about constant data defined in the archetype of the ability, and not dynamic data. Much like the actual blueprint properties would be largely static data on the blueprint class for the ability?

My question is about the dynamic situation. Dynamic data that is needed as part of the execution of the ability, but that is also usually necessary during the CanActivate validation. In fact, ideally that would be where it is calculated, as a matter of encapsulation of responsibilities. It’s quite messy to be unable to make abilities truly standalone and encapsulate their functionality when there is no mechanism out of the box for them to contribute to their own transient activation state.

The Preactivation function I added solves it quite nicely when it comes to non event activated abilities, but more recently, I needed a way for even event activated abilities to mutate their event data. Since the event data is the only piece of state payload available for dynamic parameterization of an ability execution(as part of activation), it’s really the only mutable piece of data that can reasonably hold that state.

To facilitate additional payload, I added FInstancedStruct AdditionalPayload; to FGameplayEventData.

So in the situation of our UGameAbility_LedgeGrab, our strongest example of runtime state unique to each ability activation, its GetPreActivationEvent transforms a non event based activation (TryActivateAbilities(ByTag, ByClass)) into an event based activation.

I realize these are extensions I’ve made to our GAS code, but how would Epic author such an ability? The assumption here is that a series of trace logic would need to run to probe the environment for whether or not its possible to ledge grab, and maybe hand placement, motion warp targets, etc.

You could just activate the ability blindly, and do all this work inside the ability execution, only to abort out if it fails to find what it needs, but this approach means you’re falsely activating an ability, which means that ability may apply tags, even briefly, that can trigger secondary effects, or block other abilities from activating via the same event/input/trigger if you aren’t super careful that you do nothing async in the ability prior to the failure or commit.

I just found it more logical to rely more on the conditional activation(CanActivateAbility) to do full validation that the ability can execute, and in situations where that involves the need to calculate some state on the fly, make the necessary extensions to facilitate that.

This streamlines the execution of the ability, and minimizes redundancy. Only 1 place calculates this state. The ability execution can just use it, and the ability execution doesn’t need to have a ton of logic to calculate it during activation, and you avoid the secondary issues of having abilities activate, even momentarily within the same frame, and all the churn this can cause with respect to tag applications, blockage flags, and the fact that the return value of TriggerAbilityFromGameplayEvent becomes useless in determining if an ability meaningfully activated, when you are doing the heavy lifting of its validation within the execution of the ability, and not within the CanActivateAbility flow as you would probably prefer. Logically, CanActivateAbility()==false if I don’t have suitable transient state, in situations like this. I don’t want the ability to even activate.

I’m interested in understanding how Epic handles these situations, and also a little bit of trying to make a case for a better future approach.

Hope that clarifies the questioning a bit.

Thanks again

[Attachment Removed]

Thanks for the additional detail.

It sounds like yall deal with this in non ideal ways as well(using out of the box GAS at least).

For what it’s worth, extending FGameplayEventData with an FInstancedStruct payload for extensibility, passing it to CanActivateAbility (by ref) so the event can be modified by the ability were simple changes that open up a lot more flexible workflow for abilities. This really only addresses mutable activation data payloads, and not ongoing replication needs, but the activation data is the biggest need we’ve had so far.

This change also allows the deprecation of ShouldAbilityRespondToEvent, which I found beneficial after running into issues [Content removed]

Consolidating determination of ability activation to 1 function is a significant simplification to usage.

[Attachment Removed]

Thanks! I’ve put these in our notes for streamlining ability flow. You’re questions also helped flesh out some test cases we’ll be using as we move forward.

[Attachment Removed]