Boss Fights — a fight the player can learn, rather than one they can only survive
You gave the enemy ten times the health and it did not become a boss. The player still wins by holding attack and out-healing the damage, or loses to a hit that arrived with no warning — and cannot tell which of those two things happened. What separates a boss from a large enemy is not its numbers. It is that the fight can be read: every attack announces itself before it lands, every attack ends in a moment where the boss is open, and a player who dies knows what they did wrong. This shelf builds that fight in Godot 4.7 — the timing inside a single attack, the phases stacked on top of it, an arena that takes part in the fight, and a death and retry that decide whether the player tries again.
What this shelf teaches#
| Part | You build | The idea |
|---|---|---|
| 01 | A boss that idles, telegraphs, strikes, then stands open for a beat. | Commitment before the hit, and an opening after it, turn health into a fight. |
| 02 | One attack whose windup, hitbox, and recovery all come from one animation clip. | Windup is the difficulty dial; two clocks for one event is the usual bug. |
| 03 | Two phases sharing one attack machine; phase two swaps the table, shortens windups. | A phase is a state that contains the attacks, not one beside them. |
| 04 | An arena attack solved by geometry, a wall-charge stun, a death sequence, fast retry. | If the fight plays the same in an empty rectangle, the arena is wallpaper. |
Part 01's health bar drops only during the opening, so you can watch the loop working — a big enemy otherwise answers none of the three questions a player asks every second. Part 02's hitbox opens and closes on keyframes rather than a Timer, landing exactly one hit per swing, because windup, active, and recovery are unequal on purpose. Part 03's phase change is itself a state the boss enters — invulnerable, never left mid-swing — since the change between phases has duration and needs a state of its own. Part 04's retry returns control in under two seconds, and its death sequence cannot outlive the boss it animates, because the ending is a separate object from the fight, which is exactly why the retry keeps replaying it.
Where reasonable people differ#
Four forks run through this shelf. None of them has a right answer, and each one has a question that decides it for your project.
Enum state machine, or AnimationTree state machine. Both ship real bosses. An enum machine plus data-driven attack tables keeps transition timing in code you can read and breakpoint. An AnimationTree keeps it in a graph where the animator can see it, at the cost of four defaults that will bite you before you learn them. The deciding question: who do you want owning the moment an attack ends — your script, or the animation graph? A related question: are your attacks mostly logic with animation attached, or mostly animation with logic attached?
One inner machine reading swappable tables, or genuinely separate states per phase. Table-swapping is small and it scales to three or four phases without ceremony. Separate states are honest when phase two is a different creature — different locomotion, different silhouette, different rules for being interrupted. The deciding question: do the phases share a movement model? If they do, swap tables. If phase two flies and phase one walks, stop pretending they are the same machine.
Telegraph in the animation, or telegraph in a separate effect. A windup carried entirely by the boss's pose is cheap, always in sync, and readable only if the player is looking at the boss. A separate tell — a ground decal, an audio cue, a screen-edge flash — reads from anywhere, and is one more thing to keep aligned with the attack that owns it. The deciding question: can the player see the whole boss at the moment of the windup? Large bosses and tight cameras push you toward the separate tell.
Reload the scene on death, or reset the fight in place. Reloading is one line and cannot leave stale state behind. Resetting in place is more code and more chances to forget a field, but it is the only way the fifth attempt starts faster than the first. The deciding question: how long is the walk from the door to the boss? If it is longer than a couple of seconds, reloading is quietly making your fight worse every time the player dies.
Prerequisites#
You should be comfortable with:
- Typed GDScript — declared variable types, typed function signatures,
-> voidreturns. If that is new, read typed GDScript first. CharacterBody2Dandmove_and_slide(), including readingis_on_floor()andis_on_wall()after the call rather than before.Area2Dwith a childCollisionShape2D, and the fact that collision layers and masks are directional.- A state machine you have already written yourself. This shelf assumes one and then breaks it on purpose. If you do not have one, build the enum machine and come back — Part 03 starts from exactly that shape.
AnimationPlayerwith at least one clip you keyframed by hand, andTweencreated from a node.
Everything else — AnimationMixer callback modes, CONNECT_ONE_SHOT, get_real_velocity(), AnimationTree playback paths — is introduced where the fight needs it.
Where this shows up in the projects#
The movement half of a boss fight is the platformer's problem, not the boss's. If the player cannot dodge precisely, no amount of telegraphing makes an attack feel fair — see the precision platformer curriculum for the movement that makes reading an attack worth doing.
The phase machine in Part 03 is the same hierarchy problem the state machine shelf ends on. If the nesting in Part 03 feels like it arrives from nowhere, pushing and popping is where the shape is argued from first principles.
Next: Part 01 — What makes it a boss. Start there even if you already have a boss in a project — it builds the wrong version first, and the rest of the shelf is a sequence of answers to problems Part 01 makes you feel.