Skip to main content

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:

DataFieldDescription
Global variablesvariablesAll global variable values (int, float, bool, string)
Visited nodesvisitedNodesPer-dialogue visit tracking with count and timestamps
Dialogue statedialogueStatesPlay count, completion status, timestamps per dialogue
Actor variablesactorDataPer-character variable values (e.g., Friendship, Mood)
RelationshipsrelationshipsDirectional relationships between actors (from/to/type/value)
Mid-dialogue statemidDialogueStateSnapshot for resuming a dialogue in progress
LanguagecurrentLanguageActive localization language code
Play timetotalPlayTimeTotal play time in seconds (tracked automatically)
Metadatatimestamp, saveNameUnix 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​

FieldDefaultDescription
saveSlotPrefix"DialogueCraft_Save_"PlayerPrefs key prefix
defaultSlotName"default"Slot used by QuickSave()/QuickLoad()
maxSaveSlots10Maximum 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:

FlagDescription
OnConversationEndAfter each dialogue completes
OnChoiceMadeWhen the player selects a choice
OnNodeVisitedEach time a node is processed
OnVariableChangedWhen any variable changes (batched per frame)
IntervalTimer-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 active
  • currentNodeGuid -- The node the player is currently viewing
  • state -- The DialogueState enum value (WaitingForContinue, WaitingForChoice, etc.)
  • subDialogueStack -- Return points for nested sub-dialogues
  • stickyChoiceStack -- Return points for sticky/hub choices
  • localVariables -- All dialogue-scoped variable values
  • previousNodeGuid -- 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:

  1. Finds the DialogueAsset by dialogueId
  2. Stops any running dialogue without firing OnDialogueEnd
  3. Restores the graph, node position, and all stacks
  4. Re-presents the current node (text or choices) to the UI
  5. Fires OnDialogueStart
  6. 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:

FlagDefaultDescription
syncActorVariablestrueInclude per-character fields (Friendship, Mood, etc.)
syncRelationshipstrueInclude directional relationships between actors
saveLocalizationPreferencetrueRemember 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);