We've discussed how coroutines can be a great tool to employ while making games in our article: Coroutines - A useful tool in every developer's toolbelt. Now we'll have a look at four ways in which a coroutine can be useful using code examples in the Godot game engine.

Note: A project containing the examples below is available on our Github account. The code examples were written in GDScript using Godot version 3.5.

Using a coroutine as a timer

This was the example detailed in the article mentioned above, now applied to Godot. It consists of starting a coroutine, pausing execution for three seconds, and ending it with a print statement. Essentially emulating a countdown timer or time delay.

GDScript
extends Node2D #Extending a 2D or 3D node is not relevant

func wait_and_print() -> void:
	yield(get_tree().create_timer(3), "timeout")
	print("Waited for three seconds!")

func _ready():
	wait_and_print()

Using a coroutine for animation

In this example I created a coroutine to fade the opacity of a sprite through time until it gets completely transparent. The project setup is just a main 2D node with the script below attached to it and a sprite named "Icon" as a child. As for the code this time the yield statement pauses execution and then resumes in the following frame. This behaviour is similar to the Godot's _process() method, however a coroutine is more convenient because using _process() you would need to control the start and end of the fade with conditional checks as this method is always running.

GDScript
extends Node2D

func fade() -> void:
	var range_value :float = -100
	for x in range(range_value, 1):
		var alpha_value := x/range_value
		var color := Color(1, 1, 1, alpha_value)
		$Icon.self_modulate = color
		yield(get_tree(), "idle_frame")

func _ready():
	fade()

Using a coroutine for a regular occurring task

You can use a coroutine for a task that needs to occur on a time interval or for a task that could be executed less frequently than every frame for performance reasons, such as a function that checks for the current position of an enemy, for example.

Using the same sprite as before, I changed the code to make it blink this time around. The new code makes the sprite repeatedly appear and disappear within a one second time interval.

Here's how this code works:

When entering play mode the _ready() method is called initiating the toggle_visibility() coroutine. The coroutine enters an infinite loop set by the statement while(true). Inside the while loop the sprite's visibility option is inverted (toggled). The execution stops for a second with the yield(get_tree().create_timer(1), "timeout") statement and then resumes entering the next iteration of the while loop.

GDScript
extends Node2D

func toggle_visibility() -> void:
	while(true):
		$Icon.visible = !$Icon.visible
		yield(get_tree().create_timer(1), "timeout")

func _ready():
	toggle_visibility()

I made this coroutine endless by using an infinite while loop but you could of course add a way to end it. The easiest way to do this would be to change the condition for the while loop like I did in the example below. The coroutine can now be stopped by using the keyboard keys bound to the ui_cancel action of the input map (escape by default).

GDScript
extends Node2D

var cancel := false

func toggle_visibility() -> void:
	while(!cancel):
		$Icon.visible = !$Icon.visible
		yield(get_tree().create_timer(1), "timeout")

func _ready():
	toggle_visibility()

func _process(_delta):
	if (Input.is_action_just_pressed("ui_cancel")):
		cancel = true

Creating an animation chain using coroutines

In this example I trigger a new animation after the first one finishes, creating an animation sequence. Just like in the "Using a coroutine for animation" example above I used the opacity fade for both animations. You can, of course, sequence totally different kinds of animations and behaviours. Since I wanted to apply the same fade to the additional sprite, I just adapted the coroutine to be run a second time.

GDScript
extends Node2D

var faded :bool = false

func fade(sprite :Sprite) -> void:
	var range_value :float = -100
	for x in range(range_value, 1):
		var alpha_value := x/range_value
		var color := Color(1, 1, 1, alpha_value)
		sprite.self_modulate = color
		yield(get_tree(), "idle_frame")
	if (!faded):
		fade($Icon2)
		faded = true
	
func _ready():
	fade($Icon1)

The additional sprite was just duplicated from the existing one and kept as a child of the main node containing the script.

Here's an explanation of the code above:

I made the coroutine receive the desired sprite node to fade as an argument. The coroutine is started with the first sprite node named "Icon1". After the fade completes, the coroutine is started again inside the conditional check, this time with the second sprite "Icon2". I've put this conditional check here to prevent an unwanted infinite loop as without it the coroutine would continue to trigger itself with the second sprite over and over again.

Additional info

In the examples above to control the "yielding" stage of the coroutine, I used the yield(get_tree().create_timer(n), "timeout") and the yield(get_tree(), "idle_frame") statements. The former yields after the defined number of seconds by creating a SceneTree timer, the latter yields on every frame update.

The string values "timeout" and "idle_frame" are called signals. You can create your own signals to resume coroutines, however that goes beyond the scope of this article and you probably won't need more than what's detailed here while developing your projects.

Below are some yielding signals that I think are important for you to know about. They can be useful while using coroutines to control physics, camera behaviours and more.

physics_frame - Waits until the next fixed frame update.

frame_pre_draw - Emitted at the beginning of the frame, before the VisualServer updates all the Viewports.

frame_post_draw - Emitted at the end of the frame, after the VisualServer has finished updating all the Viewports.

Note that frame_pre_draw and frame_post_draw are VisualServer signals. You can use them like this:

yield(VisualServer, "frame_pre_draw")

yield(VisualServer, "frame_post_draw")

As an additional note:

Using coroutines on nodes that can be destroyed (removed from the scene) during gameplay may not be the best option, as a yielded function will still try to resume even if the instance that contains it is gone, creating runtime errors. In those cases you can use the timer node as an alternative to the SceneTree timer used in the previous examples. You can also implement custom functionality to deal with this problem (like creating a queue for safe destruction).

Conclusion

We've gone over four ways in which you can make use of coroutines in your projects. I hope you found the examples useful and easy to understand. During the development of your projects and now armed with this knowledge you will probably find instances where coroutines could be the perfect solution for your specific problem.

Do you have any questions or want to share your thoughts with us? Let us know in the comments!

Jack Type
Blips founder, video game music composer & technical sound designer

As a child I never missed an opportunity to tear a toy with some electronics apart and use the parts to create something new. I ended up taking an electronics/programming course in my teens while also developing a deep passion for music. Owing much of this passion to a family owned night club and venturing briefly as a DJ, I embarked on a music production journey, and being an avid gamer triggered the desire to be involved in the creation of a game's music and sound. While continuing to grow both my technical and creative skillsets, I found that video game development fits me like a glove. It allows me to fully apply those skills for an endless number of possibilities.

Blips Game Music Packs

Note: You're leaving the Blips Blog. The visited links may be subject to different privacy and cookie policies.