Sprint Ability – Advanced Movement System (AMS) Plugin in Lyra UE5
Learn how the Sprint Ability is implemented using the Advanced Movement System (AMS) plugin in Lyra (UE5).
Introduction
This tutorial explains how sprinting is handled through Gameplay Abilities, with a clean separation between input handling, movement logic, and gameplay conditions like blocking.

Configuration Variables
Several key variables used to control sprinting are exposed directly in the CharacterMovementComponent
. They allow you to fine-tune sprinting behavior to match the desired gameplay style.

Gameplay Ability
To handle sprinting in our system, we have a dedicated Gameplay Ability named ULyraGameplayAbility_Sprint
, which is very simple and inherits from the ULyraGameplayAbility
class provided by the Lyra framework.
This ability contains two methods:
StartSprinting
StopSprinting
These methods are called when the ability is activated or ended, and they simply delegate the action to the ACharacter
, which already contains the StartSprinting
and StopSprinting
functions as part of its own movement logic.
/** * ULyraGameplayAbility_Sprint * * Gameplay ability used for character sprinting. */ UCLASS() class LYRAGAME_API ULyraGameplayAbility_Sprint : public ULyraGameplayAbility { GENERATED_BODY() public: ULyraGameplayAbility_Sprint(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); protected: virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const override; virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override; protected: /** Indicates that the player intends to sprint */ UFUNCTION(BlueprintCallable, Category = "Lyra|Ability") void StartSprint(); /** Indicates that the player intends to stop sprinting */ UFUNCTION(BlueprintCallable, Category = "Lyra|Ability") void StopSprinting(); };
In the constructor of this ability, we simply call the parent class constructor. This constructor sets up the ability to allow immediate client-side activation. At the same time, a request is sent to the server, which then validates or cancels the activation based on the game’s rules.
ULyraGameplayAbility::ULyraGameplayAbility(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo; InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor; NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted; NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer; ActivationPolicy = ELyraAbilityActivationPolicy::OnInputTriggered; ActivationGroup = ELyraAbilityActivationGroup::Independent; bLogCancelation = false; ActiveCameraMode = nullptr; }
Although the Gameplay Ability System (GAS) is meant to handle the activation and deactivation of abilities, it should be noted that the CharacterMovementComponent
can change the character's movement mode in certain situations, for example when the character falls off a cliff. In this case, the movement mode automatically switches to MOVE_Falling
, which physically stops the slide by adapting the physics to falling.
In my implementation, I assume that the CharacterMovementComponent
handles this physical movement mode change and the associated cleanup, while I override the OnSprintInterrupted
method to send a Gameplay Event to the Ability System. This allows the Ability to become aware of the interruption and properly terminate itself via GAS.
void ULyraCharacterMovementComponent::OnSprintInterrupted() { SendGameplayEventToSelf(LyraGameplayTags::GameplayEvent_SprintInterrupted); }
That's all for the C++ setup. Now let's move on to the Blueprint part.
In our case, we are using an ability Blueprint named GA_Hero_Sprint
, which inherits from the ULyraGameplayAbility_Sprint
class defined in C++. This ability calls the StartSprinting
method when activated, and StopSprinting
when the ability ends, through a call to EndAbility
.
This Ability Blueprint also listens to the GameplayEvent.Sprint.Interrupted
event emitted by the CharacterMovementComponent
. When this event is received, the ability calls EndAbility
to properly terminate the ability through the GAS system.


Triggering Ability via Player Input
To bind an Ability to player inputs in Lyra, you need to follow two main steps:
Define the input in the Input Data Asset
In the InputData_Hero
Data Asset, I added a new entry to the AbilityInputActions
list.
This entry includes:
An
InputAction
, which corresponds to the specific input action.An
InputTag
, which serves as a semantic identifier to link this input to the corresponding Ability in the system.

Associate the Ability in the Ability Set Data Asset
Then, in the AbilitySet_ShooterHero
Data Asset, I added a new entry to the GrantedGameplayAbilities
list.
This entry contains:
The Ability we want to grant.
The level of this Ability.
The corresponding Input Tag, which links the input to the activation of the Ability.

