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.
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")
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
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.
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
The script keeps printing numbers to the Output window until the button is pressed.
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.
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.
- Godot Keyboard and Mouse Button Input Programming
- Godot Event Handling
- Signals in Godot
- How to Save and Load Godot Game Data
- Godot Timing Tutorial
- Using Anchor Positioning in Godot
- UI Layout using Containers in Godot
- Shaders in Godot
- Godot State Machine
- Godot Behaviour Tree
- Godot Popups
- Parsing XML Data
- Godot Parallax Background
- How to Make a Godot Plugin
- Godot Regex - Regular Expressions
- Random Numbers
- GraphNode and GraphEdit Tutorial