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

image1

In the Inspector, set the Label's Text to a placeholder like "Score: 0".

image2

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)

image3

Finally, click and drag on the text in the viewport to move it away from the top-left corner.

image4

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.

image5

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.

image6

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.

image7

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

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.

image8

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())

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

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.

image9

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.

image10

Open it and apply the Full Rect command.

image11

Nothing happens. Well, almost nothing; only the four green pins move to the corners of the selection box.

image12

This is because UI nodes (all the ones with a green icon) work with anchors and margins relative to thei