Score and replay¶
In this part, we'll add the score, music playback, and the ability to restart the game.
We have to keep track of the current score in a variable and display it on screen using a minimal interface. We will use a text label to do that.
In the main scene, add a new child node Control to Main
and name it
UserInterface
. You will automatically be taken to the 2D screen, where you can
edit your User Interface (UI).
Add a Label node and name it ScoreLabel
In the Inspector, set the Label's Text to a placeholder like "Score: 0".
Also, the text is white by default, like our game's background. We need to change its color to see it at runtime.
Scroll down to Theme Overrides, and expand Colors and enable Font Color in order to tint the text to black (which contrasts well with the white 3D scene)
Finally, click and drag on the text in the viewport to move it away from the top-left corner.
The UserInterface
node allows us to group our UI in a branch of the scene tree
and use a theme resource that will propagate to all its children. We'll use it
to set our game's font.
Creating a UI theme¶
Once again, select the UserInterface
node. In the Inspector, create a new
theme resource in Theme -> Theme.
Click on it to open the theme editor In the bottom panel. It gives you a preview of how all the built-in UI widgets will look with your theme resource.
By default, a theme only has one property, the Default Font.
See also
You can add more properties to the theme resource to design complex user interfaces, but that is beyond the scope of this series. To learn more about creating and editing themes, see Introduction to GUI skinning.
This one expects a font file like the ones you have on your computer. Two common font file formats are TrueType Font (TTF) and OpenType Font (OTF).
In the FileSystem dock, expand the fonts
directory and click and drag the
Montserrat-Medium.ttf
file we included in the project onto the Default Font.
The text will reappear in the theme preview.
The text is a bit small. Set the Default Font Size to 22
pixels to increase the text's size.
Keeping track of the score¶
Let's work on the score next. Attach a new script to the ScoreLabel
and define
the score
variable.
extends Label
var score = 0
using Godot;
public partial class ScoreLabel : Label
{
private int _score = 0;
}
The score should increase by 1
every time we squash a monster. We can use
their squashed
signal to know when that happens. However, because we instantiate
monsters from the code, we cannot connect the mob signal to the ScoreLabel
via the editor.
Instead, we have to make the connection from the code every time we spawn a monster.
Open the script main.gd
. If it's still open, you can click on its name in
the script editor's left column.
Alternatively, you can double-click the main.gd
file in the FileSystem
dock.
At the bottom of the _on_mob_timer_timeout()
function, add the following
line:
func _on_mob_timer_timeout():
#...
# We connect the mob to the score label to update the score upon squashing one.
mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())
private void OnMobTimerTimeout()
{
// ...
// We connect the mob to the score label to update the score upon squashing one.
mob.Squashed += GetNode<ScoreLabel>("UserInterface/ScoreLabel").OnMobSquashed;
}
This line means that when the mob emits the squashed
signal, the
ScoreLabel
node will receive it and call the function _on_mob_squashed()
.
Head back to the ScoreLabel.gd
script to define the _on_mob_squashed()
callback function.
There, we increment the score and update the displayed text.
func _on_mob_squashed():
score += 1
text = "Score: %s" % score
public void OnMobSquashed()
{
_score += 1;
Text = $"Score: {_score}";
}
The second line uses the value of the score
variable to replace the
placeholder %s
. When using this feature, Godot automatically converts values
to string text, which is convenient when outputting text in labels or
when using the print()
function.
See also
You can learn more about string formatting here: GDScript format strings. In C#, consider using string interpolation with "$".
You can now play the game and squash a few enemies to see the score increase.
Note
In a complex game, you may want to completely separate your user interface from the game world. In that case, you would not keep track of the score on the label. Instead, you may want to store it in a separate, dedicated object. But when prototyping or when your project is simple, it is fine to keep your code simple. Programming is always a balancing act.
Retrying the game¶
We'll now add the ability to play again after dying. When the player dies, we'll display a message on the screen and wait for input.
Head back to the main.tscn
scene, select the UserInterface
node, add a
child node ColorRect, and name it Retry
. This node fills a
rectangle with a uniform color and will serve as an overlay to darken the
screen.
To make it span over the whole viewport, you can use the Anchor Preset menu in the toolbar.
Open it and apply the Full Rect command.
Nothing happens. Well, almost nothing; only the four green pins move to the corners of the selection box.
This is because UI nodes (all the ones with a green icon) work with anchors and margins relative to thei