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:
- UI Nodes:
Labelfor displaying text,Buttonfor choices, andControlnode groups for arranging them. - Dialogue Data: Data structures holding conversation content, speakers, choices, and transition information to the next conversation.
- 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)VBoxContainerLabel(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_DATAas 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.