Load the entry ID and use set_current_entry() to restore the position:
func load_state() -> void: if FileAccess.file_exists(SAVE_PATH): var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.READ) var entry_id: int = file_handle.get_var() if dialogue_engine.has_entry_id(entry_id): dialogue_engine.set_current_entry(entry_id) print("State Loaded")
Always check if an entry ID is valid with has_entry_id() before setting it. Entry IDs can become invalid if you modify your dialogue structure.
You typically want to save more than just the dialogue position:
const SAVE_PATH: String = "user://game_save.dat"var counter: int = 0var log_history: Array = []func save_state() -> void: var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.WRITE) # Save dialogue position file_handle.store_var(dialogue_engine.get_current_entry().get_id()) # Save custom game state file_handle.store_var(counter) file_handle.store_var(log_history) print("State Saved")func load_state() -> void: if FileAccess.file_exists(SAVE_PATH): var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.READ) # Load dialogue position var entry_id: int = file_handle.get_var() if dialogue_engine.has_entry_id(entry_id): dialogue_engine.set_current_entry(entry_id) # Load custom game state counter = file_handle.get_var() log_history = file_handle.get_var() print("State Loaded")
You can create infinite or dynamically generated dialogue that persists across sessions:
extends DialogueEngineconst SAVE_PATH: String = "user://save.dat"var counter: int = 0var log_history: Array = []func get_log_history() -> Array: return log_historyfunc _setup() -> void: add_text_entry("This is an example of an infinite dynamically generated/saved/loaded dialogue.") add_text_entry("You can save the dialogue progress at any time by clicking the save button above.") add_text_entry("And when you restart this scene, the dialogue will continue from where it left off.") add_text_entry("As the dialogue progresses, the graph in the debugger will update automatically as well.") add_text_entry("Let's count to infinity!!") # Track history dialogue_continued.connect(__log_history) # Generate new entries dynamically dialogue_about_to_finish.connect(__continue_counting) # Load previous state if any load_state()func __log_history(p_dialogue_entry: DialogueEntry) -> void: # Always track the log history: log_history.push_back(p_dialogue_entry.get_formatted_text())func __continue_counting() -> void: counter += 1 add_text_entry(str(counter))func save_state() -> void: var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.WRITE) file_handle.store_var(counter) file_handle.store_var(get_current_entry().get_id()) file_handle.store_var(log_history) print("State Saved")func load_state() -> void: if FileAccess.file_exists(SAVE_PATH): var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.READ) counter = file_handle.get_var() var entry_id: int = file_handle.get_var() if has_entry_id(entry_id): set_current_entry(entry_id) else: # Entry doesn't exist yet, create it set_current_entry( add_text_entry("Let's continue counting!!").get_id() ) log_history = file_handle.get_var() print("State Loaded")func clear_state() -> void: if FileAccess.file_exists(SAVE_PATH): DirAccess.remove_absolute(SAVE_PATH) print("State Cleared")
The dialogue_about_to_finish signal is perfect for generating new dialogue entries dynamically, creating endless conversations.
When loading saved games, always validate entry IDs:
func load_state() -> void: if FileAccess.file_exists(SAVE_PATH): var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.READ) var saved_entry_id: int = file_handle.get_var() # Check if the entry still exists if dialogue_engine.has_entry_id(saved_entry_id): dialogue_engine.set_current_entry(saved_entry_id) print("Loaded successfully at entry %d" % saved_entry_id) else: # Entry doesn't exist - dialogue structure may have changed print("Warning: Saved entry ID %d no longer exists" % saved_entry_id) # Handle gracefully - maybe start from beginning dialogue_engine.reset()
You can also save and restore the entire dialogue tree structure:
func save_dialogue_tree() -> void: var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.WRITE) # Get the entire dialogue tree data var dialogue_data: Array[Dictionary] = dialogue_engine.get_data() # Save it file_handle.store_var(dialogue_data) file_handle.store_var(dialogue_engine.get_current_entry_id())func load_dialogue_tree() -> void: if FileAccess.file_exists(SAVE_PATH): var file_handle: FileAccess = FileAccess.open(SAVE_PATH, FileAccess.READ) # Restore the dialogue tree var dialogue_data: Array[Dictionary] = file_handle.get_var() dialogue_engine.set_data(dialogue_data) # Restore position var entry_id: int = file_handle.get_var() dialogue_engine.set_current_entry(entry_id)
Saving the entire tree is useful for procedurally generated dialogue that needs to persist exactly as it was. However, it uses more storage than just saving the entry ID.
const SAVE_VERSION: int = 1func save_state() -> void: file.store_var(SAVE_VERSION) file.store_var(dialogue_engine.get_current_entry_id()) # ... other datafunc load_state() -> void: var version: int = file.get_var() if version != SAVE_VERSION: print("Save file is outdated") return # ... load data
Consider compression for large dialogue trees
If saving entire trees, compress the data:
var json_data: String = JSON.stringify(dialogue_engine.get_data())var compressed: PackedByteArray = json_data.to_utf8_buffer().compress()file.store_var(compressed)