Godot 4 でメニュー画面やデバッグパネルを作るとき、ついこんな実装をしがちですよね。

  • プレイヤーのスクリプトの中で if Input.is_action_just_pressed("ui_cancel"): と書いて、直接メニューの visible をいじる
  • メニュー用のベースシーンを継承して、「ポーズメニュー」「オプションメニュー」などを全部サブクラスで作る
  • シーンごとに「このメニューはどのキーで開くんだっけ?」というロジックがバラバラに散らばる

これ、規模が小さいうちは動くんですが、

  • 入力処理があちこちに分散する
  • メニューの表示ロジックを変えたいときに、複数スクリプトを修正しないといけない
  • 「このシーンだけ別のキーで開きたい」などのカスタマイズがしづらい

といった地味なストレスにつながります。
「メニューを表示/非表示する」というのは、立派に 1 つの「機能」なので、本当はコンポーネントとして切り出したいところですね。

そこで今回は、親ノード(メニュー画面など)の表示/非表示を、特定のキー入力でトグルするだけに特化したコンポーネント VisibilityToggle を用意しました。
親にペタッとアタッチするだけで、「このメニューは F1 で開閉」「このデバッグパネルは F3 で開閉」といったことが簡単に実現できます。

【Godot 4】ワンキーでメニュー開閉!「VisibilityToggle」コンポーネント

まずはフルコードから載せます。
このまま res://components/visibility_toggle.gd などに保存して使えます。


extends Node
class_name VisibilityToggle
## 親ノード(メニューなど)の visible を
## 指定した入力アクションでトグルするコンポーネント。
##
## 使い方:
## - メニューシーンの直下にこのノードを追加
## - toggle_action に InputMap 上のアクション名を設定
## - 親ノードの visible がアクション入力ごとに切り替わる

@export_group("基本設定")
## トグルに使う InputMap のアクション名。
## 例: "ui_cancel", "toggle_menu", "debug_panel_toggle" など。
@export var toggle_action: StringName = &"ui_cancel"

## ゲーム開始時に親ノードを非表示にするかどうか。
## タイトル画面のポーズメニューなど、
## 「最初は閉じておきたい」UIで便利です。
@export var hide_on_start: bool = true

## 入力を受け付ける条件。
## false にすると、一時的にこのコンポーネントを無効化できます。
@export var enabled: bool = true

@export_group("表示挙動")
## トグルではなく「押している間だけ表示」にするモード。
## - false: 押すたびに ON/OFF 切り替え(トグル)
## - true: 押している間だけ表示、離したら非表示
@export var hold_to_show: bool = false

## トグル時に親ノードのプロセスを自動で止めるかどうか。
## UI のみの場合は false で OK。アニメ付きメニューなどで
## 非表示中も処理させたい場合は true にします。
@export var pause_processing_when_hidden: bool = false

## デバッグ用: トグルのたびにログを出すかどうか。
@export var print_debug_log: bool = false


func _ready() -> void:
	# このコンポーネントは「親ノードの表示状態」を管理するので、
	# 親がいない場合は意味がありません。
	if not get_parent():
		push_warning("VisibilityToggle: 親ノードが存在しません。親の visible を制御できません。")
		return

	if hide_on_start:
		_set_parent_visible(false)


func _process(delta: float) -> void:
	if not enabled:
		return

	if not toggle_action:
		return  # アクション名未設定

	# 入力チェック
	if hold_to_show:
		# 押している間だけ表示するモード。
		# 押している間は常に表示、離した瞬間に非表示にする。
		if Input.is_action_pressed(toggle_action):
			_set_parent_visible(true)
		elif Input.is_action_just_released(toggle_action):
			_set_parent_visible(false)
	else:
		# 通常のトグルモード。
		if Input.is_action_just_pressed(toggle_action):
			_toggle_parent_visible()


func _toggle_parent_visible() -> void:
	var parent := get_parent()
	if not parent:
		return

	var new_visible := not parent.visible
	_set_parent_visible(new_visible)


func _set_parent_visible(visible: bool) -> void:
	var parent := get_parent()
	if not parent:
		return

	parent.visible = visible

	# 親ノードの処理を止めたり再開したりするオプション。
	if pause_processing_when_hidden:
		parent.process_mode = Node.PROCESS_MODE_INHERIT if visible else Node.PROCESS_MODE_DISABLED

	if print_debug_log:
		print("VisibilityToggle: %s visible = %s" % [parent.name, str(visible)])


## 外部から明示的に ON/OFF したいとき用のヘルパー。
func show_parent() -> void:
	_set_parent_visible(true)


func hide_parent() -> void:
	_set_parent_visible(false)


func toggle() -> void:
	_toggle_parent_visible()

使い方の手順

ここでは、典型的な「ポーズメニュー」と「デバッグパネル」の 2 例で使い方を見ていきましょう。

