Quick answer: Tweens in Godot 4 are single-use. After they finish (or you call kill()), they refuse new tween_property calls. Call create_tween() fresh each time you want to animate.
The first time the player picks up a coin, the UI counter scales up nicely. The second pickup — nothing. The label sits at scale 1.0 like the tween never ran. You re-read the code, you log the function entry, you confirm tween_property is being called. The tween silently does nothing.
What Changed from Godot 3
In Godot 3, Tween was a node. You could call interpolate_property repeatedly across its lifetime and start() to play. In Godot 4, Tween is a transient object created by SceneTree.create_tween() (or Node.create_tween()). Once it finishes its chain of operations, it’s marked invalid. Subsequent tween_property calls produce an error in --verbose mode but no error in normal play — just silent no-ops.
The Fix: Fresh Tween Each Time
Replace your stored reference with a new one whenever you want to animate:
# WRONG: tween stored once, reused
var tween: Tween
func _ready():
tween = create_tween()
func animate_pickup():
tween.tween_property(label, "scale", Vector2.ONE * 1.5, 0.2) # no-op after first finish
# RIGHT: new tween each call
var tween: Tween
func animate_pickup():
if tween: tween.kill()
tween = create_tween()
tween.tween_property(label, "scale", Vector2.ONE * 1.5, 0.2)
tween.tween_property(label, "scale", Vector2.ONE, 0.2)
The if tween: tween.kill() guard cancels any in-progress animation from a previous call before starting the new one. Without it, two rapid pickups would produce two competing tweens scaling the label simultaneously.
When You Want a Looping Tween
For an idle bob or breathing animation, create one tween and call set_loops():
func _ready():
var tween = create_tween().set_loops().set_trans(Tween.TRANS_SINE)
tween.tween_property(self, "position:y", position.y - 4, 0.6)
tween.tween_property(self, "position:y", position.y, 0.6)
set_loops() with no argument loops forever. Pass an integer to loop a finite number of times. The tween stays alive until you explicitly kill() it or the node leaves the tree.
Parallel and Sequential Steps
By default each tween_property runs after the previous one. To run them in parallel, call set_parallel(true) on the tween or use parallel() on each step:
var tween = create_tween().set_parallel(true)
tween.tween_property(self, "position", target_pos, 0.3)
tween.tween_property(self, "modulate", Color.WHITE, 0.3)
# Both finish at the same time
Verifying
Run with --verbose in a terminal. After the first animation finishes, look for “Tween is invalid” warnings on subsequent calls — if you see them, you’re still reusing the reference. After the fix, no warnings appear and each call animates as expected.
“Tweens in Godot 4 are one-shot. Create a new one every time. The pattern is
kill old, create new.”
Stale tween references are the single most common Godot 4 animation bug — fix it once and you’ll recognize it forever.