diff --git a/src/diary/entries/220811 b/src/diary/entries/220811 deleted file mode 100644 index 3d059c4..0000000 --- a/src/diary/entries/220811 +++ /dev/null @@ -1,112 +0,0 @@ - -
Demonstrating coroutines in Godot Engine with a simple application.
Coroutines are functions that, instead of running to completion, yield until certain criteria are met. Godot Engine supports coroutines through yield(), resume(), and the GDScriptFunctionState object.
Coroutines allow for scripted game scenarios that respond dynamically to the player and the changing game world. They let you bounce between functions, step-by-step, and respond to interruptions. This means functions can be automatically called at the completion of other functions, animations, player actions, in-game events, or timers. Add in interruptions and conditionals, and you have a tool for building a responsive game world.
As a simple demonstration, I made a stoplight. Follow along with my code on GitLab.
The light changes every few seconds, going from green, yellow, then red. The light changes immediately if the walk button is pressed. This demonstrates that methods can wait for criteria (a timed duration in this case) to be met before resuming, and they can be influenced by player action.
I have a TextureRect background, an AnimatedSprite stoplight, a Sprite walk button with a TextureButton, and a label for displaying a timer. Most of the code is attached to the root. It's better to have code closer to where it's being used and to mind your separation of concerns in real projects, though.
The light is changed by setting its animation to one of these options. Each is one-frame - just the stoplight with the one or none of the lights colored in.
extends Node
-
-onready var stoplight = $Stoplight
-
-func _ready():
- stoplight.play()
-
- var result = wait(5, 'green')
- $WalkButton/TextureButton.connect('pressed', result, 'resume',
- ['interrupted on green'], CONNECT_ONESHOT)
- yield(result, 'completed')
-
- result = wait(5, 'yellow')
- $WalkButton/TextureButton.connect('pressed', result, 'resume',
- ['interrupted on yellow'], CONNECT_ONESHOT)
- yield(result, 'completed')
-
- result = wait(5, 'red')
- $WalkButton/TextureButton.connect('pressed', result, 'resume',
- ['interrupted on red'], CONNECT_ONESHOT)
- yield(result, 'completed')
-
-func wait(time, color):
- print('waiting for: ' + color)
- var result = yield(get_tree().create_timer(time), 'timeout')
- if result:
- print(result)
- stoplight.animation = color
- print('done: ' + color)
-
-func _on_completed():
- print('completed')
-
-func _on_WalkButton_gui_input(event):
- if event is InputEventMouseButton and event.pressed:
- print ("Walk Button not functioning.")
extends Label
-var time_start = 0
-var time_now = 0
-
-func _ready():
- time_start = OS.get_unix_time()
- set_process(true)
-
-func _process(delta):
- time_now = OS.get_unix_time()
- var elapsed = time_now - time_start
- var minutes = elapsed / 60
- var seconds = elapsed % 60
- var str_elapsed = "%02d" % [seconds]
- text = str(str_elapsed)
At _ready(), wait() is assigned to the GDScriptFunctionState result and is called for the first color, green. _ready() yields until wait() is completed.
The wait method yields for the given amount of seconds then sets the stoplight to the given color.
At wait()'s completion, _ready() calls wait() for yellow, then red. Each is called one at a time, waiting for the color to complete before moving on.
The Wait Button interrupts the wait times between colors. Before _ready() yields, it connects the 'pressed' signal on the Wait Button.
If the Wait Button is clicked during wait()'s yield, the GDScriptFunctionState result resumes immediately, ignoring wait()'s yield timer. This time, result has a string arg "interrupted on green," so it will print the result, change the stoplight's color, then print "done: green." The wait method is complete, so _ready() resumes and calls wait() for the next color.
The outcomes in this example can be swapped out with anything. I use coroutines in Blessfrey's skills to manage the flow of phases from activation, different phases of effects, cooldown, and interactions with any counters. I also use it in the basic weapon attack so the character continuously swings at the rate of his attack speed until he cancels, uses a skill, or moves. It could also be used for something like cars that stop and honk when the player walks in front of them then drive off once the path is clear. Anything influenced by other entities is a good coroutine candidate.
Coroutines enable practical ways to improve the flow and interactivity of games, so practice the concept a lot!
Demonstrating coroutines in Godot Engine with a simple application.
Coroutines are functions that, instead of running to completion, yield until certain criteria are met. Godot Engine supports coroutines through yield(), resume(), and the GDScriptFunctionState object.
Coroutines allow for scripted game scenarios that respond dynamically to the player and the changing game world. They let you bounce between functions, step-by-step, and respond to interruptions. This means functions can be automatically called at the completion of other functions, animations, player actions, in-game events, or timers. Add in interruptions and conditionals, and you have a tool for building a responsive game world.
As a simple demonstration, I made a stoplight. Follow along with my code on GitLab.
The light changes every few seconds, going from green, yellow, then red. The light changes immediately if the walk button is pressed. This demonstrates that methods can wait for criteria (a timed duration in this case) to be met before resuming, and they can be influenced by player action.
I have a TextureRect background, an AnimatedSprite stoplight, a Sprite walk button with a TextureButton, and a label for displaying a timer. Most of the code is attached to the root. It's better to have code closer to where it's being used and to mind your separation of concerns in real projects, though.
The light is changed by setting its animation to one of these options. Each is one-frame - just the stoplight with the one or none of the lights colored in.
extends Node
+
+onready var stoplight = $Stoplight
+
+func _ready():
+ stoplight.play()
+
+ var result = wait(5, 'green')
+ $WalkButton/TextureButton.connect('pressed', result, 'resume',
+ ['interrupted on green'], CONNECT_ONESHOT)
+ yield(result, 'completed')
+
+ result = wait(5, 'yellow')
+ $WalkButton/TextureButton.connect('pressed', result, 'resume',
+ ['interrupted on yellow'], CONNECT_ONESHOT)
+ yield(result, 'completed')
+
+ result = wait(5, 'red')
+ $WalkButton/TextureButton.connect('pressed', result, 'resume',
+ ['interrupted on red'], CONNECT_ONESHOT)
+ yield(result, 'completed')
+
+func wait(time, color):
+ print('waiting for: ' + color)
+ var result = yield(get_tree().create_timer(time), 'timeout')
+ if result:
+ print(result)
+ stoplight.animation = color
+ print('done: ' + color)
+
+func _on_completed():
+ print('completed')
+
+func _on_WalkButton_gui_input(event):
+ if event is InputEventMouseButton and event.pressed:
+ print ("Walk Button not functioning.")
extends Label
+var time_start = 0
+var time_now = 0
+
+func _ready():
+ time_start = OS.get_unix_time()
+ set_process(true)
+
+func _process(delta):
+ time_now = OS.get_unix_time()
+ var elapsed = time_now - time_start
+ var minutes = elapsed / 60
+ var seconds = elapsed % 60
+ var str_elapsed = "%02d" % [seconds]
+ text = str(str_elapsed)
_ready()
, wait()
is assigned to the GDScriptFunctionState result
and is called for the first color, green. _ready()
yields until the given function wait()
is completed. At _ready(), wait() is assigned to the GDScriptFunctionState result and is called for the first color, green. _ready() yields until wait() is completed.
The wait method yields for the given amount of seconds then sets the stoplight to the given color.
wait()
's completion, _ready()
calls wait()
for yellow, then red. Each is called one at a time, waiting for the color to complete before moving on. At wait()'s completion, _ready() calls wait() for yellow, then red. Each is called one at a time, waiting for the color to complete before moving on.
The Wait Button interrupts the wait times between colors. Before _ready() yields, it connects the 'pressed' signal on the Wait Button.
If the Wait Button is clicked during wait()'s yield, the GDScriptFunctionState result resumes immediately, ignoring wait()'s yield timer. This time, result has a string arg "interrupted on green," so it will print the result, change the stoplight's color, then print "done: green." The wait method is complete, so _ready() resumes and calls wait() for the next color.
_ready()
yields, it connects the 'pressed'
signal on the Wait Button. wait()
's yield, the GDScriptFunctionState result
resumes immediately, ignoring wait()
's yield timer. This time, result
has a string arg 'interrupted on green'
, so it will print the result, change the stoplight's color, then print 'done: green'
. The wait
method is complete, so _ready()
resumes and calls wait()
for the next color. The outcomes in this example can be swapped out with anything. I use coroutines in Blessfrey's skills to manage the flow of phases from activation, different phases of effects, cooldown, and interactions with any counters. I also use it in the basic weapon attack so the character continuously swings at the rate of his attack speed until he cancels, uses a skill, or moves. It could also be used for something like cars that stop and honk when the player walks in front of them then drive off once the path is clear. Anything influenced by other entities is a good coroutine candidate.
Coroutines enable practical ways to improve the flow and interactivity of games, so practice the concept a lot!