Mukund Pareek / mukund pareek
  • Projects
  • Blog
  • Notes
  • Resume

MP Mukund Pareek

Unreal Engine C++ Developer

  • Projects
  • Blog
  • Notes
  • Resume

© 2026 Mukund Pareek. Built with Next.js and Tailwind CSS.

Blog
unreal
lyra
gas
architecture

March 1, 2026

Understanding Lyra's Experience System

Lyra ships with a lot of moving parts. The ULyraExperienceDefinition is the central data asset that drives the whole game mode, but its role isn't immediately obvious from the class hierarchy. Here's a practical breakdown of how the system works, what connects to what, and how to extend it.

What the Experience System Does

Every Lyra map references an ULyraExperienceDefinition asset. When the game state loads this asset, it triggers a cascade of setup steps:

  1. The pawn data (ULyraPawnData) is resolved - this defines which pawn class to spawn, which ability sets to grant, and which input config to use.
  2. Action sets are applied - these add components to actors (useful for adding features like team assignment or respawn handling without subclassing).
  3. Ability sets are granted to the UAbilitySystemComponent on the PlayerState.
Lyra experience loading chain - from GameState to granted abilities.

The Deferred Init Problem

The most confusing part of Lyra's architecture is why ULyraHeroComponent::InitializePlayerInput() doesn't just run in BeginPlay. It runs inside CheckPawnReadyToInitialize(), which gates on:

  • The pawn extension component being valid
  • The controller being set
  • The experience being fully loaded

This is important for dedicated servers: the experience asset is replicated as part of game state. If you set up input bindings before replication completes, the input config asset reference is null and the whole binding chain silently fails.

The fix is always to use the OnExperienceLoaded delegate:

ULyraExperienceManagerComponent* ExperienceComponent =
    GameState->FindComponentByClass<ULyraExperienceManagerComponent>();

ExperienceComponent->CallOrRegister_OnExperienceLoaded(
    FOnLyraExperienceLoaded::FDelegate::CreateUObject(
        this, &AMyCharacter::OnExperienceLoaded));

Granting and Revoking Ability Sets

Ability sets use a handle pattern for clean revocation. Always store the handles:

FLyraAbilitySet_GrantedHandles GrantedHandles;
AbilitySet->GrantToAbilitySystem(ASC, &GrantedHandles, SourceObject);

// Later, on pawn death or experience switch:
GrantedHandles.TakeFromAbilitySystem(ASC);

Forgetting to revoke causes duplicate ability specs on respawn, which produces unpredictable activation behavior.

Adding a Custom Ability Set

  1. Create a ULyraAbilitySet data asset.
  2. Add your UGameplayAbility subclasses to the Granted Gameplay Abilities array.
  3. Reference this asset in your ULyraExperienceDefinition under Ability Sets to Grant.

That's it. Lyra's ULyraPawnData also has a direct Ability Sets array - use that for pawn-specific grants, and the experience-level array for global (per-map) grants.

What I Learned

  • The Experience system is essentially a data-driven dependency injection framework for game features.
  • Never bypass CheckPawnReadyToInitialize - the guards are there because the initialization order is genuinely non-deterministic in multiplayer.
  • Ability set handles are a lightweight RAII pattern - treat them like smart pointers and store them wherever you store the thing that owns the granted abilities.
  • Lyra's ULyraGameFeatureAction_AddAbilities shows a clean pattern for adding abilities without subclassing ULyraExperienceDefinition.