doc 2 of 9

Action RPG — Architecture

Fast-paced top-down action: dash through attacks, hit hard, get out. Think small-scale Hyper Light Drifter energy at 32×32. The design has one organizing idea: combat is a conversation between components — health, hitboxes, and hurtboxes are reusable pieces that don't know whether they're attached to the player, a slime, or a training dummy. Build them once, compose them everywhere.

Systems map#

SystemPatternWhy
MovementCharacterBody2D with acceleration and friction (velocity.move_toward)Fast games live or die on feel; instant velocity (the farming approach) feels stiff here — ramps make weight
Player statesState machine from day one: RUN / DASH / ATTACK (+HURT later)Fast-paced means overlapping intentions — dash pressed mid-swing, hit mid-dash. Without a state machine this becomes flag soup by stage 3
DashA state with a duration timer, a cooldown timer, and invincibility framesThe defining verb of the genre; also the cleanest possible state-machine lesson — enter, commit, exit
AttacksHitbox component (Area2D + damage data), switched on for the swing's active framesDamage dealing as an attachable scene: the sword swing, an enemy's bite, and a trap are the same component with different numbers
Taking damageHurtbox component (Area2D) + Health component (plain Node with damaged/died signals)Damage receiving decoupled from everything: the HUD listens to the player's Health, the death poof listens to the slime's — same code
KnockbackSee decision 1The feel of impact; wrong choice makes combat mushy
Enemy AIPer-enemy state machine: IDLE / CHASE / WINDUP / ATTACK / STUNNED — style per decision 2The windup state IS the gameplay: readable telegraphs are what make fast combat fair
Game feelHitstop, screenshake, flash — a small Juice toolkit"Fast-paced" is mostly perception; these three sell every hit
ArenaWave spawner + score + restartThe smallest shape that makes combat a game; rooms/dungeons come later

Later modules (designed for, not built): stats & leveling · loot/equipment · dungeon rooms & doors · ranged enemies · a boss · saving · audio.

Scene tree (slice)#

Main (Node2D)
├── Arena (Node2D)
│   ├── Ground (TileMapLayer)
│   ├── Walls (StaticBody2D)
│   ├── Enemies (Node2D)               ← spawner fills this
│   └── Player (CharacterBody2D)
│       ├── Sprite2D
│       ├── CollisionShape2D
│       ├── Hurtbox (Area2D)           ← the reusable component
│       ├── Health (Node)              ← ditto
│       └── SwordPivot (Node2D)        ← rotates to facing
│           └── Hitbox (Area2D)        ← on only during active frames
├── Camera2D                           ← with trauma-based shake (stage 06)
└── HUD (CanvasLayer)                  ← hearts, score

Collision layers (named in project settings; stage 03 is where they first matter)#

world · player · enemy · player_hit (player's outgoing damage) · enemy_hit. The rule that prevents every friendly-fire bug: hitboxes live on a *_hit layer and mask nothing; hurtboxes live nowhere and mask the opposing side's hit layer. Work out why that direction (hurtbox does the looking) before stage 04 — it's checkable in one experiment.

Data model#

  • Definitions: AttackData extends Resource (damage, knockback force, hitstop ms) — the sword's swing and a slime's lunge are .tres files · EnemyStats (speed, health, detection radius) — three slime variants, zero new code.
  • Runtime: current health lives in each Health node · score and wave live with the arena · nothing global in the slice.

The design decisions — yours to make#

  1. Knockback mechanism (stage 04): (A) inject velocity into the victim and let friction bleed it off — simple, composes with movement, can fight the AI's own motion; (B) a dedicated STUNNED/knockback state that owns the body until it ends — more machinery, fully predictable, and the AI's stun comes free. Your call and reason: ______
  2. Enemy AI style (stage 05): (A) enum + match, like your player — consistent, one file; (B) node-based: one child node per state — states become editable scenes, per-state code stays tiny, and adding a second enemy type gets cheaper. This is the right moment to try node-based even if you keep enums for the player — comparing them on real code is the lesson. Your call and reason: ______
  3. Cancel policy (stages 03/06): can dash cancel an attack's active frames? Attack cancel dash? This is your game's identity — strict commitment (Souls-ish) vs fluid cancels (Hades-ish). Play both for ten minutes before deciding; write down which and why. Your call and reason: ______

Build order#

Walking skeleton: an arena you can run and dash around, one dummy you can hit, numbers appearing. Then: components & damage → a real enemy → juice → waves. Every stage ends playable; combat should feel a little better every single sitting.