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 にトグル用アクションを登録する
- Godot エディタ上部メニューから Project > Project Settings… を開く
- Input Map タブを選択
- 右下の Add… から、例えば
toggle_pause_menuというアクションを追加 toggle_pause_menuに対して、EscやF1など好みのキーを割り当てる
同様に、デバッグパネル用に toggle_debug_panel などのアクションも作っておくと便利です。
手順②: コンポーネントスクリプトを用意する
- 上記の GDScript を
res://components/visibility_toggle.gdに保存 - Godot エディタでリロードすると、Node を追加 ダイアログに
VisibilityToggleが出てくるようになります
手順③: メニューシーンに VisibilityToggle をアタッチする
例として、2D のポーズメニューシーンを考えます。
PauseMenu (CanvasLayer) ├── Panel (Panel) │ ├── Label │ └── Button └── VisibilityToggle (Node)
PauseMenuシーンを開く- ルート(ここでは
CanvasLayer)の子として + ボタンからVisibilityToggleを追加 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)を押すたびに、PauseMenu の visible が ON/OFF されます。
手順④: 別の用途にもどんどん再利用する
同じコンポーネントを、例えばデバッグ用の情報パネルにも使えます。
DebugPanel (Control) ├── VBoxContainer │ ├── Label (FPS表示) │ └── Label (プレイヤー座標など) └── VisibilityToggle (Node)
toggle_action:"toggle_debug_panel"(F3 などを割り当て)hide_on_start:truehold_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 クラスから卒業して、合成ベースのコンポーネント設計にシフトしていきましょう。
