Week 5 packet
Student instructions, tasks, and checks. The answer key is not in this packet. Links stay as text. This page does not embed a clip or run a file.
# Week 05 — Collect and communicate
## VGC-L021 — Detect an overlap without blocking movement
**Objective:** Build an Area2D pickup and explain body_entered.
**Time:** 55 minutes
### Instruction
An Area2D senses overlaps but does not act as a solid wall. Build C-PICKUP with a circle collider and visible gold marker. It checks layer 1 because it only needs to notice the Player body. Add Player to the player group in the Node dock; groups express a role without depending on an instance’s exact name. The signal callback receives the entering body. Testing the group prevents a wall or another body from collecting the coin accidentally.
### Worked example
A pickup emits collected(1); a manager receives that event and changes one score variable. The label formats that score rather than keeping a second independent total.
Scaffold C-PICKUP — An exactly-once pickup signal
Setup: Create Area2D root Coin, CollisionShape2D Circle radius 10. Layer 3 (bit value 4), mask layer 1 only. Monitoring on. Attach script and save Coin.tscn. In the Node > Groups editor add Player root to group player. Put a Coin instance and Player instance in World. Manager connects each Coin.collected to its score handler (see C-MANAGER). Signal wiring here is code-only: do not also connect body_entered in editor.
```gdscript
extends Area2D
signal collected(points: int)
@export var points: int = 1
var taken: bool = false
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _draw() -> void:
draw_circle(Vector2.ZERO, 10.0, Color.GOLD)
draw_line(Vector2(-5, 0), Vector2(5, 0), Color.BLACK, 2.0)
func _on_body_entered(body: Node2D) -> void:
if taken or not body.is_in_group("player"):
return
taken = true
collected.emit(points)
queue_free()
```
### Assignment
1. Create Coin.tscn and put one instance on a reachable route.
2. Add Player to group player and test collection.
### Acceptance checks
- Player passes through the coin’s position.
- Only a body in player group makes the coin disappear.
**Submit:** Coin source and one-coin overlap test note.
**Question:** Why use Area2D rather than StaticBody2D for a collectible?
**Support:** Use one coin and one player in a blank World. Confirm the group and mask before adding a score label. Start with the first assigned task and re-run its acceptance test before adding the remaining feature.
**Optional extension:** Add a second pickup value using the same signal instead of duplicating the whole scene. Document one changed parameter, your prediction, and the observed effect; optional, not required for completion.
**Optional free reading:** [Godot step by step: nodes, scenes, GDScript, input, signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/index.html)
---
## VGC-L022 — Protect an event from being counted twice
**Objective:** Use a guard flag before emitting and removing a pickup.
**Time:** 55 minutes
### Instruction
queue_free schedules removal rather than removing a node halfway through every physics calculation. More than one overlap event may occur before that removal completes. C-PICKUP sets taken to true before emitting collected, so later calls return without awarding another point. This is a small transaction: check eligibility, mark used, announce the reward, then remove the object. Do not connect the same body_entered signal both in the editor and in code; duplicate connections can hide an otherwise correct design.
### Worked example
A pickup emits collected(1); a manager receives that event and changes one score variable. The label formats that score rather than keeping a second independent total.
Scaffold C-PICKUP — An exactly-once pickup signal
Setup: Create Area2D root Coin, CollisionShape2D Circle radius 10. Layer 3 (bit value 4), mask layer 1 only. Monitoring on. Attach script and save Coin.tscn. In the Node > Groups editor add Player root to group player. Put a Coin instance and Player instance in World. Manager connects each Coin.collected to its score handler (see C-MANAGER). Signal wiring here is code-only: do not also connect body_entered in editor.
```gdscript
extends Area2D
signal collected(points: int)
@export var points: int = 1
var taken: bool = false
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _draw() -> void:
draw_circle(Vector2.ZERO, 10.0, Color.GOLD)
draw_line(Vector2(-5, 0), Vector2(5, 0), Color.BLACK, 2.0)
func _on_body_entered(body: Node2D) -> void:
if taken or not body.is_in_group("player"):
return
taken = true
collected.emit(points)
queue_free()
```
### Assignment
1. Trace the order of the four pickup operations.
2. Temporarily print from the collected handler and test approaching the same coin from a corner.
### Acceptance checks
- One coin produces exactly one award.
- No duplicate signal connection is present.
**Submit:** Annotated pickup script and event-count log.
**Question:** Why set taken before emitting collected?
**Support:** Use one coin and one player in a blank World. Confirm the group and mask before adding a score label. Start with the first assigned task and re-run its acceptance test before adding the remaining feature.
**Optional extension:** Add a second pickup value using the same signal instead of duplicating the whole scene. Document one changed parameter, your prediction, and the observed effect; optional, not required for completion.
**Optional free reading:** [Godot step by step: nodes, scenes, GDScript, input, signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/index.html)
---
## VGC-L023 — Let the level own the score
**Objective:** Connect a signal to a single authoritative state variable.
**Time:** 55 minutes
### Instruction
A coin should announce its points without knowing where the HUD lives. The World manager owns the round’s score and connects each coin’s collected signal to one handler. This keeps a reusable pickup from depending on a fragile node path into the level. Begin with three fixed coins rather than random spawning. Read C-MANAGER’s loop: each coin connects the same signal to _on_collected. Start score at zero when the scene is created and add points only inside that one handler.
### Worked example
A pickup emits collected(1); a manager receives that event and changes one score variable. The label formats that score rather than keeping a second independent total.
Scaffold C-MANAGER — A complete three-coin collection round
Setup: Create World Node2D root with Player (C-BODY), three Coin instances (C-PICKUP), and CanvasLayer HUD > Label named Score plus Button named Restart (text Restart, position 500,10). Root and parents unscaled. Name coins Coin1, Coin2, Coin3. Place them at (250,100),(380,220),(520,100). Player group player. Attach script to World. UI paths and coin names must match exactly. Connect nothing else in the editor. Label at (10,10).
```gdscript
extends Node2D
var score: int = 0
var playing: bool = true
@onready var score_label: Label = $HUD/Score
func _ready() -> void:
$HUD/Restart.pressed.connect(_restart)
for coin in [$Coin1, $Coin2, $Coin3]:
coin.collected.connect(_on_collected)
_refresh()
func _on_collected(points: int) -> void:
if not playing:
return
score += points
if score >= 3:
playing = false
$Player.set_physics_process(false)
_refresh()
func _refresh() -> void:
score_label.text = "Score: %d / 3" % score
if not playing:
score_label.text += " — Orchard complete!"
func _restart() -> void:
get_tree().reload_current_scene()
```
Scaffold C-PICKUP — An exactly-once pickup signal
Setup: Create Area2D root Coin, CollisionShape2D Circle radius 10. Layer 3 (bit value 4), mask layer 1 only. Monitoring on. Attach script and save Coin.tscn. In the Node > Groups editor add Player root to group player. Put a Coin instance and Player instance in World. Manager connects each Coin.collected to its score handler (see C-MANAGER). Signal wiring here is code-only: do not also connect body_entered in editor.
```gdscript
extends Area2D
signal collected(points: int)
@export var points: int = 1
var taken: bool = false
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _draw() -> void:
draw_circle(Vector2.ZERO, 10.0, Color.GOLD)
draw_line(Vector2(-5, 0), Vector2(5, 0), Color.BLACK, 2.0)
func _on_body_entered(body: Node2D) -> void:
if taken or not body.is_in_group("player"):
return
taken = true
collected.emit(points)
queue_free()
```
### Assignment
1. Build the named World children required by C-MANAGER.
2. Connect all three coins and print score changes in the handler.
### Acceptance checks
- Collecting coins produces 1, then 2, then 3.
- Moving without collection does not change score.
**Submit:** Three-coin manager project and score trace.
**Question:** Why should each coin not keep its own copy of the total score?
**Support:** Use one coin and one player in a blank World. Confirm the group and mask before adding a score label. Start with the first assigned task and re-run its acceptance test before adding the remaining feature.
**Optional extension:** Add a second pickup value using the same signal instead of duplicating the whole scene. Document one changed parameter, your prediction, and the observed effect; optional, not required for completion.
**Optional free reading:** [Godot step by step: nodes, scenes, GDScript, input, signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/index.html)
---
## VGC-L024 — Make feedback readable in a HUD
**Objective:** Update a Label from state rather than storing state in text.
**Time:** 55 minutes
### Instruction
The HUD is feedback layered over the game world. A CanvasLayer keeps it separate from world motion. Store score as an integer in the manager, then derive the Label text using formatting. Do not read the visible string to decide whether the player won: changing wording or translation would break the rule. Put the HUD where it does not cover the route. Use both a number and a word or icon so color alone is not the only way to understand progress.
### Worked example
A pickup emits collected(1); a manager receives that event and changes one score variable. The label formats that score rather than keeping a second independent total.
Scaffold C-MANAGER — A complete three-coin collection round
Setup: Create World Node2D root with Player (C-BODY), three Coin instances (C-PICKUP), and CanvasLayer HUD > Label named Score plus Button named Restart (text Restart, position 500,10). Root and parents unscaled. Name coins Coin1, Coin2, Coin3. Place them at (250,100),(380,220),(520,100). Player group player. Attach script to World. UI paths and coin names must match exactly. Connect nothing else in the editor. Label at (10,10).
```gdscript
extends Node2D
var score: int = 0
var playing: bool = true
@onready var score_label: Label = $HUD/Score
func _ready() -> void:
$HUD/Restart.pressed.connect(_restart)
for coin in [$Coin1, $Coin2, $Coin3]:
coin.collected.connect(_on_collected)
_refresh()
func _on_collected(points: int) -> void:
if not playing:
return
score += points
if score >= 3:
playing = false
$Player.set_physics_process(false)
_refresh()
func _refresh() -> void:
score_label.text = "Score: %d / 3" % score
if not playing:
score_label.text += " — Orchard complete!"
func _restart() -> void:
get_tree().reload_current_scene()
```
### Assignment
1. Add the Score label at (10,10) and format Score: n / 3.
2. Increase its font size and move one route obstacle if HUD overlap occurs.
### Acceptance checks
- Displayed count matches the numeric variable after every coin.
- The HUD remains readable against the playfield.
**Submit:** Project checkpoint and HUD screenshot at scores zero and two.
**Question:** Which is authoritative: score or the label text?
**Support:** Use one coin and one player in a blank World. Confirm the group and mask before adding a score label. Start with the first assigned task and re-run its acceptance test before adding the remaining feature.
**Optional extension:** Add a second pickup value using the same signal instead of duplicating the whole scene. Document one changed parameter, your prediction, and the observed effect; optional, not required for completion.
**Optional free reading:** [Godot step by step: nodes, scenes, GDScript, input, signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/index.html)
---
## VGC-L025 — Finish and restart a small playable game
**Objective:** Implement a completion state and a clean reset.
**Time:** 55 minutes
### Instruction
A complete small game needs a beginning, active play and an ending the player can recognize. C-MANAGER marks playing false after the third coin and disables the Player’s physics processing. The Restart button reloads the current scene, rebuilding the original coins and zero score. This is a useful reset for a small game, but it also discards unsaved in-memory data. Confirm the main scene is World so restarting the intended game does not return to an earlier lab.
### Worked example
A pickup emits collected(1); a manager receives that event and changes one score variable. The label formats that score rather than keeping a second independent total.
Scaffold C-MANAGER — A complete three-coin collection round
Setup: Create World Node2D root with Player (C-BODY), three Coin instances (C-PICKUP), and CanvasLayer HUD > Label named Score plus Button named Restart (text Restart, position 500,10). Root and parents unscaled. Name coins Coin1, Coin2, Coin3. Place them at (250,100),(380,220),(520,100). Player group player. Attach script to World. UI paths and coin names must match exactly. Connect nothing else in the editor. Label at (10,10).
```gdscript
extends Node2D
var score: int = 0
var playing: bool = true
@onready var score_label: Label = $HUD/Score
func _ready() -> void:
$HUD/Restart.pressed.connect(_restart)
for coin in [$Coin1, $Coin2, $Coin3]:
coin.collected.connect(_on_collected)
_refresh()
func _on_collected(points: int) -> void:
if not playing:
return
score += points
if score >= 3:
playing = false
$Player.set_physics_process(false)
_refresh()
func _refresh() -> void:
score_label.text = "Score: %d / 3" % score
if not playing:
score_label.text += " — Orchard complete!"
func _restart() -> void:
get_tree().reload_current_scene()
```
### Assignment
1. Add the Restart button and completion feedback.
2. Complete, restart, then complete again without closing the editor.
### Acceptance checks
- Each round begins with three coins and score zero.
- Movement stops after completion and resumes after restart.
**Submit:** Pocket Orchard first full game ZIP and two-round checklist.
**Question:** Why is resetting only the score variable insufficient?
**Support:** Use one coin and one player in a blank World. Confirm the group and mask before adding a score label. Start with the first assigned task and re-run its acceptance test before adding the remaining feature.
**Optional extension:** Add a second pickup value using the same signal instead of duplicating the whole scene. Document one changed parameter, your prediction, and the observed effect; optional, not required for completion.
**Optional free reading:** [Godot step by step: nodes, scenes, GDScript, input, signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/index.html)
---
