Turn-based combat has a reputation problem. People who haven't played great turn-based games think it means "waiting for your turn." People who have played great turn-based games know the reality: when designed well, every single turn is a micro-puzzle, a moment of genuine decision-making, a beat of drama.
I spent most of February asking myself: what makes a turn feel weighty? What makes a player lean forward instead of wait? Here's what I landed on.
The Energy System
The core of Etherwyn's combat is an Energy system rather than a simple "one action per turn." Each character (player and enemy alike) has 3 Energy Points per turn. Different actions cost different amounts:
Move 1 tile = 1 EP. Basic attack = 2 EP. Special ability = 2-3 EP. Use item = 1 EP. Wait/Defend = 0 EP (and grants a defense bonus).
This means each turn is a small resource allocation puzzle. Do you move into flanking position (2 EP) and then attack (2 EP), spending 1 borrowed from next turn? Do you use your 3 EP all on movement to reposition? The math is simple enough to do in your head but the decisions are genuinely interesting.
Making Each Hit Feel Like Something
Turn-based games live or die on feedback. If an attack connects and nothing much happens visually or audibly, the interaction feels hollow even if the numbers are interesting.
My feedback stack for a basic attack hit:
1. Screen shake — 4-6 frames, magnitude scales with damage
2. Hit pause — 2 frames of freeze-frame on impact (this is the secret sauce)
3. Knockback — half-tile knockback animation, 0.12s duration
4. Damage number — floats up, color-coded (white = normal, yellow = critical, red = heavy)
5. Enemy flash — 1 frame white flash, then back to normal
The hit pause (sometimes called "hitstop") is something I borrowed from fighting games. Even two frames of freeze on impact makes attacks feel infinitely more satisfying. It's imperceptible consciously but your brain registers the impact as more "real."
# Hit pause implementation in Godot
func apply_hit_pause(frames: int) -> void:
Engine.time_scale = 0.0
await get_tree().create_timer(
frames * (1.0 / 60.0), # real time, not game time
true # process_always = true, ignores time_scale
).timeout
Engine.time_scale = 1.0
Enemy Design: Making Fights a Conversation
The biggest mistake I made early on was designing enemies as "damage dealers" and "tanks." That framing creates boring fights. I switched to designing enemies around verbs — what unique action does this enemy want to do?
The Shade wants to flank — it will spend all its energy repositioning to get diagonal advantage. The Bone Knight wants to push you into walls — its attacks have knockback and it loves backing you into corners. The Venom Crawler wants to close distance fast and then make you move — it applies a Burning status that does damage if you stay still.
Each of these enemies creates a different spatial challenge, and when you encounter two of them together, the combination creates emergent situations that I never explicitly designed.
Status Effects: The Spice Layer
I currently have six status effects implemented: Poison (damage over time), Burning (punishes staying still), Frozen (can't move, takes extra physical damage), Confused (random movement), Weakened (attacks cost +1 EP), and Blessed (crits on odd-numbered attacks).
The key design constraint: each status has to be visually obvious at a glance. Burning enemies have a flame particle effect. Frozen enemies have an ice overlay tint. Confused enemies have a small spinning icon. In a game where you need to quickly assess a room's threat level, a status effect you can't immediately parse is a design failure.
That's the combat system as it stands. Next month is inventory and loot tables — turning "a sword dropped" into "a sword that I want and that feels good to find." The roguelite loop hinges on this, so no pressure.