doc 6 of 9

Stage 04 — Something That Bleeds

You will build: the receiving side of combat — Hurtbox and Health components, a training dummy that takes damage, knocks back, and dies — and design decision 1 (knockback) gets made. You'll learn: component composition for real · signal chains · knockback design Warm-up — from memory:

  1. The layer rule: which side does the looking, hitbox or hurtbox — and why?
  2. Your swing's three phases and their rough durations.

Why this exists#

Damage is a chain of small, deaf components: Hitbox rings when it touches something · Hurtbox hears it, reads the AttackData, and tells its own Health · Health counts, and announces damaged and died to whoever cares. Nobody in the chain knows what a player or a slime is — which is exactly why, next stage, an enemy gets all three for free. Today the chain gets built and proven on the humblest possible victim.

Build it#

A — The Health component#

components/health.gd, a plain Node: @export var max_health: int, a current value, signal damaged(amount: int), signal died, and a take_damage(amount: int) -> void that clamps, emits, and emits died exactly once (a dead thing hit again should not re-die — decide how that's guaranteed and be ready to defend it).

B — The Hurtbox component#

components/hurtbox.tscn: Area2D + shape. Mask: the opposing hit layer (exported or configured per instance — the player's hurtbox will mask enemy_hit; the dummy's masks player_hit). On area entered: read the hitbox's AttackData, pass damage to a sibling Health — and how the hurtbox finds its Health is a design moment: @export var health: Health wired in the editor is the honest, visible dependency. Also: check the owner's is_invincible if it exposes one — and just like that, stage 02's dash flag becomes a feature.

C — The dummy#

enemies/dummy.tscn: StaticBody2D (layer enemy), sprite, Hurtbox + Health composed in. Remove stage 03's temporary hitbox-masks-enemy TODO — the chain is real now. Wire damaged to a floating number or a print; died to queue_free (a poof comes in stage 06). Hit it. Watch the chain fire. This moment — damage flowing through components you built — is the architecture's thesis proven.

D — Knockback (decision 1)#

Give the dummy mass in your mind, then make the call the architecture frames: velocity injection with friction bleed-off vs a dedicated knockback state. The dummy is static, so prototype the mechanism on a quick RigidBody or a CharacterBody version of it — you're choosing for stage 05's slime, and choosing blind is how mushy combat happens. Record choice + reason. The AttackData.knockback number finally means something; tune it until a hit looks like a hit even before juice exists.

git add . && git commit -m "stage 04: health + hurtbox components, dummy dies, knockback chosen"

Checkpoint — definition of done#

  • Sword → dummy: damage number appears, health decrements, dummy dies at zero, exactly one died
  • Overkill test: hitting the corpse-frame does nothing rude (no error, no double death)
  • Dash-through test: with a hitbox aimed at the player's hurtbox (fake one enemy-side hitbox in the arena for a minute), dashing through it takes no damage — i-frames work
  • Decision 1 recorded with reason; knockback prototyped and tuned on something movable
  • Zero warnings; committed. Explain: trace one sword hit from activate() to queue_free, naming every node and signal in order — out loud, no notes.

Stretch (no instructions)#

Damage numbers that pop, drift, and fade — instanced labels, a tween, self-cleanup. You've owned every ingredient since the farming game.

If you get stuck#

  • Nothing happens on hit → the chain has five links (hitbox on? layers? masks? signal connected? health wired?); print at each link and find the first silent one. That technique outlives this game.
  • Damage fires multiple times per swing → area_entered fires once when the overlap starts, so repeats mean the overlap is starting more than once. Two usual causes: the hitbox is being re-armed mid-swing (toggling monitoring, or the shape's disabled, re-fires it), or both area_entered and body_entered are connected and each is landing on the same target. Once you know which, decide whose job it is to care — the hitbox (one ring per activation) or the hurtbox (ignore repeats) — and implement it deliberately.
  • died twice → your guarantee from section A has a hole; write the failing sequence as a comment above the fix.