Lesson 07
The win condition. We build an exit that stays locked until every fragment is collected, then activates and ends the game when the player reaches it. This ties the whole loop together.
Exit. Save as exit.tscn.Attach exit.gd. The exit starts inactive and dim; it lights up when unlocked.
extends Area3D
signal reached()
@onready var mesh: MeshInstance3D = $MeshInstance3D
var active: bool = false
func _ready() -> void:
body_entered.connect(_on_body_entered)
_set_visual_locked()
# Called by Main when all fragments are collected.
func activate() -> void:
active = true
_set_visual_unlocked()
func _on_body_entered(body: Node3D) -> void:
if active and body.is_in_group("player"):
reached.emit()
func _set_visual_locked() -> void:
_tint(Color(0.3, 0.3, 0.35)) # dull grey = locked
func _set_visual_unlocked() -> void:
_tint(Color(0.35, 0.9, 0.6)) # bright green = open
# Apply an emissive tint to the exit's material at runtime.
func _tint(c: Color) -> void:
var mat := StandardMaterial3D.new()
mat.albedo_color = c
mat.emission_enabled = true
mat.emission = c
mesh.material_override = matmaterial_override at runtime
Setting material_override in code swaps the look instantly — no editor material needed. We build a fresh StandardMaterial3D and assign it. This is how you show state changes (locked → unlocked) visually.
exit.tscn under Main, placed somewhere the player must return to.Update main.gd: grab the exit, activate it on completion, and win when reached:
@onready var exit: Area3D = $Exit
func _ready() -> void:
var frags: Array = get_tree().get_nodes_in_group("fragments")
total_fragments = frags.size()
for frag in frags:
frag.collected.connect(_on_fragment_collected)
for hz in get_tree().get_nodes_in_group("hazards"):
hz.player_hit.connect(_on_player_hit)
exit.reached.connect(_on_exit_reached)
_respawn_player()
func _on_fragment_collected(_frag: Area3D) -> void:
collected_count += 1
if collected_count >= total_fragments:
exit.activate()
func _on_exit_reached() -> void:
print("YOU WIN! All fragments collected and exit reached.")
# HUD win screen comes in the next lesson.Guard the activate call
If total_fragments is 0 (you forgot to place fragments or group them), collected_count >= total_fragments is true immediately and the exit activates at once. During testing that's a useful shortcut; before shipping, make sure fragments are actually grouped as fragments.
Checkpoint — expected state
exit.tscn that starts locked and tints to show state.