Quick answer: Tweens are owned by the instance that created them. If that instance is destroyed or its room ends before completion, callbacks are dropped. Run long-lived tweens from a persistent controller object.
Here is how to fix GameMaker Tween library on_complete callbacks that never fire. The tween animates correctly but the completion callback never runs because the owning instance was freed early.
The Symptom
You start a tween (TweenFire or via TweenManager). Visual animates. on_complete callback never executes. No errors.
What Causes This
Owner destroyed. Instance freed before tween completes.
Room change. Non-persistent instances are wiped on room change.
Wrong scope. Callback references “self” that points at the destroyed instance.
The Fix
Step 1: Run from a persistent controller.
// In oController Create event
persistent = true;
// Trigger the tween from oController, not the destroyed instance
TweenFire(oController, EaseOutQuad, 0, 0, 1, "x", 100, 200);
Step 2: Use TweenCallSelf for callbacks.
var _t = TweenFire(id, EaseOutQuad, 0, 0, 1, "x", x, x + 100);
TweenCallback(_t, TWEEN_EV_FINISH, on_arrived);
function on_arrived() {
show_debug_message("Tween done");
}
TweenCallback registers handlers for events; survives as long as the tween itself.
Step 3: Validate instance before callback runs.
function on_arrived() {
if (!instance_exists(self)) exit;
// safe to use self
}
Step 4: For per-instance tweens, kill on destroy.
// Destroy event
TweenDestroy(my_tween);
Cleans up registered tweens to avoid lingering callbacks.
Step 5: Use the GMS2 built-in animation curve as alternative. For simple tweens, GMS2’s native animcurve_get_channel + lerp by your Step time is more transparent than the Tween library.
“Persistent controller for long tweens. TweenCallback for events. Validate instance before use. Callbacks land.”
Related Issues
For shader uniforms, see Shader Uniform Per Frame. For surface lifecycle, see Surface Lost.
Persistent owner. TweenCallback. instance_exists guard. Tween events fire.