All lessons
Lesson 25

Beat the Clock

Ten coins, twenty seconds, no second chances. Assembles Top-Down Movement, Collect Coins, and Timer Countdown.

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

This is a project, not a lesson: nothing in this folder is new. Ten coins are scattered across the field and the clock gives you twenty seconds.

BUILD IT FROM

  1. Top-Down Movement: the player, copied straight in.
  2. Collect Coins and Items: the coins, emitting on the bus.
  3. Timer Countdown: the clock, counting down in _process.

WHAT THE GLUE DOES

Two lessons already knew how to end a game; here they finally disagree. The coins want you to win, the clock wants you to lose, and the race_over flag is the referee: whichever fires first wins the argument, and the flag stops the other one from firing at all.

The scenes

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

beat_the_clock.tscn
  • BeatTheClock Node2D beat_the_clock.gd
  • ClockPlayer CharacterBody2D player.tscn
  • HUD CanvasLayer hud.tscn
  • Coins Node2D
  • Coin1 Area2D coin.tscn
  • Coin2 Area2D coin.tscn
  • Coin3 Area2D coin.tscn
  • Coin4 Area2D coin.tscn
  • Coin5 Area2D coin.tscn
  • Coin6 Area2D coin.tscn
  • Coin7 Area2D coin.tscn
  • Coin8 Area2D coin.tscn
  • Coin9 Area2D coin.tscn
  • Coin10 Area2D coin.tscn
coin.tscn
  • Coin Area2D coin.gd
  • ColorRect
  • CollisionShape2D
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • HBoxContainer
  • TimerLabel Label %
  • ScoreLabel Label %
  • ResultLabel Label %
  • HomeButton Button %
player.tscn
  • ClockPlayer CharacterBody2D player.gd
  • Sprite2D
  • CollisionShape2D
extends Node2D

## How many seconds the player gets.
@export var countdown_time: float = 20.0
## How many coins are in the scene. Change this if you add coins!
@export var total_coins: int = 10

var time_remaining: float
var collected: int = 0
var race_over: bool = false

@onready var player: ClockPlayer = $ClockPlayer

func _ready() -> void:
	time_remaining = countdown_time
	# coin.gd emits on the bus; the HUD and this file both connect. One
	# emit, two reactions: the Collect Coins pattern, unchanged.
	GameEvents.coin_collected.connect(_on_coin_collected)

func _process(delta: float) -> void:
	if race_over:
		return

	time_remaining = max(time_remaining - delta, 0.0)
	# hud.gd connects to this and redraws the clock, ~60 times a second.
	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
		player.set_physics_process(false)
		# hud.gd shows the lose message.
		GameEvents.timer_expired.emit()
		_restart_soon()

func _on_coin_collected(_value: int) -> void:
	if race_over:
		return
	collected += 1
	if collected >= total_coins:
		race_over = true
		player.set_physics_process(false)
		# hud.gd shows the win message.
		GameEvents.goal_reached.emit()
		_restart_soon()

func _restart_soon() -> void:
	var timer: SceneTreeTimer = get_tree().create_timer(2.0)
	timer.timeout.connect(_on_restart_timeout)

func _on_restart_timeout() -> void:
	get_tree().reload_current_scene()