【Godot】GodotのButton入門:押せない原因を切り分け、Enterだけで答えられる確認ダイアログを作る

作成: 2026-09-22最終更新: 2026-10-10

Godotのボタンが押せない原因を順に切り分け、EnterとEscで答えられる確認ダイアログを作る方法を解説します。

タイトル画面やメニューのボタンは、マウスでもキーボードでも押せて、終了の確認のような2択にも Enter と Esc で答えられると、遊ぶ人が迷いません。Godot では、Button と ConfirmationDialog でこれを作ります。

ただ、pressed を繋いだボタンをクリックしても何も起きなかったり、マウスでは押せるのに Enter で別のボタンが反応したりすることがあります。マウスとキーボードで、押されるボタンの決まり方が違うからです。

この記事では、押せない原因を切り分ける手順と、Enter と Esc が最初から効く確認ダイアログの作り方を解説します。

マウスで当たったボタンと、Enterで選ばれているボタンを人形が見比べているイメージ図

この記事でわかること

  • マウスとキーボードでは、答えるボタンの決まり方が違う
  • 「押しても何も起きない」を上から順に切り分ける手順
  • pressed と button_pressed の違いと、繋ぎ方の使い分け
  • 実践:Enter と Esc だけで「終了しますか?」に答える

動作確認環境: Godot 4.7

Sponsored

マウスとキーボードで押されるボタン

クリックはカーソルの真下にある Control に届き、Enter はフォーカスを持つ Control に届きます。届け先を決める仕組みが、そもそも別々です。

マウスはカーソルの真下のボタンへ、Enterは枠の付いたボタンへ届く。2つの道は別々に動く

フォーカスは、キーボードやパッドの操作を受け取る相手のことです。ビューポート(ゲーム画面やダイアログのような、描画と入力のひと区切り)ごとに、同時に持てるのは Control 1つまでです。キーボードで選ぶと枠が付くので、どこにあるかが見えます。

この2つの道は別々に動きます。クリックにフォーカスは要りません。逆に、フォーカスが別のボタンにあるなら、どれだけカーソルを合わせても Enter はそちらを押します。

クリックしたボタンは枠が出ないままフォーカスを持ち、次のEnterもそのボタンへ届く

見落としやすいのが、クリックでもフォーカスが移ることです。4.7 の既定では、マウスでボタンに移ったフォーカスには枠が出ません。「終了」をクリックしてダイアログを閉じたあとに Enter を押すと、枠はどこにも見えないのに「終了」がもう一度押されます。

他のエンジンでいうと: フォーカスは、Unity の uGUI では EventSystem の「選択中のオブジェクト」(EventSystem.current.currentSelectedGameObject)、Unreal Engine の UMG ではキーボードフォーカス(Set Keyboard Focus)にあたります。

押せない原因の切り分け

原因を当てにいかず、上から順に切ります。上にあるものほど確かめるのが安く、確実です。

切り分けの6段。関数は呼ばれているか、エラー、真下、disabled、離した場所、フォーカスの順に確かめる

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 にすると、クリックが下まで届きます(マウス入力入門)。

透明な空のControlがボタンの手前に重なると、クリックはそこで止まる。Mouse FilterをIgnoreにするとボタンまで届く

4つ目は、disabled がオフかです。オンだと押せません。テーマによっては見た目が灰色にならないので、目で見て判断できません。

5つ目は、ボタンの上で離したかです。pressed が出るのは、既定では押した瞬間ではなく、ボタンの上で離したときです。押したまま指をずらして外で離すと、何も起きません。Action Mode を Button Press に変えてあると、押した瞬間になります。

6つ目は、フォーカスがそのボタンにあるかです。ここまで来て、マウスでは押せるのにキーボードだけ効かないなら、フォーカスが別の場所にあります。

ポーズ中だけ効かないときは、ここではなく Process Mode の話です(ポーズとProcess Mode)。

Sponsored

真下と選択中のボタンを画面に出す

3つ目と6つ目は、目で見ても分かりません。名前を画面に出してしまうのが早道です。get_viewport().gui_get_hovered_control() がカーソルの真下の Control を、gui_get_focus_owner() がフォーカスを持つ Control を返すので、毎フレーム Label に書き出します(実践の _process())。

画面左上のラベルに、真下はカーソルの乗ったQuitButton、選択は枠の付いたStartButtonと出る

見るところは2つだけです。クリックしたい瞬間の 真下 が押したいボタンの名前か。Enter を押す瞬間の 選択 が押したいボタンの名前か。

この2つが見ているのは、そのスクリプトが乗っているビューポートの中です。ダイアログは自分のビューポートを持つので、ダイアログの中のフォーカスは、このラベルには出てきません。そちらを見たいときは confirm.gui_get_focus_owner() のように、ダイアログ自身に聞きます。

ボタン以外の場所でも なし にはなりません。画面いっぱいに広げた Control も、その内側のコンテナも Control なので、そちらの名前が出ます。なし が出るのは、マウスがウィンドウの外にあるときか、ダイアログの上にあるときです。ダイアログは別のビューポートなので、外側からは何も無いように見えます。

disabled のボタンは、押せないのに 真下 には出ます。「真下はそのボタンなのに関数が呼ばれない」なら、disabled を疑う番です。この2つが切り分けの3つ目と4つ目を分けています。

pressed と button_pressed の違い

名前がよく似ていて、種類が違います。ここの取り違えが、切り分けの2つ目でいちばん多い原因です。

名前種類意味
pressedシグナル・引数なし確定した。離したとき、または選択中の Enter
button_pressedbool のプロパティ押されている状態かどうか
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を押した時点で閉じます。

開いたダイアログはOKにフォーカスがあり、EnterでconfirmedへEscでcanceledへ進む

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

Exclusiveがオンなら背後のボタンへのクリックはダイアログの外で止まり、オフなら背後まで届く

出すときは、画面の中央に開く 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() を入れるとエディタの再生が止まって、キャンセルとの違いが画面に残らないからです。

③ 動かして確かめる

起動直後はStartButtonが選択。Tab、Enterでダイアログが開いてOKにフォーカスが移り、Enterで終了しましたと出る

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() も一緒に消します。

Sponsored

うまくいかないときは

症状原因直し方
クリックしたあとに 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 は付いてくる

いま作っているメニューを、マウスに触らずに開いて閉じてみてください。途中で手が止まったのは、どのボタンでしたか。

次に読む学習パスの次の記事
UI

GodotのHPバー入門:減った分が遅れて追いつくゲージを作り、敵の頭上にも置く

この記事を読む
Godot このセクションのノート104