【Godot】Building a Simple Dialogue System (Text, Choices, Branching)

Created: 2025-12-08

Learn how to build a basic dialogue system in Godot Engine from scratch, implementing text display, typewriter effect, and branching choices.

Introduction: Why Dialogue Systems Are Important

In games, conversation with characters is one of the most important elements for conveying the world and drawing players into the story. Especially in RPGs and adventure games, the dialogue system greatly influences the quality of the game experience.

This article, aimed at beginners to intermediate users, explains how to simply implement the core features of a dialogue system: text display, presenting choices, and conversation branching.

1. Core Concepts of Dialogue Systems

The three main elements needed to build a simple dialogue system are:

  1. UI Nodes: Label for displaying text, Button for choices, and Control node groups for arranging them.
  2. Dialogue Data: Data structures holding conversation content, speakers, choices, and transition information to the next conversation.
  3. Control Script: GDScript that loads data, manipulates UI, and advances conversation based on player input.

The simplest and most extensible dialogue data structure combines GDScript's arrays and dictionaries.

const DIALOGUE = [
    {
        "id": 1,
        "speaker": "Villager A",
        "text": "Welcome, traveler. Is this your first time in this village?",
        "next_id": 2
    },
    {
        "id": 2,
        "speaker": "Villager A",
        "text": "Actually, we have a problem. I have a favor to ask you...",
        "choices": [
            {"text": "Hear the story", "next_id": 3},
            {"text": "I'm busy now", "next_id": 4}
        ]
    }
]

2. Implementing Text Display and Typewriter Effect

Rather than displaying dialogue text instantly, adding a typewriter effect that displays one character at a time creates a more game-like presentation.

UI Setup

Create a scene with a Control node as root and place the following nodes:

  • Control (root node)
    • PanelContainer (background)
      • VBoxContainer
        • Label (for displaying speaker name)
        • Label (for dialogue text display, dialogue_text)
        • HBoxContainer (for placing choice buttons, choice_container)

Control Script (DialogueSystem.gd)

extends Control

@onready var dialogue_text: Label = $PanelContainer/VBoxContainer/DialogueText
@onready var speaker_name: Label = $PanelContainer/VBoxContainer/SpeakerName
@onready var choice_container: HBoxContainer = $PanelContainer/VBoxContainer/ChoiceContainer

const DIALOGUE_DATA = preload("res://dialogue_data.gd").DIALOGUE
var current_dialogue_index: int = 0
var is_typing: bool = false
var full_text: String = ""
const TYPING_SPEED: float = 0.05

@onready var typing_timer: Timer = Timer.new()

func _ready():
    typing_timer.timeout.connect(_on_typing_timer_timeout)
    add_child(typing_timer)
    start_dialogue(1)

func start_dialogue(id: int):
    var dialogue_entry = get_dialogue_by_id(id)
    if dialogue_entry == null:
        queue_free()
        return

    for child in choice_container.get_children():
        child.queue_free()

    current_dialogue_index = id
    speaker_name.text = dialogue_entry.get("speaker", "???")
    full_text = dialogue_entry.text
    dialogue_text.text = ""
    dialogue_text.visible_characters = 0
    is_typing = true
    typing_timer.start(TYPING_SPEED)

func _on_typing_timer_timeout():
    if dialogue_text.visible_characters < full_text.length():
        dialogue_text.visible_characters += 1
    else:
        typing_timer.stop()
        is_typing = false

        var dialogue_entry = get_dialogue_by_id(current_dialogue_index)
        if dialogue_entry.has("choices"):
            display_choices(dialogue_entry.choices)

func get_dialogue_by_id(id: int) -> Dictionary:
    for entry in DIALOGUE_DATA:
        if entry.id == id:
            return entry
    return null

3. Implementing Choices and Conversation Branching

By introducing choices into conversations, you can implement branching where player actions affect the story.

Dynamic Choice Button Generation

func display_choices(choices: Array):
    for child in choice_container.get_children():
        child.queue_free()

    for choice in choices:
        var button = Button.new()
        button.text = choice.text
        button.pressed.connect(func(): _on_choice_button_pressed(choice.next_id))
        choice_container.add_child(button)

func _on_choice_button_pressed(next_id: int):
    for child in choice_container.get_children():
        child.queue_free()

    start_dialogue(next_id)

4. Practical Usage Example: Integration into Game Scenes

Instance this DialogueSystem scene into your main game scene for use.

# MainScene.gd
var dialogue_scene = preload("res://DialogueSystem.tscn")
var is_dialogue_active: bool = false

func _on_npc_interact():
    if not is_dialogue_active:
        var dialogue_instance = dialogue_scene.instantiate()
        add_child(dialogue_instance)
        is_dialogue_active = true
        dialogue_instance.tree_exited.connect(_on_dialogue_finished)
        dialogue_instance.start_dialogue(1)

func _on_dialogue_finished():
    is_dialogue_active = false

Summary

The simple dialogue system introduced in this article implements all core features—text display, typewriter effect, and branching choices—using only Godot Engine's basic UI nodes and GDScript functionality.

Building on this system as a foundation, you can provide richer conversation experiences by adding extensions like:

  • External Data Management: Externalize DIALOGUE_DATA as JSON or CSV files
  • Enhanced Presentation: Display speaker portraits, change SE/BGM during conversation
  • Conditional Branching: Dynamic changes based on player status or in-game flags

Note: Dialogic Addon as an Alternative

This article explained how to build a dialogue system from scratch, but if you want to efficiently create a more feature-rich dialogue system, Dialogic is a compelling alternative.

Dialogic is a visual dialogue editor addon for Godot Engine with the following features:

  • Visual Editor: Design conversation flows using a node-based editor without writing code
  • Rich Features: Character management, timelines, conditional branching, and variable management built-in
  • Community Support: Active community and comprehensive documentation

Building your own system is ideal for learning purposes or when full customization is required, but for large-scale projects or when non-programmers need to edit dialogues, consider adopting Dialogic. It can be installed for free from the Godot Asset Library.