Tutorial: Creating a Combat Ability with Combos¶
This tutorial walks through building a complete three-hit melee combo system. You will create an AbilitySet, implement a GameplayAbility subclass, configure combo windows via anim notifies, bind input with EnhancedInput, and optionally use GameFeature actions for modular content injection.
Overview¶
By the end of this tutorial you will have:
- An AbilitySet DataAsset grouping combat abilities and attributes
- A melee ability subclass that plays montages and applies damage via effect containers
- A three-hit combo chain driven by anim notify states
- EnhancedInput binding so the player attacks with a single button
- GameFeature action configuration for clean content separation
Step 1: Create the AbilitySet¶
Create a new UFWAbilitySet DataAsset at /Game/Data/AbilitySets/AS_CombatMelee.
Granted Attributes¶
Add one entry:
| Attribute Set | Initialization Data |
|---|---|
FWAttributeSet |
DT_DefaultAttributes (your DataTable) |
Granted Abilities¶
Leave empty for now -- we will add the ability after creating it in Step 2.
Granted Effects¶
Add your passive effects:
| Effect Type | Level |
|---|---|
GE_HealthRegen |
1.0 |
GE_StaminaRegen |
1.0 |
Owned Tags¶
Add State.Combat.Ready so the character is tagged as combat-capable when this set is active.
Step 2: Create the Melee Ability¶
Header¶
#pragma once
#include "Abilities/FWGameplayAbility.h"
#include "GA_MeleeCombo.generated.h"
UCLASS()
class UGA_MeleeCombo : public UFWGameplayAbility
{
GENERATED_BODY()
public:
UGA_MeleeCombo();
virtual void ActivateAbility(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility,
bool bWasCancelled) override;
protected:
/** Montages for each combo step */
UPROPERTY(EditDefaultsOnly, Category = "Combo")
TArray<TObjectPtr<UAnimMontage>> ComboMontages;
/** Stamina cost tag for the effect container */
UPROPERTY(EditDefaultsOnly, Category = "Combo")
FGameplayTag StaminaCostTag;
/** Gets the appropriate montage for the current combo index */
UAnimMontage* GetMontageForComboIndex(int32 ComboIndex) const;
};
Implementation¶
#include "GA_MeleeCombo.h"
#include "AbilitySystemComponent.h"
#include "Components/FWComboManagerComponent.h"
UGA_MeleeCombo::UGA_MeleeCombo()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
// Enable the ability queue so players can buffer inputs
bEnableAbilityQueue = false; // We use notify states instead
}
void UGA_MeleeCombo::ActivateAbility(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
return;
}
// Get the combo manager to determine which montage to play
AActor* AvatarActor = GetAvatarActorFromActorInfo();
UFWComboManagerComponent* ComboComp = AvatarActor
? AvatarActor->FindComponentByClass<UFWComboManagerComponent>()
: nullptr;
int32 CurrentCombo = ComboComp ? ComboComp->ComboIndex : 0;
UAnimMontage* Montage = GetMontageForComboIndex(CurrentCombo);
if (!Montage)
{
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
return;
}
// Play the montage
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
if (ASC)
{
ASC->PlayMontage(this, ActivationInfo, Montage, 1.0f);
}
// Increment the combo for the next activation
if (ComboComp)
{
ComboComp->IncrementCombo();
}
}
void UGA_MeleeCombo::EndAbility(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility,
bool bWasCancelled)
{
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
UAnimMontage* UGA_MeleeCombo::GetMontageForComboIndex(int32 ComboIndex) const
{
if (ComboMontages.IsValidIndex(ComboIndex))
{
return ComboMontages[ComboIndex];
}
return ComboMontages.IsEmpty() ? nullptr : ComboMontages[0];
}
Step 3: Create the Attack Montages¶
Create three animation montages for the combo chain:
| Montage | Animation | Purpose |
|---|---|---|
AM_Combo_Slash1 |
Horizontal slash | First hit |
AM_Combo_Slash2 |
Vertical slash | Second hit |
AM_Combo_Slash3 |
Heavy overhead | Third hit (finisher) |
Add Anim Notify States¶
On each montage, add the following notify tracks:
Combo Window (FWComboWindowNotifyState)¶
This notify state defines when the player's input is accepted for chaining to the next combo step.
Place the FW Combo Window Notify State to cover the window after the strike impact but before the recovery finishes. Typical placement: 40--70% of the montage duration.
Trigger Combo (FWTriggerComboNotify)¶
This instant notify triggers the transition to the next combo ability. Place it within the combo window, typically at the point where the animation allows a natural transition.
Ability Queue Window (FWAbilityQueueNotifyState)¶
Optionally add this to allow queuing of non-combo abilities (e.g., a dodge roll) during the montage.
Notify Placement
The combo window and ability queue window can overlap, but they serve different purposes:
- Combo Window -- accepts the same ability input to chain the combo.
- Ability Queue Window -- accepts a different ability input to queue for after this ability ends.
Step 4: Configure the Effect Container¶
On your GA_MeleeCombo Blueprint (or in the CDO), configure the EffectContainerMap:
| Tag Key | Target Type | Effects |
|---|---|---|
Ability.Melee.Hit |
TargetType_UseEventData |
GE_MeleeDamage |
Ability.Melee.Cost |
TargetType_Self |
GE_StaminaCost |
In the ability's damage notification (e.g., from a hit trace or overlap event):
void UGA_MeleeCombo::OnHitConfirmed(const FGameplayEventData& EventData)
{
// Apply the damage container
ApplyEffectContainer(
FGameplayTag::RequestGameplayTag(FName("Ability.Melee.Hit")),
EventData
);
}
Step 5: Set Up Input Binding¶
Create the Input Action¶
- Create a new
UInputActionasset:IA_MeleeAttack. - Set Value Type to
Bool(digital input). - Add it to your Input Mapping Context with the desired key binding (e.g., Left Mouse Button).
Add to the AbilitySet¶
Go back to your AS_CombatMelee AbilitySet and add the ability:
| Ability Type | Level | Input Action | Trigger Event |
|---|---|---|---|
GA_MeleeCombo |
1 |
IA_MeleeAttack |
Started |
Ensure Input Binding Component Exists¶
Your character must have a UFWAbilityInputBindingComponent. If using AFWModularCharacter or adding components manually, verify it is present:
AbilityInputBinding = CreateDefaultSubobject<UFWAbilityInputBindingComponent>(
TEXT("AbilityInputBinding"));
How Input Binding Works
When the ASC grants an ability with an Input Action, it finds the UFWAbilityInputBindingComponent on the avatar actor and calls SetInputBinding(). This registers an Enhanced Input callback that calls AbilityLocalInputPressed/Released on the ASC when the Input Action fires.
Step 6: Grant the AbilitySet¶
On your character's UFWAbilitySystemComponent, add AS_CombatMelee to the Granted Ability Sets array. The set is automatically granted on InitAbilityActorInfo.
In your GameFeature plugin's GameFeatureData asset:
- Add an action: Add Abilities (FW GAS System).
- Configure an entry:
- Actor Class: Your character class.
- Granted Ability Sets:
AS_CombatMelee.
When the GameFeature activates, the combat set is granted. When deactivated, it is cleanly removed.
void AMyCharacter::GrantCombatAbilities()
{
UFWAbilitySystemComponent* ASC = FindComponentByClass<UFWAbilitySystemComponent>();
if (!ASC) return;
// Load the AbilitySet
UFWAbilitySet* CombatSet = LoadObject<UFWAbilitySet>(
nullptr, TEXT("/Game/Data/AbilitySets/AS_CombatMelee.AS_CombatMelee"));
if (CombatSet)
{
FFWAbilitySetHandle Handle;
ASC->GiveAbilitySet(CombatSet, Handle);
// Store handle to remove later if needed
CombatSetHandle = Handle;
}
}
Step 7: Wire Up the Combo Manager¶
Add a UFWComboManagerComponent to your character:
The combo manager automatically:
- Tracks combo state via replicated properties (
ComboIndex,bComboWindowOpened). - Responds to combo window notifies -- when
FWComboWindowNotifyStatefires, it opens/closes the combo window. - Handles combo activation -- when the ASC receives input during an open combo window, it routes through the combo manager instead of creating a new ability activation.
Combo Reset¶
The combo resets automatically when:
- The combo window closes without input.
- The ability ends or is cancelled.
ResetCombo()is called explicitly.
Step 8: Test the Combo Chain¶
- Place your character in the level.
- Ensure the Input Mapping Context with
IA_MeleeAttackis active. - Press Play and press the attack button.
- You should see
AM_Combo_Slash1play. - During the combo window, press attack again to see
AM_Combo_Slash2. - Press again during its combo window for
AM_Combo_Slash3. - After the third hit, the combo resets to the beginning.
Debug Tools¶
Add the debug widgets to verify combo state:
UFWUWDebugComboWidget-- Shows combo index, window state, trigger state.UFWUWDebugAbilityQueue-- Shows queue state, allowed abilities, queued ability.
Use the console command showdebug abilitysystem to see granted abilities, active effects, and tags.
Step 9: Add Damage Application¶
Create a gameplay effect for melee damage:
GE_MeleeDamage¶
| Property | Value |
|---|---|
| Duration Policy | Instant |
| Modifiers[0].Attribute | FWAttributeSet.Damage |
| Modifiers[0].ModOp | Additive |
| Modifiers[0].Magnitude | Scalable Float: 25.0 (or use SetByCaller) |
The UFWAttributeSet::PostGameplayEffectExecute automatically routes Damage meta attribute changes to reduce Health, fires OnDamage on the UFWGASCoreComponent, and triggers OnDeath when Health reaches zero.
Step 10: GameFeature Action Setup (Optional)¶
For modular content, use GameFeature actions to inject the combat system.
GameFeature Plugin Structure¶
GameFeatures/
GF_CombatMelee/
GF_CombatMelee.uplugin
Content/
GF_CombatMelee.uasset (GameFeatureData)
GameFeatureData Configuration¶
Add three actions:
1. Add Abilities¶
| Actor Class | Granted Ability Sets |
|---|---|
AMyCharacter |
AS_CombatMelee |
2. Add Anim Layers¶
Link your combat animation layer class to the character's anim instance.
3. Add Input Mapping Context¶
| Input Mapping Context | Priority |
|---|---|
IMC_Combat |
1 |
When GF_CombatMelee is activated (via Game Features subsystem), the character receives combat abilities, animations, and input bindings. Deactivating the feature cleanly removes everything.
Feature-Driven Design
Using GameFeature actions allows you to build combat systems, magic systems, and crafting systems as independent features that can be toggled at runtime. This is ideal for games with class switching, temporary power-ups, or modular content packs.