Video Game Design and Coding · PLECK lesson
PLECK lesson 45 · suggested week 9, day 5
Explain and assess the arcade game
Objective. Demonstrate code ownership through prediction and reproducible tests.
Godot Engine Standard desktop editor 4.7.2-stable, GDScript (Godot 4), Compatibility renderer. Headless Godot 4.7.2.stable.official.ed1daf0bf passed 106 assertions on the supplied scaffolds, including malformed JSON saves. Audio used the Dummy driver and a synthetic WAV, not a speaker. The visual editor, browser export, and signed-in submission were not tested. The desk still does not run a student's own upload.
About 55 minutes. The due date is chosen by a parent.
Fair randomization, difficulty, accessibility and assessment
Suggested pace: Recall and predict 5 min; Read instruction and scaffold 12 min; Build or design the assigned artifact 25 min; Run acceptance tests 8 min; Explain and checkpoint 5 min.
Materials: Godot Standard 4.7.2 desktop editor (GDScript, Compatibility renderer), Local course folder and prior checkpoint, No paid assets or public account required.
Comes after VGC-L044.
A programming assessment combines the running artifact, source inspection and explanation. Before running, predict the result of a hit on Title, two rapid hits in Play and a hit after Lose. Then execute those tests. Explain how one spawned hazard reaches the manager’s damage function. Include one repaired bug with before/after evidence. You may present the explanation as text or private audio/video, but the editable project and event test log remain required. Keep optional polish separate from required functionality.
Worked example
An incoming three-lane wave blocks left and center but leaves right clear. Randomly choosing among valid two-lane combinations creates variation while preserving an escape route. Scaffold C-TIMER — Timer-based spawning with a hard cap Setup: World Node2D root contains Node2D Hazards and Timer SpawnTimer: wait_time 1.2, autostart On, one_shot Off. Hazard scene root Area2D has a CircleShape2D radius 12 and C-HAZARD. Attach script to World and drag Hazard.tscn into hazard_scene Inspector field. Set viewport 640x360. Name Hazards group not needed. This script is a separate lab; merge its _ready into an existing manager rather than declaring a second _ready. ```gdscript extends Node2D @export var hazard_scene: PackedScene @export var spawn_limit: int = 12 func _ready() -> void: $SpawnTimer.timeout.connect(_spawn) func _spawn() -> void: if hazard_scene == null or $Hazards.get_child_count() >= spawn_limit: return var hazard := hazard_scene.instantiate() as Area2D $Hazards.add_child(hazard) hazard.position = Vector2(randf_range(20.0, 620.0), -20.0) ``` Scaffold C-HAZARD — Falling hazard with player notification and cleanup Setup: Area2D Hazard root on layer 4 (bit 8), mask layer 1 only, child CollisionShape2D circle radius 12. Attach this script. Player in group player. A Player damage method is deliberately not hardcoded: connect hit_player to a manager’s damage handler when instantiating. Parent Hazards must be unscaled and unrotated. ```gdscript extends Area2D signal hit_player @export var fall_speed: float = 110.0 var hit: bool = false func _ready() -> void: body_entered.connect(_on_body_entered) func _draw() -> void: draw_rect(Rect2(-12, -12, 24, 24), Color.TOMATO) draw_line(Vector2(-8,-8),Vector2(8,8),Color.BLACK,2.0) func _physics_process(delta: float) -> void: position.y += fall_speed * delta if position.y > 390.0: queue_free() func _on_body_entered(body: Node2D) -> void: if hit or not body.is_in_group("player"): return hit = true hit_player.emit() queue_free() ``` Scaffold C-STATES — Explicit game states and gated scoring Setup: Attach to Node root StateLab with child Label Status. This is a standalone demonstrator; call start_round, add_points, finish_round from buttons or Remote Inspector. In a real game the manager owns this state. Additional Player and spawner scripts must also honor the manager’s state, or be disabled when finishing. ```gdscript extends Node enum Phase { TITLE, PLAY, WIN, LOSE } var phase: Phase = Phase.TITLE var score: int = 0 func _ready() -> void: _show() func start_round() -> void: score = 0 phase = Phase.PLAY _show() func add_points(amount: int) -> void: if phase != Phase.PLAY: return score += maxi(amount, 0) _show() func finish_round(won: bool) -> void: if phase != Phase.PLAY: return phase = Phase.WIN if won else Phase.LOSE _show() func _show() -> void: $Status.text = "State: %s | Score: %d" % [Phase.keys()[phase], score] ```
Timer-based spawning with a hard cap
GDScript (Godot 4, not Scratch, Python, JavaScript or C#). Scaffold C-TIMER. Copy starts from this text. The desk does not run it.
Scene and node setup
World Node2D root contains Node2D Hazards and Timer SpawnTimer: wait_time 1.2, autostart On, one_shot Off. Hazard scene root Area2D has a CircleShape2D radius 12 and C-HAZARD. Attach script to World and drag Hazard.tscn into hazard_scene Inspector field. Set viewport 640x360. Name Hazards group not needed. This script is a separate lab; merge its _ready into an existing manager rather than declaring a second _ready.
extends Node2D
@export var hazard_scene: PackedScene
@export var spawn_limit: int = 12
func _ready() -> void:
$SpawnTimer.timeout.connect(_spawn)
func _spawn() -> void:
if hazard_scene == null or $Hazards.get_child_count() >= spawn_limit:
return
var hazard := hazard_scene.instantiate() as Area2D
$Hazards.add_child(hazard)
hazard.position = Vector2(randf_range(20.0, 620.0), -20.0)
Checks to run in the editor
- One object appears about each 1.2 seconds.
- At most 12 hazards exist, even during a long run.
Native Godot 4.7.2.stable.official.ed1daf0bf headless compile and representative node-tree/runtime tests passed; see native-qa-results.json. Visual UI, audible output, exports and website submission still need separate checks.
Falling hazard with player notification and cleanup
GDScript (Godot 4, not Scratch, Python, JavaScript or C#). Scaffold C-HAZARD. Copy starts from this text. The desk does not run it.
Scene and node setup
Area2D Hazard root on layer 4 (bit 8), mask layer 1 only, child CollisionShape2D circle radius 12. Attach this script. Player in group player. A Player damage method is deliberately not hardcoded: connect hit_player to a manager’s damage handler when instantiating. Parent Hazards must be unscaled and unrotated.
extends Area2D
signal hit_player
@export var fall_speed: float = 110.0
var hit: bool = false
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _draw() -> void:
draw_rect(Rect2(-12, -12, 24, 24), Color.TOMATO)
draw_line(Vector2(-8,-8),Vector2(8,8),Color.BLACK,2.0)
func _physics_process(delta: float) -> void:
position.y += fall_speed * delta
if position.y > 390.0:
queue_free()
func _on_body_entered(body: Node2D) -> void:
if hit or not body.is_in_group("player"):
return
hit = true
hit_player.emit()
queue_free()
Checks to run in the editor
- Hazard moves downward and disappears beyond y390.
- One overlap emits one damage event.
Native Godot 4.7.2.stable.official.ed1daf0bf headless compile and representative node-tree/runtime tests passed; see native-qa-results.json. Visual UI, audible output, exports and website submission still need separate checks.
Explicit game states and gated scoring
GDScript (Godot 4, not Scratch, Python, JavaScript or C#). Scaffold C-STATES. Copy starts from this text. The desk does not run it.
Scene and node setup
Attach to Node root StateLab with child Label Status. This is a standalone demonstrator; call start_round, add_points, finish_round from buttons or Remote Inspector. In a real game the manager owns this state. Additional Player and spawner scripts must also honor the manager’s state, or be disabled when finishing.
extends Node
enum Phase { TITLE, PLAY, WIN, LOSE }
var phase: Phase = Phase.TITLE
var score: int = 0
func _ready() -> void:
_show()
func start_round() -> void:
score = 0
phase = Phase.PLAY
_show()
func add_points(amount: int) -> void:
if phase != Phase.PLAY:
return
score += maxi(amount, 0)
_show()
func finish_round(won: bool) -> void:
if phase != Phase.PLAY:
return
phase = Phase.WIN if won else Phase.LOSE
_show()
func _show() -> void:
$Status.text = "State: %s | Score: %d" % [Phase.keys()[phase], score]
Checks to run in the editor
- Calling add_points before start does not change score.
- After finish_round, extra score events do nothing.
Native Godot 4.7.2.stable.official.ed1daf0bf headless compile and representative node-tree/runtime tests passed; see native-qa-results.json. Visual UI, audible output, exports and website submission still need separate checks.
Explain and assess the arcade game — evidence task
- 1. Submit the arcade source, readme and state/cooldown test log.
- 2. Explain signal flow and predict the three damage cases before testing.
Deliverable. Meteor Mail assessment ZIP or private downloadable ZIP link, explanations and test log.
Acceptance checks
- VGC-L045-T1. Predictions are compared with actual results.
- VGC-L045-T2. Teacher can locate manager, spawner and hazard scripts in the artifact.
Understanding question. What is the signal path for a runtime hazard hit?
Include a downloadable project ZIP with project.godot, .tscn, .gd and original asset files when code changes. A private, teacher-accessible download link to that ZIP is equivalent. Screenshots or a playthrough do not replace editable source.
If this is still shaky. Tune just hazard speed and spawn interval on a copied project. Use fixed test runs before adding more mechanics. Start with the first assigned task and re-run its acceptance test before adding the remaining feature.
Optional extra. Offer an optional challenge mode while retaining a gentle default. Document one changed parameter, your prediction, and the observed effect; optional, not required for completion.
- Your first 2D game
Free public tutorial; no account; external asset download is optional, not used by this course.
· Opening this link does not submit the lesson.
Name ____________________ Date ____________________