タイトル画面やメニューのボタンは、マウスでもキーボードでも押せて、終了の確認のような2択にも Enter と Esc で答えられると、遊ぶ人が迷いません。Godot では、Button と ConfirmationDialog でこれを作ります。
ただ、pressed を繋いだボタンをクリックしても何も起きなかったり、マウスでは押せるのに Enter で別のボタンが反応したりすることがあります。マウスとキーボードで、押されるボタンの決まり方が違うからです。
この記事では、押せない原因を切り分ける手順と、Enter と Esc が最初から効く確認ダイアログの作り方を解説します。
この記事でわかること
- マウスとキーボードでは、答えるボタンの決まり方が違う
- 「押しても何も起きない」を上から順に切り分ける手順
pressedとbutton_pressedの違いと、繋ぎ方の使い分け- 実践:Enter と Esc だけで「終了しますか?」に答える
動作確認環境: Godot 4.7
マウスとキーボードで押されるボタン
クリックはカーソルの真下にある Control に届き、Enter はフォーカスを持つ Control に届きます。届け先を決める仕組みが、そもそも別々です。

フォーカスは、キーボードやパッドの操作を受け取る相手のことです。ビューポート(ゲーム画面やダイアログのような、描画と入力のひと区切り)ごとに、同時に持てるのは Control 1つまでです。キーボードで選ぶと枠が付くので、どこにあるかが見えます。
この2つの道は別々に動きます。クリックにフォーカスは要りません。逆に、フォーカスが別のボ タンにあるなら、どれだけカーソルを合わせても Enter はそちらを押します。

見落としやすいのが、クリックでもフォーカスが移ることです。4.7 の既定では、マウスでボタンに移ったフォーカスには枠が出ません。「終了」をクリックしてダイアログを閉じたあとに Enter を押すと、枠はどこにも見えないのに「終了」がもう一度押されます。
他のエンジンでいうと: フォーカスは、Unity の uGUI では EventSystem の「選択中のオブジェクト」(
EventSystem.current.currentSelectedGameObject)、Unreal Engine の UMG ではキーボードフォーカス(Set Keyboard Focus)にあたります。
押せない原因の切り分け
原因を当てにいかず、上から順に切ります。上にあるものほど確かめるのが安く、確実です。

1つ目は、関数が呼ばれているかです。受け取る関数の1行目に print("呼ばれた") を置きます。これが出るなら、ボタンもシグナルも生きています。あとの処理か、結果が画面に出ていないほうが原因です。
出ないときは、まず接続そのものを疑います。繋いだつもりの行が別の関数を指していたり、そもそも繋がっていなかったりします。エディタ側の接続は、そのボタンを選ぶと Signals ドックの一覧で確かめられます。
2つ目は、エラーで落ちていないかです。button_pressed.connect(...) と書いていたり、Godot 3 の connect("pressed", self, "_on_pressed") を写していたりすると、実行する前か、実行した瞬間にエラーになります。赤い行を読めば分かります。
3つ目は、真下がそのボタンかです。全画面の ColorRect や、空の Control が上に乗っていると、クリックはそちらへ行きます。Control の Mouse Filter は既定が Stop なので、透明でも止めます。乗っているものの Mouse Filter を Ignore にすると、クリックが下まで届きます(マウス入力入門)。

4つ目は、disabled がオフかです。オンだと押せません。テーマによっては見た目が灰色にならないので、目で見て判断できません。
5つ目は、ボタンの上で離したかです。pressed が出るのは、既定では押した瞬間ではなく、ボタンの上で離したときです。押したまま指をずらして外 で離すと、何も起きません。Action Mode を Button Press に変えてあると、押した瞬間になります。
6つ目は、フォーカスがそのボタンにあるかです。ここまで来て、マウスでは押せるのにキーボードだけ効かないなら、フォーカスが別の場所にあります。
ポーズ中だけ効かないときは、ここではなく Process Mode の話です(ポーズとProcess Mode)。
真下と選択中のボタンを画面に出す
3つ目と6つ目は、目で見ても分かりません。名前を画面に出してしまうのが早道です。get_viewport().gui_get_hovered_control() がカーソルの真下の Control を、gui_get_focus_owner() がフォーカスを持つ Control を返すので、毎フレーム Label に書き出します(実践の _process())。

