Skip to main content
Dynamic text lets you insert variables, game state, and computed values into your dialogue, making conversations feel personalized and responsive to the player’s actions.

Overview

The Dialogue Engine supports two ways to format dynamic text:
  • Format Operator (%): GDScript’s built-in string formatting
  • Format Function (String.format()): Dictionary-based formatting
Both approaches support Callables - functions that are evaluated when the dialogue is displayed, ensuring the text always shows current game state.

Using the Format Operator (%)

The format operator is perfect for simple variable substitution:
1

Add an entry with format placeholders

Use standard format specifiers in your text:
var entry: DialogueEntry = add_text_entry(
    "You have %d gold coins and %d health points."
)
2

Set the format data

Provide an array of values and specify the format operation:
entry.set_format(
    [player.gold, player.health],
    DialogueEntry.FORMAT_OPERATOR
)
3

Get the formatted text

Use get_formatted_text() instead of get_text():
func __on_dialogue_continued(p_dialogue_entry: DialogueEntry) -> void:
    var formatted_text: String = p_dialogue_entry.get_formatted_text()
    display_text(formatted_text)
    # Output: "You have 150 gold coins and 80 health points."

Format Specifiers

SpecifierTypeExample
%d or %sInteger/String"You are level %d"
%fFloat"Temperature: %f°C"
%.2fFloat (2 decimals)"Price: $%.2f"
%%Literal %"Success rate: %d%%"

Using String.format()

The format function uses dictionaries for named placeholders:
var entry: DialogueEntry = add_text_entry(
    "Greetings, {player_name}! You have {quest_count} active quests."
)

entry.set_format(
    {
        "player_name": "Hero",
        "quest_count": 3
    },
    DialogueEntry.FORMAT_FUNCTION
)

# Result: "Greetings, Hero! You have 3 active quests."
Use FORMAT_FUNCTION when you have many variables or want self-documenting placeholder names.

Dynamic Values with Callables

Callables let you compute values at display time, not when the dialogue is created:
func get_player_level() -> int:
    return player.level

var entry: DialogueEntry = add_text_entry(
    "You are level %d. Impressive!"
)

entry.set_format(
    [Callable(self, "get_player_level")],
    DialogueEntry.FORMAT_OPERATOR
)
Callables are evaluated every time get_formatted_text() is called, so the dialogue will always show the current value.

Nested Data Structures

You can use nested arrays and dictionaries:
var entry: DialogueEntry = add_text_entry(
    "Stats - STR: %d, DEX: %d, INT: %d"
)

entry.set_format(
    [
        func() -> int: return player.stats["strength"],
        func() -> int: return player.stats["dexterity"],
        func() -> int: return player.stats["intelligence"]
    ],
    DialogueEntry.FORMAT_OPERATOR
)

Practical Examples

var quest_entry: DialogueEntry = add_text_entry(
    "You have completed %d out of %d quests."
)

quest_entry.set_format(
    [
        func() -> int: return quest_manager.get_completed_count(),
        func() -> int: return quest_manager.get_total_count()
    ],
    DialogueEntry.FORMAT_OPERATOR
)

DialogueEntry Format Methods

MethodDescription
set_format(data: Variant, operation_id: int)Sets format data and operation
get_format() -> DictionaryReturns the format dictionary
get_formatted_text() -> StringReturns text with formatting applied
get_evaluated_format() -> VariantReturns format data with callables evaluated
has_format() -> boolReturns true if format is set
remove_format()Removes format data

Format Operation Constants

ConstantValueDescription
FORMAT_OPERATOR3Use % operator
FORMAT_FUNCTION2Use String.format()
FORMAT_NONE1No formatting (text as-is)

Common Patterns

Use callables instead of static values:
# ❌ Bad - value is fixed when dialogue is created
entry.set_format([player.health], DialogueEntry.FORMAT_OPERATOR)

# ✅ Good - value updates each time text is displayed
entry.set_format(
    [func() -> int: return player.health],
    DialogueEntry.FORMAT_OPERATOR
)
You can use BBCode formatting alongside dynamic text:
var entry: DialogueEntry = add_text_entry(
    "[b]{name}[/b]: You have [color=yellow]{gold}[/color] gold."
)

entry.set_format(
    {
        "name": func() -> String: return player.name,
        "gold": func() -> int: return player.gold
    },
    DialogueEntry.FORMAT_FUNCTION
)
Use callables to return different strings based on conditions:
var entry: DialogueEntry = add_text_entry(
    "The door is {door_state}."
)

entry.set_format(
    {
        "door_state": func() -> String:
            return "open" if door.is_open else "locked"
    },
    DialogueEntry.FORMAT_FUNCTION
)
Handle singular/plural forms dynamically:
var entry: DialogueEntry = add_text_entry(
    "You have %d %s remaining."
)

entry.set_format(
    [
        func() -> int: return item_count,
        func() -> String: return "item" if item_count == 1 else "items"
    ],
    DialogueEntry.FORMAT_OPERATOR
)

Best Practices

  1. Always use get_formatted_text() when you’ve set format data
  2. Use callables for dynamic values that change during gameplay
  3. Cache expensive computations in your callable if needed
  4. Test edge cases like zero values, empty strings, and extreme numbers
  5. Consider localization - some languages may need different format strings

Next Steps

Build docs developers (and LLMs) love