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:
- The layer rule: which side does the looking, hitbox or hurtbox — and why?
- 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()toqueue_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_enteredfires 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 (togglingmonitoring, or the shape'sdisabled, re-fires it), or botharea_enteredandbody_enteredare 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. diedtwice → your guarantee from section A has a hole; write the failing sequence as a comment above the fix.