Await function makes a for loop repeat 6 more times and crash the game

Godot Version

Godot 4

Question

I’m making a little 3d game in which the player jumps from platform to platform. Every jump, another row of platforms is spawned and moves toward the rest of the platforms with a tween. Below is the part of the code that generates it:

func move_player(direction_x, direction_z):
# spawn new row of platforms with every move forward
var xcounter = 0
for number_of_steps in range(int(direction_z)):
spawn(int(player.global_position.z)+rows_of_platforms_on_first_spawn+number_of_steps)
await get_tree().create_timer(0.1).timeout #wait 0.1 second between spawns
xcounter += 1
print(xcounter)

When I added the await timer, for every jump (+ 1 on the z-axis), it makes the same loop go 6 times (and create 6 rows of platforms in the same location. Without it, the correct number of rows of platforms is generated. Am I misapplying the await function here? How would I solve this? Thank you in advance!

Coroutines are tricksy. I suspect that at the await, it ‘return’s’ from your move_player function and continues other code, calling move_player six times within 0.1 seconds. Then all the timeout signals happen.

When you call the move_player func try to use await move_player(....) so it pauses there too.

Thanks! I tried this, but unfortunately it keeps doing the same thing

The issue lies in the way you’re using the await keyword in your code. The await keyword is used to wait for a specific task to complete, in this case, waiting for a 0.1 second timer to timeout. However, it does not block the execution of the code, it only yields the execution to other coroutines.

In your case, when you call await get_tree().create_timer(0.1).timeout, the execution of the move_player function is yielded, but the loop continues to run. This means that the spawn function is called multiple times in rapid succession, resulting in 6 rows of platforms being spawned at the same location.

To fix this, you can use a while loop instead of a for loop, and increment the number_of_steps variable manually. Here’s an example:

func move_player(direction_x, direction_z):
    # spawn new row of platforms with every move forward
    var xcounter = 0
    var number_of_steps = 0
    while number_of_steps < int(direction_z):
        spawn(int(player.global_position.z)+rows_of_platforms_on_first_spawn+number_of_steps)
        await get_tree().create_timer(0.1).timeout #wait 0.1 second between spawns
        xcounter += 1
        print(xcounter)
        number_of_steps += 1

This way, the loop will wait for the timer to timeout before spawning the next row of platforms, and it will spawn the correct number of rows.

Thank you for your suggestion! Unfortunately, it yielded the same result. I’ve tried to make a generalized case of it in a new scene, see below, and the problem is probably that it’s part of the process function. Is there another way to make it work?

func _process(delta):
	if Input.is_action_pressed("ui_left"):
		var number_of_steps: int = 0
		while number_of_steps < 2:
			print(number_of_steps)
			await get_tree().create_timer(1).timeout #wait 0.1 second between spawns
			number_of_steps += 1

Yeah, _process is called every frame. You should guard the async block to prevent calling it multiple times.

It can be simple such as:

var is_doing_steps = false

func _process(_d):
	if Input.is_action_pressed("ui_left") and not is_doing_steps:
		is_doing_steps = true
		for i in 2:
			print('STEP ', i)
			await get_tree().create_timer(1).timeout
		is_doing_steps = false

It may be good for you to implement a StateMachine. Here’s the great chapter on state from game programming patterns: State.

This did the trick, thanks so much! Also for the link to the states