Vault Save System

Attributes

Mark fields on a MonoBehaviour or ScriptableObject - they are scanned automatically when you call the builder overload Vault.Save(key, mb, build) or Vault.Save(key, so).

[Save]

Marks a field for inclusion in Save / Load object scanning.

public class PlayerStats : MonoBehaviour
{
    [Save] public int   health = 100;    // stored at "player.PlayerStats.health"
    [Save] public float speed  = 5f;     // stored at "player.PlayerStats.speed"

    [Save("gold_count")]
    public int gold = 0;                 // stored at "player.gold_count"

    public int transientScore;           // no [Save] - never written
}

[NoSave]

Prevents a member from being saved. Wins over [Save] - useful for suppressing a field inherited from a base class.

[Save][NoSave] public int ignored;  // NoSave wins

[VaultRename]

Renames a [Save] field or property - or a field inside a [Serializable] blob - without losing existing save data, no migration code needed. On load, if Vault can't find the current name in the saved JSON, it falls back to the historical name(s) you list.

[Save]
[VaultRename("hp")]          // this field used to be called "hp"
public int health = 100;

Multiple old names

The attribute takes params string[] and allows multiple attributes, so there are two ways to record more than one previous name:

// (a) several old names in one attribute
[Save]
[VaultRename("health", "hp")]
public int hitPoints;

// (b) stacked attributes - reads as a rename history
[Save]
[VaultRename("health")]
[VaultRename("hp", "hitpoints")]
public int hitPoints;

Resolution order

Say the field evolved hp (v1) → health (v2) → hitPoints (current):

[Save]
[VaultRename("health", "hp")]   // most-recent old name first
public int hitPoints = 100;

On load Vault checks names in this order, and the first one found in the save file wins (it stops searching immediately):

  1. hitPoints - the current name (always checked first)
  2. health - first old name
  3. hp - second old name

Across stacked attributes it reads top → bottom; within a single attribute, left → right.

Order old names newest → oldest

A save file normally holds only one of the historical keys, so order is harmless. It matters when a save somehow contains more than one - legacy or partially-migrated data, or a file hand-edited in the viewer. Then "first match wins" decides which value is used:

// Save file (messy) contains BOTH:  "health": 80,  "hp": 50

[VaultRename("health", "hp")]   // reads "health" = 80  (newer value - correct)
[VaultRename("hp", "health")]   // reads "hp" = 50      (staler value - wrong)

Rule of thumb: list old names newest → oldest, matching the order you actually renamed the field. That way the most recent historical value wins over an older, staler leftover key.