Tween doesn't wait

Godot Version

4.7.1

Question

I was trying to get tween to work for scene transition effects. Previously I used animation player, but tween should work and is theoretically easier to set up (no new animation, new track, new node, new method, set keys for each property and time, etc..).

In the code below, SceneTransition is my CanvasLayer node with a full_rect ColorRect to cover the whole screen. I instance it in my main scene and call scene_transition.fade_out(), change scenes, and then scene_transition.fade_in().

However, it doesn’t work. Actually, it does work, but it fires off immediately without waiting. So my fade to black happens at the same time as my fade to clear. I even try to convince it to work with:

await tween.finished

That didn’t work, so I even tried a delay timer:

await get_tree().create_timer(duration).timeout

What am I missing? Again, the individual tweens work, but since they fire at the same time, they cancel each other out.

extends CanvasLayer

@onready var overlay = $ColorRect
	
func fade_out(duration = 0.5):
	var tween = get_tree().create_tween()
	tween.tween_property(overlay, "color", Color(0, 0, 0, 1), duration)  
	await tween.finished
	wait(duration)

func fade_in(duration = 0.5):
	var tween = get_tree().create_tween()
	tween.tween_property(overlay, "color", Color(0, 0, 0, 0), duration)  
	await tween.finished
	wait(duration)
	
func wait(duration):
	await get_tree().create_timer(duration).timeout

You would have to await your wait function, or you are missing an await when calling fade_out

func fade_out(duration = 0.5):
	var tween = get_tree().create_tween()
	tween.tween_property(overlay, "color", Color(0, 0, 0, 1), duration)  
	await tween.finished
	await wait(duration)

If you don’t await an async function Godot does not wait for it to finish either.

Timers, delays, waits and awaits can be hard, that’s why I personally prefer solutions which connect to tween.finished. For example:

func close_old_scene() -> void:
  pass # do things to remove an old scene.
func open_new_scene() -> void:
  pass # do things to load the new scene.
fade_out_tween.finished.connect(close_old_scene)
fade_in_tween.finished.connect(open_new_scene)

If there is a global scene loader it might be especially nice to combine the mentioned code with the bind function and an enum for all your game’s scenes:

enum GAME_SCENE {MAIN_MENU, SPLASH_SCREEN} # add more scenes.
func close_old_scene(scene: GAME_SCENE) -> void:
  pass # do things to remove an old scene.
func open_new_scene(scene: GAME_SCENE) -> void:
  pass # do things to load the new scene.

func switch_scenes(from: GAME_SCENE, to : GAME_SCENE) -> void:
  fade_out_tween.finished.connect(close_old_scene.bind(to))
  fade_in_tween.finished.connect(close_old_scene.bind(from))

And call it like this:

MySceneManager.switch_scene(MySceneManager.SPLASH_SCREEN, MySceneManager.MAIN_MENU)

Get rid of the wait() function, and instead put the delay into the tween itself using tween_interval().

gertkeno: Nice idea. Still doesn’t work though. :frowning:

kraasch: You’re probably right. All the examples just say use await though. Maybe a node/instance/scenetree thing?

normalized: I tried this, but it still didn’t work:

func fade_in(duration = 0.5):
	var tween = get_tree().create_tween()
	tween.tween_property(overlay, "color", Color(0, 0, 0, 0), duration)  
	await get_tree().create_tween().tween_interval(duration).finished

Are you thinking of something else?

Thanks for all the feedback!

You can stack things one after another in a single tween object:

	tween.tween_property(...)
	tween.tween_interval(...)
	await tween.finished

What do you mean by “Doesn’t work” Can you share how you are using this fade_in function, and what you expect it to do?

gertkeno: the forum won’t let me upload a sample project. But try it sometime. If you write two tweens one after the other. They don’t wait for each other. They fire at the same time no matter what you do. You fade in and you fade out so the net effect is nothing happens on screen except a faint flicker. I wanted a fade to black and then a fade to clear after the new scene was loaded.

normalized: you came up with the solution. The solution is a fade function that does both in a chain:

func fade(duration = 0.2):
	var t = get_tree().create_tween()
	t.tween_property(overlay, "color", Color(0, 0, 0, 1), duration)  
	t.tween_interval(duration)
	t.tween_property(overlay, "color", Color(0, 0, 0, 0), duration)
	await t.finished

Share just the code of you using fade_in and fade_out. You should chain the tweens together as described, but you absolutely can await on tween finished, creating a tween isn’t special code that transcends the scheduler.

They’d wait if you await for each to finish before starting the next one. However, putting the whole sequence into a single tween is a much simpler, safer and more elegant solution.

Let me know if you figure out a way for them to wait for each other. I made a sample project and uploaded it as a bug so the Godotengine people know. Here’s how I call the code I posted above from a main scene with the scene transition instanced:

extends Node2D
@onready var transition = $Transition
@onready var label = $Label

func _ready():
	label.text = "before"
	await get_tree().create_timer(1).timeout
	transition.fade_out()
	label.text = "after fade out"
	transition.fade_in()
	label.text = "after fade in"

What you see if you run the code is the “before” label, a flicker, and then the “after fade in” label. Really weird error, but I’m glad we figured out a solution.

await for finished signals

But all this is moot. Just use a single multi-part tween. And in general avoid await as much as possible, ideally never use it.

Yeah this is what I was talking about in my first post, you aren’t using await on your async functions so it does not wait for the function to finish. You would need await transition.fade_out() and await transition.fade_in()