Godotのawait入門

今回はGodotのawaitを使ってみます。 簡単な例を添えてどのように処理が進んでいくのかを整理して行きます。
2023.11.29

Godotについて

GodotはMITライセンスで開発されているゲームエンジンです。

複数プラットフォームに対応したゲームエンジンで、PCだけでなくスマートフォンやWeb向けにもビルド可能です。

2014年から開発が開始されており、割と成熟したエンジンではありますが日々改善が施されています。

バージョン

今回使用しているのはGodot Engine v4.1.3です。

GDScriptのawaitについて

GDScriptのawaitはシグナルの待機やコルーチンを作りたい時に便利です。

以下のような形式で書きます。

awaitのサンプル

await {SINGAL}

このように書くとシグナルが発行されるまで残りの処理を待機します。 例えば以下のようなケースで便利です。

awaitを使った例

await {サーバーとの通信完了シグナル}
await {アニメーション処理の完了シグナル}

この例では、サーバーとの通信完了を待ってアニメーションを実行したいのですが、素朴にsignalのconnectを使って書くと少し大変です。 非同期的な処理をあたかも同期的に書きたいシーンで便利だと思います。

もう少し複雑な例

以下は5秒間待ってからLabelノードのテキストを変更するような例です。 少し複雑にして、クラス内部で定義されたシグナルも使っています。

Label.gd

extends Label

signal text_updated(counter: int)
var timeout_counter := 0

func _rewrite_label(timer: SceneTreeTimer):
	print("Wait timer timeout")
	await timer.timeout
	print("Timeout")
	text = "Time Out"
	print("Text updated")
	timeout_counter += 1
	text_updated.emit(timeout_counter)
	print("Fin _rewrite_label")
	
func _on_text_updated():
	print("Wait singal: text_updated")
	var counter = await text_updated
	print("Catch signal: text_updated")
	print("Current counter: {counter}".format({"counter": counter}))

# Called when the node enters the scene tree for the first time.
func _ready():
	text = "Start"
	print("Timer start")
	var timer = get_tree().create_timer(5)
	print("Call async func")
	_rewrite_label(timer)
	_on_text_updated()
	print("Ready")

実行すると以下のようなコンソールの出力が得られます。

実行時の出力

Timer start
Call async func
Wait timer timeout
Wait singal: text_updated
Ready
Timeout
Text updated
Catch signal: text_updated
Current counter: 1
Fin _rewrite_label

処理の流れを整理すると以下のような感じになります。

ポイントとしてはreturnまたはawaitのタイミングで非同期関数を呼び出した場所に帰ってくるということです。

注意点としてはシグナルを発行するとそれを待機しているところに処理が移るということです。

もう少し詳しく

func _rewrite_label(timer: SceneTreeTimer):
    # ...
	text_updated.emit(timeout_counter) # <-シグナルの発行後、待機していた_on_text_updatedの処理が再開される
	print("Fin _rewrite_label") # <-ここは_on_text_updatedの処理が完了した時に実行される

最後に

awaitを使うことでシンプルにコードを書けるケースもあると思います。 ただ、シグナルのconnectメソッドを使った方が簡単に書けるケースもあると思うので使い分けは難しいです...

個人的はいくつものシグナルの実行順を制御したい場合はawaitを使っています。