CHALLENGE 2: A Health Bar Wired to the Chapter 7 Health System ==================================================================== TASK ---- Build the full Chapter 7 health.gd health system plus a ProgressBar HUD element that connects to its health_changed signal, matching this chapter's own health_bar.gd example. Call take_damage() a few times and confirm the bar's value visibly drops each time. SCENE STRUCTURE ---------------- Root (Node2D) |-- Health (Node) <- health.gd |-- HUD (CanvasLayer) |-- HealthBar (ProgressBar) <- health_bar.gd, Layout: Top Left SOLUTION CODE - health.gd (attached to Health, unchanged from Chapter 7) ------------------------------------------------------------------------------- extends Node signal health_changed(current: int, max_health: int) signal died var current: int = 100 var max_health: int = 100 func take_damage(amount: int) -> void: current = max(current - amount, 0) health_changed.emit(current, max_health) if current == 0: died.emit() SOLUTION CODE - health_bar.gd (attached to HealthBar) ------------------------------------------------------------ extends ProgressBar func _ready() -> void: var health = get_node("/root/Root/Health") health.health_changed.connect(_on_health_changed) min_value = 0 max_value = health.max_health value = health.current func _on_health_changed(current: int, max_health: int) -> void: value = current TRIGGERING IT (e.g. from Root) ----------------------------------- func _ready() -> void: await get_tree().create_timer(1.0).timeout $Health.take_damage(25) await get_tree().create_timer(1.0).timeout $Health.take_damage(25) await get_tree().create_timer(1.0).timeout $Health.take_damage(25) WHY THIS WORKS AS AN ANSWER ---------------------------- health.gd is copied over unchanged - the whole point of this exercise, per the chapter's own framing, is that the health system itself needs no modification at all to gain a visible HUD. health_bar.gd does two things in _ready(): it looks up the Health node once (via the same kind of absolute path the chapter's own example uses, with its own caveat about fragility noted), connects to its health_changed signal, and also immediately syncs the bar's starting min_value/max_value/value from the health system's own current numbers - so the bar shows the correct full-health state from the very first frame, not just after the first change. From then on, _on_health_changed(current, max_health) is the only thing that needs to run on every future health change - it simply sets this ProgressBar's own value property to whatever current now is. Since ProgressBar automatically redraws itself whenever its value changes, calling health.take_damage(25) three times (spaced out with await/create_timer so each drop is visible rather than instant) visibly shrinks the bar three times in a row, from full down toward empty, entirely driven by the signal - health_bar.gd never calls take_damage() itself or reads Health's own variables directly outside of that one initial sync.