"Juice" is the game dev word for all the little motion and feedback that makes a game feel good to touch. A button that pops when you hover it. A coin that arcs into your wallet. A character that squashes on landing. Most of it comes from tweens, and Godot 4's tween system is one of the nicest you'll use. Here's how to add the feel.
What a tween is
A tween smoothly changes a value from one number to another over time. Instead of snapping a button to twice its size, you tween its scale from 1 to 1.2 over a fifth of a second, and it grows. That's it. The magic is in how many things you can animate this way: position, scale, rotation, color, volume, anything that's a property.
You make one in code with create_tween():
func _ready() -> void: var tween := create_tween() tween.tween_property(self, "modulate:a", 0.0, 1.0)That fades the node out over one second by animating its alpha to 0. tween_property takes the object, the property (as a string, and you can target sub-properties like modulate:a), the final value, and the duration.
Easing makes it feel alive
A linear tween moves at a constant speed, which looks robotic. Easing curves fix that. set_ease and set_trans control the shape of the motion:
var tween := create_tween()tween.tween_property($Button, "scale", Vector2(1.2, 1.2), 0.2) \ .set_trans(Tween.TRANS_BACK) \ .set_ease(Tween.EASE_OUT)TRANS_BACK with EASE_OUT gives that satisfying little overshoot, where the button grows slightly too big and settles back. TRANS_ELASTIC bounces. TRANS_SINE is a gentle ease. Try a few. The right curve is the difference between "fine" and "ooh."
Chaining and parallel
By default, tween steps run one after another, which is perfect for sequences:
var tween := create_tween()tween.tween_property(coin, "position", target_pos, 0.4)tween.tween_property(coin, "scale", Vector2.ZERO, 0.1)tween.tween_callback(coin.queue_free)The coin flies to the target, then shrinks, then deletes itself. Each step waits for the last.
When you want steps at the same time, mark them parallel:
var tween := create_tween().set_parallel(true)tween.tween_property(popup, "scale", Vector2.ONE, 0.3)tween.tween_property(popup, "modulate:a", 1.0, 0.3)Now the popup scales up and fades in together.
The squash-and-stretch landing
Here's a classic bit of juice. When a character lands, squash them flat for an instant, then snap back. It reads as weight and impact:
func land() -> void: var tween := create_tween() tween.tween_property(sprite, "scale", Vector2(1.3, 0.7), 0.05) tween.tween_property(sprite, "scale", Vector2.ONE, 0.1) \ .set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)Two lines of motion and your jump suddenly has heft.
Waiting on a tween
Sometimes you need to do something after a tween finishes. Tweens emit a finished signal you can await, which reads top to bottom like a little script:
func play_intro() -> void: var tween := create_tween() tween.tween_property(title, "position:y", 100, 0.5) await tween.finished show_menu()That await pattern comes straight from signals, and it's everywhere once you start animating.
Sprinkle it everywhere
Juice is the cheapest upgrade in game dev. The systems you've already built (menus, pickups, health bars) all get better with a tween or two. A health bar that slides instead of snapping. A menu button that pops on hover. Go add motion to one thing in your current project today, and you'll feel the difference immediately. The free Inventory System quest is full of spots begging for it.
FAQ
How do I create a tween in Godot 4?
Call create_tween() to get a Tween, then tween.tween_property(object, "property", final_value, duration). Godot 4 creates tweens in code rather than as nodes, and the tween runs automatically once you've defined its steps.
How do I make a tween run several animations at once?
Call set_parallel(true) on the tween, or use tween.parallel() before a step. Without it, steps run sequentially, one finishing before the next begins, which is what you want for multi-step sequences.
What's the difference between set_ease and set_trans?
set_trans picks the type of curve (like elastic, back, or sine) and set_ease picks how that curve is applied (in, out, or both). Together they shape the motion. A linear tween with no easing moves at constant speed and tends to look robotic.