Coroutines, Await and Yield in Godot

A coroutine has the ability to pause execution before the end of a function, return to its caller, and be resumed where it left off. It can be useful where you want to perform a repetitive action but not on every frame. For example, checking for the proximity of an enemy.

The yield function is used to mark the point in code to pause and return.

Yield has 2 use-cases.

Note that Godot 4 has replaced yield with the await keyword

If you are using Godot 3.x, skip ahead to the Yield section.

Await for a signal

In Godot 4 we may access the available signals of objects via the dot notation such as:

var button = Button.new()
var signal = button.pressed
await signal # Will pause in this sequence of code until the signal is received but return to the calling function.
# When the signal arrives, the following code will be executed:
print("Welcome back!")

Using await to wait for a scene tree timer to finish:

# Pause for a second
await get_tree().create_timer(1.0).timeout
print("After timeout")

So that the game continues to run whilst the await is waiting for a signal, it passes back control to the caller.

Now for something that may be a bit tricky to grasp.

The await immediately returns a value that may be ignored such that the calling function code or the game loop continues to run.

But, if you call a function containing an await, then it becomes a coroutine that can be used with await in the calling function so that code execution is paused in that calling function. Here is an example:

extends Node2D

func _ready():
	print("Started")
	try_await()
	print("Done")

func try_await():
	await get_tree().create_timer(1.0).timeout
	print("After timout")

This prints:

Started
Done
After timout

We ignored the return value in the caller of our coroutine.

Now add an await to the caller:

extends Node2D

func _ready():
	print("Started")
	await try_await()
	print("Done")

func try_await():
	await get_tree().create_timer(1.0).timeout
	print("After timout")

This prints:

Started
After timout
Done
Await Documentation

The following content is related to Godot 3.x

Yield in a function to use coroutines

When you add a yield statement within function code, it returns to the caller function with an object of type GDScriptFunctionState.

You can then return later to the point of execution in the function by calling the resume() function on this object. And you may pass arguments to it as a variant value resume(args).

func my_func():
    print("Start")
    yield()
    print("Resume")

func _ready():
    var y = my_func()
    # Function state saved in 'y'.
    print("Waiting to resume")
    y.resume()
    # 'y' resumed and is now an invalid state.

You may also check if the function call may be resumed by examining the is_valid() call result on this object. If the function state was already resumed then it returns a false value. Calling it with is_valid(true) does an extended check to see if the object or script is still valid.

Example that passes a variant back to the resumed function:

func my_func():
    print("Start")
    var msg = yield()
    print(msg)

func _ready():
    var y = my_func()
    # Function state saved in 'y'.
    print("Waiting to resume")
    y.resume("I'm back")
    print("Valid? ", y.is_valid())

Yield with internal signals

A typical use for yield may be to perform checks in a loop or make changes to the scene. Without a pause, the loop executes as fast as possible without the updates being visible until the function ends.

We can instantiate a timer and use yield to wait for its timeout signal to resume.

func _ready():
	fade_icon()

func fade_icon():
	var a = 1.0
	while a > 0:
		yield(get_tree().create_timer(0.2), "timeout")
		a -= 0.1
		$icon.modulate.a = a

In this case though it would be better to use an Animation player to achieve a smooth fade.

Many internal Nodes produce signals that we may use with yield.

One common need is to pause until after the current video frame has been completed to be sure that the nodes that you just added are set up. For this we may use the following internal signal with yield.

# Wait until the next frame is about to be drawn
yield(VisualServer, "frame_pre_draw")

or

yield(VisualServer, "frame_post_draw")

Another common use case is to string together a sequence of animations.

So you set up a AnimationPlayer with several animations such as a fight move of the player. And call a perform_animation_sequence function.

onready var player = get_node("AnimationPlayer")

func perform_animation_sequence:
    player.play("move_1")
    yield(player, "animation_finished")
    player.play("move_2")
    yield(player, "animation_finished")
    player.play("move_3")

Yield with custom signals

Here is an example of using yield to wait for a custom signal to be emitted.

Button.tscn

extends Control

signal stop_button_pressed(msg)

var n = 0
var enabled = true

func _on_Button_pressed():
	enabled = false
	emit_signal("stop_button_pressed", "Stopped")

func print_numbers():
	while enabled:
		n += 1
		print(n)
		yield(get_tree().create_timer(0.5), "timeout")

This scene has a Button whose Pressed signal is connected to the script _on_Button_pressed.

The script keeps printing numbers to the Output window until the button is pressed.

Yield.tscn

extends Node2D

func _ready():
	wait_for_button_press()

func wait_for_button_press():
	$ButtonScene.print_numbers()
	var msg = yield($ButtonScene, "stop_button_pressed")
	print(msg)

The above script connects to the Button script signal in the yield function and waits to receive the signal and the msg String before continuing. If the signal emitted several values then the msg value would be an array of these values.

Timing issue

If our custom signal is emitted quickly (within the current frame) then yield will return instead of waiting, so we should use call_deferred to call the function that emits the signal to ensure that the signal is emitted after the current frame.

Here is a YouTube video about Yield

This is not my video, just one I found interesting.

More solutions