前提
本情報はGodot_v4.2.2-stable_win64
のものです。
Godotのフォーカスシステム
Godotのフォーカスシステム、便利ですよね。
コントローラー用のゲームだと全てフォーカスベースで操作できるようにしないといけないので、ほとんどデフォルトで動作してくれるのはありがたいです。
https://docs.godotengine.org/ja/4.x/tutorials/ui/gui_navigation.html
困っていること
ただ1つ困ったことがあって、Godotって完全にフォーカスを失ったときに復帰する仕組みがないんですよね。
つまり、どのコンポーネントにもフォーカスが当たってない状態になると、コントローラーでは完全に操作できない状況になります。
どんな時にそうなるかというと…
- ポーズ画面を開いて、閉じるとき。ポーズ画面のフォーカスが失われて、フォーカスがない状態になる。
- アイテムショップ画面とかで、アイテムを購入後フォーカスしていたアイテムが削除されたとき。フォーカスしていたアイテムが消えるので、フォーカスがない状態になる。
それぞれのアクション(ポーズ画面を閉じた後、ショップで買い物をした後)に既定のコンポーネントへフォーカスが当たるようなコードを書いてもいいのですが、めんどくさい。
今回の記事はフォーカスが失われた場合に自動で復旧するための仕組みです。
方針
-
Viewport.gui_focus_changed
シグナルでフォーカスの変更を監視し、フォーカスが当たるたび配列にNodeを記録 - 毎フレーム
Viewport.gui_get_focus_owner()
でフォーカスが当たっているかを監視し、フォーカスが失われていれば1.の配列から取り出してフォーカスを当ててあげる
これだけです。
もっといい方法があれば教えてください。
gui_focus_changed
でフォーカスが無くなった時もシグナルが飛んでくると嬉しいのですが、「なにかにフォーカスが当たった時」しかシグナルが発生しないみたいなので、2.で常時監視しています。
コード
以下のコードをFocusManager.md
とか適当な名前を付けて、プロジェクト設定
> 自動読み込み
に追加します。
extends Node
var focused_node_history = [];
func _ready():
get_viewport().gui_focus_changed.connect(_on_gui_focus_changed)
func _process(delta):
_next_focus(); # フォーカスを探索
func _on_gui_focus_changed(node):
focused_node_history.push_back(node); # フォーカスの履歴に登録
func _next_focus():
while true:
var focused_node = get_viewport().gui_get_focus_owner()
if (focused_node != null): break; # フォーカスされているので離脱
if (focused_node_history.size() == 0): break; # 履歴からフォーカス出来るものが無いので離脱(画面はフォーカスを失う)
var node = focused_node_history.pop_back(); # 前回フォーカスされていたものを取得
if (node == null): continue; # 既に削除済みなどの理由で取得できなければ次を試す
if (!node.is_visible_in_tree()): continue; # 画面に表示されていなくても次を一度試す
node.grab_focus();
初回のフォーカスは以下のように自分でフォーカスを当てる必要があります。(どこにフォーカスを当てるべきか分からないので)
func _ready():
$StartButton.grab_focus()