手順①: InputMap にトグル用アクションを登録する

  1. Godot エディタ上部メニューから Project > Project Settings… を開く
  2. Input Map タブを選択
  3. 右下の Add… から、例えば toggle_pause_menu というアクションを追加
  4. toggle_pause_menu に対して、EscF1 など好みのキーを割り当てる

同様に、デバッグパネル用に toggle_debug_panel などのアクションも作っておくと便利です。

手順②: コンポーネントスクリプトを用意する

  1. 上記の GDScript を res://components/visibility_toggle.gd に保存
  2. Godot エディタでリロードすると、Node を追加 ダイアログに VisibilityToggle が出てくるようになります

手順③: メニューシーンに VisibilityToggle をアタッチする

例として、2D のポーズメニューシーンを考えます。

PauseMenu (CanvasLayer)
 ├── Panel (Panel)
 │    ├── Label
 │    └── Button
 └── VisibilityToggle (Node)
  1. PauseMenu シーンを開く
  2. ルート(ここでは CanvasLayer)の子として + ボタンから VisibilityToggle を追加
  3. VisibilityToggle ノードを選択し、インスペクタで以下のように設定:
    • toggle_action: "toggle_pause_menu"
    • hide_on_start: true(ゲーム開始時は閉じておきたい場合)
    • hold_to_show: false(押すたびに開閉)
    • pause_processing_when_hidden: UI だけなら false で OK

これで、ゲーム中に toggle_pause_menu のキー(例: Esc)を押すたびに、PauseMenuvisible が ON/OFF されます。

手順④: 別の用途にもどんどん再利用する

同じコンポーネントを、例えばデバッグ用の情報パネルにも使えます。

DebugPanel (Control)
 ├── VBoxContainer
 │    ├── Label (FPS表示)
 │    └── Label (プレイヤー座標など)
 └── VisibilityToggle (Node)
  • toggle_action: "toggle_debug_panel"(F3 などを割り当て)
  • hide_on_start: true
  • hold_to_show: true にすると「押している間だけデバッグ情報を表示」みたいな挙動も簡単にできます

プレイヤーや敵など、ゲームオブジェクトにも同じように差し込めます。

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── VisibilityToggle (Node)

例えば、hold_to_show = true にして toggle_action = "ui_focus" 的なアクションにすれば、「キーを押している間だけプレイヤーを点滅させる」などのギミックのベースにもなります(点滅処理は別コンポーネントにするとさらに綺麗ですね)。

メリットと応用

この VisibilityToggle コンポーネントを導入するメリットはかなりシンプルですが、積み重ねると効いてきます。

  • 表示ロジックを UI 本体から切り離せる
    メニューのスクリプトは「ボタンが押されたら何をするか」だけに集中させて、
    「どのキーで表示/非表示を切り替えるか」という責務は VisibilityToggle に丸投げできます。
  • 継承ではなく合成でカスタマイズ
    「ポーズメニューの開き方を変えたい」= VisibilityToggle の設定を変えるだけ。
    メニュークラスを継承してサブクラスを増やす必要がありません。
  • シーン構造がフラットで見通しが良い
    深いノード階層にロジックを埋め込むのではなく、「機能ごとの小さな Node」が横に並ぶ構造になります。
    後から見返したときに「このメニューは何で開くんだっけ?」が一目でわかります。
  • レベルデザイン時の再利用がラク
    デバッグパネル、ステージごとのヘルプウィンドウ、一時的なチュートリアルポップアップなど、
    「表示/非表示をキーで切り替えたいもの」に片っ端からコピペで使い回せます。

応用として、例えば「トグル時にサウンドを鳴らす」「フェードイン/アウトのアニメを再生する」なども簡単に足せます。
以下は、トグル時に親ノード内の AnimationPlayer を探して、"show"/"hide" アニメを再生する改造案です。


func _set_parent_visible(visible: bool) -> void:
	var parent := get_parent()
	if not parent:
		return

	parent.visible = visible

	if pause_processing_when_hidden:
		parent.process_mode = Node.PROCESS_MODE_INHERIT if visible else Node.PROCESS_MODE_DISABLED

	# ★ 追加: アニメーションを再生する
	var anim_player := parent.get_node_or_null("AnimationPlayer")
	if anim_player and anim_player is AnimationPlayer:
		var anim_name := visible ? "show" : "hide"
		if anim_player.has_animation(anim_name):
			anim_player.play(anim_name)

	if print_debug_log:
		print("VisibilityToggle: %s visible = %s" % [parent.name, str(visible)])

こうやって「表示切り替え」「アニメーション」「サウンド」などを、それぞれ小さなコンポーネントに分解していくと、Godot プロジェクト全体の見通しもかなり良くなります。
継承ベースの巨大な UI クラスから卒業して、合成ベースのコンポーネント設計にシフトしていきましょう。