Common Abilities¶
These are abilities that appear in nearly every action game. For each pattern, we cover the key setup steps and design decisions. These aren't full tutorials -- they're architectural guidance to get you moving in the right direction.
Stun / Crowd Control¶
Goal: Apply a stun that prevents the target from using abilities and moving.
Key Setup¶
- Create a tag:
CrowdControl.Stun - Create a Duration GE (
GE_Stun) that:- Grants the tag
CrowdControl.Stunto the target (via TargetTagsGameplayEffectComponent) - Duration set via SetByCaller so different abilities can stun for different lengths
- Grants the tag
- On your abilities, add
CrowdControl.Stunto the Activation Blocked Tags -- any ability with this will refuse to activate while stunned - For movement blocking, check for the stun tag in your character movement component:
bool AMyCharacter::CanMove() const
{
return !AbilitySystemComponent->HasMatchingGameplayTag(
FGameplayTag::RequestGameplayTag(TEXT("CrowdControl.Stun")));
}
Variations¶
- Slow: Instead of blocking abilities, apply a movement speed modifier via attribute
- Silence: Block only abilities tagged with
Ability.Type.Spell, not physical attacks - Root: Block movement but allow abilities
- Knockback: Pair the stun GE with a root motion ability task
Stun Immunity¶
Create an Infinite GE that grants Immunity.CrowdControl.Stun. Use the Immunity GE Component on the stun GE to check for this tag.
Sprint¶
Goal: Hold a button to move faster, draining stamina.
Key Setup¶
- Create a passive Gameplay Ability (
GA_Sprint) withInputTag.Sprint - On activation, apply an Infinite GE (
GE_Sprint) that:- Modifies
MoveSpeedattribute (Multiply Additive, e.g., +0.5 for 50% faster) - Applies a periodic stamina cost (Period = 0.2s, modifying
Staminawith negative value) - Grants tag
State.Sprinting
- Modifies
- End condition: End the ability when input is released (use
WaitInputReleasetask) or when stamina reaches 0 - On ability end, the GE is automatically removed, reverting move speed
Input Binding¶
Use the input binding system with InputTag.Sprint. The ability starts on press and you wire the end to release.
Integration with Character Movement¶
Read MoveSpeed attribute in GetMaxSpeed():
float UMyCharacterMovement::GetMaxSpeed() const
{
float Base = Super::GetMaxSpeed();
if (const UMyAttributeSet* Attrs = GetOwnerAttributes())
{
return Base * Attrs->GetMoveSpeed();
}
return Base;
}
Aim Down Sights (ADS)¶
Goal: Hold a button to enter an aiming state with different camera, sensitivity, and movement speed.
Key Setup¶
- Create
GA_AimDownSights-- toggle or hold-based ability - On activation, apply an Infinite GE that:
- Grants
State.Aimingtag - Reduces
MoveSpeed(Multiply Additive, e.g., -0.3 for 30% slower)
- Grants
- Use the
State.Aimingtag in your camera system to switch to ADS camera - Use the tag in your input handling to adjust sensitivity
Camera Integration¶
void AMyCharacter::UpdateCamera(float DeltaTime)
{
bool bAiming = ASC->HasMatchingGameplayTag(
FGameplayTag::RequestGameplayTag(TEXT("State.Aiming")));
TargetFOV = bAiming ? ADSFieldOfView : DefaultFieldOfView;
CurrentFOV = FMath::FInterpTo(CurrentFOV, TargetFOV, DeltaTime, FOVInterpSpeed);
}
Lifesteal¶
Goal: A portion of damage dealt heals the attacker.
Key Setup¶
The cleanest approach is to handle this in the damage Execution Calculation:
- In your damage ExecCalc, after calculating final damage:
- Check if the source has a
Stat.LifestealPercentattribute (or a tag likeBuff.Lifesteal) - Calculate heal amount:
FinalDamage * LifestealPercent - Output the heal as an additional modifier to the source's Health attribute
// In your ExecCalc, after computing FinalDamage:
float LifestealPct = 0.f;
if (SourceTags->HasTag(LifestealTag))
{
LifestealPct = GetCapturedAttributeMagnitude(
LifestealPercentDef, EvaluationParameters);
}
if (LifestealPct > 0.f)
{
float HealAmount = FinalDamage * LifestealPct;
// Apply heal to source (not target)
OutExecutionOutput.AddOutputModifier(
FGameplayModifierEvaluatedData(
UMyAttributeSet::GetHealthAttribute(),
EGameplayModOp::Additive,
HealAmount));
}
Alternative: Separate Heal GE¶
If you prefer not to mix it into the ExecCalc, apply a separate instant heal GE to the source in PostGameplayEffectExecute after damage is applied. This is slightly less efficient but more modular.
Critical Hits¶
Goal: Attacks have a chance to deal bonus damage.
Key Setup¶
Handle crits in the damage Execution Calculation:
- Add
Stat.CritChanceandStat.CritMultiplierattributes - In the ExecCalc, roll for crit:
float CritChance = GetCapturedAttributeMagnitude(CritChanceDef, Params);
float CritMultiplier = GetCapturedAttributeMagnitude(CritMultiplierDef, Params);
bool bCrit = FMath::FRand() < CritChance;
float FinalDamage = BaseDamage * (bCrit ? CritMultiplier : 1.0f);
- Pass the crit flag to the cue via a custom GameplayEffectContext:
FMyGameplayEffectContext* Context = static_cast<FMyGameplayEffectContext*>(
Spec.GetContext().Get());
Context->bIsCriticalHit = bCrit;
- The damage number cue checks
bIsCriticalHitto show a bigger/flashier number.
One-Button Interaction System¶
Goal: A single "interact" button that contextually does different things (open door, pick up item, talk to NPC).
Key Setup¶
- Create
GA_Interact-- triggered byInputTag.Interact - On activation, the ability does a trace or overlap check to find the nearest interactable
- Interactables implement an interface (
IInteractable) withGetInteractionAbility()that returns a specific ability class - The interact ability grants and activates the specific interaction ability, then ends itself
void UGA_Interact::ActivateAbility(...)
{
AActor* Target = FindBestInteractable();
if (!Target) { EndAbility(...); return; }
if (IInteractable* Interactable = Cast<IInteractable>(Target))
{
TSubclassOf<UGameplayAbility> InteractAbility =
Interactable->GetInteractionAbility();
FGameplayAbilitySpecHandle Handle =
ASC->GiveAbility(FGameplayAbilitySpec(InteractAbility));
ASC->TryActivateAbility(Handle);
}
EndAbility(...);
}
Why This Pattern?¶
Each interactable type (door, chest, NPC) gets its own ability class with its own costs, cooldowns, tags, and logic. The interaction system is just a router.
Passive Abilities¶
Goal: Abilities that are always active and provide ongoing effects.
Key Setup¶
- Create the ability with
ActivationPolicyset to activate on granted (or activate manually at game start) - On activation, apply an Infinite GE that provides the passive bonuses
- The ability never calls
EndAbility-- it stays active indefinitely - If the ability is removed (ungranted), it ends and the Infinite GE is cleaned up
void UGA_PassiveHealthRegen::ActivateAbility(...)
{
if (HasAuthority(&CurrentActivationInfo))
{
FGameplayEffectSpecHandle Spec = MakeOutgoingGameplayEffectSpec(
RegenEffectClass, GetAbilityLevel());
ApplyGameplayEffectSpecToOwner(CurrentActivationInfo, Spec);
}
}
Tag-Based Passives¶
Some passives don't even need an ability. An Infinite GE that grants tags and modifies attributes, applied at character initialization, works fine for simple stat bonuses.
Toggle Abilities¶
Goal: Press once to activate, press again to deactivate.
Key Setup¶
- Create the ability with
InputTag.ToggleAbility - Track toggle state using tags:
void UGA_ToggleShield::ActivateAbility(...)
{
if (ASC->HasMatchingGameplayTag(ShieldActiveTag))
{
// Already active -- deactivate
ASC->RemoveActiveGameplayEffectBySourceEffect(
ShieldEffectClass, ASC);
EndAbility(...);
}
else
{
// Activate
ApplyGameplayEffectSpecToOwner(...);
// Don't end -- stay active, wait for next input
WaitInputPress(); // Ability task to listen for next press
}
}
Alternative: Two Abilities¶
Some projects use separate activate/deactivate abilities. The activate ability grants a tag that blocks its own re-activation but allows the deactivate ability. The deactivate ability removes the effect and the blocking tag. This is more verbose but clearer in the ability list.
Related Pages¶
- Damage Pipeline -- the full damage flow
- Buff/Debuff System -- duration effect patterns
- Add an Ability -- step-by-step ability creation
- Dodge Roll Walkthrough -- complete worked example