We argue endlessly about engines, ECS, shaders, netcode. But the thing players actually feel isn't your architecture. It's the loop.
You know it the instant it works. Doom Eternal never explains why combat feels good; your hands already know. Tetris needs no tutorial. And you know the inverse just as fast: you load in, try to move, something's off, and you close the game in ten minutes without being able to say why. A broken loop kills games faster than bad writing or bad art ever could, and players never report it as a broken loop. They just say "boring" or "clunky" and churn.
Here's how I think about it, with the implementation bits where they matter. Curious how you all handle this, because nobody seems to agree.
The loop is seven links
intent -> input -> interpretation -> outcome -> feedback -> perception -> prediction
^ |
+--------------------------- next input <---------------------------------+
Seven links. The last feeds the next. Most teams obsess over two or three and ignore the rest, which is how you ship a game with gorgeous hit feedback and laggy input, or pin-sharp input feeding opaque outcomes. Every link has to carry its weight.
Feedback is communication, not juice
Feedback is any signal (visual, audio, haptic, systemic) that confirms an input and says what it did. Chris Crawford's old point still holds: this isn't decoration, it's communication. Juice is decorative; communication is functional.
Two things separate feedback that works from feedback that's just present:
- Discernibility — can the player tell exactly what happened, not approximately? In a 6-enemy fight, which hit landed on which target, for how much, with what effect?
- Integration — does the outcome change the rest of the game? A hit that deals damage but never staggers or interrupts feels weaker than one that visibly bends the fight.
Friction: the tax players pay to act
Some friction is the point: a hard boss, a weighty decision, a tense reload. That's where meaning lives. Dark Souls is brutal on purpose and every death teaches you something.
Avoidable friction is the tax players pay for nothing: the 3-press menu that should be 1, the inventory that buries the potion under 40 items, the 90-second unskippable cutscene before every boss retry.
The trap: intentional and avoidable friction look identical on paper. Both make the game "harder." The difference is whether the player gets mastery back or just annoyance. A Dark Souls with input delay has the same difficulty and is unplayable.
Four buckets of friction
Most frustration falls into one of four buckets. Name the bucket and you can usually name the fix:
- Cognitive — "I don't know what to do." Too many choices (Hick's Law) or heavy state tracking. The silent killer in strategy games and RPGs: players drown in options and stop, not because the game is bad but because thinking became work.
- Mechanical — "Controls feel sticky or slippery." Tiny hitboxes (Fitts's Law) or missing forgiveness windows. It's why some platformers feel like silk and others like wading through jello with identical-looking mechanics.
- Social — "My team is falling apart." Poor signaling tools or misaligned incentives. It's why so many MOBA matches end in a rage-quit: not the gameplay, but no good way to agree on a plan.
- Technical — "It's laggy and unresponsive." Latency, frame drops, or long loads.
Technical friction is the one teams fix last and should fix first. 100ms of input lag makes every other system feel worse, and no player will ever tell you that's the reason.
Playbook (with code)
Externalize state. Recognition over recall. Every cooldown, buff, and resource that lives in the UI is one less thing in the player's head. Into the Breach shows every enemy's next move and becomes pure tactics. Surfacing info doesn't break immersion; hiding it just makes players take notes.
Forgiveness windows. Celeste's coyote time lets you jump a few frames after leaving a ledge. Physically impossible, feels perfect. The correct pattern is a single per-frame update that combines coyote time (grace after leaving the ground) with an input buffer (grace for a jump pressed slightly before landing):
js
const COYOTE_FRAMES = 6; // grace after walking off a ledge
const BUFFER_FRAMES = 6; // grace for an early jump press
let coyote = 0;
let jumpBuffer = 0;
// call once per fixed update, before moving the player
function updateJump(grounded, jumpPressedThisFrame) {
// refill coyote time while grounded, otherwise count it down
coyote = grounded ? COYOTE_FRAMES : Math.max(0, coyote - 1);
// remember a jump press for a few frames
jumpBuffer = jumpPressedThisFrame ? BUFFER_FRAMES : Math.max(0, jumpBuffer - 1);
// jump if we have a buffered press AND we're grounded or still within coyote time
if (jumpBuffer > 0 && coyote > 0) {
jump();
jumpBuffer = 0;
coyote = 0; // consume it so we can't double-jump off one window
}
}
The point: widen the execution window (the how) so players spend attention on the decision (the why). Fighting games buffer inputs, shooters add bullet magnetism, rhythm games run timing windows wider than they admit. None of it is cheating.
Layer your feedback. No critical event should ride on one channel. Sound might be off, the player might be colorblind, they might be watching the minimap. Fire several at once (names here are placeholders for your own systems):
js
function onHit(target, dmg) {
spawnHitSpark(target.position); // visual
flinch(target); // visual / systemic
playSfx('hit', { volume: scaleByDamage(dmg) }); // audio
rumble({ strength: 0.6, ms: 80 }); // haptic
popDamageNumber(target.position, dmg); // systemic
}
If any two channels fail, the player should still get the message. Overwatch's kill confirmation does this; plenty of shooters ship hit feedback so subtle the weapon feels broken even when the numbers are right.
You can measure "feel"
Treat each design as a hypothesis. Track:
Time to first meaningful action— how long after load before the player does something?
Input-to-response latency— ms between press and on-screen response.
Error recovery time — after a misinput, how fast can they retry?
You don't need frame-perfect numbers on a single-player game, but you do need to catch when your "smooth" build is quietly running 120ms input-to-action. Players feel it; they just say it's "off."
So how do you tune yours?
- What forgiveness windows do you ship, and do you tell players they exist?
- How do you catch input latency before players do?
- Any game you bounced off in 10 minutes and only later worked out why?
Drop it below.
I wrote the longer version with the full loop diagram and the feedback/friction breakdown on my blog: The invisible conversation: feedback, friction, and why players stay. More on game feel over there.
Top comments (0)