Skip to main content
This example demonstrates how to create dialogue that branches based on conditions using conditional entries. The dialogue will change depending on whether the player has talked to the NPC before.

Overview

In this example, you’ll learn how to:
  • Create conditional entries with callable functions
  • Use branch IDs to organize dialogue paths
  • Set condition goto IDs for true/false branches
  • Track state between dialogue runs
  • Connect signals to update state

The Dialogue

extends DialogueEngine

var have_we_talked_before: bool = false

enum branch {
    STRANGERS,
    ACQUAINTANCES,
}

func __have_we_talked_before() -> bool:
    return have_we_talked_before

func _setup() -> void:
    add_text_entry("Hello!")
    var condition_entry: DialogueEntry = add_conditional_entry(__have_we_talked_before)
    var if_true: DialogueEntry = add_text_entry("Hey! We meet again!", branch.STRANGERS)
    var if_false: DialogueEntry = add_text_entry("It's nice to meet you!", branch.ACQUAINTANCES)
    condition_entry.set_condition_goto_ids(if_true.get_id(), if_false.get_id())
    add_text_entry("<Press 'Enter' or 'Space' to exit>")
    
    dialogue_finished.connect(func() -> void: have_we_talked_before = true)

Code Breakdown

1. State Variable

var have_we_talked_before: bool = false
This variable tracks whether the player has completed the dialogue before. It persists between dialogue runs.

2. Branch IDs

enum branch {
    STRANGERS,
    ACQUAINTANCES,
}
Branch IDs are used to organize different dialogue paths. While not strictly necessary in this simple example, they help keep dialogue organized in more complex trees.
Branch ID 0 is the default branch (DEFAULT_BRANCH_ID). All entries without an explicit branch ID are added to this branch.

3. Condition Function

func __have_we_talked_before() -> bool:
    return have_we_talked_before
This callable function returns the condition value. It must return a boolean (true or false).
Condition functions can access any game state - player stats, quest progress, inventory items, time of day, etc. This makes dialogue highly dynamic.

4. Building the Dialogue Tree

func _setup() -> void:
    add_text_entry("Hello!")
    var condition_entry: DialogueEntry = add_conditional_entry(__have_we_talked_before)
    var if_true: DialogueEntry = add_text_entry("Hey! We meet again!", branch.STRANGERS)
    var if_false: DialogueEntry = add_text_entry("It's nice to meet you!", branch.ACQUAINTANCES)
    condition_entry.set_condition_goto_ids(if_true.get_id(), if_false.get_id())
    add_text_entry("<Press 'Enter' or 'Space' to exit>")
Let’s break this down step by step:
  1. First text entry: “Hello!” (always shown)
  2. Conditional entry: Checks __have_we_talked_before()
  3. True branch: “Hey! We meet again!” (shown if condition is true)
  4. False branch: “It’s nice to meet you!” (shown if condition is false)
  5. Set goto IDs: Links the condition to the appropriate branches
  6. Final text: Exit message (always shown after either branch)
The set_condition_goto_ids() method takes two parameters: the entry ID for when the condition returns true, and the entry ID for when it returns false.

5. Updating State

dialogue_finished.connect(func() -> void: have_we_talked_before = true)
When the dialogue finishes, we set have_we_talked_before to true so that the next time the dialogue runs, it will take the “We meet again!” branch.

Dialogue Flow

First Conversation

1. "Hello!" (text entry)
2. [Check: have_we_talked_before = false] (conditional entry)
3. "It's nice to meet you!" (false branch)
4. "<Press 'Enter' or 'Space' to exit>" (text entry)
5. [State updated: have_we_talked_before = true]

Second Conversation

1. "Hello!" (text entry)
2. [Check: have_we_talked_before = true] (conditional entry)
3. "Hey! We meet again!" (true branch)
4. "<Press 'Enter' or 'Space' to exit>" (text entry)

Complete UI Example

Here’s how to display this dialogue:
extends VBoxContainer

@export var dialogue_gdscript: GDScript = null
var dialogue_engine: DialogueEngine = null

func _ready() -> void:
    dialogue_engine = dialogue_gdscript.new()
    dialogue_engine.dialogue_started.connect(__on_dialogue_started)
    dialogue_engine.dialogue_continued.connect(__on_dialogue_continued)
    dialogue_engine.dialogue_finished.connect(__on_dialogue_finished)

func _input(p_input_event: InputEvent) -> void:
    if p_input_event.is_action_pressed(&"ui_accept"):
        dialogue_engine.advance()

func __on_dialogue_started() -> void:
    print("Dialogue Started!")

func __on_dialogue_continued(p_dialogue_entry: DialogueEntry) -> void:
    var label: RichTextLabel = RichTextLabel.new()
    label.set_use_bbcode(true)
    label.set_fit_content(true)
    label.set_text("  > " + p_dialogue_entry.get_text())
    add_child(label)

func __on_dialogue_finished() -> void:
    print("Dialogue Finished! Exiting...")
    get_tree().quit()
Notice that the UI code doesn’t need to know about conditionals - the DialogueEngine handles all branching logic internally.

Advanced: Multiple Conditions

You can chain multiple conditional entries for complex branching:
func _setup() -> void:
    add_text_entry("Hello, adventurer!")
    
    # First condition: check if player is high level
    var level_check: DialogueEntry = add_conditional_entry(__is_high_level)
    
    # High level branch
    var high_level_entry: DialogueEntry = add_text_entry("A powerful warrior!", branch.HIGH_LEVEL)
    
    # Low level branch - check if player has quest item
    var item_check: DialogueEntry = add_conditional_entry(__has_special_item, branch.LOW_LEVEL)
    var has_item: DialogueEntry = add_text_entry("I see you have the amulet!", branch.HAS_ITEM)
    var no_item: DialogueEntry = add_text_entry("Come back when you're stronger.", branch.NO_ITEM)
    
    # Set up the condition branches
    level_check.set_condition_goto_ids(high_level_entry.get_id(), item_check.get_id())
    item_check.set_condition_goto_ids(has_item.get_id(), no_item.get_id())
    
    # Merge back to common dialogue
    var common_end: DialogueEntry = add_text_entry("Farewell!")
    high_level_entry.set_goto_id(common_end.get_id())
    has_item.set_goto_id(common_end.get_id())
    no_item.set_goto_id(common_end.get_id())

func __is_high_level() -> bool:
    return player.level >= 10

func __has_special_item() -> bool:
    return player.inventory.has("ancient_amulet")

Key Takeaways

Conditional entries don’t display text - They only determine which branch to take. Use text entries for content that should be shown.
Condition functions must return bool - Make sure your callable returns true or false.
Always set condition goto IDs - Without proper goto IDs, the dialogue will be canceled with an error.
Branch IDs organize dialogue - While optional for simple cases, they become essential for complex dialogue trees.
If a conditional entry has an invalid goto ID (pointing to a non-existent entry), the dialogue_canceled signal will be emitted.

Next Steps

Player Options

Give players choices that affect dialogue flow

Metadata Handling

Attach custom data to dialogue entries

Build docs developers (and LLMs) love