Quick answer: Pass arguments via method.bind(arg). Ensure the callback’s target node remains in the tree until the tween reaches it. Check that the parent node binding the tween is alive.
A tween chain: fade in, wait 1 second, then call on_intro_done. The fade and wait run; the callback never fires. The target object was freed during the wait, killing the tween silently.
Tween Auto-Kill
Tweens created by node.create_tween() are bound to that node. If the node leaves the tree (queue_free, scene change), the tween dies and remaining steps are skipped. Callbacks queued after the death point silently drop.
Fix 1: Bind to a Stable Node
# If self is short-lived, anchor on a longer-lived ancestor
var tween = get_tree().create_tween()
tween.tween_property(self, "modulate:a", 1.0, 0.5)
tween.tween_interval(1.0)
tween.tween_callback(on_intro_done)
get_tree().create_tween() binds the tween to the SceneTree, which lives until program exit. Even if your node is freed, the callback fires (though the callback method may target a freed object).
Fix 2: Check Validity in the Callback
func on_intro_done():
if not is_instance_valid(self): return
# body
Defensive: callback runs but skips work if its target object is gone. Combined with tree-bound tween, you get safe long-running tween chains.
Fix 3: bind for Arguments
func spawn_explosion(strength: int):
# ...
var tween = create_tween()
tween.tween_interval(0.3)
tween.tween_callback(spawn_explosion.bind(5))
bind attaches arguments to the Callable. The tween runs spawn_explosion(5). Without bind, the callable has no args and the method’s strength parameter is missing — producing an error or silent skip depending on Godot version.
Diagnose
Print at each step:
tween.tween_callback(func(): print("step A"))
tween.tween_interval(1.0)
tween.tween_callback(func(): print("step B"))
If step A prints but step B doesn’t, something between killed the tween. Usually a freed node.
Verifying
Build a test sequence with multiple callbacks separated by intervals. All should fire in order. If one skips, check what happens to the tween-binding node between callbacks.
“Tweens die with their host node. Use bind for args; use tree.create_tween or a stable host for long chains.”
For purely visual tween chains (animations on transient sprites), keep tween bound to sprite — the death of the sprite also kills the unneeded animation. Perfect.