How to Implement Save and Load Functionality in the Undertale‑Changer Template
The Undertale‑Changer Template provides a complete JSON‑based save system centered around SaveController, SaveService, and SaveBoxController, allowing you to serialize PlayerControl data and FactTables to Assets/Data/ and restore them later with automatic file management.
The Undertale‑Changer Template is a Unity-based framework that ships with a production-ready save system. You do not need to build serialization logic from scratch; instead, you interface with three core components that handle JSON conversion, file I/O, and UI flow according to the arch-aik/undertale-changer-template source code.
Understanding the Core Save Architecture
The save system relies on three coordinated components:
SaveController(Assets/Scripts/UCT/Core/SaveController.cs) – Handles low-level serialization, file writes to theAssets/Data/directory, and automatic renaming of save slots.SaveService(defined withinAssets/Scripts/UCT/Overworld/SaveBoxController.cs) – A thin façade that triggers saves and persists player preferences (language, resolution) viaPlayerPrefs.SaveBoxController(Assets/Scripts/UCT/Overworld/SaveBoxController.cs) – Manages the save UI dialog, captures confirmation input, and callsSaveService.SaveGame().
How the Save System Works
The Saving Process
When the player initiates a save, the following chain executes:
- UI Confirmation –
SaveBoxController.HandleConfirmationInputdetects the Z key press and callsProcessSaveConfirmation. - Service Trigger –
ProcessSaveConfirmationinvokesSaveService.SaveGame(), which delegates toSaveController.SaveData(PlayerControl player, string dataName). - JSON Serialization –
SaveDataensures theAssets/Datafolder exists, serializes thePlayerControlinstance usingJsonConvert.SerializeObject, and writes it to{dataName}.json. - FactTable Persistence – The same method calls
SaveFactTablesToJsonto capture the current state of allFactTableassets inResources/Tables. - Preferences Update –
SaveService.SaveGamefinishes by callingSavePlayerPreferencesto cache settings like language and resolution.
The Loading Process
Loading reverses the flow via SaveController.LoadData(string dataName):
- File Management –
LoadDatafirst callsSortAndRenameDatato ensure save files follow a continuous sequence (Data0.json,Data1.json, etc.). - Instance Creation – A fresh
PlayerControlScriptableObject is created withScriptableObject.CreateInstance<PlayerControl>(). - Deserialization –
JsonConvert.PopulateObjectreads the JSON file and populates the instance fields (name, level, position). - State Restoration – The loaded
PlayerControlis cached inUsersData, andLoadFactTablesFromJsonre‑hydrates the event system state.
FactTable Persistence
FactTables are ScriptableObjects used by the event system (EventTable, RuleTable). The save system walks the Resources/Tables folder, serializes each table’s facts list to JSON, and restores it on load, ensuring events continue exactly where they left off.
Implementing Save Functionality
Triggering a Save from the UI
Use the built-in save dialog to handle confirmation flow automatically:
using UnityEngine;
using UCT.Overworld;
public class MySaveButton : MonoBehaviour
{
public void OnClick()
{
// Opens the built-in save UI and handles confirmation
SaveBoxController.Instance.OpenSaveBox();
}
}
Calling SaveBoxController.Instance.OpenSaveBox() presents the player with the standard save dialog, which eventually reaches SaveService.SaveGame() and writes the file.
Saving Custom Data Automatically
Because SaveController.SaveData serializes the entire PlayerControl instance, you can extend the save payload by adding fields to the PlayerControl ScriptableObject:
[Serializable]
public class PlayerControl : ScriptableObject
{
public List<string> inventory = new List<string>(); // Custom data
public int level;
public Vector3 position;
// Existing fields...
}
No additional changes are required; JsonConvert.SerializeObject will capture the inventory list automatically.
Implementing Load Functionality
Loading a Specific Slot Programmatically
To load a specific save slot (e.g., "Data3") from a custom menu:
using UnityEngine;
using UCT.Core;
public class MyLoadButton : MonoBehaviour
{
public void OnClick()
{
var player = SaveController.LoadData("Data3");
if (player != null)
{
MainControl.Instance.playerControl = player;
// Fact tables are restored automatically by LoadData
}
else
{
Debug.LogWarning("Save slot not found.");
}
}
}
This restores the PlayerControl state and all associated FactTables from Assets/Data/Data3.json.
Managing Save Files
The template includes utilities for save slot maintenance:
GetDataNumber– Counts existing save files.DeleteData(string dataName)– Removes a specific save file.SortAndRenameData– Renames remaining files to maintain a continuous sequence after deletion.
Deleting a Save Slot
using UCT.Core;
public class DeleteSaveSlot : MonoBehaviour
{
public void RemoveSlot(int slotIndex)
{
SaveController.DeleteData($"Data{slotIndex}");
// Add UI refresh logic here
}
}
After deletion, SortAndRenameData ensures the remaining files are renamed to Data0.json, Data1.json, etc., preventing gaps in the numbering.
Summary
- The Undertale‑Changer Template implements save/load via SaveController, SaveService, and SaveBoxController.
- Save data is stored as JSON in
Assets/Data/, with automatic file renaming handled bySortAndRenameData. - PlayerControl instances are serialized with
JsonConvert.SerializeObjectand restored withJsonConvert.PopulateObject. - FactTables are persisted alongside player data to maintain event system state.
- Player preferences (language, resolution) are cached via
PlayerPrefsthroughSaveService.
Frequently Asked Questions
Where are save files physically stored?
Save files are written to the Assets/Data/ directory within your Unity project as JSON files (e.g., Data0.json, Data1.json). This path is hardcoded in SaveController.SaveData, which ensures the directory exists before writing.
How do I add support for multiple save slots?
The system natively supports multiple slots. Simply pass a unique name (e.g., "Data0", "Data1") to SaveController.SaveData or SaveController.LoadData. The SortAndRenameData utility automatically renumbers files to maintain a continuous sequence when slots are deleted.
What happens to the event system state when I save?
The event system state is preserved via FactTables. SaveFactTablesToJson serializes all FactTable assets found in Resources/Tables, and LoadFactTablesFromJson restores them when loading. This ensures story flags and rule states persist across sessions.
Can I customize what gets saved without modifying the core system?
Yes. Extend the PlayerControl ScriptableObject with additional [Serializable] fields. Because SaveController.SaveData serializes the entire PlayerControl instance using JsonConvert.SerializeObject, any new fields you add will be automatically included in the JSON output without touching the save logic in SaveController.cs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →