Lesson 25

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 the Enemy Spawner lesson uses. The count lives here; the HUD hears about it on the bus.

The scenes

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

game.tscn
  • Game Node2D game.gd
  • Player CharacterBody2D player.tscn
  • HUD CanvasLayer hud.tscn
  • SpawnTimer Timer %
bullet.tscn
  • Bullet Area2D bullet.gd
  • Polygon2D
  • CollisionShape2D
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • ScoreLabel Label %
  • ResultLabel Label %
player.tscn
  • Player CharacterBody2D player.gd
  • Sprite2D
  • Gun Polygon2D
  • Muzzle Marker2D %
  • ShootTimer Timer %
  • CollisionShape2D
target.tscn
  • Target StaticBody2D target.gd
  • Polygon2D
  • CollisionShape2D

FileSystem

  • game.tscn
  • game.gd
  • bullet.tscn
  • bullet.gd
  • hud.tscn
  • hud.gd
  • player.tscn
  • player.gd
  • target.tscn
  • target.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

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: Player = $Player

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. No bus
	# needed to hear from something this file built itself.
	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()