All lessons
Lesson 11

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.

Build style

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

The scenes

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

timer_countdown.tscn
  • TimerCountdown Node2D timer_countdown.gd
  • TimerPlayer CharacterBody2D player.tscn
  • GoalArea Area2D %
  • ColorRect
  • CollisionShape2D
  • HUD CanvasLayer hud.tscn
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • VBoxContainer
  • TimerLabel Label %
  • MessageLabel Label %
  • HomeButton Button %
player.tscn
  • TimerPlayer CharacterBody2D player.gd
  • Sprite2D
  • CollisionShape2D
Build style
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

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.
	%GoalArea.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
	# "TimerPlayer" is the class_name at the top of player.gd.
	if body is TimerPlayer:
		race_over = true
		GameEvents.goal_reached.emit()