Build.csfile(in my case it's
GameplayAbilitiesTut.Build.cs) and change this
IAbilitySystemInterface. The guide assumes basic programming knowledge, you should know about what an interface does, so I won't get too much into detail, but it allows us to define a pseudo-parent of sorts that defines functions we have to override. This interface here gives other actors an easy way to both know we have an ability system, and a way to get it without doing something dumb and inefficient like iterating through our components for an ability system. Many features will not run properly without this interface implemented. Our code until now would run fine without it, but you will head into trouble once we're done with our initial setup and want to throw buffs and similar things on our character.
GameplayAbilitiesTutCharacter.cpp. We need to actually create the component, and have our pointer point to it. As you will actually create an object of type
UAbilitySystemComponentnow, you must include
"AbilitySystemComponent.h"in your cpp file. Top of the file up to constructor should look a little like this now.
FGameplayAbiliyInputBinds. This is not a typo! It is not called FGameplayAbilityInputBinds, but FGameplayAbiliyInputBinds!
FGameplayAbiliyInputBindstakes at least 3 parameters: The first two are strings, and represent the input names that will be used to define "Confirm" and "Cancel"-input commands. You do not necessarily need these depending on your game, but abilities can be set up to listen to these while they're active, and targeting actors (basically, actors that return an ability viable targets/locations to aim at for an ability, if an ability requests one) will use these too, so generally it can't hurt to have these even if you will never use them. The third parameter is the name of an arbitrary UEnum of all things. This is one of the witchcraft-ier aspects of the system: The ability system component will look into the enum whose name you've given and will map its ability slots to the names of the elements contained within the enum. This probably sounds way complicated from the way I'm describing this, but it's actually quite simple. This is an input enum lifted from my own project:
GameplayAbilitiesTutCharacter's header. You may copy-paste this enum here if you wish, (and this tutorial will do just that), even if 5 slots may be a little overkill for the purpose of example. Finally, our function should look something like this:
SetupPlayerInputComponentfunction, and you should be gravy. You have successfully bound your ability system's ability activation to player input!
TSubclassOf<UGameplayAbility>, because we get all relevant info from the class alone. In fact, GameplayAbilities can be set up to only instance per activation or not to instance at all even, so giving an instance we can freely change beforehand would be a weird idea, anyway.
GiveAbilityfunction. We actually wrap this in an if-statement that first checks if we are authority. If a client tries to give himself an ability, an assert is violated and the game goes to crash and burn, taking the editor with it. You've been warned. Only give abilities on the server... or else! We'll also need to check if Ability is valid, and not NULL/nullptr.
FGameplayAbilitySpecas parameters. An
FGameplayAbilitySpecis the data surrounding a
GameplayAbility, notably which level (the system has built-in support for a level variable, quite good for RPGs/MOBAs as mentioned) and which input ID it is.
GameplayAbilityobject as parameter, but that's not a problem; we can just give the Ability class' default object as parameter. There is very little reason to use anything other than the default object of a
GameplayAbilityclass as far as I've understood it from going through the source. Finally, while on the topic of BeginPlay, we should also call
AbilitySystem->InitAbilityActorInfo. It tells the AbilitySystem what its Owner (the actor responsible for the AbilitySystem) and Avatar (the actor through which the AbilitySystem acts, uses Abilities from etc.) is. In our case our character is both. Our final BeginPlay should look something like this:
EndAbilitynode will prematurely end all pending ability tasks originating from that ability, as well. Because of this, you probably want to place things that HAVE to happen, no matter how the ability ended, in the EndAbility function. One example I could think off probably being purging off a buff that roots you in place as you play your casting animation.
GameplayAbilityTargetActor_SingleLineTraceto do a "hitscan"-type weapon. It fires a ray from the player's origin in the direction they're looking (handled by the GameplayTask). When it hits something, it reports back to the Blueprint graph. The Blueprint graph then draws a pink line based on the origin and ending points of the line trace and ends the ability.
GameplayAbilityTargetActor_SingleLineTraceto determine which Pawn you hit (if any) and apply a GameplayEffect to it, reducing its health or applying buffs of some kind. Speaking of GameplayEffects...
GameplayEffectSpecsto move around, which are huge behemoths of structs that store everything from effect context (what level, who is instigator, who is target, which ability spawned me, why do I even exist?) to reference to the GameplayEffect class that defines most default behaviour and variables to stack count to potential extra modifiers/tags/whatever to pass in alongside what the class reference defines upon applying. In a strange sort of way,
GameplayEffectSpecsare much closer to object instances than the GameplayEffect instances themselves are. As such, if you want to apply a gameplay effect via ability, either apply it directly through a class reference or create a
GameplayEffectSpecwithin the ability and use that to apply a GameplayEffect.
HealthAttribute()is defined using that same macro we used earlier:
InvalidScopedModifierAttributes). These are these calculation-only modifiers I talked about. Basically, you can now easily define each gameplay effect's BaseAttackPower individually by adding/setting BaseAttackPower to a value of choice.
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const overridein your header, and create a fitting definition in your cpp file.
FGameplayEventData, has the following variables:
SendGameplayEventToActor(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)from the
AbilitySystemBlueprintLibraryclass (which is pretty much just one big class of convenience methods exposed to blueprints)
HandleGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload)function directly.
IAbilitySystemInterfaceimplemented for it to work properly. It also does not return the amount of abilities that got triggered by the particular EventTag we use as parameter, though chances are most abilities and systems will very rarely need it. As such, using AbilitySystemBlueprintLibrary's function is often a better idea
ActivateAbilitynode with the Event activation event in your GameplayAbility blueprint. This surrenders your ability to call your ability through conventional means which does not provide the ability with a Gameplay Event struct to work with (such as via action mapping or through TryActivate functions), but you may be okay with this if the ability is meant to be purely passive and response-based.
UAbilitySystemComponent::TargetConfirm()or have Confirm as part of you input binds discussed earlier. You can also call
UAbilitySystemComponent::TargetCancel()or use Cancel from the input binding if you want to give the player the option to see where they're aiming and then stop the ability from doing anything if they change their mind. This is particularly handy if creating abilities that build or place things in the world. If you're doing user confirmed remember to link the cancelled pin to EndAbility to clean things up!
virtual void StartTargeting(UGameplayAbility* Ability) overrideand
virtual void ConfirmTargetingAndContinue() override.
FGameplayAbilityTargetData_LocationInfoand other variants inherit from. So if you want to send a location or two, use
FGameplayAbilityTargetData_LocationInfo, if you want to send some actors, use
FGameplayAbilityTargetData_ActorArray, if you want to send a hitresult, use
FGameplayAbilityTargetData_SingleTargetHit. These cover most common use cases, but let's assume you're a special snowflake and you want to send some other piece of data that isn't covered. Remember, this is your method for pushing data client->server for ability activation, and as such it can be tampered with by cheaters, so be really careful with what you send and what you do with it. I (/u/woppin) use this for sending a float that states how long the button has been held for. The server also has this value, but it's not exact, so it's checked against the player's ping and the value the player sent to make sure it's reasonable. You have been warned.