Skip to main content
Player choices allow your dialogue to respond to player decisions, creating interactive and branching conversations.

Adding Options to Dialogue

Options are choices presented to the player. When an entry has options, the dialogue pauses until the player chooses one.
1

Create an entry with options

Add options to a dialogue entry and specify where each option leads:
extends DialogueEngine

enum {
    DEFAULT_TOPIC = 0,
    GO_BACK_TO_SLEEP = 1,
    KEEP_WORKING = 2,
}

func _setup() -> void:
    var entry: DialogueEntry = add_text_entry(
        "The storm rages right outside the window. I should..."
    )
    
    # Add option 1
    var option_id_1: int = entry.add_option("Go back to sleep.")
    var option_id_1_entry: DialogueEntry = add_text_entry(
        "That's right, sleep is for the strong 💪.",
        GO_BACK_TO_SLEEP
    )
    entry.set_option_goto_id(option_id_1, option_id_1_entry.get_id())
    
    # Add option 2
    var option_id_2: int = entry.add_option("Get back to work.")
    var option_id_2_entry: DialogueEntry = add_text_entry(
        "That's right, let's get back to work 🫡",
        KEEP_WORKING
    )
    entry.set_option_goto_id(option_id_2, option_id_2_entry.get_id())
2

Display options in your UI

When a dialogue entry has options, create buttons for each one:
func __on_dialogue_continued(p_dialogue_entry: DialogueEntry) -> void:
    # Display the dialogue text
    display_text(p_dialogue_entry.get_text())
    
    # Check if this entry has options
    if p_dialogue_entry.has_options():
        for option_id in p_dialogue_entry.get_option_count():
            var option_text: String = p_dialogue_entry.get_option_text(option_id)
            create_option_button(option_text, option_id, p_dialogue_entry)
3

Handle player choice

When the player clicks an option, tell the DialogueEntry which option was chosen, then advance:
func _on_option_button_pressed(option_id: int, dialogue_entry: DialogueEntry) -> void:
    # Set the chosen option
    dialogue_entry.choose_option(option_id)
    
    # Clear option buttons from UI
    clear_option_buttons()
    
    # Advance to the chosen branch
    dialogue_engine.advance()

Rejoining Branches

After branching, you often want the dialogue to converge back to a common path:
func _setup() -> void:
    var entry: DialogueEntry = add_text_entry(
        "The storm rages right outside the window. I should..."
    )
    
    # Branch 1: Sleep
    var option_id_1: int = entry.add_option("Go back to sleep.")
    var option_id_1_entry: DialogueEntry = add_text_entry(
        "That's right, sleep is for the strong 💪.",
        GO_BACK_TO_SLEEP
    )
    entry.set_option_goto_id(option_id_1, option_id_1_entry.get_id())
    
    # Branch 2: Work
    var option_id_2: int = entry.add_option("Get back to work.")
    var option_id_2_entry: DialogueEntry = add_text_entry(
        "That's right, let's get back to work 🫡",
        KEEP_WORKING
    )
    entry.set_option_goto_id(option_id_2, option_id_2_entry.get_id())
    
    # Join both branches back to the default topic
    var default_topic: DialogueEntry = add_text_entry("Some time passes...")
    option_id_1_entry.set_goto_id(default_topic.get_id())
    option_id_2_entry.set_goto_id(default_topic.get_id())
    
    add_text_entry("<Press 'Space' or 'Enter' to quit>")
Using gotos to rejoin branches lets you avoid duplicating dialogue that should appear regardless of the player’s choice.

Conditional Branching

Conditional entries let you branch based on game state without showing options to the player:
1

Create a condition function

Define a function that returns a boolean:
extends DialogueEngine

var have_we_talked_before: bool = false

func __have_we_talked_before() -> bool:
    return have_we_talked_before
2

Add a conditional entry

Use add_conditional_entry() instead of add_text_entry():
enum branch {
    STRANGERS,
    ACQUAINTANCES,
}

