09.adding-the-countdown-timer

Adding the countdown timer

In this lesson, we’ll display a countdown timer to our game. We’ll use the timer to know if the player won or lost the race in the following lesson.

We’ll use two nodes to do so:

  1. A Timer.
  2. A new Label.

The Timer will keep track of the remaining time, and we’ll use the Label to display it on screen.

To start with, add a new Timer node as a child of ObstacleCourse.

In the Inspector, set the Wait Time property to 12 seconds and enable One Shot so the timer doesn’t cycle.

That will give the player 12 seconds to reach the finish line. If the timer times out, however, the player loses the race.

Next, add a Label as a child of the CanvasLayer node and name it RemainingTime.

Your scene tree should now look like this.

In the Inspector, set the Text to a number with two digits and two decimals, like 00.00. This is how we’ll write the remaining time: always with the same number of characters.

In the toolbar, use Layout -> Top Right to move the node to the top-right corner of the screen.

Click and drag on its bottom-left resize handle to give a bit of space to display the text.

Then, in the Inspector, set the Align property to Right.

Now, right-click on Theme Overrides -> Fonts -> Font and select Quick Load. Load the file named remaining_time_font.tres.

Finally, in the view, click and drag on the node’s bounding box to add a bit of margin between the game window’s edges and the text.

Animating the remaining time

We can now code the animated time display. In ObstacleCourse.gd, we first get our two new nodes using onready variables.

onready var ui_remaining_time := $CanvasLayer/RemainingTime
onready var timer := $Timer

Our timer gives us a time value as a float, but we need a String to update the label’s text.

Because we’ll need to update the text in three places by the end of the series, we write a short function to convert the time value into text.

func get_time_as_text(time: float) -> String:
    return str(time).pad_decimals(2).pad_zeros(2)

We first convert the time into a String with the str() function. We then call String.pad_decimals() and String.pad_zeros() to ensure there are always two digits on either side of the decimal place.

If we call the function with a time value of 9.0, it will output "09.00", and if we call it with a value of 11.4, the function will output "11.40", keeping the text’s length consistent.

We call our function in _process() to update the displayed text every frame.

func _process(delta: float) -> void:
    # The Timer.time_left variable tells us the timer's remaining time.
    ui_remaining_time.text = get_time_as_text(timer.time_left)

We then need to update our start() function to start the timer.

func start() -> void:
    # ...
    timer.start()

If you run the scene now, you’ll see the label start by displaying 00.00. After the countdown animation, it jumps to 12.00 and goes down progressively.

When starting the scene, we need to update the text in the _ready() function. We also need to turn off calls to _process() as, until we start the timer, the value of timer.time_left will be 0.0.

Below, notice how we pass the timer’s wait_time value to get_time_as_text() instead of timer.time_left. It’s the value we set in the Inspector earlier: 12 seconds.

func _ready() -> void:
    # ...
    ui_remaining_time.text = get_time_as_text(timer.wait_time)
    # This turns off calls to _process() every frame for this node.
    set_process(false)


func start() -> void:
    # ...
    # Once we started the timer, we can reactivate calls to _process() to update
    # the text every frame.
    set_process(true)

With that, our countdown timer is working. However, when the time reaches zero, nothing happens.

We’re missing our game’s win and lose conditions. We’ll add them in the next and last lesson for this series.