Skip to main content

Overview

Conditional entries allow your dialogue to branch dynamically based on game state, player choices, or any other logic. Unlike text entries, conditional entries execute a callable that returns a boolean, then jump to different branches based on the result.

Creating Conditional Entries

Conditional entries are created using add_conditional_entry() with a callable that returns bool:
extends DialogueEngine

var have_we_talked_before = false

func check_if_talked() -> bool:
    return have_we_talked_before

func _setup() -> void:
    add_text_entry("Hello!")
    
    # Add a conditional entry
    var condition_entry = add_conditional_entry(check_if_talked)
    
    # Set where to go based on the condition result
    var if_true = add_text_entry("Hey! We meet again!")
    var if_false = add_text_entry("It's nice to meet you!")
    condition_entry.set_condition_goto_ids(if_true.get_id(), if_false.get_id())

How Conditions Work

When advance() encounters a conditional entry:
  1. The callable is executed immediately
  2. The boolean return value determines the path:
    • true: Jumps to the “true” goto ID
    • false: Jumps to the “false” goto ID
  3. The branch ID needle updates automatically to match the target entry’s branch
  4. The conditional entry emits entry_visited but not dialogue_continued
# This happens automatically inside advance()
var result = condition.call()
var target_goto_id = get_entry_condition_goto_ids(entry_id)[result]
Conditional entries don’t pause dialogue flow - they’re processed instantly and dialogue continues to the target entry.

Setting Condition Gotos

You must specify both the true and false paths for a conditional entry:
var condition_entry = add_conditional_entry(some_callable)

# Set both paths
var true_entry = add_text_entry("Condition was true")
var false_entry = add_text_entry("Condition was false")
condition_entry.set_condition_goto_ids(true_entry.get_id(), false_entry.get_id())

# You can also get the current goto IDs
var goto_ids = condition_entry.get_condition_goto_ids()
print(goto_ids[true])  # Entry ID for true path
print(goto_ids[false]) # Entry ID for false path
Failing to set valid condition goto IDs will cause the dialogue to be canceled with a warning when the condition is reached.

Using Callables

Conditions can use any callable that returns a boolean:

Method Reference

func is_player_friendly() -> bool:
    return player_reputation >= 50

var condition = add_conditional_entry(is_player_friendly)

Lambda Function

var gold = 100

var condition = add_conditional_entry(func() -> bool:
    return gold >= 50
)

Bound Callable

func has_item(item_name: String) -> bool:
    return inventory.has(item_name)

var condition = add_conditional_entry(has_item.bind("key"))

Conditional Entry with Branches

Conditional entries work seamlessly with branch IDs:
extends DialogueEngine

var have_we_talked_before = 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 = add_conditional_entry(__have_we_talked_before)
    
    # Each path goes to a different branch
    var if_true = add_text_entry("Hey! We meet again!", branch.STRANGERS)
    var if_false = 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>")
    
    # Update state when dialogue finishes
    dialogue_finished.connect(func() -> void:
        have_we_talked_before = true
    )

Managing Conditions

You can modify conditional entries after creation:
var entry = add_conditional_entry(some_condition)

# Check if entry has a condition
if entry.has_condition():
    var condition = entry.get_condition()
    
# Change the condition
entry.set_condition(different_condition)

# Remove the condition (converts to text entry)
entry.remove_condition()

Text vs Conditional Entries

An entry can be either text-based or conditional, not both. Setting a condition on a text entry will cause the text to be ignored.
var entry = add_text_entry("Hello!")
entry.set_condition(some_callable) # The text "Hello!" will be ignored!
If you need dialogue text after a condition, create separate entries:
# Good: Separate entries
var condition = add_conditional_entry(check_something)
var true_text = add_text_entry("Condition was true!")
var false_text = add_text_entry("Condition was false!")
condition.set_condition_goto_ids(true_text.get_id(), false_text.get_id())

Multiple Conditions in Sequence