Thanks to this setup, when the player presses the key associated with sprinting, the event is automatically detected and triggers the activation of the linked Ability.
Stamina Management
To manage sprint stamina, I added a ULyraStaminaSet
component that inherits from the ULyraAttributeSet
component, as well as a dedicated ULyraStaminaComponent
to handle the stamina-related logic.
LyraStaminaSet
An AttributeSet is a class used in the Gameplay Ability System (GAS) to define and store a character's attributes (such as health, stamina, damage, etc.).
It allows you to:
Centralize gameplay-modifiable values.
React to changes through functions like
PreAttributeChange
orPostGameplayEffectExecute
.Automatically handle network replication.
Connect these data to Abilities and GameplayEffects.
Our ULyraStaminaSet
component has 3 attributes:
ATTRIBUTE_ACCESSORS(ULyraStaminaSet, Stamina); ATTRIBUTE_ACCESSORS(ULyraStaminaSet, MaxStamina); ATTRIBUTE_ACCESSORS(ULyraStaminaSet, StaminaRegen); // Delegate when stamina changes, some information may be missing on the client mutable FLyraAttributeEvent OnStaminaChanged; // Delegate when max stamina changes mutable FLyraAttributeEvent OnMaxStaminaChanged; // Delegate to broadcast when the stamina attribute reaches zero mutable FLyraAttributeEvent OnOutOfStamina;
Stamina:
The character’s current stamina value.
Decreases when the character expends energy.
Increases with buffs.
If it reaches 0, it triggers the
OnOutOfStamina
event.
MaxStamina:
The maximum stamina value the character can have.
Serves as an upper limit for
Stamina
.Can be modified by effects (buffs, passives, equipment, etc.).
Often used to display a full stamina bar (Stamina / MaxStamina).
StaminaRegen:
A transient attribute used to regenerate the character’s stamina.
It does not represent a permanent state like
Stamina
but rather a temporary value.When an effect is applied,
StaminaRegen
is set and then converted intoStamina
gain inPostGameplayEffectExecute
.
LyraStaminaComponent
This component allows connection to the AbilitySystemComponent
to access the AttributeSet
containing the stamina attributes.
The connection is made via the InitializeWithAbilitySystem
method, which is responsible for:
Retrieving the
AttributeSet
,Subscribing to the necessary events,
And preparing the stamina management logic.
This method should be called in OnAbilitySystemInitialized
, usually from the LyraCharacter
, to ensure that the ability system is fully ready.
void ALyraCharacter::OnAbilitySystemInitialized() { ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent(); check(LyraASC); HealthComponent->InitializeWithAbilitySystem(LyraASC); StaminaComponent->InitializeWithAbilitySystem(LyraASC); InitializeGameplayTags(); }
The LyraStaminaComponent also has an UninitializeFromAbilitySystem
method, which allows it to cleanly disconnect from the AbilitySystemComponent
.
Its role is to:
🔌 Unsubscribe the component from attribute-related events (such as stamina changes),
🧹 Clean up internal references (e.g., pointers to the AttributeSet, timers, delegates),
🛑 Stop any ongoing logic (like automatic regeneration or active callbacks).
void ALyraCharacter::OnAbilitySystemUninitialized() { HealthComponent->UninitializeFromAbilitySystem(); StaminaComponent->UninitializeFromAbilitySystem(); }
The component also has an important method: HandleOutOfStamina
.
HandleOutOfStamina
acts as a callback, automatically triggered when stamina reaches zero. It is connected to the OnOutOfStamina
event of the AttributeSet
and is used to notify the ability system via a Gameplay Event.
void ULyraStaminaComponent::HandleOutOfStamina(AActor* DamageInstigator, AActor* DamageCauser, const FGameplayEffectSpec* DamageEffectSpec, float DamageMagnitude, float OldValue, float NewValue) { #if WITH_SERVER_CODE if (AbilitySystemComponent && DamageEffectSpec) { // Send the "GameplayEvent.Exhausted" gameplay event through the owner's ability system { FGameplayEventData Payload; Payload.EventTag = LyraGameplayTags::GameplayEvent_Exhausted; Payload.Instigator = DamageInstigator; Payload.Target = AbilitySystemComponent->GetAvatarActor(); Payload.OptionalObject = DamageEffectSpec->Def; Payload.ContextHandle = DamageEffectSpec->GetEffectContext(); Payload.InstigatorTags = *DamageEffectSpec->CapturedSourceTags.GetAggregatedTags(); Payload.TargetTags = *DamageEffectSpec->CapturedTargetTags.GetAggregatedTags(); Payload.EventMagnitude = DamageMagnitude; FScopedPredictionWindow NewScopedWindow(AbilitySystemComponent, true); AbilitySystemComponent->HandleGameplayEvent(Payload.EventTag, &Payload); } // Send a standardized verb message that other systems can observe { FLyraVerbMessage Message; Message.Verb = TAG_Lyra_Exhausted_Message; Message.Instigator = DamageInstigator; Message.InstigatorTags = *DamageEffectSpec->CapturedSourceTags.GetAggregatedTags(); Message.Target = ULyraVerbMessageHelpers::GetPlayerStateFromObject(AbilitySystemComponent->GetAvatarActor()); Message.TargetTags = *DamageEffectSpec->CapturedTargetTags.GetAggregatedTags(); //@TODO: Fill out context tags, and any non-ability-system source/instigator tags //@TODO: Determine if it's an opposing team kill, self-own, team kill, etc... UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(GetWorld()); MessageSystem.BroadcastMessage(Message.Verb, Message); } } #endif // #if WITH_SERVER_CODE }
This allows our GA_Hero_Sprint
ability to be informed of the character’s exhaustion and react accordingly.

Gameplay Effects
To reduce the player’s stamina while sprinting, we use a Gameplay Effect named GE_Sprint_Stamina
. This GE directly modifies the Stamina
attribute with a negative value, allowing the GAS system to automatically check if the player has enough stamina before activating the Ability. It is assigned to the CostGameplayEffectClass
field in the sprint Ability and is applied automatically when CommitAbility
is called. If the available stamina is insufficient, the Ability automatically fails without being activated.


To allow stamina regeneration outside of sprinting, we use a Gameplay Effect called GE_Sprint_RegenStamina
. It modifies the StaminaRegen
attribute, which is then processed in the AttributeSet
to increase the current stamina value.
This Gameplay Effect is added to the DefaultGameplayEffects
array in the Character_Default
blueprint, enabling it to be automatically applied to the character upon initialization, without needing manual activation through an Ability. This system ensures passive stamina regeneration as long as certain conditions are met: the character is not sprinting, is not dead, and no cooldown period is active.


To temporarily prevent stamina regeneration after stopping the sprint, we use a Gameplay Effect called GE_Sprint_StaminaCooldown
. When applied, it adds the tag Status.Sprinting_Cooldown
to the character. This tag is then used as a condition in GE_Sprint_RegenStamina
, via the Must Not Have Tags field, to block regeneration while the cooldown is active. GE_Sprint_StaminaCooldown
is applied in the GA_Hero_Sprint Ability through a call to ApplyGameplayEffectToOwner
triggered in the OnEndAbility
function. This introduces a short delay between the end of sprinting and the resumption of stamina regeneration.


Blocking Sprint While Aiming
By default, after adding sprinting, it was possible to aim while the character was running. However, in most shooter games, aiming with a weapon automatically cancels sprinting for consistency and gameplay reasons.
Aiming is handled by the GA_ADS
ability. So, one might be tempted to block the sprint ability by adding its tag to the BlockAbilitiesWithTag
or CancelAbilitiesWithTag
properties of the aiming ability.
That would work, but Lyra offers a more modular and centralized approach: using a GameplayTagRelationshipMapping
Data Asset, such as the TagRelationships_ShooterHero
file.
This system allows you to define, in a single place, the hierarchical relationships between gameplay tags, and therefore:
Tags that block other abilities,
Tags that cancel certain active abilities,
And even tags that can replace or precede other actions.
By using this Data Asset, you maintain a clean, easily manageable configuration that is decoupled from the ability code. This greatly simplifies the evolution of the project (adding new abilities, changing interactions, etc.), while allowing dynamic adaptation of blocking or canceling rules depending on the game context, such as a specific experience or an activated Game Feature.

And there you have it! Our sprint is automatically blocked as soon as the player aims.
Dynamic Camera During Sprint
To enhance the visual impact of the player's acceleration, I implemented a dynamic camera offset system. This system smoothly pulls the camera back slightly when the character is sliding, improving both readability and immersion.
The implementation follows the same logic used by Lyra to handle crouching, with the methods:
UpdateForCrouchTarget
SetTargetCrouchOffset
UpdateCrouchOffset
1. Initialize Interpolation Values
When a new offset is set (via SetTargetAccelerationOffset
), the variables are prepared for interpolation.
void ULyraCameraMode_ThirdPerson::SetTargetAccelerationOffset(float NewTargetOffset) { AccelerationOffsetBlendPct = 0.0f; InitialAccelerationOffset = CurrentAccelerationOffset; TargetAccelerationOffset = NewTargetOffset; }
2. Triggering Based on Character State
In the UpdateForAccelerationTarget
method, we check whether the player is currently sprinting.
If so, a backward offset of 100.f
is applied using the SetTargetAccelerationOffset
method.
void ULyraCameraMode_ThirdPerson::UpdateForAccelerationTarget(float DeltaTime) { if (const ALyraCharacter* TargetCharacter = Cast<ALyraCharacter>(GetTargetActor())) { if (UAMS_CharacterMovementComponent* CharacterMovement = Cast<UAMS_CharacterMovementComponent>(TargetCharacter->GetCharacterMovement())) { if (CharacterMovement->bIsSprinting) { SetTargetAccelerationOffset(100.f); return; } } } SetTargetAccelerationOffset(0.f); }
3. Smooth Camera Offset Interpolation
Each frame, the UpdateAccelerationOffset
method interpolates the current offset toward the target value using FMath::InterpEaseInOut
, ensuring a smooth transition.
void ULyraCameraMode_ThirdPerson::UpdateAccelerationOffset(float DeltaTime) { if (AccelerationOffsetBlendPct < 1.0f) { AccelerationOffsetBlendPct = FMath::Min(AccelerationOffsetBlendPct + DeltaTime * CrouchOffsetBlendMultiplier, 1.0f); CurrentAccelerationOffset = FMath::InterpEaseInOut(InitialAccelerationOffset, TargetAccelerationOffset, AccelerationOffsetBlendPct, 1.0f); } else { AccelerationOffsetBlendPct = 1.0f; CurrentAccelerationOffset = TargetAccelerationOffset; } }