In this lesson, you will learn how to design a modular persistence layer that bridges your runtime ScriptableObject state with long-term disk storage.
Up until now, you have been using ScriptableObjects to manage shared game state and cross-scene communication. While these are excellent for runtime flexibility, they reset every time the application closes. To make these persistent, we must treat our ScriptableObject assets as "Data Containers" and decouple them from a "Serializer" that writes their values to a file (like JSON).
The Data Transfer Object (DTO) Pattern
Because ScriptableObjects themselves contain engine-specific overhead (like their guid and hideFlags), you should not save the asset file directly. Instead, create a lightweight serializable class—a Data Transfer Object—that mirrors the data inside your ScriptableObject.
[System.Serializable]
public class PlayerSaveData {
public int currentHealth;
public float experiencePoints;
}
public class SaveSystem : MonoBehaviour {
public PlayerStatsSO playerStats; // Your existing SO
public void SaveGame() {
PlayerSaveData data = new PlayerSaveData {
currentHealth = playerStats.health,
experiencePoints = playerStats.xp
};
string json = JsonUtility.ToJson(data);
System.IO.File.WriteAllText(Application.persistentDataPath + "/save.json", json);
}
}
Decoupling Persistence
The secret to modularity is to avoid hardcoding every variable inside your SaveSystem. Instead, define an interface like ISaveable that your ScriptableObjects or their managers implement. Each module is then responsible for its own data serialization.
When SaveGame() is called, your system iterates through a list of registered ISaveable objects, collects their DTOs, and writes them to a single save file. This allows you to add new features—like a complex inventory system or a quest tracker—by simply creating a new ScriptableObject that implements ISaveable, without ever touching your core saving logic. By keeping the serialization logic local to the data source, your architecture remains highly extensible.
Key Takeaway: Decouple your runtime state from your storage format by mapping ScriptableObject data into lightweight serializable DTOs, allowing you to add and remove saveable modules without refactoring your entire persistence engine.