Tween.finished not emitted from custom tween handler

Godot Version

4.2.2.stable

Question

Hi everyone,

I have some problems with my custom tween handler.
In an attempt to avoid boilerplate code for every tween I create, I thought I would write my own Utility function for this. Since I often tween from inside the process function, I end up writing this:

var tween1 : Tween
if tween:
	tween.kill()
tween = create_tween()
tween.tween_property(self, "scale", Vector2.ZERO, .25)

So I added this to my global utilities script:

var tween_dict: Dictionary = {}

func tween(target_node: Node, property: String, final_value, duration: float) -> Tween:
    var key = "%s_%s" % [target_node.get_instance_id(), property]
    
   if tween_dict.has(key):
         tween_dict[key].kill()
    
   tween_dict[key] = create_tween()
   tween_dict[key].tween_property(target_node, property, final_value, duration)
    
    tween_dict[key].finished.connect(func(): _on_tween_finished_deferred(key))
    return tween_dict[key]


func _on_tween_finished_deferred(key: String):
    call_deferred("_kill_tween_if_exists", key)


func _kill_tween_if_exists(key: String):
    if tween_dict.has(key):
        tween_dict[key].kill()
        tween_dict.erase(key)

This allows me to write the same code with 1 short line, like this:

GlobalUtilities.tween(self, "scale", Vector2.ZERO, 0.25)

And while it works as expected, I have one issue:

var tween1 : Tween
if tween1:
	tween1.kill()
tween1 = create_tween()
tween1.tween_property(self, "scale", Vector2.ZERO, .25)
await tween1.finished
print("this gets executed.")
		
var tween2: Tween = GlobalUtilities.tween(self, "scale", Vector2.ZERO, 0.25)
await tween2.finished
print("this does not get executed.")

The tween.finished signal is not emitted, and I am not sure why that is the case.
Can anyone help me out here?Preformatted text

i think your global utilities kill your tween first so the await is never finished because tween already gone

try disable this line to see, if it’s true

Hi, thanks for the reply!

I should have mentioned that I thought about this being the issue, but disabling the line results in the same problem.

i see, if you print the tween2, what does it say?

It says

<Tween#-9223371217036112117> (bound to GlobalUtilities)

I cannot reproduce your issue. Copied your code without any changes, it ran fine, both tween1 and tween2 finished and their respective prints were shown.

Can you provide an example project?

I have an empty project with the code and it does not run in process.
It works in ready, however, but my main issue is getting the tween to work in process, because that is where the tween.kill() comes in.

Unfortunately I’m unable to edit the original post to make it clear that the issue occurs only in process, sorry for the confusion.

I don’t see why you would want to call that in _process, but…

	var tween1 : Tween
	if tween1:
		tween1.kill()
	tween1 = create_tween()
	tween1.tween_property(self, "scale", Vector2.ZERO, .25)
	await tween1.finished
	print("this gets executed.")

Here tween1.kill() will never be executed (because you create a new variable tween1 each frame, and only initialize it later), which allows the tween to finish regularly and emit the finished signal you’re waiting for.

This is not the case for your global function, which will kill the existing tween each frame and start a new one, so it will never emit finished.

Hi, thanks for the reply!

I modified the code like this so the finished signal gets emitted.

func tween(target_node: Node, property: String, final_value, duration: float) -> Tween:
	var key = "%s_%s" % [target_node.get_instance_id(), property]
	var new_tween: Tween
	tween_dict[key] = new_tween
	if tween_dict[key]:
		tween_dict[key].kill()
	
	new_tween = create_tween()
	new_tween.tween_property(target_node, property, final_value, duration)
	new_tween.finished.connect(func() -> void:
		_kill_tween_if_exists(key)
	)
	return new_tween

func _kill_tween_if_exists(key: String) -> void:
	if tween_dict.has(key):
		tween_dict.erase(key)

There are still two problems I’m trying to work through:

  1. I think the set_ease() and set_trans() functions stopped working.
  2. I get occasional warnings at runtime:

W 0:00:10:0470 PropertyTweener::start: Target object freed before starting, aborting Tweener.
<C++ Source> scene\animation\tween.cpp:550 @ PropertyTweener::start()

Since there is nothing in the docs about this I’m not sure what’s happening or how to avoid this, however.

I don’t see why you would want to call that in _process , but…

It has many use cases for me, to name a few: camera following, velocity manipulation for a dash mechanic, crosshair aim smoothing, object scale tweening before queue_free() gets called, etc.

Again: this code does nothing, it never get’s executed! This:

	var new_tween: Tween
	tween_dict[key] = new_tween
	if tween_dict[key]:
		tween_dict[key].kill()
	
	new_tween = create_tween()

is equivalent to this:

	var new_tween := create_tween()

Yeah, but when you do this in _process, you create a new tween every frame.

No. It never worked in the first place! These functions operate on variables of type PropertyTweener (returned by tween_property) or MethodTweener (returned by tween_method). What your global function returns is a Tween!

I cannot reproduce this on my end, but I feel like the error is pretty self-explanatory? Something in your code free’d the target_node before the tween got started.

What you are saying regarding the code being equivalent to this

to me, contradicts the information from this video.

The person in question uses create_tween() in process, and to avoid having two or more tweens that animate the same property resulting in bugs, the current one is killed. My current understanding is that calling tween.kill() is the intended way to handle tweens created in process, but please enlighten me with the correct way if this is wrong.

The information about the PropertyTweener type is very helpful, thanks!