All lessons
Lesson 24

Target Blitz

Targets keep appearing, so destroy fifteen before they crowd you out. Assembles Mouse Aiming, Shooting Bullets, and Enemy Spawner.

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

This is a project, not a lesson: no new idea lives in this folder. Aim, shoot, and destroy fifteen targets. They keep appearing whether you keep up or not.

BUILD IT FROM

  1. Mouse Aiming: the player turns to face the mouse.
  2. Shooting Bullets: hold to fire, bullets hit targets.
  3. Enemy Spawner: a Timer plus instantiate(), spawning targets instead of enemies, at random spots instead of the screen edges.

WHAT THE GLUE DOES

Each spawned target has a local "destroyed" signal, and since this file creates every target, it connects to each one right after instantiate(), the same parent-reaches-down pattern as the Shooting Bullets lesson. The count lives here; the HUD hears about it on the bus.

The scenes

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

target_blitz.tscn
  • TargetBlitz Node2D target_blitz.gd
  • BlitzPlayer CharacterBody2D player.tscn
  • HUD CanvasLayer hud.tscn
  • SpawnTimer Timer %
bullet.tscn
  • Bullet Area2D bullet.gd
  • ColorRect
  • CollisionShape2D
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • ScoreLabel Label %
  • ResultLabel Label %
  • HomeButton Button %
player.tscn
  • BlitzPlayer CharacterBody2D player.gd
  • Sprite2D
  • Gun ColorRect
  • Muzzle Marker2D %
  • ShootTimer Timer %
  • CollisionShape2D
target.tscn
  • Target StaticBody2D target.gd
  • ColorRect
  • CollisionShape2D
extends Node2D

## Which scene to copy each time we spawn. Set to target.tscn in Godot.
@export var target_scene: PackedScene
## How many seconds to wait between spawns.
@export var spawn_interval: float = 1.1
## The most targets allowed on screen at once.
@export var max_targets: int = 6
## Destroy this many to win.
@export var targets_to_win: int = 15

var _destroyed: int = 0
var _alive: int = 0
var _game_over: bool = false

@onready var spawn_timer: Timer = %SpawnTimer
@onready var player: BlitzPlayer = $BlitzPlayer

func _ready() -> void:
	spawn_timer.wait_time = spawn_interval
	spawn_timer.timeout.connect(_on_spawn_timer_timeout)
	spawn_timer.start()

func _on_spawn_timer_timeout() -> void:
	if _game_over or _alive >= max_targets:
		return
	if target_scene == null:
		return

	var target: BlitzTarget = target_scene.instantiate()
	# Anywhere in the middle of the play field, away from the edges and
	# never right on top of the player's corner.
	target.global_position = Vector2(randf_range(260, 1080), randf_range(90, 580))
	# We made this target, so we can connect to its local signal, exactly
	# how the Shooting Bullets level counts its four hand-placed targets.
	target.destroyed.connect(_on_target_destroyed)
	target.tree_exiting.connect(_on_target_removed)
	add_child(target)
	_alive += 1

func _on_target_destroyed() -> void:
	if _game_over:
		return
	_destroyed += 1
	# hud.gd connects to this bus signal and redraws the score.
	GameEvents.target_destroyed.emit()
	if _destroyed >= targets_to_win:
		_game_over = true
		spawn_timer.stop()
		player.set_physics_process(false)
		# hud.gd shows the win message.
		GameEvents.goal_reached.emit()
		_restart_soon()

func _on_target_removed() -> void:
	_alive -= 1

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