Overview
DialogueEngine is a minimalistic dialogue engine that fits into your GUI nodes and automatically graphs the branching dialogues for easy debugging. It provides an API for creating dialogue trees and walking them, while you are responsible for providing the GUI code that presents the text to the player.
The engine internally manages an array of dictionaries, each accessed as a DialogueEntry, which are the basic representation of entries within the dialogue tree.
Each DialogueEntry can be of two types: text or conditional. Text-based entries represent a block of text with possible player options. Conditional-based entries represent bifurcation points dictated by a user-supplied boolean Callable.
Constructor
var dialogue_engine = DialogueEngine.new()
Creates a new DialogueEngine instance. After initialization, the _setup() pseudo-virtual function is called, which can be overridden when extending the class.
Quick Start Example
var dialogue_engine : DialogueEngine = DialogueEngine.new()
dialogue_engine.add_text_entry("Hello")
var print_dialogue : Callable = func (dialogue_entry : DialogueEntry) -> void:
print(dialogue_entry.get_text())
dialogue_engine.dialogue_continued.connect(print_dialogue)
dialogue_engine.advance() # prints "Hello"
dialogue_engine.advance() # Nothing prints -- the dialogue finished.
dialogue_engine.advance() # prints "Hello"
dialogue_engine.advance() # Nothing prints -- the dialogue finished.
Signals
Emitted when the first DialogueEntry is read.
Emitted when advance() visits a DialogueEntry that has text.Parameters:
p_dialogue_entry (DialogueEntry): The entry that was visited
Emitted when advance() visits a DialogueEntry that either has text or has a condition.Parameters:
p_dialogue_entry (DialogueEntry): The entry that was visited
Emitted when the dialogue is about to finish (i.e., when advance() is called and the internal read needle is at the last DialogueEntry).
Emitted when the dialogue finishes.
Emitted when reset() is called and the dialogue started but hasn’t finished, or an invalid goto or chosen option has been encountered.
Constants
Denotes the default branch ID when none is provided.
Adding Entries
add_text_entry()
func add_text_entry(p_text: String = "", p_branch_id: int = DEFAULT_BRANCH_ID) -> DialogueEntry
Adds a new text DialogueEntry to the engine and returns it.
The dialogue text for this entry
p_branch_id
int
default:"DEFAULT_BRANCH_ID"
The branch ID for organizing dialogue trees
Returns: The created DialogueEntry
Example:
var entry = dialogue_engine.add_text_entry("Hello, traveler!")
entry.add_option("Greetings")
entry.add_option("Goodbye")
add_conditional_entry()
func add_conditional_entry(p_callable: Callable, p_branch_id: int = DEFAULT_BRANCH_ID) -> DialogueEntry
Adds a new conditional DialogueEntry to the engine and returns it.
A callable that returns a boolean to determine the branch path
p_branch_id
int
default:"DEFAULT_BRANCH_ID"
The branch ID for organizing dialogue trees
Returns: The created DialogueEntry
Example:
var condition = func() -> bool:
return player.has_item("key")
var conditional = dialogue_engine.add_conditional_entry(condition)
conditional.set_condition_goto_ids(entry_with_key.get_id(), entry_without_key.get_id())
Navigation Methods
advance()
func advance(p_instant_finish: bool = false) -> void
Advances the dialogue and emits the next DialogueEntry with text data through the dialogue_continued signal. Entries with conditional data are emitted through the entry_visited signal.
If true, immediately finishes the dialogue
Example:
dialogue_engine.advance() # Move to next entry
reset()
Resets the internal reading needle. If the dialogue is in progress, emits the dialogue_canceled signal.
Example:
dialogue_engine.reset() # Start over from the beginning
Branch Management
set_branch_id()
func set_branch_id(p_branch_id: int) -> void
Sets the branch ID used for the next advance() calls. Useful when initializing the engine. This function is called automatically by advance() when a jump to a different branch ID is detected.
The branch ID to switch to
get_branch_id()
func get_branch_id() -> int
Returns the currently tracked branch ID that is being read by advance() calls.
Returns: The current branch ID
get_unique_branch_ids()
func get_unique_branch_ids() -> Array
Returns a list of unique branch IDs associated with the engine.
Returns: Array of unique branch IDs
Entry Access
get_entry()
func get_entry(p_entry_id: int) -> DialogueEntry
Returns the specified dialogue entry. Returns null if the dialogue entry does not exist.
The ID of the entry to retrieve
Returns: The DialogueEntry or null
get_current_entry()
func get_current_entry() -> DialogueEntry
Returns the current dialogue entry. Returns null if there’s no available entry or the dialogue has not started, been canceled, or finished.
Returns: The current DialogueEntry or null
get_current_entry_id()
func get_current_entry_id() -> int
Returns the current dialogue entry ID. Returns -1 when no current entry can be found.
Returns: The current entry ID or -1
set_current_entry()
func set_current_entry(p_id: int) -> void
Sets the current dialogue entry if available.
The ID of the entry to set as current
get_entry_at()
func get_entry_at(p_entry_id: int) -> DialogueEntry
Returns the DialogueEntry at the provided ID. Returns null when the ID is invalid.
The ID of the entry to retrieve
Returns: The DialogueEntry or null
get_entry_with_name()
func get_entry_with_name(p_dialogue_entry_name: String) -> DialogueEntry
Returns the DialogueEntry with the provided name. See DialogueEntry.set_name().
The name of the entry to find
Returns: The DialogueEntry or null
Example:
var intro = dialogue_engine.add_text_entry("Welcome!")
intro.set_name("intro")
# Later...
var entry = dialogue_engine.get_entry_with_name("intro")
has_entry_id()
func has_entry_id(p_entry_id: int) -> bool
Returns true if a DialogueEntry ID is available.
Returns: true if the entry exists, false otherwise
Entry Properties
get_entry_text()
func get_entry_text(p_entry_id: int) -> String
Returns the text of the entry at the specified ID.
Returns: The entry text or empty string if invalid
get_entry_name()
func get_entry_name(p_entry_id: int) -> String
Returns the name of the entry at the specified ID.
Returns: The entry name or empty string if invalid
get_entry_branch_id()
func get_entry_branch_id(p_entry_id: int) -> int
Returns the branch ID of the entry at the specified ID.
Returns: The branch ID or DEFAULT_BRANCH_ID if invalid
get_entry_goto_id()
func get_entry_goto_id(p_entry_id: int) -> int
Returns the goto ID of the entry at the specified ID.
Returns: The goto ID or DialogueEntry.GOTO_DEFAULT
Conditional Entry Methods
entry_has_condition()
func entry_has_condition(p_entry_id: int) -> bool
Returns true if the entry at the specified ID has a condition.
Returns: true if the entry has a condition
get_entry_condition()
func get_entry_condition(p_entry_id: int) -> Callable
Returns the condition Callable of the entry at the specified ID.
Returns: The condition Callable or an empty Callable
get_entry_condition_goto_ids()
func get_entry_condition_goto_ids(p_entry_id: int) -> Dictionary
Returns the condition goto IDs dictionary of the entry at the specified ID.
Returns: Dictionary with true and false keys mapping to entry IDs
Option Methods
entry_has_options()
func entry_has_options(p_entry_id: int) -> bool
Returns true if the entry at the specified ID has options.
Returns: true if the entry has options
get_entry_chosen_option()
func get_entry_chosen_option(p_entry_id: int) -> int
Returns the chosen option ID of the entry at the specified ID.
Returns: The chosen option ID or DialogueEntry.INVALID_CHOSEN_OPTION
get_entry_option_goto_id()
func get_entry_option_goto_id(p_entry_id: int, p_option_id: int) -> int
Returns the option goto ID for the specified option ID.
Returns: The goto ID or DialogueEntry.INVALID_OPTION_GOTO
get_entry_option_text()
func get_entry_option_text(p_entry_id: int, p_option_id: int) -> String
Returns the option text for the specified option ID.
Returns: The option text or empty string
Collection Methods
size()
Returns the number of dialogue tree entries stored.
Returns: Number of entries
is_empty()
Returns true if no dialogue tree entry is stored.
Returns: true if empty
push_back()
func push_back(p_dialogue_entry: DialogueEntry) -> void
Injects the DialogueEntry at the end of the chain of its current branch.
pop_back()
func pop_back() -> DialogueEntry
Pops a DialogueEntry from the chain.
The returned entry will be detached from the dialogue engine.
Returns: The popped DialogueEntry or null if empty
clear()
Clears all the dialogue added. After calling this function, is_empty() will return true.
set_entry_at()
func set_entry_at(p_entry_id: int, p_dialogue_entry: DialogueEntry) -> void
Replaces the DialogueEntry at the provided ID.
The ID of the entry to replace
State Methods
has_started()
func has_started() -> bool
Returns true if the dialogue has started.
Returns: true if dialogue has started
Data Serialization
set_data()
func set_data(p_dialogue_engine_data: Array[Dictionary]) -> void
Sets the DialogueEngine data. This will reset the dialogue engine state.
p_dialogue_engine_data
Array[Dictionary]
required
The dialogue tree data to load
Example:
var saved_data = dialogue_engine.get_data()
# Later...
dialogue_engine.set_data(saved_data)
get_data()
func get_data() -> Array[Dictionary]
DialogueEngine by itself does not store any Object subclass — each DialogueEntry is simply an API to manage data stored within a Dictionary.
Returns: The dialogue tree data as an array of dictionaries
Naming and Debugging
set_name()
func set_name(p_name: String) -> void
Sets a name to the engine. Internally it’s only useful in debug builds since the name is only used for the dialogue viewer in the debugger.
get_name()
func get_name() -> String
Gets the name of the engine. Internally it’s only useful in debug builds since the name is only used for the dialogue viewer in the debugger.
Returns: The engine name
deregister()
func deregister() -> void
Deregisters the dialogue engine from the debugger.
Virtual Methods
_setup()
Pseudo-virtual function. Called when DialogueEngine._init() finishes, useful when extending DialogueEngine.
Example:
class_name MyDialogue extends DialogueEngine
func _setup() -> void:
var entry1 = add_text_entry("Hello!")
var entry2 = add_text_entry("Goodbye!")
entry1.set_goto_id(entry2.get_id())
Overriding _init() will make the debugger behave unexpectedly under certain scenarios. Make sure to call super() within the subclass for proper debugger support.
Complete Example
extends Node
var dialogue_engine: DialogueEngine
var current_text_label: Label
var options_container: VBoxContainer
func _ready() -> void:
dialogue_engine = DialogueEngine.new()
dialogue_engine.set_name("Main Dialogue")
# Connect signals
dialogue_engine.dialogue_started.connect(_on_dialogue_started)
dialogue_engine.dialogue_continued.connect(_on_dialogue_continued)
dialogue_engine.dialogue_finished.connect(_on_dialogue_finished)
# Build dialogue tree
var greeting = dialogue_engine.add_text_entry("Hello, traveler!")
greeting.set_name("greeting")
var ask_name = dialogue_engine.add_text_entry("What is your name?")
var opt1_id = ask_name.add_option("My name is Alex")
var opt2_id = ask_name.add_option("I'd rather not say")
var response1 = dialogue_engine.add_text_entry("Nice to meet you, Alex!")
var response2 = dialogue_engine.add_text_entry("That's okay, stranger!")
# Set up navigation
greeting.set_goto_id(ask_name.get_id())
ask_name.set_option_goto_id(opt1_id, response1.get_id())
ask_name.set_option_goto_id(opt2_id, response2.get_id())
# Start dialogue
dialogue_engine.advance()
func _on_dialogue_started() -> void:
print("Dialogue started!")
func _on_dialogue_continued(entry: DialogueEntry) -> void:
current_text_label.text = entry.get_text()
# Clear previous options
for child in options_container.get_children():
child.queue_free()
# Add new options if available
if entry.has_options():
for i in entry.get_option_count():
var button = Button.new()
button.text = entry.get_option_text(i)
button.pressed.connect(func(): _on_option_chosen(entry, i))
options_container.add_child(button)
else:
# No options, add continue button
var button = Button.new()
button.text = "Continue"
button.pressed.connect(func(): dialogue_engine.advance())
options_container.add_child(button)
func _on_option_chosen(entry: DialogueEntry, option_id: int) -> void:
entry.choose_option(option_id)
dialogue_engine.advance()
func _on_dialogue_finished() -> void:
print("Dialogue finished!")
current_text_label.text = "[End of conversation]"