You can chain conditional entries for complex logic:
func _setup() -> void:
    add_text_entry("Evaluating your character...")
    
    # First condition: Check reputation
    var rep_check = add_conditional_entry(func() -> bool:
        return reputation >= 50
    )
    
    # If high reputation, check gold
    var gold_check = add_conditional_entry(func() -> bool:
        return gold >= 100
    )
    
    # Different outcomes
    var rich_and_famous = add_text_entry("Welcome, honored guest!")
    var famous_only = add_text_entry("Welcome, friend!")
    var low_rep = add_text_entry("What do you want?")
    
    # Wire up the conditions
    rep_check.set_condition_goto_ids(gold_check.get_id(), low_rep.get_id())
    gold_check.set_condition_goto_ids(rich_and_famous.get_id(), famous_only.get_id())

Condition Signals

Conditional entries emit the entry_visited signal but not dialogue_continued:
# This fires for ALL entries (text and conditional)
dialogue_engine.entry_visited.connect(func(entry: DialogueEntry) -> void:
    if entry.has_condition():
        print("Condition evaluated!")
)

# This ONLY fires for text entries
dialogue_engine.dialogue_continued.connect(func(entry: DialogueEntry) -> void:
    print(entry.get_text())
)

Common Patterns

State Machine

Use conditions to implement quest states:
enum QuestState { NOT_STARTED, IN_PROGRESS, COMPLETED }
var quest_state = QuestState.NOT_STARTED

func check_quest_started() -> bool:
    return quest_state != QuestState.NOT_STARTED

func check_quest_completed() -> bool:
    return quest_state == QuestState.COMPLETED

func _setup() -> void:
    var state_check = add_conditional_entry(check_quest_started)
    
    var not_started = add_text_entry("I need your help with something...")
    var started_check = add_conditional_entry(check_quest_completed)
    
    var in_progress = add_text_entry("How's the quest going?")
    var completed = add_text_entry("Thank you for your help!")
    
    state_check.set_condition_goto_ids(started_check.get_id(), not_started.get_id())
    started_check.set_condition_goto_ids(completed.get_id(), in_progress.get_id())

Item Checks

Gate dialogue behind inventory items:
var inventory = ["sword", "shield"]

func has_key() -> bool:
    return "key" in inventory

func _setup() -> void:
    add_text_entry("The door is locked.")
    
    var key_check = add_conditional_entry(has_key)
    var with_key = add_text_entry("You unlock the door with your key.")
    var no_key = add_text_entry("You need a key to open this door.")
    
    key_check.set_condition_goto_ids(with_key.get_id(), no_key.get_id())

Time-Based Dialogue

func is_daytime() -> bool:
    var hour = Time.get_time_dict_from_system()["hour"]
    return hour >= 6 and hour < 18

func _setup() -> void:
    var time_check = add_conditional_entry(is_daytime)
    var day = add_text_entry("Good morning!")
    var night = add_text_entry("Good evening!")
    
    time_check.set_condition_goto_ids(day.get_id(), night.get_id())

Debugging Conditions

The dialogue debugger shows conditional entries as special nodes in the graph, making it easy to visualize branching logic.
Conditions can be complex, so consider adding debug logging:
func check_complex_condition() -> bool:
    var result = (gold >= 100 and reputation >= 50) or has_special_item
    if OS.is_debug_build():
        print("Condition result: ", result)
    return result

Best Practices

  1. Keep conditions simple: Complex logic should be in your game code, not in dialogue callables
  2. Always set both paths: Never leave condition gotos unset
  3. Use descriptive names: Name your condition functions clearly (e.g., has_completed_quest not check1)
  4. Test both branches: Make sure to test both true and false paths
  5. Consider using enums for branches: Makes it clear which branch each condition leads to
If a condition throws an error or returns a non-boolean value, it may cause unexpected behavior. Always ensure your callables return a valid boolean.

Next Steps

Options

Let players make choices with options

Branching

Learn more about branch IDs and gotos

Build docs developers (and LLMs) love