見るところは2つだけです。クリックしたい瞬間の 真下 が押したいボタンの名前か。Enter を押す瞬間の 選択 が押したいボタンの名前か。
この2つが見ているのは、そのスクリプトが乗っ ているビューポートの中です。ダイアログは自分のビューポートを持つので、ダイアログの中のフォーカスは、このラベルには出てきません。そちらを見たいときは confirm.gui_get_focus_owner() のように、ダイアログ自身に聞きます。
ボタン以外の場所でも なし にはなりません。画面いっぱいに広げた Control も、その内側のコンテナも Control なので、そちらの名前が出ます。なし が出るのは、マウスがウィンドウの外にあるときか、ダイアログの上にあるときです。ダイアログは別のビューポートなので、外側からは何も無いように見えます。
disabled のボタンは、押せないのに 真下 には出ます。「真下はそのボタンなのに関数が呼ばれない」なら、disabled を疑う番です。この2つが切り分けの3つ目と4つ目を分けています。
pressed と button_pressed の違い
名前がよく似ていて、種類が違います。ここの取り違えが、切り分けの2つ目でいちばん多い原因です。
| 名前 | 種類 | 意味 |
|---|---|---|
pressed | シグナル・引数なし | 確定した。離したとき、または選択中の Enter |
button_pressed | bool のプロパティ | 押されている状態かどうか |
button_down | シグナル・引数なし | 押した瞬間。確定ではない |
toggled | シグナル・bool ひとつ | toggle_mode(押すたびにオン・オフが残る設定)のボタンで、押された状態が切り替わった |
button_pressed は bool なので、connect() を呼べません。繋いだつもりで書くと実行時に落ちます。
繋ぎ方は2通りあります。
画面に最初から置いてあるボタンは、エディタでボタンを選び、Signals ドックから pressed() を繋ぐのが手早いです。この接続はシーンファイルに保存されます。スクリプトの中を connect で検索しても出てこないので、「繋いでいない」と勘違いしやすい場所です。
実行中に作るボタンは、生成したその場でコードから繋ぎます。
start_button.pressed.connect(_on_start_pressed)
第2引数に self、第3引数にメソッド名の文字列、という書き方は Godot 3 のものです。
同じシグナルを同じ関数へ二重に繋ぐと、2回走るのではなくエラーになります。2回走るのは、エディタとコードで別々の関数を繋いで、その両方が同じ処理を呼んでいるときです。
ConfirmationDialog(はい/いいえの確認)
「終了しますか?」のような2択は、ConfirmationDialog がそのまま持っています。AcceptDialog は OK だけのお知らせで、2択には使いません。
- 本文は
dialog_text。ウィンドウ上部の見出しはtitle - ボタンの文言は
ok_button_textとcancel_button_text - OK で
confirmed、キャンセル・Esc・閉じる操作でcanceled。どちらも引数なし - OK とキャンセルの左右は OS の慣習に合わせて変わる。Windows では OK が左
受け取るのはこの2つだけです。OKボタンの pressed は繋ぎません。confirmed と両方に同じ処理を置くと、1回の確定で2回走ります。閉じる処理も書きません。dialog_hide_on_ok が既定でオンなので、OKを押した時点で閉じます。

Enter と Esc のためのコードも書きません。開いた時点で OK ボタンにフォーカスが移るので、そのまま Enter が confirmed になります。Esc は canceled です。UIが使わなかった入力を受け取る _unhandled_input() を、ここで足す必要はありません。

出すときは、画面の中央に開く popup_centered() を呼びます。ダイアログの Exclusive がオンなら、出しているあいだ背後のボタンは押せません。オフだと、ダイアログの後ろの「はじめる」が押せてしまうので、インスペクタで確かめておきます。
自作の Panel で2択を作ると、フォーカスの初期位置、Enter、Esc、背後を塞ぐ処理を、全部自分で書くことになります。組み込みに任せると、書かずに済むのがこの4つです。
実践:「終了」に確認を出す
タイトル画面、設定のリセット、セーブデータの削除——「押す前に一度止める」形はどれも同じです。ボタンを2つとダイアログを1つだけ置き、マウスに触らずに答えられるところまで作ります。
① 置くもの
新しいシーンを1つ作ります。
- ルートに Control を置いて Title。Anchors Preset を Full Rect にして、
title.gdを付ける - Title の子に VBoxContainer を置いて Menu。Anchors Preset を Center
- Menu の子に Button を2つ、StartButton(テキストは「はじめる」)と QuitButton(「終了」)
- Title の子に Label を2つ、DebugLabel(位置
(16, 16))と StatusLabel(位置(16, 480)) - Title の子に ConfirmationDialog を置いて Confirm。Exclusive がオンになっていることを確かめる
- StartButton・QuitButton・DebugLabel・StatusLabel・Confirm は、右クリックメニューの「Access as Unique Name」をオンにする(コードから
%StartButtonの形で取れるようになる)
Confirm はシーンに置いたままにし、表示のたびに作り直さず同じノードを何度も出します。Dialog Text はコードから入れるので、インスペクタでは空のままでかまいません。
② 組み立て
# title.gd
extends Control
@onready var status: Label = %StatusLabel
@onready var debug: Label = %DebugLabel
@onready var confirm: ConfirmationDialog = %Confirm
func _ready() -> void:
%StartButton.pressed.connect(_on_start_pressed)
%QuitButton.pressed.connect(_on_quit_pressed)
confirm.confirmed.connect(_on_confirmed)
confirm.canceled.connect(_on_canceled)
confirm.dialog_text = "終了しますか?"
%StartButton.grab_focus() # 最初のフォーカスをここに置く
func _process(_delta: float) -> void:
var hovered := get_viewport().gui_get_hovered_control()
var focused := get_viewport().gui_get_focus_owner()
debug.text = "真下: %s / 選択: %s" % [
hovered.name if hovered else "なし",
focused.name if focused else "なし"
]
func _on_start_pressed() -> void:
_log("はじめました")
func _on_quit_pressed() -> void:
confirm.popup_centered()
func _on_confirmed() -> void:
_log("終了しました")
func _on_canceled() -> void:
_log("キャンセルしました")
func _log(text: String) -> void:
status.text += text + "\n"
ダイアログのために書いたのは、popup_centered() の1行と、受け取る関数が2つだけです。Enter も Esc も、閉じる処理も出てきません。
終了しました はラベルに書くだけで、ゲームは終わりません。get_tree().quit() を入れるとエディタの再生が止まって、キャンセルとの違いが画面に残らないからです。
③ 動かして確かめる

