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]