Quest/Dialogue Save System
Overview
Quest Forge uses two complementary singleton components to persist game state: Quest Save System and Dialogue Save System. In their default configuration these are tightly integrated — a single JSON file on disk captures quest progress, dialogue history, location discoveries, active waypoints, fog-of-war exploration data, and the player's world position all in one atomic write.
Quest Save System (QuestSaveSystem) is the primary owner of the save file. It serialises the full state of QuestManager, collects sub-data from its sibling systems, writes it all to disk as human-readable JSON via JSON.NET, and reads it back on load.
Dialogue Save System (DialogueSaveSystem) tracks which DialogueTrigger conversations have been completed and stores the DialogueManager's runtime variables (flags, strings, integers). It contributes its data to the quest save and restores from it — or, optionally, manages its own standalone file.
Both components call DontDestroyOnLoad and survive scene transitions.


Scene Setup
Place both components on a single persistent GameObject (e.g. your _Managers object). The recommended configuration is:
Quest Save System + Dialogue Save System on the same GameObject, with
Integrate With Quest Savesenabled on the Dialogue Save System.Both singletons auto-destroy duplicates in Awake.
Quest Save System must have access to
QuestManager— it callsQuestManager.Instancein Start.
Quest Save System
Inspector Properties
Save Settings
Save File Name
string
"quest_save"
Base name of the save file, without the .json extension.
Auto Save Enabled
bool
true
Enables both the state-change auto-save and the periodic timer save.
Auto Save Interval
float
300 s
How often a periodic auto-save fires (in seconds). Set to 0 to disable periodic saves while keeping state-change saves active.
Save On Quit
bool
true
Calls SaveGame() in OnApplicationQuit.
Load On Start
bool
true
If a save file exists, calls LoadGame() in Start automatically.
Debug Mode
bool
false
Enables verbose logging via QuestLogger at Debug level.
Save Location
Use Persistent Data Path
bool
true
Writes to Application.persistentDataPath/Saves/{saveFileName}.json. Recommended for shipping.
Custom Save Path
string
""
Directory used when Use Persistent Data Path is off. The .json filename is still appended automatically.
Auto-Save Triggers
Quest Save System monitors for changes every Update frame and saves immediately when any of the following occur:
Quest state change
The count of active or completed quests differs from the last-saved counts.
Tracked quest change
The currently tracked quest ID changed (without a count change).
Periodic timer
autoSaveInterval seconds have elapsed since the last save.
Application quit
OnApplicationQuit fires and saveOnQuit is true.
Quest trigger activated
A DialogueTrigger or scene quest trigger marks itself activated.
Location discovered
A new location is marked discovered via MarkLocationDiscovered().
Dialogue completed
DialogueSaveSystem.MarkDialogueTriggerCompleted() calls QuestSaveSystem.SaveGame() when integration is enabled.
After any state-change or trigger save, the periodic timer resets so the next periodic save fires a full interval later.
What Gets Saved
Every call to SaveGame() captures a complete snapshot of the following:
Metadata
Save file name, timestamp, save version ("1.0").
Player position
World-space position and Y-axis (yaw) rotation. Written from QuestManager.playerTransform.
Active quests
Full QuestInstanceSaveData for every quest currently in progress.
Completed quests
Full QuestInstanceSaveData for every finished quest.
Failed quests
Full QuestInstanceSaveData for every failed quest.
Abandoned quests
Full QuestInstanceSaveData for every abandoned quest.
Available quest IDs
String list of quest IDs that are currently available to the player.
Dialogue data
Completed trigger IDs, trigger metadata, and all DialogueManager runtime variables (flags, strings, integers).
Quest trigger data
IDs of scene quest triggers that have been activated and destroyed/deactivated.
Location discoveries
HashSet of discovered location IDs and per-location metadata.
World map waypoints
Name, world position, colour (RGBA hex), and icon index for every custom waypoint.
Fog-of-war breadcrumbs
List of world positions recorded as the player explored.
Load Sequence
When LoadGame() runs, the following steps execute in order:
Deserialise — JSON file is read and converted to
QuestSaveData.Version check — If
saveVersion != "1.0",MigrateSaveData()is attempted. If migration fails, loading is aborted.Clear quests — All four quest dictionaries in
QuestManagerare emptied via reflection.Restore quests — Each
QuestInstanceSaveDatais converted back to a liveQuestInstanceand injected into the correct dictionary.UpdateObjectiveStatus()is called on each instance.Refresh available quests —
questManager.ForceRefreshAvailableQuests().Restore dialogue —
DialogueSaveSystem.RestoreSaveData()repopulates completed triggers and restores dialogue variables toDialogueManager.Restore quest triggers — The
activatedQuestTriggersHashSet and metadata dictionary are repopulated.Restore locations —
LocationManager.LoadFromSaveData()syncs all discovered location IDs.Restore waypoints —
POIManager.ClearAllWaypoints(), then eachWaypointSaveEntryis re-created viaPOIManager.CreateWaypoint(). Colour is parsed from the hex string; icon sprite is resolved fromWaypointPickerUI.GetIconByIndex().Restore fog of war —
WorldMapFogOfWar.LoadBreadcrumbs()if breadcrumbs are present.Restore player position (deferred one frame) — A coroutine waits for
WaitForEndOfFrameto ensure physics and character controllers are initialised before the teleport.Notify listeners (deferred one frame) — A second coroutine publishes
QuestProgressUpdatedfor all active quests andQuestCompletedfor all completed quests onQuestEventBus, allowing POI Markers, UI, and other subscribers to re-evaluate their state.
Save File Format
Files are plain UTF-8 JSON, indented, with DateTimeFormat = "yyyy-MM-ddTHH:mm:ss". Vector3 values are serialised by a custom Vector3Converter. The files are human-readable and can be inspected or edited directly in any text editor.
Default path (Windows): C:\Users\{User}\AppData\LocalLow\{Company}\{Product}\Saves\quest_save.json
Public API
Core Save / Load
SaveGame(string slotName = null)
bool
Saves to the named slot, or the default saveFileName if null. Returns true on success.
LoadGame(string slotName = null)
bool
Loads from the named slot. Returns true on success.
QuickSave()
void
Saves to the hardcoded "quicksave" slot.
QuickLoad()
void
Loads from the "quicksave" slot.
CreateNamedSave(string saveName)
bool
Alias for SaveGame(saveName).
Save File Management
SaveFileExists(string slotName = null)
bool
Returns true if the named (or default) save file is present on disk.
DeleteSave(string slotName = null)
bool
Deletes the save file. Returns true if deletion succeeded.
GetSaveInfo(string slotName = null)
QuestSaveData
Deserialises the save file and returns the data object without applying it to the game state. Use for save-slot UI (timestamps, quest counts, etc.).
GetAvailableSaveSlots()
List<string>
Scans the saves directory and returns all slot names (file names without .json).
ExportSaveAsJson(string slotName = null)
string
Returns the raw JSON content of the save file as a string.
ImportSaveFromJson(string json, string slotName = null)
bool
Validates the JSON, writes it to disk, and returns true on success. Does not automatically load — call LoadGame() afterwards.
Quest Trigger API
Scene objects that represent one-time quest triggers call these methods to persist their activated state.
MarkQuestTriggerActivated(triggerId, questName, actionPerformed, position, wasDestroyed, wasDeactivated)
void
Records the trigger as used. Creates rich metadata on first call; increments timesActivated on subsequent calls. Triggers an auto-save.
IsQuestTriggerActivated(string triggerId)
bool
Returns true if the trigger has been recorded as used.
GetQuestTriggerMetadata(string triggerId)
QuestTriggerMetadata
Returns the stored metadata, or null if the trigger has never been activated.
GetActivatedQuestTriggerCount()
int
Total count of recorded activated triggers.
ResetQuestTrigger(string triggerId)
void
Removes the trigger from the activated set, allowing it to fire again.
ResetAllQuestTriggers()
void
Clears all activated trigger data.
Location Discovery API
GetLocationDiscoveryData()
LocationDiscoverySaveData
Returns the live discovery data object (creates empty if none exists).
MarkLocationDiscovered(LocationDiscoverySaveData data)
void
Replaces the cached discovery data with the updated version and triggers an auto-save.
IsLocationDiscovered(string locationId)
bool
Returns true if the location ID is in the discovered set.
GetDiscoveredLocationCount()
int
Total number of discovered locations.
ResetAllLocationDiscoveries()
void
Clears discovery data and calls LocationManager.ResetAllDiscoveries().
Dialogue Save System
Inspector Properties
Integrate With Quest Saves
bool
true
When enabled, dialogue state is bundled into the quest save file. Calling MarkDialogueTriggerCompleted() automatically triggers QuestSaveSystem.SaveGame(). Recommended — keeps a single unified save file.
Debug Mode
bool
false
Enables verbose logging at Debug level.
Integration Modes
Integrated (default) — integrateWithQuestSaves = true:
Dialogue data lives inside
QuestSaveData.dialogueData.QuestSaveSystemcallsDialogueSaveSystem.CreateSaveData()during every save andRestoreSaveData()during every load.No separate file is ever written by
DialogueSaveSystem.Any call to
MarkDialogueTriggerCompleted()triggersQuestSaveSystem.SaveGame()immediately.
Standalone — integrateWithQuestSaves = false:
Use
SaveToFile(path)andLoadFromFile(path)directly onDialogueSaveSystem.Useful for projects that have a separate dialogue system with its own save pipeline.
Runtime State
completedDialogueTriggers
HashSet<string>
IDs of all triggers that have fired. O(1) lookup.
triggerMetadata
Dictionary<string, DialogueTriggerMetadata>
Rich metadata for each trigger: dialogue name, fire count, timestamps, world position.
Public API
Trigger State
MarkDialogueTriggerCompleted(triggerId, dialogueName, triggerPosition)
void
Adds the ID to the completed set and creates or updates metadata. If integration is enabled, triggers QuestSaveSystem.SaveGame() immediately.
IsDialogueTriggerCompleted(string triggerId)
bool
HashSet.Contains() — O(1) lookup.
ResetDialogueTrigger(string triggerId)
void
Removes the ID, allowing the trigger to fire again. Does not remove metadata.
ResetAllDialogueTriggers()
void
Clears both the completed set and all metadata.
GetTriggerMetadata(string triggerId)
DialogueTriggerMetadata
Returns the metadata object, or null if the trigger has never fired.
GetCompletedDialogueCount()
int
Count of unique completed trigger IDs.
GetCompletedDialogueTriggers()
List<string>
A copy of all completed trigger IDs. Safe to iterate without modification risk.
Save / Load (Standalone Mode)
CreateSaveData()
DialogueSaveData
Packages current runtime state — completed triggers, metadata, and all DialogueManager variables — into a serialisable object. Called by QuestSaveSystem internally during integrated saves.
RestoreSaveData(DialogueSaveData saveData)
void
Clears runtime state and restores from the provided data object. Also calls DialogueManager.RestoreVariables() to reinstall flags, strings, and integers. Called by QuestSaveSystem internally during integrated loads.
SaveToFile(string savePath)
bool
Serialises and writes a standalone dialogue save file. Returns true on success.
LoadFromFile(string savePath)
bool
Reads and deserialises a standalone dialogue save file. Returns true on success.
Data Structures Reference
QuestSaveData — Top-Level Container
QuestSaveData — Top-Level ContainerThe root object serialised to the .json file.
saveVersion
string
Always "1.0". Triggers migration if mismatched on load.
saveName
string
The slot name used when saving.
saveTime
DateTime
ISO-formatted timestamp of the save.
playerName
string
Reserved for future use.
totalPlayTime
int
Reserved for future use (seconds).
playerPosition
Vector3
World position at save time.
playerYRotation
float
Y-axis (yaw) rotation in degrees.
hasPlayerPosition
bool
False for saves predating position tracking — skips teleport on load.
activeQuests
List<QuestInstanceSaveData>
All in-progress quests.
completedQuests
List<QuestInstanceSaveData>
All finished quests.
failedQuests
List<QuestInstanceSaveData>
All failed quests.
abandonedQuests
List<QuestInstanceSaveData>
All abandoned quests.
availableQuestIds
List<string>
Quest IDs currently available to accept.
dialogueData
DialogueSaveData
Dialogue trigger state and runtime variables.
questTriggerData
QuestTriggerSaveData
Scene trigger activation history.
locationDiscoveryData
LocationDiscoverySaveData
Discovered location IDs and metadata.
worldMapData
WorldMapSaveData
Custom waypoints and fog-of-war breadcrumbs.
Helper methods: GetTotalQuestCount(), GetFormattedSaveTime().
QuestInstanceSaveData — Per-Quest State
QuestInstanceSaveData — Per-Quest StatequestId
string
The Quest.QuestID used to look up the Quest ScriptableObject on load.
questName
string
Stored for readability in the JSON file; not used for lookup.
status
QuestStatus
Active / Completed / Failed / Abandoned.
startTime
DateTime
When the quest was accepted.
completeTime
DateTime?
When the quest was completed (nullable).
isTracked
bool
Whether the player had this quest tracked in the HUD.
timesCompleted
int
Supports repeatable quests.
progress
QuestProgressSaveData
Full copy of all objective counters and flags.
QuestProgressSaveData — Objective Counters
QuestProgressSaveData — Objective CountersAll seven progress dictionaries are deep-copied on save and restored on load via direct calls to QuestProgress setter methods.
killCounts
EnemyType → count
itemCounts
ItemId → count
flags
FlagId → bool
locations
LocationId → Vector3
stringValues
Key → string
interactionCounts
ObjectId → count
uniqueInteractions
ObjectId → List<string>
DialogueSaveData — Dialogue State
DialogueSaveData — Dialogue StatecompletedDialogueTriggers
List<string>
All trigger IDs that have been fired.
triggerMetadata
Dict<string, DialogueTriggerMetadata>
Per-trigger rich data.
saveTime
DateTime
Timestamp of the last save.
dialogueFlags
Dict<string, bool>
All boolean variables from DialogueManager.
dialogueStrings
Dict<string, string>
All string variables from DialogueManager.
dialogueIntegers
Dict<string, int>
All integer variables from DialogueManager.
DialogueTriggerMetadata
DialogueTriggerMetadatatriggerId
The trigger's unique ID.
dialogueName
Name of the dialogue asset that was played.
timesTriggered
How many times this trigger has fired (for non-once triggers).
firstTriggerTime
DateTime of the first activation.
lastTriggerTime
DateTime of the most recent activation.
triggerPosition
World position of the trigger object.
QuestTriggerSaveData — Scene Trigger History
QuestTriggerSaveData — Scene Trigger HistoryactivatedTriggers
List of trigger IDs that have fired and should remain spent.
triggerMetadata
Per-trigger rich data (quest name, action, timestamps, position, wasDestroyed, wasDeactivated).
LocationDiscoverySaveData — Location History
LocationDiscoverySaveData — Location HistorydiscoveredLocationIds
HashSet<string> of all discovered location IDs.
locationMetadata
Per-location rich data: name, player position at discovery, timestamp, whether quest-related, and which quest triggered it.
totalLocationsDiscovered
Running count.
firstDiscoveryTime / lastDiscoveryTime
Optional aggregate timestamps.
WorldMapSaveData — Map State
WorldMapSaveData — Map StatemapWaypoints
List of WaypointSaveEntry — one per custom waypoint.
explorationBreadcrumbs
List of Vector3 world positions recorded for fog-of-war reveal.
lastZoom
Last map zoom level (only applied if saveMapViewState = true).
lastPanOffset
Last map pan offset (only applied if saveMapViewState = true).
WaypointSaveEntry
WaypointSaveEntryname
Display name of the waypoint.
position
World-space position.
createdTime
When the player placed it.
colorHex
RGBA hex string without # (e.g. "FFFF00FF"). Parsed with ColorUtility.TryParseHtmlString() on load. Empty = use yellow fallback.
iconIndex
Zero-based index into WaypointPickerUI.availableIcons. -1 = use POIManager default icon.
Other Tips
JSON.NET is required — Both save systems use
Newtonsoft.Json. Ensure the JSON.NET for Unity package is present in the project. Missing it will causeJsonConverttype errors at compile time.Quest definitions must exist on load —
RestoreQuestInstance()looks up theQuestScriptableObject by ID viaQuestManager.GetQuestDefinition(). If you rename or delete aQuestasset between saves, that quest is silently skipped. Keep a log of removed quest IDs if players may have saves containing them.Save version migration is stubbed — The current migration framework logs a warning and returns
falsefor any version other than"1.0", aborting the load. Add version-specific migration blocks insideMigrateSaveData()before shipping any format-breaking update.Player position is deferred — The teleport happens at end-of-frame, not during
LoadGame(). If your game has any first-frame logic that reads the player position, it may see the pre-load position. Defer your own post-load logic with a shortyield return nullif needed.Quest events are deferred — POI Markers and UI components that subscribe to
QuestEventBusinStart()will not receive events published duringLoadGame()because theirStart()may not have run yet. The save system handles this by firing events at end-of-frame. Custom listeners should follow the same pattern.Reflection for QuestManager access —
RestoreQuestInstance()andClearAllQuests()useSystem.Reflectionto write intoQuestManager's private dictionaries. This is necessary because QuestManager encapsulates those collections. IfQuestManager's private field names are ever refactored, update the string literals in these methods accordingly.GetSaveInfo()for UI — Use this method to preview a save slot in a save/load menu without actually loading the game state. It deserialises the fullQuestSaveDataobject, which includes timestamps, quest counts, and player name, without touchingQuestManager.Fog-of-war breadcrumbs can grow large — Every exploration step is stored as a
Vector3. For large open worlds, the breadcrumb list can grow to thousands of entries. Consider periodically pruning it viaWorldMapFogOfWarsettings.
Last updated