Lesson 10

Timer Countdown

Race the clock. Reach the green goal in time and you win; let the timer hit zero and you lose.

Loading the game engine. This takes a moment the first time.

Perspective

Race the clock. Reach the green goal in time and you win. Let the timer hit zero and you lose.

New code in this lesson

None of the earlier lessons use these. Each one shows the line where it appears here.

max()

The bigger of the numbers given. min() gives the smaller.

game.gd
time_remaining = max(time_remaining - delta, 0.0)

The scenes

■ UI node · ■ 2D node · % unique name · .tscn instanced scene

game.tscn
  • Game Node2D game.gd
  • Player CharacterBody2D player.tscn
  • GoalArea Area2D %
  • Polygon2D
  • CollisionShape2D
  • HUD CanvasLayer hud.tscn
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • VBoxContainer
  • TimerLabel Label %
  • MessageLabel Label %
player.tscn
  • Player CharacterBody2D group: Player player.gd
  • Sprite2D
  • CollisionShape2D

FileSystem

  • game.tscn
  • game.gd
  • hud.tscn
  • hud.gd
  • player.tscn
  • player.gd

game_events.gd

Switch it on once, under Project > Project Settings, on the Globals tab. Not made yet? Press Create Script to make it right there. Already made it? Press Select Script/Scene, pick it, and press Add. Its Name column should say GameEvents, which Godot fills in for you. That's the name your code uses.

The code

Perspective
extends Node2D

## How many seconds the player gets.
@export var countdown_time: float = 10.0

# How many seconds are left. "_ready()" fills it in.
var time_remaining: float
# Becomes true once the race ends, either way. It stops the countdown so
# the result stays frozen on screen instead of ticking past.
var race_over: bool = false

@onready var goal_area: Area2D = %GoalArea

func _ready() -> void:
	time_remaining = countdown_time
	# GoalArea is the green zone. We put "_on_goal_reached" inside
	# ".connect()". That is the function Godot runs when it is touched.
	goal_area.body_entered.connect(_on_goal_reached)

# "_process()" runs every frame. Godot calls it for you.
func _process(delta: float) -> void:
	# Race already finished, so do nothing. "return" leaves early.
	if race_over:
		return

	# Take away the time that just passed. "max(..., 0.0)" means "use the
	# bigger of these two", which stops the clock going below zero.
	time_remaining = max(time_remaining - delta, 0.0)
	# hud.gd connects to "timer_updated" and redraws the clock. This one
	# gets emitted about 60 times a second, once per frame.
	GameEvents.timer_updated.emit(time_remaining)

	if time_remaining <= 0.0:
		# Set the flag FIRST, so this block can never run twice.
		race_over = true
		GameEvents.timer_expired.emit()

# "body" is whatever touched the goal zone.
func _on_goal_reached(body: Node2D) -> void:
	# If the clock already ran out, reaching the goal is too late.
	if race_over:
		return
	# "Player" is a group ticked on the player's root node (Node dock >
	# Groups). Both versions of the player are in it, so both can win.
	if body.is_in_group("Player"):
		race_over = true
		GameEvents.goal_reached.emit()