A Unity system for separating static game content from dynamic player state.
Entry Blueprint - A ScriptableObject that defines game content (abilities, items, quests). Designers build these in the Inspector by adding Properties.
Runtime Entry - An instance created from a Blueprint. Handles deserialization, runtime modifications, and re-serialization. The Blueprint itself is never modified.
Property - A small data structure that stores a specific type of game data. Properties are added to Blueprints to compose functionality.
- Design - Designer creates an Entry Blueprint and adds Properties in the Inspector
- Runtime - Game creates a Runtime Entry from the Blueprint
- Gameplay - Properties are modified during play
- Save - Modified properties are serialized to storage
- Load - Saved data is deserialized and applied to a fresh Runtime Entry created from the Blueprint
Some properties should always load from the Blueprint (static), others should save/load with user data (dynamic).
Static Properties - Visual assets, audio clips, base stats. Marked with [AlwaysFromBlueprint] attribute. Never saved to user files. Updates automatically when you patch the game.
Dynamic Properties - Current health, unlocked skins, progress. Serialized to user save files.
Example static property:
[Serializable]
public class VisualsProperty : EntryProperty
{
[AlwaysFromBlueprint] public GameObject ModelPrefab;
[AlwaysFromBlueprint] public AudioClip UnsheatheSound;
public override object Clone() { /* ... */ }
}Example dynamic property:
[Serializable]
public class SkinProperty : EntryProperty
{
public string CurrentSkinId;
public List<string> UnlockedSkins;
public override object Clone() { /* ... */ }
}// Create from Blueprint
Entry fireball = new Entry(fireballBlueprint);
// Modify at runtime
var skin = fireball.GetProperty<SkinProperty>();
skin.CurrentSkinId = "Inferno_V2";
// Save
fireball.OnBeforeSerialize();
string saveData = JsonUtility.ToJson(fireball);
// Load
Entry loaded = new Entry(fireballBlueprint);
JsonUtility.FromJsonOverwrite(saveData, loaded);
loaded.OnAfterDeserialize(); // Enforces [AlwaysFromBlueprint] rulesThe system includes an ISerializer interface for swapping storage backends:
public interface ISerializer
{
string Serialize(object data);
void DeserializeOverwrite(string data, object target);
}Implementations:
UnityJsonSerializer- Standard Unity JSON for local savesMockCloudSerializer- Example cloud storage implementation
Swap serializers without changing game logic:
ISerializer serializer = new UnityJsonSerializer();
// or
ISerializer serializer = new MockCloudSerializer();