Save & Load
Persist dialogue state across sessions: variables, visited nodes, dialogue progress, actor data, relationships, and mid-dialogue snapshots.
What Gets Saved
DialogueCraftSaveData (version 2) stores all persistent dialogue state in a single serializable object:
| Data | Field | Description |
|---|---|---|
| Global variables | variables | All global variable values (int, float, bool, string) |
| Visited nodes | visitedNodes | Per-dialogue visit tracking with count and timestamps |
| Dialogue state | dialogueStates | Play count, completion status, timestamps per dialogue |
| Actor variables | actorData | Per-character variable values (e.g., Friendship, Mood) |
| Relationships | relationships | Directional relationships between actors (from/to/type/value) |
| Mid-dialogue state | midDialogueState | Snapshot for resuming a dialogue in progress |
| Language | currentLanguage | Active localization language code |
| Play time | totalPlayTime | Total play time in seconds (tracked automatically) |
| Metadata | timestamp, saveName | Unix timestamp and display name for save slot UI |
Quick Start
Add the DialogueCraftPersistence component to a GameObject in your scene, or let it auto-create as a singleton:
// Save to default slot
DialogueCraftPersistence.QuickSave();
// Load from default slot
DialogueCraftPersistence.QuickLoad();
The persistence manager automatically subscribes to all DialogueRunner instances in the scene and tracks visited nodes, play counts, and dialogue completion.
Save Slots
DialogueCraft supports up to 100 save slots (configurable via maxSaveSlots). Slots are stored in PlayerPrefs with a configurable key prefix.
Inspector Settings
| Field | Default | Description |
|---|---|---|
saveSlotPrefix | "DialogueCraft_Save_" | PlayerPrefs key prefix |
defaultSlotName | "default" | Slot used by QuickSave()/QuickLoad() |
maxSaveSlots | 10 | Maximum number of named save slots |
Slot Management API
var persistence = DialogueCraftPersistence.Instance;
// Save to a named slot with a display name
DialogueCraftPersistence.SaveToSlot("slot1", "Chapter 2 - The Forest");
// Load from a named slot
DialogueCraftPersistence.LoadFromSlot("slot1");
// List all save slots
List<SaveSlotInfo> slots = DialogueCraftPersistence.GetAllSlotInfo();
foreach (var slot in slots)
{
Debug.Log($"{slot.saveName} - {slot.SaveTime} - {slot.PlayTimeFormatted}");
// "Chapter 2 - The Forest" - 3/15/2026 2:30 PM - "1h 45m"
}
// Check if a slot exists
bool exists = persistence.HasSaveSlot("slot1");
// Get the next available slot name (returns null if all full)
string nextSlot = persistence.GetNextAvailableSlot();
// Delete a single slot or all slots
persistence.DeleteSlot("slot1");
persistence.DeleteAllSlots();
SaveSlotInfo
The GetSaveSlotInfo(string slotName) method returns metadata without fully deserializing the save data:
public class SaveSlotInfo
{
public string slotName;
public string saveName;
public long timestamp;
public float playTime;
public bool hasMidDialogueState;
public DateTime SaveTime { get; } // Local DateTime
public string PlayTimeFormatted { get; } // "2h 30m" or "5m 12s"
}
Auto-Save
Configure event-based auto-saving through the Inspector or code. Auto-saves write to a dedicated slot (autoSaveSlotName, default "autosave").
Auto-Save Events
AutoSaveEvent is a flags enum -- combine multiple triggers:
| Flag | Description |
|---|---|
OnConversationEnd | After each dialogue completes |
OnChoiceMade | When the player selects a choice |
OnNodeVisited | Each time a node is processed |
OnVariableChanged | When any variable changes (batched per frame) |
Interval | Timer-based, every autoSaveInterval seconds |
var persistence = DialogueCraftPersistence.Instance;
// Enable auto-save on conversation end and variable changes
persistence.autoSaveEvents = AutoSaveEvent.OnConversationEnd | AutoSaveEvent.OnVariableChanged;
// Or add interval-based auto-save (every 60 seconds)
persistence.autoSaveEvents |= AutoSaveEvent.Interval;
persistence.autoSaveInterval = 60f; // 10-600 seconds
Variable-change auto-saves are batched: multiple changes in the same frame produce a single auto-save.
Manual Auto-Save
persistence.AutoSave(); // Saves to autoSaveSlotName
Mid-Dialogue Snapshots
Save and resume a dialogue that is in progress. The snapshot captures the exact position in the graph, including sub-dialogue call stacks, sticky choice stacks, and local variables.
Capturing a Snapshot
var runner = FindObjectOfType<DialogueRunner>();
var persistence = DialogueCraftPersistence.Instance;
// Capture the current dialogue state
persistence.CaptureMidDialogueState(runner);
// Save to a slot (snapshot is included in the save data)
persistence.Save("midgame");
The DialogueStateSnapshot stores:
dialogueId-- Which dialogue asset is activecurrentNodeGuid-- The node the player is currently viewingstate-- TheDialogueStateenum value (WaitingForContinue, WaitingForChoice, etc.)subDialogueStack-- Return points for nested sub-dialoguesstickyChoiceStack-- Return points for sticky/hub choiceslocalVariables-- All dialogue-scoped variable valuespreviousNodeGuid-- For sticky choice context
Resuming from a Snapshot
// Load save data
persistence.Load("midgame");
// Check for mid-dialogue state
if (persistence.HasMidDialogueState)
{
// Resume the dialogue on a runner
bool success = persistence.ResumeMidDialogue(runner);
}
ResumeMidDialogue calls runner.RestoreFromSnapshot(), which:
- Finds the
DialogueAssetbydialogueId - Stops any running dialogue without firing
OnDialogueEnd - Restores the graph, node position, and all stacks
- Re-presents the current node (text or choices) to the UI
- Fires
OnDialogueStart - Clears the mid-dialogue state after successful restore
A snapshot is automatically cleared when a new dialogue starts or when a dialogue ends normally.
Actor Persistence
Actor variables and relationships are synced to save data automatically. Control this via Inspector flags:
| Flag | Default | Description |
|---|---|---|
syncActorVariables | true | Include per-character fields (Friendship, Mood, etc.) |
syncRelationships | true | Include directional relationships between actors |
saveLocalizationPreference | true | Remember the player's language choice |
How Sync Works
Before each save, SyncAllState() exports the current runtime state:
persistence.SyncAllState();
// Equivalent to:
persistence.SyncGlobalVariables(); // DialogueRunner.SharedVariables.Global -> save data
persistence.SyncActorVariables(); // DialogueRunner.SharedVariables actor stores -> save data
persistence.SyncRelationships(); // DialogueRunner.SharedVariables relationships -> save data
persistence.SyncLocalization(); // LocalizationManager.CurrentLanguage -> save data
On load, ApplyToRuntime() replaces all runtime state from save data:
persistence.ApplyToRuntime();
// Clears existing variables, then imports:
// save data -> DialogueRunner.SharedVariables (global, actor, relationships)
// save data -> LocalizationManager.SetLanguage()
New Game
Reset all state and initialize from database defaults:
persistence.NewGame();
This clears all runtime variables, loads default values from VariableDatabase, and initializes actor fields from CharacterDatabase.
Node Visit Tracking
The persistence manager automatically tracks which nodes the player has visited, with enhanced data per visit.
// Check if a node was visited
bool visited = persistence.WasNodeVisited("dialogue_id", "node_guid");
// Get visit count
int count = persistence.GetNodeVisitCount("dialogue_id", "node_guid");
// Get detailed visit data
NodeVisitData data = persistence.GetNodeVisitData("dialogue_id", "node_guid");
if (data != null)
{
Debug.Log($"Visited {data.visitCount} times");
Debug.Log($"First: {data.FirstVisitTime}, Last: {data.LastVisitTime}");
}
// Dialogue-level tracking
int playCount = persistence.CurrentSaveData.GetDialoguePlayCount("dialogue_id");
bool completed = persistence.CurrentSaveData.IsDialogueCompleted("dialogue_id");
Control tracking via Inspector flags:
autoTrackVisitedNodes-- Track every node the runner processes (default: true)autoTrackPlayCounts-- Count dialogue starts (default: true)
Runtime API Reference
Static Convenience Methods
These create the singleton if needed:
// Quick save/load (default slot)
DialogueCraftPersistence.QuickSave();
bool loaded = DialogueCraftPersistence.QuickLoad();
// Named slots
DialogueCraftPersistence.SaveToSlot("slot1", "My Save");
bool loaded = DialogueCraftPersistence.LoadFromSlot("slot1");
// JSON export/import
string json = DialogueCraftPersistence.ToJson(prettyPrint: true);
bool success = DialogueCraftPersistence.FromJson(json);
// Slot info
List<SaveSlotInfo> allSlots = DialogueCraftPersistence.GetAllSlotInfo();
// Raw data access (for custom save integration)
DialogueCraftSaveData data = DialogueCraftPersistence.GetSaveData();
DialogueCraftPersistence.SetSaveData(data);
Instance Methods
var p = DialogueCraftPersistence.Instance;
p.Save(); // Save to default slot
p.Save("slot1", "Display Name"); // Save to named slot
p.Load(); // Load from default slot
p.Load("slot1"); // Load from named slot
p.AutoSave(); // Save to auto-save slot
p.NewGame(); // Fresh state from database defaults
p.CaptureMidDialogueState(runner); // Snapshot current dialogue
p.ResumeMidDialogue(runner); // Resume from snapshot
p.SyncAllState(); // Push runtime state to save data
p.ApplyToRuntime(); // Push save data to runtime state
p.DeleteSlot("slot1"); // Delete one slot
p.DeleteAllSlots(); // Delete all slots
p.SubscribeToRunner(runner); // Track a new runner
DialogueCraftPersistence.RegisterRunner(runner); // Static version
Events
var p = DialogueCraftPersistence.Instance;
p.OnBeforeSave += (DialogueCraftSaveData data) => { /* modify before write */ };
p.OnAfterSave += (DialogueCraftSaveData data) => { /* update UI */ };
p.OnBeforeLoad += (string slotName) => { /* prepare for load */ };
p.OnAfterLoad += (DialogueCraftSaveData data) => { /* refresh UI */ };
Custom Save Integration
If your game has its own save system, bypass PlayerPrefs entirely and work with the data objects directly.
Option 1: JSON Strings
Extract and inject the full save as a JSON string:
// Get DialogueCraft state as JSON
string dialogueSaveJson = DialogueCraftPersistence.ToJson();
// Store in your save system
mySaveSystem.Set("dialogue_state", dialogueSaveJson);
// Restore later
string json = mySaveSystem.Get("dialogue_state");
DialogueCraftPersistence.FromJson(json);
Option 2: Data Object
Work with the DialogueCraftSaveData object directly:
// Get the save data object
DialogueCraftSaveData data = DialogueCraftPersistence.GetSaveData();
// Serialize with your system (JsonUtility, MessagePack, etc.)
byte[] bytes = MySerializer.Serialize(data);
// Restore later
DialogueCraftSaveData restored = MySerializer.Deserialize<DialogueCraftSaveData>(bytes);
DialogueCraftPersistence.SetSaveData(restored);
Option 3: Variables Only
If you only need to save variables (not visited nodes or dialogue state), use DialogueVariablesSaveData:
// Export just the variable state
DialogueVariablesSaveData varData = DialogueRunner.SharedVariables.GetSaveData();
// Restore later (full replace -- clears existing state)
DialogueRunner.SharedVariables.LoadSaveData(varData);
Version Migration
Save data includes a version field (currently 2). When loading version 1 data, DialogueCraftSaveData.FromJson() automatically migrates the visit tracking format. Custom serializers should preserve this field.
Dynamic Runners
If you spawn DialogueRunner instances at runtime, register them for tracking:
var runner = Instantiate(runnerPrefab);
DialogueCraftPersistence.RegisterRunner(runner);