I'm not sure what I'm doing wrong but I am writing some scaffolding for a game where there can be multiple different day / night colors and scenes using a procedural sky material.

In the editor the artist can create a folder of procedural sky materials resources and drag them into the time of day they can be used.

Everything seems to be working fine when I run just one of the functions by itself but when I go to run the scene I get a stack overflow from infinite recursion. I think this means that me awaiting the tween finished signal is not working for some reason by I'm not sure why.

Anyways when I litter the script with a bunch of timers, it seems to work for some reason! I really have no idea why it should work with timers and that the tween finished would not wait like I thought it should. Any ideas?

This works

extends WorldEnvironment


@export var MorningSkies : Array[ProceduralSkyMaterial] = []
@export var DaySkies: Array[ProceduralSkyMaterial] = []
@export var EveningSkies: Array[ProceduralSkyMaterial] = []
@export var NightSkies: Array[ProceduralSkyMaterial] = []

# Called when the node enters the scene tree for the first time.
func _ready():
	sunrise()


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass

func tween_to_random_sky(skies: Array):
	
	var sky = skies[randi() % skies.size()]
	
	var tween = get_tree().create_tween()
	tween.set_parallel()
	tween.tween_property(self, "environment:sky:sky_material:sky_top_color", sky.get("sky_top_color"), 5)
	tween.tween_property(self, "environment:sky:sky_material:sky_horizon_color", sky.get("sky_horizon_color"), 5)
	tween.tween_property(self, "environment:sky:sky_material:ground_bottom_color", sky.get("ground_bottom_color"), 5)
	tween.tween_property(self, "environment:sky:sky_material:ground_horizon_color", sky.get("ground_horizon_color"), 5)
	await tween.finished

func sunrise():
	
	tween_to_random_sky(MorningSkies)
	var timer = get_tree().create_timer(5)
	await timer.timeout
	print("The sun rose")
	day()
	
func sunset():
	
	tween_to_random_sky(EveningSkies)
	var timer = get_tree().create_timer(5)
	await timer.timeout
	print("The sun set")
	night()
	
func day():
	
	tween_to_random_sky(DaySkies)
	var timer = get_tree().create_timer(5)
	await timer.timeout
	print("It's day")
	sunset()
	
func night():
	
	tween_to_random_sky(NightSkies)
	var timer = get_tree().create_timer(5)
	await timer.timeout
	print("It's night")
	sunrise()

This does not work

extends WorldEnvironment


@export var MorningSkies : Array[ProceduralSkyMaterial] = []
@export var DaySkies: Array[ProceduralSkyMaterial] = []
@export var EveningSkies: Array[ProceduralSkyMaterial] = []
@export var NightSkies: Array[ProceduralSkyMaterial] = []

# Called when the node enters the scene tree for the first time.
func _ready():
	sunrise()


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass

func tween_to_random_sky(skies: Array):
	
	var sky = skies[randi() % skies.size()]
	
	var tween = get_tree().create_tween()
	tween.set_parallel()
	tween.tween_property(self, "environment:sky:sky_material:sky_top_color", sky.get("sky_top_color"), 5)
	tween.tween_property(self, "environment:sky:sky_material:sky_horizon_color", sky.get("sky_horizon_color"), 5)
	tween.tween_property(self, "environment:sky:sky_material:ground_bottom_color", sky.get("ground_bottom_color"), 5)
	tween.tween_property(self, "environment:sky:sky_material:ground_horizon_color", sky.get("ground_horizon_color"), 5)
	await tween.finished

func sunrise():
	
	tween_to_random_sky(MorningSkies)
	print("The sun rose")
	day()
	
func sunset():
	
	tween_to_random_sky(EveningSkies)
	print("The sun set")
	night()
	
func day():
	
	tween_to_random_sky(DaySkies)
	print("It's day")
	sunset()
	
func night():
	
	tween_to_random_sky(NightSkies)
	print("It's night")
	sunrise()

Oh shoot I forgot that tween_to_random_sky would have to be awaited since it awaits the tween signal.

That makes sense why it wasn't waiting now, since I wasn't telling day() to wait for tween_to_random_sky to be completed..

Your code operates under false assumption that await is completely pausing the code execution. This is not the case. When you issue an 'await' statement in a function, the execution will immediately return back to caller, only to continue the function when signal is emitted. Putting an await at the end of function does exactly nothing in regards to code execution flow.

Knowing that, note that your day(), night() ect. functions are consecutively calling each other to infinity, and the await in tween_to_random_sky() has no effect in pausing that.

I'd advise rethinking your approach so it avoids await altogether. However if you want to do it via a coroutine, here's the way it can be done:

func tween_to_sky(sky):
	var tween = get_tree().create_tween()
	# setup and start the tween here
	return tween

var cycle = [DAY, SUNSET, NIGHT, SUNRISE]
while true:
	for c in cycle:
		await tween_to_sky(c).finished

    xyz Knowing that, note that your day(), night() ect. functions are consecutively calling each other to infinity, and the await in tween_to_random_sky() has no effect in pausing that.

    Yeah it had no effect in pausing that because I wasn't doing await tween_to_random_sky(). The below code works like a treat.

    extends WorldEnvironment
    
    @export var MorningSkies : Array[ProceduralSkyMaterial] = []
    @export var DaySkies: Array[ProceduralSkyMaterial] = []
    @export var EveningSkies: Array[ProceduralSkyMaterial] = []
    @export var NightSkies: Array[ProceduralSkyMaterial] = []
    
    @onready var Sun: DirectionalLight3D = $Sun
    
    # Called when the node enters the scene tree for the first time.
    func _ready():
    	sunrise()
    
    
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta):
    	pass
    
    func tween_to_random_sky(skies: Array, duration: float):
    	
    	var sky = skies[randi() % skies.size()]
    	
    	var tween = create_tween()
    	tween.set_parallel()
    	tween.tween_property(self, "environment:sky:sky_material:sky_top_color", sky.get("sky_top_color"), duration)
    	tween.tween_property(self, "environment:sky:sky_material:sky_horizon_color", sky.get("sky_horizon_color"), duration)
    	tween.tween_property(self, "environment:sky:sky_material:ground_bottom_color", sky.get("ground_bottom_color"), duration)
    	tween.tween_property(self, "environment:sky:sky_material:ground_horizon_color", sky.get("ground_horizon_color"), duration)
    	await tween.finished
    
    func sunrise():
    	
    	await tween_to_random_sky(MorningSkies, 5)
    	print("The sun rose")
    	day()
    	
    func sunset():
    	
    	await tween_to_random_sky(EveningSkies, 5)
    	print("The sun set")
    	night()
    	
    func day():
    	
    	await tween_to_random_sky(DaySkies, 5)
    	print("It's day")
    	sunset()
    	
    func night():
    	
    	await tween_to_random_sky(NightSkies, 5)
    	print("It's night")
    	sunrise()
    • xyz replied to this.

      linkdude240 That code will still cause stack overflow. Successive function calls never return, and consequently, stack frames are never popped. It'll just happen a bit later because there are now delays between calls. But if you let it run long enough - the stack limit will be reached.

        xyz Oh, yes of course. Admittedly I was hyper-focused on the part where it wasn't waiting for the tween to finish.

        I have a lot of cleanup to do in this even small project for sure. I've only just begun learning this, and have folks like you to thank for your help!