Skip to main content

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

dialogue_started
signal
Emitted when the first DialogueEntry is read.
dialogue_continued
signal
Emitted when advance() visits a DialogueEntry that has text.Parameters:
  • p_dialogue_entry (DialogueEntry): The entry that was visited
entry_visited
signal
Emitted when advance() visits a DialogueEntry that either has text or has a condition.Parameters:
  • p_dialogue_entry (DialogueEntry): The entry that was visited
dialogue_about_to_finish
signal
Emitted when the dialogue is about to finish (i.e., when advance() is called and the internal read needle is at the last DialogueEntry).
dialogue_finished
signal
Emitted when the dialogue finishes.
dialogue_canceled
signal
Emitted when reset() is called and the dialogue started but hasn’t finished, or an invalid goto or chosen option has been encountered.

Constants

DEFAULT_BRANCH_ID
int
default:"0"
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.
p_text
String
default:"\"\""
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.
p_callable
Callable
required
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())

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.
p_instant_finish
bool
default:"false"
If true, immediately finishes the dialogue
Example:
dialogue_engine.advance() # Move to next entry

reset()

func reset() -> void
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.
p_branch_id
int
required
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.
p_entry_id
int
required
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.
p_id
int
required
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.
p_entry_id
int
required
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().
p_dialogue_entry_name
String
required
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.
p_entry_id
int
required
The ID to check
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.
p_entry_id
int
required
The entry 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.
p_entry_id
int
required
The entry 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.
p_entry_id
int
required
The entry 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.
p_entry_id
int
required
The entry 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.
p_entry_id
int
required
The entry ID
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.
p_entry_id
int
required
The entry 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.
p_entry_id
int
required
The entry 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.
p_entry_id
int
required
The entry ID
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.
p_entry_id
int
required
The entry 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.
p_entry_id
int
required
The entry ID
p_option_id
int
required
The 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.
p_entry_id
int
required
The entry ID
p_option_id
int
required
The option ID
Returns: The option text or empty string

Collection Methods

size()

func size() -> int
Returns the number of dialogue tree entries stored. Returns: Number of entries

is_empty()

func is_empty() -> bool
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.
p_dialogue_entry
DialogueEntry
required
The entry to add

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()

func clear() -> void
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.
p_entry_id
int
required
The ID of the entry to replace
p_dialogue_entry
DialogueEntry
required
The new entry

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.
p_name
String
required
The name for this engine

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()

func _setup() -> void
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]"