Skip to main content

Save System

MCE uses a JSON-based save system with slot-based saves, versioning, and extensibility. The save system persists the player's entire game state -- roster, inventory, position, badges, quest progress, and game variables.

Architecture

The save system is composed of several components:

ComponentPurpose
SavegameManagerOrchestrates save/load operations
SavableObjectBase class for any object that needs to persist data
MCESavesSerializerHandles JSON serialization and deserialization
GameVariablesGlobal variable store (booleans, ints, strings)
GameVariableIndividual variable reference
GameVariableHolderMonoBehaviour wrapper for game variables
SavegameInstallerZenject installer for the save system

SavableGlobalGameData

The core save data container is SavableGlobalGameData (a component on the GlobalGameData GameObject). It stores:

DataDescription
Player PositionCurrent scene, tile coordinates, facing direction
Monster RosterAll party monsters with full state (species, level, IVs, EVs, nature, moves, PP, HP, status, friendship, etc.)
PC StorageBoxed monsters
BagAll items and quantities
MoneyPlayer's currency
BadgesEarned gym badges
MonsterDexSeen/caught tracking per species
Game VariablesAll GameVariable values (used by CommandGraph)
Quest ProgressActive quests, completed objectives, rewards claimed
Play TimeTotal play time

Save Slots

MCE supports multiple save slots. The default configuration provides 3 slots, but this is configurable.

Saving

// Through the SDK (all tiers)
engine.SaveSystem.Save(slotIndex); // 0, 1, or 2

// Or through the SavegameManager directly (Source tier)
savegameManager.Save(slotIndex);

Loading

// Through the SDK (all tiers)
engine.SaveSystem.Load(slotIndex);

// Check if slot has data first
if (engine.SaveSystem.HasSaveData(slotIndex))
{
engine.SaveSystem.Load(slotIndex);
}

Deleting

engine.SaveSystem.DeleteSave(slotIndex);

Save File Location

Save files are stored using Unity's Application.persistentDataPath:

PlatformPath
Windows%USERPROFILE%/AppData/LocalLow/<CompanyName>/<ProductName>/
macOS~/Library/Application Support/<CompanyName>/<ProductName>/
Linux~/.config/unity3d/<CompanyName>/<ProductName>/
AndroidInternal storage (app-private)
iOSApp sandbox

Each slot is saved as a separate JSON file: save_0.json, save_1.json, save_2.json.

Save Versioning

MCE includes save versioning to handle upgrades between game versions:

  • Each save file includes a version number.
  • When loading an older save, the MCESavesSerializer applies migration steps to update the data structure.
  • This prevents save corruption when you add new fields or change data formats between updates.
Serialized Field Names

Never rename serialized fields in save-related classes without providing a migration. If you rename a field, existing save files will lose that data on load. Use [FormerlySerializedAs("oldName")] for field renames.

GameVariables

The GameVariables system is MCE's global state store. It persists arbitrary key-value data that can be read and written from CommandGraph nodes, scripts, and save files.

Variable Types

TypeDescriptionExample
BooleanTrue/false flags"defeatedBrock" = true
IntegerNumeric counters"fishCaught" = 42
StringText values"rivalName" = "Gary"

Using in CommandGraph

  • SetVariable node: Set a variable's value.
  • BranchOnVariable node: Check a variable and branch.
  • AddToVariable node: Increment a numeric variable.

Using in Code

// Read a variable
bool hasBadge = GameVariables.GetBool("defeatedBrock");
int fishCount = GameVariables.GetInt("fishCaught");

// Write a variable
GameVariables.SetBool("defeatedBrock", true);
GameVariables.SetInt("fishCaught", fishCount + 1);

Custom Save Data

Extending SavableObject (Source Tier)

With source code, create a new SavableObject:

public class CustomSaveData : SavableObject
{
[SerializeField] private int customCounter;
[SerializeField] private List<string> unlockedFeatures;

public override void OnSave()
{
// Gather current state before save
customCounter = MySystem.Counter;
unlockedFeatures = MySystem.GetUnlockedFeatures();
}

public override void OnLoad()
{
// Apply loaded state
MySystem.Counter = customCounter;
MySystem.SetUnlockedFeatures(unlockedFeatures);
}
}

Side-Loading Custom Data (All Tiers)

Without source access, use the save events to persist your own data alongside MCE saves:

public class MySaveExtension : MonoBehaviour
{
[Inject] private ISaveSystem saveSystem;

[Serializable]
private class MyData
{
public int highScore;
public List<string> achievements;
}

private MyData data = new MyData();

private void OnEnable()
{
saveSystem.OnSaved += OnSaved;
saveSystem.OnLoaded += OnLoaded;
}

private void OnSaved(int slot)
{
string json = JsonUtility.ToJson(data);
File.WriteAllText(GetPath(slot), json);
}

private void OnLoaded(int slot)
{
string path = GetPath(slot);
if (File.Exists(path))
{
data = JsonUtility.FromJson<MyData>(File.ReadAllText(path));
}
}

private string GetPath(int slot)
{
return Path.Combine(Application.persistentDataPath, $"custom_{slot}.json");
}
}

Auto-Save

MCE supports auto-save at configurable triggers:

  • On map transition: Save when entering a new scene.
  • On rest: Save when healing at a rest point.
  • Timed interval: Save every N minutes.

Configure auto-save in the game configuration ScriptableObject.

Auto-Save Best Practices
  • Always use a dedicated auto-save slot (e.g., slot 0) separate from manual save slots.
  • Show an auto-save indicator icon when saving.
  • Do not auto-save during battles or cutscenes.

Save File Format

MCE saves are plain JSON for debugging. A simplified example:

{
"version": 3,
"playerName": "Red",
"playTime": 12345.6,
"currentScene": "PalletTown",
"position": { "x": 8, "y": 12 },
"money": 5000,
"badges": ["BoulderBadge", "CascadeBadge"],
"roster": [
{
"species": 6,
"level": 36,
"nickname": "Charizard",
"currentHP": 120,
"moves": ["Flamethrower", "AirSlash", "DragonClaw", "SwordsDance"],
"ivs": [28, 31, 24, 30, 26, 31],
"evs": [0, 252, 0, 4, 0, 252]
}
],
"gameVariables": {
"defeatedBrock": true,
"defeatedMisty": true,
"fishCaught": 12
}
}
Do Not Edit Save Files

While save files are human-readable JSON, editing them manually can cause issues if you introduce invalid data (e.g., a move that does not exist in the database). Use in-game tools or debug commands instead.

Best Practices

  1. Test save/load roundtrips. Save, quit, reload, and verify everything is intact.
  2. Use GameVariables for event flags. They persist automatically and integrate with CommandGraph.
  3. Plan for save migration. If you add new data in an update, add a migration step.
  4. Provide multiple save slots. Players expect at least 3 manual save slots.
  5. Handle load failures gracefully. If a save file is corrupted, show a clear error rather than crashing.