Coroutines 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.

  • Add a yield statement to a function to interrupt the code execution and return to the caller function, and resume code execution later
  • Wait for a signal and then resume code execution without returning from the current function

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.

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.

Comments Forum

More solutions