Godot4のawaitを使って複数のシグナルを待つ
はじめに
godot engine4でawaitが追加され、シグナル待機の実装がわかりやすくなりました。ターン制RPGでよくあるコマンド入力待機なんかも、シンプルなコードで記述することができます。
例えば、使用するスキルを選択したらシグナルcommand_selected
がemitされるとして、コマンド待機は以下のようなコードになります。
signal command_selected(skill) #スキルを選んだときのシグナルがあるとする
var skill = await command_selected
これだけでも非常に便利ですが、この記事ではawaitをさらに便利に使うために複数のシグナルを待つクラスの実装を紹介します。
これが何の役に立つのか?ゲームでは何か入力中にキャンセルしたいときがよくあります。例えば、RPGのターン制バトルではコマンド入力待機中にキャンセルボタンを押すことで、一人前の仲間のコマンド入力をやり直す機能があった方が快適です。この場合はコマンド入力とキャンセルのいずれかが実行されるのを待って、処理を分岐させる必要があるわけですが、こういうときに複数のシグナルを待てるとうれしいわけです。
複数のシグナルを待つクラスの実装
以下が、複数のシグナルを待つクラスの実装です。
class_name Promise
signal _any_signal_emitted(signal_, result)
func wait_any_signals(signals: Array):
var callbacks = {}
for signal_ in signals:
var signal_info = signal_.get_object().get_signal_list().filter(func(info):
return info.name == signal_.get_name()
)
assert(signal_info.size() == 1)
var argc = signal_info[0]["args"].size()
var callback: Callable
match argc:
0: callback = _callback_0.bind(signal_)
1: callback = _callback_1.bind(signal_)
2: callback = _callback_2.bind(signal_)
3: callback = _callback_3.bind(signal_)
callbacks[signal_] = callback
signal_.connect(callback)
var ret = await _any_signal_emitted
for signal_ in callbacks:
signal_.disconnect(callbacks[signal_])
return ret
func _callback_0(signal_) -> void:
_any_signal_emitted.emit(signal_)
func _callback_1(arg1, signal_) -> void:
_any_signal_emitted.emit(signal_, arg1)
func _callback_2(arg1, arg2, signal_) -> void:
_any_signal_emitted.emit(signal_, [arg1, arg2])
func _callback_3(arg1, arg2, arg3, signal_) -> void:
_any_signal_emitted.emit(signal_, [arg1, arg2, arg3])
wait_any_signals
が処理本体で、渡したシグナル配列のいずれかがemitされたらその情報を返します。渡したシグナルそれぞれにconnect
したコールバック関数で新たなシグナル_any_signal_emitted
をemitをしています。これは内部だけで使用するシグナルで、emitされたシグナルとその引数情報をemitしています。
そして、var ret = await _any_signal_emitted
とすることで、いずれかのシグナルがemitされるまで待機しています。
シグナル毎に引数の数が違うので、引数の数毎にコールバック関数を用意しています(_callback_0、_callback_1...)。もっと良い方法があるかもしれませんが。とりあえず引数3つ分まで用意しました。
クラスを使う側の実装
使う側は以下のようになります。command_selected
とcommand_canceled
がコマンド入力、キャンセル時のシグナルです。
signal command_selected(skill) #スキルを選んだときのシグナル
signal command_canceled() # キャンセルしたときのシグナル
var promise = Promise.new()
var result = await promise.wait_any_signals([command_selected, command_canceled])
match result:
[command_selected, var skill]:
# コマンド入力処理
[command_canceled]:
# コマンドキャンセル処理
Discussion