In this lesson, we’ll add buttons to our dialogue box based on each dialogue line’s dictionary.
By the end, you’ll be able to jump to various lines of dialogue.
As we saw in a previous lesson, we use a
to represent buttons associated with a line of dialogue.It maps the text to display on buttons to the ID of the line to jump to next.
# ...
{"Let me sleep a little longer": 2,
"Let's do it!": 1,
}# ...
To create buttons based on this data, we write a new function named
create_buttons()
. It takes a like the one above as a parameter.
onready var buttons_column := $MarginContainer/VBoxContainer/ButtonsColumn
func create_buttons(buttons_data: Dictionary) -> void:
for text in buttons_data:
var button := Button.new()
= text
button.text add_child(button) buttons_column.
For each key in the buttons_data
dictionary, we create a
node and add it as a child of the
ButtonsColumn node.
That’ll allow us to remove existing buttons easily when moving to the next line.
We want to jump to the button’s target line ID when pressing the
button. We can do that by connecting to the button’s
pressed
signal and binding the ID.
Binding is the process of storing a value and “attaching” it to our signal’s callback.
We first extract the target line’s ID from the
buttons_data
. We then pass the value in the
connect()
function’s fourth argument.
func create_buttons(buttons_data: Dictionary) -> void:
for text in buttons_data:
# ...
var target_line_id: int = buttons_data[text]
# The last argument means that pressing the button will call the
# show_line() function with the value of `target_line_id`.
#
# We must store this value in an array as this is what the connect()
# function expects.
connect("pressed", self, "show_line", [target_line_id]) button.
When this button gets pressed, the show_line()
function
will be called with the target_line_id
as its argument.
The complete function should have the following code:
func create_buttons(buttons_data: Dictionary) -> void:
for text in buttons_data:
var button := Button.new()
= text
button.text add_child(button)
buttons_column.var target_line_id: int = buttons_data[text]
# The last argument means that pressing the button will call the
# show_line() function with the value of `target_line_id`.
#
# We must store this value in an array as this is what the connect()
# function expects.
connect("pressed", self, "show_line", [target_line_id]) button.
We can now call the create_buttons()
function from
show_line()
.
func show_line(id: int) -> void:
# ...
if line_data.buttons:
create_buttons(line_data.buttons)
If you run the scene now, the buttons appear, and clicking them takes you to a new line of dialogue.
However, we have a problem: each button click causes more buttons to appear.
When displaying a new line of dialogue, we need to delete the buttons
from the previous line. We can do that with the
queue_free()
member function.
Every node in Godot comes with a member function named
queue_free()
. Calling it asks the engine to mark the node
for deletion and delete it as soon as possible.
Godot keeps a queue of these nodes in an array and deletes as many as it can at the end of each frame.
We must delete existing buttons before creating new ones. Otherwise,
no button will ever appear. Add the new code before calling
create_buttons()
.
func show_line(id: int) -> void:
# ...
for button in buttons_column.get_children():
# Use this function to safely destroy a node when you don't need it
# anymore.
queue_free()
button.# ...
The function get_children()
returns an array with every
that exists as a child of the
ButtonsColumn node.
We can use it in a for
loop to free every in the ButtonsColumn.
When we reach the dialogue’s end, we don’t have any new buttons to create.
In this case, we can create a button that quits the game, as we did in the slideshow series.
We write a new function that creates a button, sets its text, and adds it as a child of the ButtonsColumn node.
func add_quit_button() -> void:
var button := Button.new()
= "Quit"
button.text add_child(button)
buttons_column.# We can directly connect a signal to a function or another object. Clicking
# the button will call the scene tree's quit() function.
connect("pressed", get_tree(), "quit") button.
We then call the new function from the show_line()
function.
func show_line(id: int) -> void:
# ...
if line_data.buttons:
create_buttons(line_data.buttons)
# If the line has no buttons, we reached the dialogue's end, so we create a
# single quit button.
else:
add_quit_button()
In the next lesson, we’ll make the dialogue box more appealing with text animation and audio.
Open the practice Main menu screen.
Once you complete the practice, you should be able to navigate between the main menu screens by clicking the buttons.
Here’s the complete code for DialogueTree.gd
so far.
extends PanelContainer
var dialogue = {
0: {
"text": "Hey, [i]wake up![/i] It's time to make video games.",
"expression": "neutral",
"buttons":
{"Let me sleep a little longer": 2,
"Let's do it!": 1,
}
},1: {
"text": "Great! Your first task will be to write a [b]dialogue tree[/b].",
"expression": "neutral",
"buttons":
{"I'm not sure if i'm ready, but i will do my best": 3,
"No, let me go back to sleep": 2,
}
},2: {
"text": "Oh, come on! It'll be fun.",
"expression": "sad",
"buttons":
{"No, really, let me go back to sleep": 0,
"Alright, I'll try": 3,
}
},3: {
"text": "That's the spirit! You can do it!\n[wave][b]YOU WIN[/b][/wave]",
"expression": "happy",
"buttons": {}
}
}
onready var texture_rect := $MarginContainer/VBoxContainer/HBoxContainer/TextureRect
onready var rich_text_label := $MarginContainer/VBoxContainer/HBoxContainer/RichTextLabel
onready var buttons_column := $MarginContainer/VBoxContainer/ButtonsColumn
func _ready() -> void:
show_line(0)
func show_line(id: int) -> void:
# We extract the line's data from the `dialogue` variable.
var line_data: Dictionary = dialogue[id]
# We then pass the text and expression values of the line's data to
# functions that update the displayed text and character portrait.
set_text(line_data.text)
set_expression(line_data.expression)
for button in buttons_column.get_children():
# Use this function to safely destroy a node when you don't need it
# anymore.
queue_free()
button.
if line_data.buttons:
create_buttons(line_data.buttons)
# If the line has no buttons, we reached the dialogue's end, so we create a
# single quit button.
else:
add_quit_button()
func set_text(new_text: String) -> void:
= new_text
rich_text_label.bbcode_text
func set_expression(expression: String) -> void:
if expression == "sad":
= preload("portraits/sophia_sad.png")
texture_rect.texture # Elif is the contraction of "else if." This block will run if the previous
# condition didn't pass.
elif expression == "happy":
= preload("portraits/sophia_happy.png")
texture_rect.texture # Any `expression` that we don't list above will display the neutral
# character portrait.
else:
= preload("portraits/sophia_neutral.png")
texture_rect.texture
func add_quit_button() -> void:
var button := Button.new()
= "Quit"
button.text add_child(button)
buttons_column.# We can directly connect a signal to a function or another object. Clicking
# the button will call the scene tree's quit() function.
connect("pressed", get_tree(), "quit")
button.
func create_buttons(buttons_data: Dictionary) -> void:
for text in buttons_data:
var button := Button.new()
= text
button.text add_child(button)
buttons_column.var target_line_id: int = buttons_data[text]
# The last argument means that pressing the button will call the
# show_line() function with the value of `target_line_id`.
#
# We must store this value in an array as this is what the connect()
# function expects.
connect("pressed", self, "show_line", [target_line_id]) button.