Games feel much better with animations. In games, we often use computer-generated animations called tweens, short for in-betweens.
We give the computer a desired starting and end state and let it animate in-between.
For example, we could tell the computer to start by showing no text and end with the whole text visible, letting the computer animate the transition.
All the frames between the start and the end are the tween animation.
Godot comes with a node dedicated to tween animation: the
node.Select the Slideshow node and add a new
node as a child.Then, head back to the Slideshow script by clicking the script icon in the Scene dock.
We want to get the
node in an onready variable and use it to animate the text.onready var tween := $Tween
We need a place to run our animation code. We want to animate the text every time we go to the next slide, so there are three places we could pick from:
advance()
function, which takes us to the next
slide.show_slide()
function, which displays the new
slide.set_text()
function, which updates the
displayed text.Which would you pick?
You will have to make this kind of decision all the time as a developer. It’s not always easy as there are often several valid options.
In this case, we’ll use the set_text() function because we’re animating the text exclusively.
To set up a tween animation, we call
Tween.interpolate_property()
. This function takes five
arguments:
The percent_visible
property that controls the percentage of the text displayed on the
screen.
When the value is 0.0
, no text is visible. When the
value is 1.0
, you can see all the text.
We use it to animate the text whenever we update it, like so.
func set_text(new_text: String) -> void:
# ...
# We want the text to display at a fixed rate. This division ensures that
# the text will appear at a rate of 60 characters per second.
var duration := new_text.length() / 60.0
interpolate_property(label, "percent_visible", 0.0, 1.0, duration)
tween.start() tween.
You can press F6 to run the scene and see the text animate.
Whenever we press the button, even if the text is still animating, we skip to the next slide. That’s not nice, as the player could miss a lot of the text.
If the text is still animating, pressing the button should show it all. And when the text is entirely visible, pressing it should advance to the next slide.
We can achieve that by adding a condition to the
advance()
function.
func advance() -> void:
# If the animation is still in progress, we seek to the end.
if tween.is_active():
seek(INF)
tween.# Otherwise, we run through our previous condition.
else:
if slide_index == last_slide_index:
get_tree().quit()
else:
show_slide(slide_index + 1)
The last four lines are the same code as in the previous lesson.
On top of that, we now call Tween.is_active()
to check
if the animation is still running. If so, we call
tween.seek()
to advance to the end of the animation.
The value INF
is a constant built into Godot that
represents infinity. We can use it to go to the end of any animation
reliably.
If you run the scene now, the text should advance more naturally when pressing the button.
Open the practice Party members list.
In this practice, you’ll reuse the same techniques you learned in the past two chapters to create party members’ displays.
This concludes the short slideshow series.
With it, you already know the core of displaying linear dialogues:
Two functions and some data, that’s all it takes. While we have a
third function, set_text()
, its code could be part of
show_slide()
.
In the next series, we’ll go further with a branching dialogue system.
Here’s the complete code for Slideshow.gd
.
extends Control
var slides := [
{"text": "In this series, you'll code a slideshow.",
"texture": preload("common/backgrounds/community_garden.jpg"),
},
{"text":
"You can use the techniques you'll see here for simple cut-scenes, linear conversations, and more.",
"texture": preload("common/backgrounds/community_garden.jpg"),
},
{"text":
"What you'll see here isn't limited to conversations. You'll learn the basics behind any sequence of events.",
"texture": preload("common/backgrounds/dani_bedroom.jpg"),
},
{"text":
'We will build upon this series to later create a branching dialogue system, a sort of "choose your own adventure" toy.',
"texture": preload("common/backgrounds/dani_bedroom.jpg"),
}
]
var slide_index := 0
var last_slide_index := slides.size() - 1
onready var label := $VBoxContainer/Label
onready var button := $VBoxContainer/Button
onready var texture_rect := $VBoxContainer/TextureRect
onready var tween := $Tween
func _ready() -> void:
connect("pressed", self, "advance")
button.show_slide(0)
func show_slide(new_slide_index: int) -> void:
= new_slide_index
slide_index
var slide: Dictionary = slides[slide_index]
set_text(slide["text"])
= slide["texture"]
texture_rect.texture if slide_index == last_slide_index:
= "Quit"
button.text
func advance() -> void:
# If the animation is still in progress, we seek to the end.
if tween.is_active():
seek(INF)
tween.# Otherwise, we run through our previous condition.
else:
if slide_index == last_slide_index:
get_tree().quit()
else:
show_slide(slide_index + 1)
func set_text(new_text: String) -> void:
= new_text
label.text # We want the text to display at a fixed rate. This division ensures that
# the text will appear at a rate of 60 characters per second.
var duration := new_text.length() / 60.0
interpolate_property(label, "percent_visible", 0.0, 1.0, duration)
tween.start() tween.