Godot Game Feel Lab
Free, and there is nothing to sign up for.
A hit that lands is not the same as a hit that feels like it landed. Swing at the crate below. It will not move, because the script that makes it move is the one you are about to write.
The editor holds real GDScript, and the crate runs it. Break the script and the crate breaks with it. No account, nothing to install, and the same code opens in Godot when you download the project.
WASD or the arrow keys to move. Space or a click to swing. Escape gives the keyboard back to the page.
enemy.gd, running live
Empty its body and no swing can move the crate.
Checks
0 / 5- Waiting for the first run.
Output
No errors.
Your script runs in the same GDScript interpreter that grades every lesson on this site.
What the lab adds
You wrote how the crate takes a hit. These five are what the hit looks like. The lab owns them, so you can switch each one off and hear what it was doing.
5. Sound: The layer people forget
A crack and a falling tone, gone inside a tenth of a second. Then the part that matters more than the sound itself: vary the pitch. The same sample at the same pitch forty times in a row turns into a machine gun, and the ear stops hearing impact at all.
While the fighters are frozen, what else stops?
The two bodies stop dead while the sparks keep flying and the camera keeps shaking. This is what shipped action games do, and it is why their pauses read as impact.
Passed to your take_hit().
Written into your @export.
The lab's freeze, not yours.
Shove settles in 0.36s
MIT licensed. No account needed, no email, nothing to sign up for.
Seven layers, and four of them are invisible
This is why game feel is hard to learn from an article. Screenshot a great hit and a bad one and half the difference does not survive the screenshot. It is in frames that were held, control that was taken away, and a camera that moved for a tenth of a second.
Two of the seven are not settings either. Knockback and hitstun are code on the thing being hit, which is why the lab gives you an editor instead of a seventh switch.
| Layer | What it does | In a screenshot? | Who writes it |
|---|---|---|---|
| Knockback | The hit shoves the target | Visible | You, in the editor |
| Hitstun | The target cannot act while the shove lasts | Invisible | You, in the editor |
| Hit-stop | Both fighters hold still for a few frames | Invisible | The lab |
| Screen shake | Trauma squared, on smooth noise, with roll | Invisible | The lab |
| Sparks | Particles along the strike, not in a ring | Visible | The lab |
| Flash and squash | The target goes white, then deforms | Visible | The lab |
| Sound | A crack and a falling tone, pitch varied | Invisible | The lab |
The one people get wrong most often is hitstun. Knockback with no hitstun means the enemy walks back at you while it is still being shoved. The hit reads as weightless however big the force is. In the lab that is a real failing check, measured off the crate, not a warning in a paragraph.
The hit-stop bug almost everyone writes
Hit-stop is three lines. Freeze time, wait, unfreeze. The obvious way to wait is the one that breaks. It breaks quietly too. The game locks up with no error at all.
create_timer runs on scaled time by default. At a time scale of zero it never finishes, so the line that unfreezes the game never runs. The fourth argument is ignore_time_scale, and it is the whole fix.
The project also carries a token, so two hits close together cannot end with the first one's timer unfreezing a game the second one still wants frozen.
A freeze should not stop everything
Here is the part that separates a hit that lands from a hit that stutters, and almost no tutorial mentions it. When you freeze on impact, freeze the fighters. Let the sparks keep flying, the camera keep shaking, and the damage number keep climbing.
A total freeze reads as a dropped frame, because that is exactly what a dropped frame looks like. A freeze with the effects still running reads as impact. Both versions are in the lab, on a switch, next to each other.
Put it in your own project
Three steps. No scene setup, no textures, nothing to import. The whole project is text, including the impact sound, which is generated in code. So there are no third party assets and no licence to chase.
The attack code does not shake the camera or spawn the sparks. It reports the hit and GameFeel emits a signal that the camera, the particles and the sound listen for. Adding a reaction later means writing the reaction, not opening the attack again.
When the checks go green
You have written the knockback and the hitstun. Every hit now weighs the same, though, and real combat does not work like that. Here is the change most action games make next, and it is one number threaded through the whole stack.
Add a heavy attack
Hold a second button to charge, then swing. A heavy hit should shove harder, freeze longer, and shake more than a light one, all from a single weight value.
- 1. Add a
heavy_attackinput action in Project Settings. - 2. Give the swing a
weight, then pass it through every layer:knockback_force * weight,hitstop_duration * weight,TRAUMA_PER_HIT * weight. - 3. Keep the heavy freeze under about 0.2 seconds. Past that it stops reading as impact.
One weight, every effect. That is the idea the whole Game Feel quest is built on. It is why a heavy hit reads as heavy instead of just slower.
Want to write it rather than read it?
Coding Quests teaches Godot by making you type the code and checking that it runs. Both of these are combat, so they pick up exactly where this lab stops.
2D Roguelike: Top-Down Controller
Build the body that does the swinging: eight direction movement, mouse aim, a dash with invincibility frames, and a camera that shakes. All of the feel is code, and you write it in the browser.
Guild Hall, 9 lessonsGame Feel: Make Combat Hit
The full version of this page. One reusable Juice autoload, built a technique at a time. It ends with a single Juice.hit(weight) call that scales every effect to the size of the blow.
Where this comes from
None of these techniques are ours. They are the ones the people below worked out and gave away, ported to Godot 4 and put behind a swing you can try. If any of it lands, go and watch the originals.
- Juicing Your Cameras With Math
Squirrel Eiserloh, GDC 2016
Trauma instead of shake, trauma squared instead of linear, smooth noise instead of random, and roll as well as offset in 2D.
- The Art of Screenshake
Jan Willem Nijman, Vlambeer
The idea that a good hit is a stack of small reactions rather than one big effect. Also that the camera is part of the hit.
- Juice It or Lose It
Martin Jonasson and Petri Purho, GDC 2012
Squash and stretch on impact, and the habit of adding one effect at a time and playing after each one.
- Hitstop, hitfreeze, hitlag, hitpause
Celia Wagar, CritPoints
Why a freeze at the moment of collision reads as force, and what a game without one loses.
Questions
What is hit-stop in Godot?
Hit-stop is a short freeze on impact. The fighters hold still for a few hundredths of a second so your eye can register that the hit landed. It costs almost nothing and it does more for the weight of a hit than any amount of knockback.
How long should hit-stop last?
Between about 0.05 and 0.2 seconds, which is 3 to 12 frames at 60fps. Scale it with the attack: a light jab gets 2 or 3 frames, a finisher can take a quarter of a second. Past that it stops reading as impact and starts reading as the game hitching.
Should hit-stop freeze the whole game?
Usually not. Freeze the fighters and let the particles, the camera shake and the floating numbers keep running. A total freeze reads as a dropped frame. Engine.time_scale = 0.0 is the one line version and it stops everything, which is why longer freezes done that way feel wrong.
Why does my game freeze forever when I set Engine.time_scale to 0?
Because the timer you are waiting on is scaled by time_scale too. At a time scale of zero it never finishes, so the line that unfreezes the game never runs. Pass true as the fourth argument to get_tree().create_timer, which is ignore_time_scale, and the timer runs on real seconds instead.
How do I add knockback in Godot 4?
Store the shove as a Vector2 on whatever got hit. Set velocity to it in _physics_process. Then bleed it toward zero with move_toward and a friction value. The shove lasts force divided by friction seconds, so 320 force against 900 friction settles in about a third of a second.
Why does my knockback still feel weak?
Almost always because the target keeps acting while it is being shoved. Knockback needs hitstun next to it: while the shove lasts, the target cannot walk, turn or attack. Without that it strolls back at you mid-flight and no amount of force will fix it.
What Godot version does the download need?
It is built for Godot 4 and developed against 4.7. Nothing in it uses a 4.7 only API, so it should open in 4.3 and up.
Can I use this in my own game?
Yes. The project is MIT licensed, there are no third party assets in it, and you do not need an account to download it. Ship whatever you build with it.