func _setup() -> void:
    add_text_entry("Hello!")
    
    # Create the conditional entry
    var condition_entry: DialogueEntry = add_conditional_entry(__have_we_talked_before)
    
    # Define where to go based on the condition
    var if_true: DialogueEntry = add_text_entry(
        "Hey! We meet again!",
        branch.ACQUAINTANCES
    )
    var if_false: DialogueEntry = add_text_entry(
        "It's nice to meet you!",
        branch.STRANGERS
    )
    
    # Set the condition gotos
    condition_entry.set_condition_goto_ids(if_true.get_id(), if_false.get_id())
    
    add_text_entry("<Press 'Enter' or 'Space' to exit>")
    
    # Update the state after dialogue finishes
    dialogue_finished.connect(func() -> void:
        have_we_talked_before = true
    )
Conditional entries are evaluated automatically when reached. The player never sees the condition itself - the dialogue just branches silently based on the result.

Complete Example

extends DialogueEngine

enum {
    DEFAULT_TOPIC = 0,
    GO_BACK_TO_SLEEP = 1,
    KEEP_WORKING = 2,
}

func _setup() -> void:
    var entry: DialogueEntry = add_text_entry(
        "The storm rages right outside the window. I should..."
    )
    
    var option_id_1: int = entry.add_option("Go back to sleep.")
    var option_id_1_entry: DialogueEntry = add_text_entry(
        "That's right, sleep is for the strong 💪.",
        GO_BACK_TO_SLEEP
    )
    entry.set_option_goto_id(option_id_1, option_id_1_entry.get_id())
    
    var option_id_2: int = entry.add_option("Get back to work.")
    var option_id_2_entry: DialogueEntry = add_text_entry(
        "That's right, let's get back to work 🫡",
        KEEP_WORKING
    )
    entry.set_option_goto_id(option_id_2, option_id_2_entry.get_id())
    
    # Join branches into the default topic (i.e. branch id 0)
    var default_topic: DialogueEntry = add_text_entry("Some time passes...")
    option_id_1_entry.set_goto_id(default_topic.get_id())
    option_id_2_entry.set_goto_id(default_topic.get_id())
    
    # None of the following entries will be connected on the graph
    add_text_entry(
        "A sleep entry skipped due to missing goto against this entry.",
        GO_BACK_TO_SLEEP
    )
    add_text_entry(
        "A working entry due to missing goto against this entry.",
        KEEP_WORKING
    )
    
    add_text_entry("<Press 'Space' or 'Enter' to quit>")

Entry Methods for Options

MethodDescription
add_option(text: String) -> intAdds an option and returns its ID
set_option_goto_id(option_id: int, goto_id: int)Sets where an option leads
get_option_text(option_id: int) -> StringGets the text of an option
get_option_count() -> intReturns the number of options
has_options() -> boolReturns true if the entry has options
choose_option(option_id: int)Sets the chosen option (call before advance)

Conditional Entry Methods

MethodDescription
add_conditional_entry(callable: Callable) -> DialogueEntryCreates a conditional branch
set_condition_goto_ids(true_id: int, false_id: int)Sets where to go for true/false
get_condition() -> CallableReturns the condition function
has_condition() -> boolReturns true if entry is conditional

Best Practices

If you forget to set a goto ID for an option, the dialogue will cancel when that option is chosen.
var option_id: int = entry.add_option("My choice")
var target: DialogueEntry = add_text_entry("Response")
entry.set_option_goto_id(option_id, target.get_id())  # Don't forget this!
Instead of arbitrary numbers, use enums that describe what each branch represents:
enum Path {
    PEACEFUL = 0,
    AGGRESSIVE = 1,
}
Always remove option buttons before calling advance() to prevent the player from clicking them again.
The condition callable is called when the entry is visited, so you can check any game state (inventory, flags, stats, etc.).

Next Steps

Build docs developers (and LLMs) love