【Godot】Godot Engineにおけるawaitとコルーチンの基礎:非同期処理の理解と実践(旧yieldからの移行含む)

作成: 2025-12-08

Godot EngineのGDScriptにおけるawaitキーワードとコルーチンの基本的な使い方、非同期処理の重要性、そしてGodot 3系のyieldからの移行方法を初心者向けに徹底解説

ゲーム開発において、処理の「待機」や「一時停止」は頻繁に発生します。例えば、アニメーションの終了を待つ、ネットワーク通信の応答を待つ、あるいは一定時間後に処理を再開するなどです。これらの処理をメインループ(フレーム処理)を止めずに効率的に行うために不可欠なのが、非同期処理コルーチン の概念です。

Godot EngineのGDScriptでは、この非同期処理をシンプルかつ強力に扱うためのキーワードとしてawaitが導入されました。本記事では、awaitの基本的な使い方から、その背後にあるコルーチンの仕組み、そしてGodot 3系で使われていたyieldからの移行方法までを、初心者から中級者向けに徹底解説します。

なぜ非同期処理とawaitが重要なのか

ゲームは常にスムーズに動作し続ける必要があります。もし、何らかの処理(例えば、巨大なファイルの読み込みや複雑な計算)がメインスレッドを長時間占有してしまうと、ゲームはフリーズし、ユーザー体験は著しく損なわれます。これを ブロッキング と呼びます。

非同期処理は、このブロッキングを防ぐための技術です。処理を一時停止し、待機が必要な間に他の処理(ゲームの描画や入力処理など)を続行させ、待機が完了した時点で元の処理を再開します。

Godot Engineにおいて、awaitは主に以下の二つの目的で使われます。

  1. シグナルの待機: 特定のノードからシグナルが発せられるまで処理を一時停止する。
  2. コルーチンの実現: 関数内で処理を一時停止し、特定の条件が満たされた後に再開する、いわゆる コルーチン (協調的マルチタスク)を実現する。

これにより、複雑なシーケンス処理(例:会話イベント、カットシーン、段階的な敵の出現)を、コールバック地獄に陥ることなく、上から下に読みやすいコードで記述できるようになります。

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. タイマーによる時間待機

ゲーム内で一定時間待機したい場合は、SceneTreecreate_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を積極的に活用し、プレイヤーを待たせない、スムーズでリッチなゲーム体験を実現しましょう。