Animated menus through tweens having issues with canceling

Godot Version

4.2

Question

I feel like I’m trying to pick at a problem with a very specific and unusual solution, so might as well ask before I introduce some needs to do some tragic refactoring.

Say I want a menu where, when entered, it slides and fades in, and I want to accomplish this through a tween since that seems like the obvious way and can account for differences like resolution/aspect ratio differences (as well as in general be more convenient than an AnimationPlayer), so the function that opens the menu sets up some preliminary information on the menu, then runs the tween that brings the menu in while awaiting for it to be complete.

Then, I want to make my menus feel better and more interactive, so I want the player to be able to further interact with or cancel out of a menu even as it is sliding/fading in. As tweens are created on runtime, this is not so easy. My idea around this was a two-step process:

  1. Declare the tween outside of the function as a script variable, but create the tween on the variable during the function itself
  2. Create a function in my UI tool singleton that takes a tween, checks if it is running, and if so, then stop it.

I could then call the tool function whenever I needed to skip over or cancel a tween by simply calling it on the script variable in another function (such as when exiting a menu). The issue with this is that as it stops the tween, the tween can no longer emit the finished signal (and trying to have the tween emit it manually in the tool function does not trigger the await), so the function simply turns into a memory leak awaiting a signal that is not coming. It doesn’t matter if the rest of the function happens or doesn’t happen as either case can be dealt with, but the memory leak itself is an issue.

Does anyone have a more elegant solution to this, or at least one that doesn’t cause a memory leak?

AnimationPlayer should also be able to handle UI transitions if you key the anchors instead of fixed pixel sizes.

If you are worried about awaits, you could instead connect to the tweens signal as a one-shot

There is an example on cancelling tween animations right in the documentation:

var tween
func animate():
	if tween:
		tween.kill() # Abort the previous animation.
	tween = create_tween()

My advice is this: Don’t bother with await. Do a callback instead. When the fade-in animation finishes, call a designated function. If the animation is interrupted (by the user trying to close the window before it fully finishes the appear animation), just kill the currently running tween, create a new one with a fade out animation and you are good to go!

1 Like

Unfortunately, my game is incredibly menu-heavy and awaits are used countless times, and splitting up all the code after awaits in my game into multiple functions may potentially take weeks of refactoring and be much messier and harder to navigate (bad coding decisions made in the past, I understand).

Would it not be viable to keep the awaits, but try the kill() + create_tween() method? I assume the finished signal would properly fire in that case.

Focus on the animations that can cancel, refactoring just problematic awaits. Make use of anonymous and bound functions where applicable.

await starttween.finished
extra_code(123)

starttween.finished.connect(func():
    extra_code(123)
, CONNECT_ONE_SHOT)

starttween.finished.connect(extra_code.bind(123), CONNECT_ONE_SHOT)

That makes sense. Should I still change my tool function from tween.stop() to tween.kill() into tween = create_tween()

Nevermind everything, try cusom_step instead of killing

bool custom_step(delta: float)

Processes the Tween by the given delta value, in seconds. This is mostly useful for manual control when the Tween is paused. It can also be used to end the Tween animation immediately, by setting delta longer than the whole duration of the Tween animation.

Returns true if the Tween still has Tweeners that haven’t finished.

If a crazy high number produces crazy results you could condition a while loop with it for smaller steps

while tween.custom_step(10):
    pass
2 Likes