F6 で再生すると、「はじめる」に枠が付いた状態で始まり、上のラベルは 選択: StartButton です。マウスに触らずに Tab を押すと 選択: QuitButton に変わり、そこで Enter を押すとダイアログが開きます。起動直後の 選択 が なし なら、grab_focus() の行が抜けていて、最初のフォーカスがどこにもありません。
開いた瞬間にフォーカスは OK へ移りますが、上のラベルの 選択 は QuitButton のままです。ラベルが見ているのはダイアログの外側のビューポートなので、それで合っています。そのまま Enter を押すと 終了しました が1行増え、Esc なら キャンセルしました が増えます。
もう一度ダイアログを開き、タイトルの帯をつかんで横へ少しずらすと、後ろに重なっていた「はじめる」が見えます。そこをクリックしても行は増えず、ダイアログを閉じてから同じ場所をクリックすると はじめました が増えます。開いたままでも増えてしまうなら、Confirm の Exclusive がオフになっています。
ポイントは2つです。
- 切り分けは「関数が呼ばれたか」から:
printが出るかどうかで、故障が前半か後半かに割れます。そのあと真下と選択の名前を見れば、残りはほぼ2手で決まります - 2択は ConfirmationDialog に渡す: フォーカスの初期位置、Enter、Esc、背後を塞ぐ処理は、こちらが書かなくても付いてきます
切り分け用の DebugLabel は、原因が分かったら Visible をオフにしておきます。ノードごと消すなら、@onready var debug の行と _process() も一緒に消します。
うまくいかないときは
| 症状 | 原因 | 直し方 |
|---|---|---|
| クリックしたあとに Enter を押すと、枠が見えないのに同じボタンがまた押される | クリックでもフォーカスは移り、4.7 の既定ではそのとき枠を出さない | プロジェクト設定の GUI > Common > Show Focus State On Pointer Event を Always にすると、クリックでも枠が出る |
| ゲーム画面を操作するスクリプトを足したら、ボタンがクリ ックに反応しなくなった | _input() はボタンより先に呼ばれ、set_input_as_handled()(処理済みの印)を付けるとボタンまで届かない | ゲーム側のクリックは _unhandled_input() で受ける |
| ポーズ中に出した確認ダイアログだけ、クリックも Enter も効かない | ツリーが止まっていて、ダイアログの Process Mode が Inherit のまま | ダイアログを Always にする(When Paused だと、ポーズしていないときに効かない) |
| パッドの決定ボタンでは答えられるのに、キャンセルのボタンで閉じない | ダイアログを閉じる操作は、Input Map の ui_close_dialog というアクションで聞いている | Input Map で Show Built-in Actions(最初から入っている ui_ のアクションを表示するスイッチ)をオンにし、ui_close_dialog にパッドのボタンを足す |
おまけ:先に知っておくと良いこと
- 押した状態が残るボタン:
toggle_modeをオンにすると、クリックのたびにオンとオフが切り替わって残ります。状態はtoggled(toggled_on)の引数か、プロパティbutton_pressedで見ます。難易度のように1つだけ選ばせたいなら、同じ ButtonGroup リソースを全部のボタンのbutton_groupに割り当て、どのボタンもtoggle_modeをオンにします - ダイアログは画面の中に出る: プロジェクト設定の
display/window/subwindows/embed_subwindowsが既定でオンなので、ゲーム画面の中に埋め込まれて出ます。オフ にすると、対応している環境では別のOSウィンドウになります - パッドで操作するなら:
ui_acceptにパッドのボタンを足す手順と、スティックでフォーカスを動かす話は ゲームパッド入門 にあります - フォーカスの枠を見やすくする: 枠の色や太さは Theme の
focusの担当です(Theme入門)。枠が見えないと、キーボード操作はいきなり難しくなります
まとめ
- 道は2つ: マウスは真下のボタン、キーボードはフォーカスを持つボタンが答える。クリックでもフォーカスは移るが、枠は出ない
- 切り分けは上から: 関数が呼ばれたか → エラー → 真下 →
disabled→ 離した場所 → フォーカスの順に切る - 名前を画面に出す:
gui_get_hovered_control()とgui_get_focus_owner()で、真下と選択が分かる button_pressedは繋げない:pressedはシグナル、button_pressedは bool のプロパティ- 2択はダイアログに任せる: 受け取るのは
confirmedとcanceledだけ。Enter と Esc は付いてくる
いま作っているメニューを、マウスに触らずに開いて閉じてみてください。途中で手が止まったのは、どのボタンでしたか。