Video Game Design and Coding · PLECK lesson

PLECK lesson 41 · suggested week 9, day 1

Add a cooldown without hiding the rule

Objective. Prevent rapid repeated damage using a visible invulnerability interval.

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-L040.

Fairness can require a short recovery window after a hit. Add an invulnerable Boolean owned by the manager and a one-shot RecoveryTimer set to one second. A valid hit sets the flag, reduces lives, starts the timer and shows “Recovering” in the HUD. Timer timeout clears the flag. Do not use rapid flashing; a steady outline and text are sufficient. Decide whether a hazard touching during recovery disappears or continues, then document that consistent rule.

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-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] ```

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

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

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.

Add a cooldown without hiding the rule — evidence task

  1. 1. Implement a one-second recovery window with nonflashing feedback.
  2. 2. Test two overlaps within half a second and one after recovery.

Deliverable. Cooldown code and timestamped test note.

Acceptance checks

Understanding question. Why is merely changing the sprite color insufficient for invulnerability?

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.

Name ____________________ Date ____________________