doc 5 of 9

Stage 03 — The Sword

You will build: a sword swing with real active frames, a reusable Hitbox component, and input buffering — the invisible feature every good action game has and no one notices. You'll learn: the Hitbox component · active frames · input buffering — and design decision 3 (your cancel policy) gets its first draft. Warm-up — from memory:

  1. Draw your state machine as it stands: states, arrows, triggers.
  2. During DASH, what happens to a dash input, and which line of code decides that?

Why this exists#

An attack is a shape that hurts, briefly. The industry pattern: an Area2D — the hitbox — positioned where the weapon sweeps, switched on for a handful of frames mid-swing, then off. No damage math lives in it; it's a bell that rings "I touched something." Built as its own small scene, the same component becomes the slime's bite in stage 05 unchanged — that reusability is the architecture's whole thesis, starting now.

Buffering is the other half: at speed, players press attack a few frames early — during a dash, during the last swing. Games that drop those inputs feel broken; games that remember the most recent one for ~0.15s feel telepathic.

Build it#

A — Collision layers, named now#

Project settings, 2D physics layer names: world, player, enemy, player_hit, enemy_hit. The architecture doc states the hitbox/hurtbox layer rule and asks you to work out why the hurtbox does the looking — do that reasoning now, write one sentence in the architecture doc, and set this stage's hitbox accordingly: layer player_hit, mask nothing.

B — The Hitbox component#

components/hitbox.tscn: Area2D root + CollisionShape2D, script exporting @export var data: AttackData (make the AttackData Resource first: damage, knockback force — numbers you'll tune in stage 04). Give it activate() / deactivate() that toggle monitoring and the shape's disabled — then, in the running scene, use the Remote tree to confirm what each actually toggles; the difference will matter someday and today is the cheap day to see it.

C — The swing#

Player gains ATTACK: a SwordPivot (Node2D) child rotated to facing, the Hitbox instanced under it, offset ~20px. The state's spine is a timeline, driven by a short Timer chain or awaited timers — your call:

windup (0.06s, hitbox off) → active (0.10s, hitbox ON) → recovery (0.12s, hitbox off) → RUN

Movement during a swing: frozen, or a heavy slow — decide with your hands. And the first half of decision 3: does dash cancel recovery? Try both for ten minutes; write your current lean in the architecture doc (stage 06 revisits it once hits have weight).

D — The buffer#

One variable and one timestamp: when attack (or dash) is pressed and can't fire, remember it and when; when the current state ends, fire the remembered action if it's fresher than ~0.15s. Test honestly: mash attack during a swing — exactly one follow-up should come out, never two, never zero.

E — Proof of touch#

No health yet — prove the bell rings: connect the hitbox's area_entered/body_entered to a print, park a plain StaticBody2D dummy in the arena, and swing at it from all four facings. (It won't print until the dummy can be seen by those masks — make the dummy detectable in whatever way your layer reasoning says is right. If your answer is "put it on enemy... but nothing masks enemy yet" — good catch: that's next stage's Hurtbox arriving. For today, let the hitbox mask enemy temporarily and note the TODO.)

git add . && git commit -m "stage 03: sword swing, hitbox component, input buffer"

Checkpoint — definition of done#

  • The swing has three visible phases (tint them) and the hitbox is on only in the middle
  • Swings come out toward all four facings, positioned correctly
  • Mash-test passes: one buffered follow-up, never two
  • The layer sentence is written in the architecture doc, and the hitbox's layer/mask match it
  • Zero warnings; committed. Explain: why does the hitbox not deal damage itself — what goes wrong later if it does?

Stretch (no instructions)#

A two-swing combo: attack during recovery chains a second, mirrored swing with slightly different timing. Your buffer already wants to do this.

If you get stuck#

  • Prints double per swing → the area is seeing body and area, or your active window toggles twice; log the signal name with the print.
  • Hitbox lags a frame behind facing → rotate the pivot when facing changes, not when the swing starts... or is it the reverse you want? (Try attacking mid-turn both ways.)
  • Buffer fires stale inputs → you stored the press but not the time; both halves matter.