Skip to main content

Overview

Branching allows you to create multiple dialogue paths that diverge and converge based on your game’s logic. Dialogue Engine uses branch IDs to organize entries into separate branches, and gotos to jump between them.

Branch IDs

Every dialogue entry belongs to a branch, identified by a branch ID (an integer). By default, all entries use DEFAULT_BRANCH_ID (which equals 0).

Creating Branches

You can assign entries to different branches by specifying a branch ID:
extends DialogueEngine

enum {
    MAIN_PATH = 0,
    FRIENDLY_PATH = 1,
    HOSTILE_PATH = 2,
}

func _setup() -> void:
    # Main branch (default)
    add_text_entry("Hello, stranger.", MAIN_PATH)
    
    # Friendly branch
    add_text_entry("It's great to see you!", FRIENDLY_PATH)
    add_text_entry("How have you been?", FRIENDLY_PATH)
    
    # Hostile branch
    add_text_entry("You're not welcome here.", HOSTILE_PATH)
    add_text_entry("Leave now.", HOSTILE_PATH)

How Branch IDs Work

When advance() is called, the engine:
  1. Processes the current entry
  2. Searches for the next entry with the same branch ID as the current branch
  3. Continues reading entries in that branch until a goto changes the branch
# The engine maintains a branch ID needle
var current_branch = dialogue_engine.get_branch_id()

# You can also set it manually (though usually handled automatically)
dialogue_engine.set_branch_id(FRIENDLY_PATH)

Goto Statements

Gotos allow you to jump to specific entries and change branches. There are two types of gotos:

Top-Level Gotos

Top-level gotos jump to a specific entry after the current one is processed:
var first_entry = add_text_entry("This is the first entry.")
var skip_entry = add_text_entry("This will be skipped!")
var target_entry = add_text_entry("We jumped here directly!")

# Jump from first_entry to target_entry, skipping skip_entry
first_entry.set_goto_id(target_entry.get_id())

Same Branch Goto Example

Here’s an example of skipping dialogue within the same branch:
extends DialogueEngine

func _setup() -> void:
    var first_entry = add_text_entry("This is an example of...")
    add_text_entry("This text will be shown on the debugger but not in the dialogue")
    var first_entry_goto = add_text_entry("a skipped dialogue!")
    first_entry.set_goto_id(first_entry_goto.get_id())
    add_text_entry("Press <Enter> or <Space> to exit.")

Different Branch Goto Example

You can jump to entries in different branches, and the engine automatically updates the branch needle:
extends DialogueEngine

enum {
    DEFAULT_BRANCH = 0,
    DIFFERENT_BRANCH_ONE,
    DIFFERENT_BRANCH_TWO,
    DIFFERENT_BRANCH_THREE
}

func _setup() -> void:
    var first_entry = add_text_entry("This is an example of...", DEFAULT_BRANCH)
    
    # Jump to a different branch
    first_entry.set_goto_id(
        add_text_entry("how gotos work against different branch IDs", DIFFERENT_BRANCH_TWO).get_id()
    )
    
    # Continue in the new branch
    add_text_entry("Once you jump to a different branch...", DIFFERENT_BRANCH_TWO)
    add_text_entry("the engine only considers entries in that branch.", DIFFERENT_BRANCH_TWO)
When you set a goto to an entry in a different branch, advance() automatically calls set_branch_id() to update the branch needle.

Option Gotos

Options also use gotos to determine where the dialogue flows after a player choice:
enum {
    DEFAULT_TOPIC = 0,
    GO_BACK_TO_SLEEP = 1,
    KEEP_WORKING = 2,
}

func _setup() -> void:
    var entry = add_text_entry("The storm rages outside. I should...")
    
    # Option 1: Go to sleep branch
    var option_id_1 = entry.add_option("Go back to sleep.")
    var sleep_entry = add_text_entry("Sleep is for the strong.", GO_BACK_TO_SLEEP)
    entry.set_option_goto_id(option_id_1, sleep_entry.get_id())
    
    # Option 2: Keep working branch
    var option_id_2 = entry.add_option("Get back to work.")
    var work_entry = add_text_entry("Let's get back to work.", KEEP_WORKING)
    entry.set_option_goto_id(option_id_2, work_entry.get_id())
    
    # Merge branches back together
    var merge_point = add_text_entry("Some time passes...")
    sleep_entry.set_goto_id(merge_point.get_id())
    work_entry.set_goto_id(merge_point.get_id())

Merging Branches

Branches can converge back to a common point using gotos:
# Create divergent branches
var friendly_ending = add_text_entry("Thanks for being kind!", FRIENDLY_PATH)
var hostile_ending = add_text_entry("I'll remember this.", HOSTILE_PATH)

# Create a merge point
var common_ending = add_text_entry("The conversation ends.", MAIN_PATH)

# Point both branches to the merge point
friendly_ending.set_goto_id(common_ending.get_id())
hostile_ending.set_goto_id(common_ending.get_id())

Managing Gotos

DialogueEntry provides methods to work with gotos:
var entry = add_text_entry("Sample text")

# Set a goto
var target = add_text_entry("Target text")
entry.set_goto_id(target.get_id())

# Check if entry has a goto
if entry.has_goto_id():
    var goto_id = entry.get_goto_id()
    var goto_entry = entry.get_goto_entry()
    print(goto_entry.get_text())

# Remove a goto
entry.remove_goto_id()

Default Behavior

When an entry has no explicit goto, the engine automatically advances to the next entry with the same branch ID:
# These entries flow naturally without explicit gotos
add_text_entry("First", 0)
add_text_entry("Second", 0)
add_text_entry("Third", 0)
# The engine finds the next entry with branch ID 0 automatically

Debugging Branches

The Dialogue Engine includes a built-in debugger that visualizes your branches:
  • Each branch appears as a separate graph
  • Gotos are shown as connections between entries
  • Disconnected entries (unreachable due to missing gotos) are highlighted
Use enums to name your branch IDs. This makes your code more readable and helps with debugging.

Common Patterns

Hub and Spoke

Create a central dialogue point with multiple branches:
var hub = add_text_entry("What would you like to know?")

var option_1 = hub.add_option("Tell me about the town.")
var town_branch = add_text_entry("This is a peaceful place...", TOWN_BRANCH)
hub.set_option_goto_id(option_1, town_branch.get_id())

var option_2 = hub.add_option("Tell me about the quest.")
var quest_branch = add_text_entry("The quest is dangerous...", QUEST_BRANCH)
hub.set_option_goto_id(option_2, quest_branch.get_id())

# All branches return to the hub
town_branch.set_goto_id(hub.get_id())
quest_branch.set_goto_id(hub.get_id())

Progressive Revelation

Use branches to track conversation progress:
enum { FIRST_TIME, SECOND_TIME, REPEAT }

var current_state = FIRST_TIME

# Branch changes based on how many times you've talked
Always ensure your gotos point to valid entry IDs. Invalid gotos will cause the dialogue to be canceled and emit the dialogue_canceled signal.

Next Steps

Conditions

Add dynamic branching with conditions

Options

Create player choice options

Build docs developers (and LLMs) love