V.3.0.0

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.

Sprint feature demonstration in Lyra using Unreal Engine and Gameplay Ability System

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.

Sprint feature demonstration in Lyra using Unreal Engine and Gameplay Ability System

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.

Blueprint of the GA_Hero_Sprint ability showing the activation and sprint start logic
Blueprint logic for handling Gameplay Event InterruptSprint and stopping the sprint ability

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.

InputData_Hero Data Asset showing the AbilityInputActions configuration for sprint input

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.

AbilitySet_ShooterHero Data Asset showing GrantedGameplayAbilities configuration for Sprint 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 or PostGameplayEffectExecute.

  • 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 into Stamina gain in PostGameplayEffectExecute.

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.

Blueprint of GA_Hero_Sprint showing WaitGameplayEvent node reacting to stamina exhaustion

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.

Gameplay Effect GE_Sprint_Stamina reducing stamina attribute for sprinting in UE5 Lyra project
Sprint ability Blueprint (GA_Hero_Sprint) in Unreal Engine showing CostGameplayEffect setup with GE_Sprint_Stamina

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.

Gameplay Effect GE_Sprint_RegenStamina configuration for passive stamina regeneration in UE5 Lyra project
DefaultGameplayEffects array setup in Character_Default Blueprint enabling automatic stamina regeneration in UE5 Lyra

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.

Gameplay Effect GE_Sprint_StaminaCooldown configuration to delay stamina regeneration after sprinting in UE5 Lyra
Blueprint GA_Hero_Sprint Ability applying StaminaCooldown Gameplay Effect on sprint end in UE5 Lyra

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.

GameplayTagRelationshipMapping Data Asset configuration showing hierarchical tag relationships for blocking and canceling abilities in Lyra UE5

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;
	}
}