ゲーム開発において、処理の「待機」や「一時停止」は頻繁に発生します。例えば、アニメーションの終了を待つ、ネットワーク通信の応答を待つ、あるいは一定時間後に処理を再開するなどです。これらの処理をメインループ(フレーム処理)を止めずに効率的に行うために不可欠なのが、非同期処理 と コルーチン の概念です。
Godot EngineのGDScriptでは、この非同期処理をシンプルかつ強力に扱うためのキーワードとしてawaitが導入されました。本記事では、awaitの基本的な使い方から、その背後にあるコルーチンの仕組み、そしてGodot 3系で使われていたyieldからの移行方法までを、初心者から中級者向けに徹底解説します。
なぜ非同期処理とawaitが重要なのか
ゲームは常にスムーズに動作し続ける必要があります。もし、何らかの処理(例えば、巨大なファイルの読み込みや複雑な計算)がメインスレッドを長時間占有してしまうと、ゲームはフリーズし、ユーザー体験は著しく損なわれます。これを ブロッキング と呼びます。
非同期処理は、このブロッキングを防ぐための技術です。処理を一時停止し、待機が必要な間に他の処理(ゲームの描画や入力処理など)を続行させ、待機が完了した時点で元の処理を再開します。
Godot Engineにおいて、awaitは主に以下の二つの目的で使われます。
- シグナルの待機: 特定のノードからシグナルが発せられるまで処理を一時停止する。
- コルーチンの実現: 関数内で処理を一時停止し、特定の条件が満たされた後に再開する、いわゆる コルーチン (協調的マルチタスク)を実現する。
これにより、複雑なシーケンス処理( 例:会話イベント、カットシーン、段階的な敵の出現)を、コールバック地獄に陥ることなく、上から下に読みやすいコードで記述できるようになります。
awaitの基本的な使い方
GDScriptのawaitキーワードは、特定の 待機可能オブジェクト(Awaitable Object)の完了を待ちます。最も一般的な待機可能オブジェクトは、シグナル と タイマー です。
1. シグナルの待機
ノードのシグナルは、非同期処理の最も一般的な待機対象です。例えば、アニメーションが終了するのを待つ、ボタンが押されるのを待つ、といった場面で利用します。
# Player.gd
func play_attack_animation():
$AnimationPlayer.play("attack")
# "animation_finished"シグナルが発せられるまで、ここで処理が一時停止する
await $AnimationPlayer.animation_finished
print("攻撃アニメーションが終了しました。次の行動に移ります。")
# 待機後、処理が再開される
この例では、$AnimationPlayer.animation_finishedというシグナルが発せられるまで、awaitの行で関数play_attack_animationの実行が一時停止します。シグナルが発せられると、関数は自動的に再開されます。
2. タイマーによる時間待機
ゲーム内で一定時間待機したい場合は、SceneTreeのcreate_timerメソッドと組み合わせて使用します。
# World.gd
func start_game_sequence():
print("ゲーム開始!")
# 3秒間待機する
await get_tree().create_timer(3.0).timeout
print("3秒経過しました。敵が出現します。")
# 待機後、敵の出現処理などが続く
get_tree().create_timer(3.0)はSceneTreeTimerオブジェクトを返します。このオブジェクトが持つtimeoutシグナルをawaitすることで、指定した秒数だけ処理を一時停止できます。
awaitとコルーチンの関係
awaitが使われている関数は、自動的に コルーチン として扱われます。コルーチンとは、実行を一時停止し、後で再開できる関数のことです。GDScriptでは、awaitを含む関数は、その関数全体が非同期的に実行されることを意味します。
コルーチンの特徴:
| 特徴 | 説明 |
|---|---|
| 協調的 | 処理の切り替えは、プログラマがawaitキーワードを使って明示的に行います。 |
| 一時停止と再開 | awaitの箇所で一時停止し、待機対象が完了した時点で自動的に再開します。 |
| スタックの保持 | 一時停止しても、ローカル変数などの実行コンテキスト(スタック)は保持されます。 |
これにより、複雑な非同期ロジックを、あたかも同期的なコードであるかのように、自然な流れで記述することが可能になります。
Godot 3のyieldからawaitへの移行
Godot Engine 4.0以降では、非同期処理の記述方法がGodot 3系から大きく変更されました。Godot 3系で使われていたyieldキーワードは廃止され、よりモダンなawaitキーワードに置き換えられました。
移行の基本パターン
yieldはシグナルを待つだけでなく、値を返すジェネレータ関数としても使えましたが、awaitは主にシグナルや関数の完了を待つことに特化しています。
Godot 3.x (yield) | Godot 4.x (await) | 備考 |
|---|---|---|
yield(object, "signal_name") | await object.signal_name | シグナル待機は最もシンプルな移行パターンです。 |
yield(get_tree().create_timer(time), "timeout") | await get_tree().create_timer(time).timeout | タイマー待機も同様にシグナル待機として記述します。 |
yield(func_call(), "completed") | await func_call() | 非同期関数の完了を待つ場合。 |
実践的な移行例:タイマー処理
Godot 3.xでよく使われたタイマー処理の例を見てみましょう。
# Godot 3.x (yield)
func wait_and_do_something():
# 2秒待機
yield(get_tree().create_timer(2.0), "timeout")
print("2秒経ったよ!")
これをGodot 4.xのawaitに置き換えると、以下のようになります。
# Godot 4.x (await)
func wait_and_do_something():
# 2秒待機
await get_tree().create_timer(2.0).timeout
print("2秒経ったよ!")
コードがより直感的になり、どのシグナルを待っているのかが一目瞭然になりました。
実践的な使用例:会話イベントの制御
awaitを使うことで、ゲーム内の複雑なイベントシーケンスを非常に簡潔に記述できます。ここでは、会話イベントとカメラ移動を組み合わせた例を紹介します。
# EventController.gd
extends Node
@export var dialogue_manager: Node2D
@export var camera_controller: Node2D
# 複雑なイベントシーケンスを同期的に記述できる
func start_cutscene():
print("--- カットシーン開始 ---")
# 1. カメラをキャラクターAに移動させ、移動が完了するまで待機
print("カメラをキャラクターAに移動中...")
await camera_controller.move_to_target($CharacterA.global_position)
# 2. キャラクターAのセリフを表示し、プレイヤーがボタンを押すまで待機
print("会話Aを開始...")
await dialogue_manager.start_dialogue("CharacterA", "やあ、久しぶりだね。")
# 3. カメラをキャラクターBに移動させ、移動が完了するまで待機
print("カメラをキャラクターBに移動中...")
await camera_controller.move_to_target($CharacterB.global_position)
# 4. キャラクターBのセリフを表示し、プレイヤーがボタンを押すまで待機
print("会話Bを開始...")
await dialogue_manager.start_dialogue("CharacterB", "本当にね。まさかここで会うとは。")
# 5. 2秒間待機
print("2秒間、余韻を楽しむ...")
await get_tree().create_timer(2.0).timeout
print("--- カットシーン終了 ---")
# camera_controllerノードに実装されるであろう非同期関数
# func move_to_target(target_pos: Vector2):
# # カメラ移動アニメーションを実行
# # アニメーション終了シグナルをawaitする
# await $CameraAnimationPlayer.animation_finished
# return # 完了を待つ
このように、awaitを使うことで、イベントの各ステップが上から順に実行されることが保証され、コードの可読性と保守性が飛躍的に向上します。
まとめ:非同期処理をマスターしてゲーム開発を加速させる
Godot Engineのawaitキーワードは、非同期処理とコルーチンをGDScriptで扱うための強力なツールです。
| 概念 | キーワード | 役割 |
|---|---|---|
| 非同期処理 | await | メインスレッドをブロックせずに、処理の待機と再開を可能にする。 |
| コルーチン | awaitを含む関数 | 実行を一時停止し、後で再開できる関数。複雑なシーケンスを簡潔に記述できる。 |
Godot 3系からGodot 4系への移行では、yieldからawaitへの置き換えが必須となりますが、awaitはより直感的でモダンな非同期処理の記述を可能にします。シグナルやタイマーの待機にawaitを積極的に活用し、プレイヤーを待たせない、スムーズでリッチなゲーム体験を実現しましょう。