How to Save and Load GAS Attributes

In most cases, you don’t need to save all the attributes in your AttributeSet. This is because many of them are set by GameplayEffects during the BeginPlay of the game. For instance, attributes like maximum health or stamina are typically determined by the character’s level and are applied consistently, whether it’s a new game or a loaded one.

However, some variable attributes, which are highly dependent on gameplay results, need to be saved and reloaded. For these attributes, the solution is relatively straightforward:

  • Save them in a map of attribute names and values.
  • On game load:
  • Create a master GameplayEffect.
  • For each retrieved name, find the related attribute.
  • Add the attribute to the master GameplayEffect.
  • Apply this effect to your character.

Here’s how you can implement this in code:

void AFirstPersonCharacter::Save(FActorSaveData& SaveData)
{
    // Prepare memory writer and archive for serialization
    FMemoryWriter MemWriter(SaveData.ByteData);
    FObjectAndNameAsStringProxyArchive Ar(MemWriter, true);
    Ar.ArIsSaveGame = true;

    // Create a map to store attribute names and values
    TMap<FString, float> SavedAttributes;
    SavedAttributes.Add(AttributeSet->GetOxygenAttribute().GetName(), AttributeSet->GetOxygen());
    SavedAttributes.Add(AttributeSet->GetUraniumOreAttribute().GetName(), AttributeSet->GetUraniumOre());

    // Serialize the attributes map
    Ar << SavedAttributes;
}

void AFirstPersonCharacter::Restore(FActorSaveData const& SaveData)
{
    // Prepare memory reader and archive for deserialization
    FMemoryReader MemReader(SaveData.ByteData);
    FObjectAndNameAsStringProxyArchive Ar(MemReader, true);
    Ar.ArIsSaveGame = true;

    // Deserialize the attributes map
    TMap<FString, float> SavedAttributes;
    Ar << SavedAttributes;

    // Create a GameplayEffect to restore the saved attributes
    UGameplayEffect* GERestoreSavedData = NewObject<UGameplayEffect>(GetTransientPackage(), FName(TEXT("RestoreSavedData")));

    // Get all attributes from the AttributeSet class
    TArray<FGameplayAttribute> Attributes;
    UAttributeSet::GetAttributesFromSetClass(AttributeSet->GetClass(), Attributes);

    // Lambda to find an attribute by name
    auto FindAttributeByName = [&Attributes](const FString& AttributeName) -> FGameplayAttribute
    {
        for (const FGameplayAttribute& Attribute : Attributes)
        {
            if (Attribute.GetName() == AttributeName)
            {
                return Attribute;
            }
        }
        check(false); // Ensure the attribute is found; consider error management here
        return FGameplayAttribute();
    };

    // Add modifications for each attribute in the map
    for (const auto& AttributePair : SavedAttributes)
    {
        const FGameplayAttribute Attribute = FindAttributeByName(AttributePair.Key);
        if (!Attribute.IsValid())
        {
            continue;
        }

        // Create a modifier for the attribute
        int32 Idx = GERestoreSavedData->Modifiers.Num();
        GERestoreSavedData->Modifiers.SetNum(Idx + 1);
        FGameplayModifierInfo& ModInfo = GERestoreSavedData->Modifiers[Idx];
        ModInfo.Attribute = Attribute;
        ModInfo.ModifierOp = EGameplayModOp::Override;
        ModInfo.ModifierMagnitude = FScalableFloat(AttributePair.Value);
    }

    // Apply the GameplayEffect to the AbilitySystemComponent
    AbilitySystemComponent->ApplyGameplayEffectToSelf(
        GERestoreSavedData,
        1.0f,
        AbilitySystemComponent->MakeEffectContext()
    );
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *