Your First Ability¶
Time to build something real. We're going to create a complete melee attack ability that touches every piece of GAS — effects, tags, abilities, input, montages, and the damage pipeline. The goal isn't to build a production-ready combat system (we'll get there in Patterns); it's to see the full loop end-to-end so you understand how the pieces connect before deep-diving into any one of them.
What We're Building¶
A basic melee attack that:
- Costs stamina (15 per swing)
- Has a cooldown (1 second)
- Plays an animation (attack montage)
- Deals damage at a specific point in the animation (not on button press)
- Uses SetByCaller for the damage amount (data-driven, not hardcoded)
By the end of this page, you'll press a button, watch your character swing, see a hit register at the right moment in the animation, and watch the target's health drop.
Step 1: Create the Gameplay Effects¶
We create the effects first, before the ability. This is intentional — effects are the data that defines what an ability actually does. The ability is just the orchestrator that decides when and how to apply them. Think of effects as the nouns and the ability as the verb.
All three effects are created in the Unreal Editor. Right-click in the Content Browser and go to Blueprint Class, then select GameplayEffect as the parent class. You can also right-click and search "Gameplay Effect" directly.
GE_Cost_MeleeAttack¶
This effect represents the stamina cost. GAS checks costs automatically before an ability activates — if the character can't afford it, the ability simply won't fire. No manual checks needed.
| Setting | Value |
|---|---|
| Duration Policy | Instant |
| Modifiers[0] — Attribute | YourProjectAttributeSet.Stamina |
| Modifiers[0] — Modifier Op | Add |
| Modifiers[0] — Magnitude | Scalable Float: -15.0 |
Why negative?
Costs are applied as instant effects that add a negative value. GAS doesn't have a "subtract" operation — you add negative numbers. It feels odd at first, but it's consistent: every attribute modification is an additive operation with a signed value.
GE_Cooldown_MeleeAttack¶
The cooldown effect uses GAS's built-in cooldown system. When an ability activates, it applies this effect. While the effect is active, the ability checks for the cooldown tag and blocks re-activation.
| Setting | Value |
|---|---|
| Duration Policy | Has Duration |
| Duration Magnitude | Scalable Float: 1.0 (1 second) |
| GrantedTags | Cooldown.Ability.BasicAttack |
The magic is in the tag. Your ability will be configured to check for Cooldown.Ability.BasicAttack — while this effect is active and that tag is present on the ASC, the ability can't activate. When the 1-second duration expires, the effect is removed, the tag is removed, and the ability is available again. No timers, no manual cleanup.
Tag hierarchy for cooldowns
We use Cooldown.Ability.BasicAttack rather than a flat name. This hierarchy matters — if you later want a "reset all cooldowns" ability, you can check for any tag under Cooldown and remove them all. Plan your tag architecture early.
GE_Damage_Melee¶
This is the damage effect. Instead of hardcoding a damage number, we use SetByCaller — the ability sets the damage value at runtime when it creates the effect spec. This means a single damage effect class can be reused with different damage values, and those values can come from attributes, data tables, or calculations.
| Setting | Value |
|---|---|
| Duration Policy | Instant |
| Modifiers[0] — Attribute | YourProjectAttributeSet.PendingDamage |
| Modifiers[0] — Modifier Op | Add |
| Modifiers[0] — Magnitude Type | Set By Caller |
| Modifiers[0] — Set By Caller Tag | SetByCaller.Damage |
Notice we target PendingDamage, not Health. This is the meta attribute pattern from Project Setup — damage flows into PendingDamage, gets processed in PostGameplayEffectExecute (armor, shields, etc.), and the final result is applied to Health. This gives you a single processing pipeline for all damage sources.
Why SetByCaller instead of a fixed value?
You could hardcode 25.0 as the damage. It would work. But the moment you want a second melee ability with different damage, or want damage to scale with a stat, you'd need a separate effect class. SetByCaller keeps the effect generic — the ability (or an Execution Calculation) sets the actual number. One effect class, many damage values. See SetByCaller for the full picture.
Step 2: Create the Ability Blueprint¶
Create a new Blueprint class in the Content Browser. The parent class should be your YourProjectGameplayAbility (the base ability class from Project Setup).
Name it something like GA_MeleeAttack.
Class Defaults¶
Open the Blueprint and set these in the Class Defaults panel:
| Property | Value | Why |
|---|---|---|
| Input Tag | InputTag.Combat.Primary |
Maps this ability to your primary attack input |
| Ability Tags | Ability.Combat.MeleeAttack |
Identifies this ability — used for queries and blocking |
| Activation Blocked Tags | State.Dead, CrowdControl.Hard |
Can't attack while dead or stunned |
| Cancel Abilities With Tag | (leave empty for now) | Could cancel other abilities on activation |
| Cost Gameplay Effect Class | GE_Cost_MeleeAttack |
GAS checks this automatically before activation |
| Cooldown Gameplay Effect Class | GE_Cooldown_MeleeAttack |
GAS applies this automatically on activation |
Tags you need to create
If these tags don't exist in your project yet, you'll need to create them. Go to Project Settings > Gameplay Tags or add them in a GameplayTags.ini file. The tag names shown here follow the naming conventions used throughout this guide.
Event Graph¶
Here's the ability logic. This runs when the ability activates (after cost and cooldown checks pass):
Event ActivateAbility
│
▼
Play Montage and Wait (AM_MeleeAttack, rate 1.0)
├── On Completed ──────────► End Ability (End on all paths!)
├── On Interrupted ─────────► End Ability
├── On Cancelled ───────────► End Ability
└── On Blend Out ───────────► (optional: End Ability)
Wait Gameplay Event (Tag: Event.Montage.MeleeHit)
│
▼
Make Outgoing Gameplay Effect Spec (GE_Damage_Melee)
│
▼
Assign Set By Caller Magnitude
├── Spec Handle: (from above)
├── Data Tag: SetByCaller.Damage
└── Magnitude: 25.0
│
▼
Apply Gameplay Effect Spec to Target
├── Spec: (from above)
└── Target: Event Data → Target (from Wait Gameplay Event)
- ActivateAbility fires when GAS activates the ability
- Play Montage and Wait — an Ability Task that plays
AM_MeleeAttackand gives you delegates for completion, interruption, and cancellation - Wait Gameplay Event — another Ability Task that listens for a gameplay event with tag
Event.Montage.MeleeHit. This event is sent by an AnimNotify in your montage at the exact frame the weapon should deal damage - Make Outgoing GE Spec — creates a spec from
GE_Damage_Melee, ready to be configured - Assign Set By Caller Magnitude — sets the
SetByCaller.Damagevalue to 25.0 (this is where the damage number lives) - Apply GE Spec to Target — applies the configured damage spec to whoever was hit. The target comes from the gameplay event's payload
Always End Ability
Every code path must call End Ability. If you forget, the ability stays "active" forever — blocking re-activation, holding its slot, and leaking resources. This is the most common ability bug. Connect End Ability to every completion delegate from Play Montage and Wait.
The AnimNotify¶
Open your attack montage (AM_MeleeAttack) in the Montage Editor. At the frame where the weapon should connect with a target, add an AnimNotify that sends a gameplay event:
- Right-click the Notifies track at the desired frame
- Add Notify > AN_SendGameplayEvent (or your custom notify class)
- Set the Event Tag to
Event.Montage.MeleeHit - The event payload should include the target actor (populated by your hit detection logic — a trace, overlap, etc.)
Why Wait Gameplay Event instead of a delay or timer?
Timing damage to a specific animation frame is critical for game feel. A delay node is fragile — if you change the animation speed or swap montages, the timing breaks. Wait Gameplay Event decouples the ability from specific frame timing. The montage itself declares when the hit happens (via the AnimNotify), and the ability reacts to that event. Change the montage, change the timing — the ability code doesn't need to change at all.
Step 3: Wire Up Input¶
You need three things to connect a keyboard/gamepad input to your ability:
1. InputAction Asset¶
Create an Input Action asset in the editor (right-click > Input > Input Action). Name it IA_PrimaryAttack. Set the Value Type to Digital (Bool) — it's a press, not an axis.
2. InputMappingContext¶
Open (or create) your Input Mapping Context (IMC_Default or similar). Add a mapping:
- Input Action:
IA_PrimaryAttack - Key: Left Mouse Button (or whatever you prefer)
3. Route Input to the ASC¶
The connection between Enhanced Input and GAS happens in your character's SetupPlayerInputComponent. The exact implementation depends on your input binding approach, but the core idea is:
When IA_PrimaryAttack fires, you find all granted abilities whose InputTag matches InputTag.Combat.Primary and try to activate them.
// In your character's SetupPlayerInputComponent:
void AYourProjectCharacterBase::SetupPlayerInputComponent(
UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInput =
Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
// Bind the primary attack input action
EnhancedInput->BindAction(
PrimaryAttackAction, // UPROPERTY pointing to IA_PrimaryAttack
ETriggerEvent::Started,
this, &AYourProjectCharacterBase::OnPrimaryAttackInput);
}
}
void AYourProjectCharacterBase::OnPrimaryAttackInput()
{
if (!AbilitySystemComponent) return;
// Find and activate abilities with matching InputTag
for (FGameplayAbilitySpec& Spec :
AbilitySystemComponent->GetActivatableAbilities())
{
if (const UYourProjectGameplayAbility* Ability =
Cast<UYourProjectGameplayAbility>(Spec.Ability))
{
if (Ability->InputTag.MatchesTagExact(
FGameplayTag::RequestGameplayTag(
FName("InputTag.Combat.Primary"))))
{
AbilitySystemComponent->TryActivateAbility(
Spec.Handle);
}
}
}
}
In your Character Blueprint's Event Graph:
- Add an Enhanced Input Action event node for
IA_PrimaryAttack - From the exec pin, call Get Ability System Component on Self
- Call a custom function that iterates activatable abilities and tries to activate any whose Input Tag matches
InputTag.Combat.Primary
Production input systems
The approach above is simplified for clarity. Production projects often use a more scalable pattern — an InputAction-to-Tag mapping table that automatically routes all inputs to abilities by tag, without per-action binding functions. See Input Binding for the full architecture.
Step 4: Grant the Ability to Your Character¶
Open your Character Blueprint (the one based on YourProjectCharacterBase). In Class Defaults, find the Startup Abilities array and add your GA_MeleeAttack class.
That's it. When the character spawns and InitializeAbilities runs (from Project Setup), this ability will be granted to the ASC and ready to activate.
Runtime granting
Startup arrays are for abilities the character always has. For abilities gained later (from equipment, level-ups, pickups), you call AbilitySystemComponent->GiveAbility() at runtime. See Ability Sets for a scalable approach to runtime ability management.
Step 5: Test It¶
Quick Smoke Test¶
- Place your character Blueprint in a level
- Hit Play
- Press your attack button — the character should play the attack montage
- If you have a second character in range, they should take 25 damage
Debugging with ShowDebug¶
Open the console (`) and type:
This overlay shows you everything happening in the ASC in real time:
- Granted Abilities — you should see
GA_MeleeAttacklisted - Active Abilities — lights up while your attack is playing
- Active Effects — you'll see the cooldown effect appear for 1 second after attacking
- Attributes — watch Stamina drop by 15 each attack, Health drop on the target
- Tags — the cooldown tag appears and disappears
What to look for at each step
| You Press Attack | What Should Happen |
|---|---|
| Stamina >= 15, not on cooldown | Montage plays, stamina drops by 15, cooldown tag appears |
| Stamina < 15 | Nothing happens — ability fails cost check |
| On cooldown | Nothing happens — ability blocked by cooldown tag |
| While stunned (has CrowdControl.Hard) | Nothing happens — blocked by Activation Blocked Tags |
| Hit lands on target | Target's PendingDamage receives 25, PostGameplayEffectExecute processes it, Health drops |
Nothing happening?
The most common issues at this stage:
- Ability not granted — Check that
GA_MeleeAttackis in the StartupAbilities array - Input not firing — Check your InputMappingContext is added to the local player's Enhanced Input subsystem
- Montage doesn't play — Make sure the montage's skeleton matches your character's skeleton
- Hit doesn't register — Check that the AnimNotify fires
Event.Montage.MeleeHitand that your hit detection populates the event payload with a target - Damage doesn't apply — Verify the target has an ASC (the target also needs to be a GAS character)
See Troubleshooting for a complete debugging checklist.
What Just Happened?¶
Let's trace the full flow one more time, now that you've seen every piece:
- Input fires
IA_PrimaryAttack - Your input handler finds abilities with
InputTag.Combat.Primaryand callsTryActivateAbility - The ASC checks: Can this ability activate? It evaluates:
- Cost: Do we have 15+ Stamina? (checks
GE_Cost_MeleeAttack) - Cooldown: Is
Cooldown.Ability.BasicAttacktag present? (checksGE_Cooldown_MeleeAttack) - Blocked tags: Does the owner have
State.DeadorCrowdControl.Hard?
- Cost: Do we have 15+ Stamina? (checks
- If all checks pass, the ability activates:
- Cost effect is applied (Stamina -15)
- Cooldown effect is applied (tag granted for 1 second)
ActivateAbilityfires in the Blueprint
- The ability plays a montage and waits for the
Event.Montage.MeleeHitgameplay event - An AnimNotify in the montage sends the event at the right frame
- The ability creates a damage GE spec, sets the damage amount via SetByCaller, and applies it to the target
- The target's
PostGameplayEffectExecuteprocessesPendingDamageand subtracts from Health - The montage completes, the ability calls End Ability
Every piece of GAS was involved. The ASC managed it. Tags controlled flow. Effects modified attributes. The ability orchestrated the sequence. This is the GAS loop.
What's Next¶
You've built an ability that works. But should everything be an ability? Jumping? Sprinting? Opening a door? Head to Should It Be an Ability? for the decision framework you'll use on every feature going forward.