Lyra is as amazing as it is difficult to work with, and this is a solution that’s taken me far too long to develop.
I wanted to let players switch hero classes at runtime with an entirely new class, inventory/equipment, abilities, effects, and cues. Lyra will fight you hard if you don’t do it properly as it has multiple listeners that try to ensure the pawn state remains consistent even with poor network conditions. The good news is that if you know how to do it right, it’s easy!
Note: This will require creating a simple C++ function in the LyraPlayerState class. There’s no way around it, as too many of the necessary parts are not exposed to blueprint.
Lyra’s player pawns are set by a LyraPawnData variable inside the LyraPlayerState class. For an example, see “Default Pawn Data” in a Lyra Experience Definition. It contains the pawn’s class, abilities, tag relationships, input configuration, and camera. Upon pawn reset, the game automatically initializes the pawn with these elements. What the game does NOT do well is provide a way to effectively “uninitialize” the pawn so players can switch to a new one. To that end, this function will take in a new PawnData, unset the existing pawn’s abilities, replace the old PawnData with the new, add the new data’s abilities back in, and then restart the controller.
LyraPlayerState.h
public:
UFUNCTION(BlueprintCallable, Category = "Lyra|PlayerState")
void ForceNewPawnData(ULyraPawnData* NewData);
LyraPlayerState.cpp
#include "Inventory/LyraInventoryManagerComponent.h"
#include "Equipment/LyraQuickBarComponent.h"
void ALyraPlayerState::ForceNewPawnData(ULyraPawnData* NewData)
{
if (NewData)
{
// (1) Clear abilities, cues, effects, quick bar, and inventory
if (AbilitySystemComponent)
{
// Clear abilities
for (const FGameplayAbilitySpec& AbilitySpec : AbilitySystemComponent->GetActivatableAbilities())
{
AbilitySystemComponent->ClearAbility(AbilitySpec.Handle);
}
// Clear cues
AbilitySystemComponent->RemoveAllGameplayCues();
// Clear effects
FGameplayEffectQuery Query;
AbilitySystemComponent->RemoveActiveEffects(Query);
}
// Clear quick bar
if (ULyraQuickBarComponent* QuickBar = GetLyraPlayerController()->FindComponentByClass<ULyraQuickBarComponent>())
{
for (int32 SlotIndex = 0; SlotIndex < QuickBar->GetSlots().Num(); ++SlotIndex)
{
QuickBar->RemoveItemFromSlot(SlotIndex);
}
}
// Clear inventory
if (ULyraInventoryManagerComponent* InventoryManager = GetLyraPlayerController()->FindComponentByClass<ULyraInventoryManagerComponent>())
{
TArray<ULyraInventoryItemInstance*> AllItems = InventoryManager->GetAllItems();
for (ULyraInventoryItemInstance* ItemInstance : AllItems)
{
InventoryManager->RemoveItemInstance(ItemInstance);
}
}
// (2) Set new pawn data
PawnData = NewData;
// (3) Kill old pawn
if (AController* Controller = GetOwningController())
{
if (APawn* CurrentPawn = Controller->GetPawn())
{
CurrentPawn->Destroy();
}
}
// (4) Load abilities of the new pawn into this PlayerState's ASC
for (const ULyraAbilitySet* AbilitySet : PawnData->AbilitySets)
{
if (AbilitySet)
{
AbilitySet->GiveToAbilitySystem(AbilitySystemComponent, nullptr);
}
}
// (5) Load abilities from the current experience's action sets
if (ULyraExperienceManagerComponent* ExperienceManager = GetWorld()->GetGameState()->FindComponentByClass<ULyraExperienceManagerComponent>())
{
if (const ULyraExperienceDefinition* CurrentExperience = ExperienceManager->GetCurrentExperienceChecked())
{
for (const UGameFeatureAction* Action : CurrentExperience->Actions)
{
if (const UGameFeatureAction_AddAbilities* AddAbilitiesAction = Cast<UGameFeatureAction_AddAbilities>(Action))
{
for (const FGameFeatureAbilitiesEntry& Entry : AddAbilitiesAction->AbilitiesList)
{
for (const FLyraAbilityGrant& Ability : Entry.GrantedAbilities)
{
if (!Ability.AbilityType.IsNull())
{
FGameplayAbilitySpec NewAbilitySpec(Ability.AbilityType.LoadSynchronous());
AbilitySystemComponent->GiveAbility(NewAbilitySpec);
}
}
for (const TSoftObjectPtr<const ULyraAbilitySet>& SetPtr : Entry.GrantedAbilitySets)
{
if (const ULyraAbilitySet* Set = SetPtr.Get())
{
Set->GiveToAbilitySystem(CastChecked<ULyraAbilitySystemComponent>(AbilitySystemComponent), nullptr);
}
}
}
}
}
}
}
// (6) Restart controller to spawn and possess new pawn
if (AController* Controller = GetOwningController())
{
if (AGameModeBase* GameMode = GetWorld()->GetAuthGameMode<AGameModeBase>())
{
GameMode->RestartPlayer(Controller);
}
}
}
else
{
PawnData = nullptr;
PawnData = Cast<ALyraGameMode>(GetWorld()->GetAuthGameMode())->GetPawnDataForController(GetLyraPlayerController());
}
}
This function is blueprint accessible so you can add the node to a Character Selection UI.