Godot 4 で FPS やアクションゲームを作っていると、「ゲーム開始時に毎回カーソルを隠してロックする処理」を各シーンのスクリプトに書いていませんか?
たとえば Player のスクリプトに書いたり、Main シーンのルートノードに書いたり…。最初はそれでも動きますが、
- タイトル画面ではロックしたくない
- デバッグ中だけカーソルを表示したい
- マルチシーン構成(ゲーム本編・チュートリアル・別モード…)で毎回同じ処理を書くのが面倒
といった理由で、だんだん 「どこにカーソル制御を書いたっけ?」 状態になりがちです。
Godot 標準のやり方だと、_ready() や _input() に散らばりやすく、さらに継承ベースで BaseGame.gd を作ったりすると、今度は継承ツリーが重くなります。
そこで今回は、「継承より合成(Composition)」 の考え方で、カーソル固定だけを担当する小さなコンポーネントを作ってみましょう。
シーンにポンと置くだけで、ゲーム開始時にマウスカーソルを非表示&ウィンドウ内にロックしてくれる「CursorLock」コンポーネントです。
【Godot 4】ゲーム開始と同時にマウスを拘束!「CursorLock」コンポーネント
このコンポーネントは、Node を継承した非常にシンプルなスクリプトです。
どのシーンにもアタッチでき、「カーソルの状態管理」だけを担当します。
フルコード(GDScript)
extends Node
class_name CursorLock
## カーソル固定コンポーネント
##
## シーンに追加しておくだけで、
## - ゲーム開始時にマウスカーソルを非表示
## - ウィンドウ内にロック
## を自動で行うコンポーネントです。
##
## 「プレイヤー」「カメラ」「ゲーム管理」などとは独立しているので、
## どのシーンにも簡単に再利用できます。
@export_category("CursorLock Settings")
## ゲーム開始時(_ready)に自動でロックするかどうか。
## タイトル画面などでは false にしておくと便利です。
@export var lock_on_ready: bool = true
## ウィンドウがフォーカスを失ったときにロックを解除するかどうか。
## Alt+Tab などで他のアプリに切り替えたとき、
## マウスが外に出せないと不便なので、通常は true 推奨です。
@export var unlock_on_focus_lost: bool = true
## デバッグ中はロックしたくない場合に使うフラグ。
## エディタ実行中だけロックを無効にしたいときに便利です。
@export var disable_in_editor: bool = true
## カーソルロック時に使うモード。
## - MOUSE_MODE_CAPTURED: ウィンドウ内にロックし、カーソル非表示
## - MOUSE_MODE_CONFINED_HIDDEN: ウィンドウ内に閉じ込め、非表示
## など、好みで切り替えられます。
@export var lock_mode: Input.MouseMode = Input.MOUSE_MODE_CAPTURED
## ロック解除時に戻すマウスモード。
## 通常は VISIBLE で OK です。
@export var unlock_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
## 起動時に一度だけロックをかけたかどうかのフラグ。
var _has_locked_once: bool = false
func _ready() -> void:
## エディタ実行時に無効化するオプション
if disable_in_editor and Engine.is_editor_hint():
return
if lock_on_ready:
lock_cursor()
func _notification(what: int) -> void:
## ウィンドウのフォーカスが変化したときに呼ばれる。
## NOTIFICATION_APPLICATION_FOCUS_IN / OUT は Godot 4 で利用可能。
match what:
NOTIFICATION_APPLICATION_FOCUS_OUT:
if unlock_on_focus_lost:
unlock_cursor()
NOTIFICATION_APPLICATION_FOCUS_IN:
## すでに一度ロックしていて、かつ lock_on_ready が true の場合、
## フォーカス復帰時に再度ロックするかは好みで分かれます。
## 必要であれば、以下のコメントアウトを外してください。
##
## if lock_on_ready and _has_locked_once:
## lock_cursor()
pass
## カーソルをロックして非表示にする
func lock_cursor() -> void:
_has_locked_once = true
Input.set_mouse_mode(lock_mode)
## カーソルのロックを解除して、指定モードに戻す
func unlock_cursor() -> void:
Input.set_mouse_mode(unlock_mode)
## ロック状態をトグルするユーティリティ。
## 例: ESC キーでロック/解除を切り替えたいときに使えます。
func toggle_lock() -> void:
if Input.get_mouse_mode() == lock_mode:
unlock_cursor()
else:
lock_cursor()
使い方の手順
ここからは、実際にシーンへ組み込む手順を見ていきましょう。
例として、FPS 風の 3D プレイヤーにカーソルロックを付けるパターンで説明します。
手順①: スクリプトファイルを用意する
- Godot エディタで
res://components/など、コンポーネント用フォルダを作成します。 CursorLock.gdという名前で新規スクリプトを作成し、上記のコードをコピペします。- 保存すると、
class_name CursorLockによって、インスペクタの「ノードを追加」画面から CursorLock を直接選べるようになります。
手順②: シーンにコンポーネントを追加する
例えば、以下のような 3D プレイヤーシーンがあるとします:
Player (CharacterBody3D) ├── Camera3D ├── CollisionShape3D └── CursorLock (Node)
Playerシーンを開きます。- シーンツリーで
Playerを右クリック → 「子ノードを追加」。 - 検索欄に「CursorLock」と入力し、CursorLock を追加します。
- 特に親子関係に依存しないので、
Playerの直下でも、Camera3Dの直下でも OK です。
2D の場合も同様で、例えば:
Player (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D └── CursorLock (Node)
のように差し込んでおけば、ゲーム開始時に自動でカーソルが隠れてロックされます。
手順③: インスペクタで挙動を調整する
CursorLock ノードを選択すると、インスペクタに以下のパラメータが表示されます:
- lock_on_ready:
- デフォルト:
true - シーンが読み込まれた瞬間にカーソルをロックしたい場合は
trueのまま。 - タイトル画面など「ボタンを押したらロックしたい」場合は
falseにして、後述のlock_cursor()を手動で呼び出します。
- デフォルト:
- unlock_on_focus_lost:
- デフォルト:
true - Alt+Tab などで他のアプリに切り替えたとき、ロックを解除したいなら
trueのまま。 - 常にロックし続けたい特殊な用途があれば
falseにします(あまり推奨はしません)。
- デフォルト:
- disable_in_editor:
- デフォルト:
true - エディタから実行したときにカーソルを奪われるのが嫌な場合は
trueのまま。 - 本番と同じ挙動を確認したければ
falseにします。
- デフォルト:
- lock_mode / unlock_mode:
- 通常は
lock_mode = MOUSE_MODE_CAPTURED、unlock_mode = MOUSE_MODE_VISIBLEで OK。 - 2D ゲームで「ウィンドウ内には閉じ込めたいけど、見える状態にしたい」などの特殊な要件があれば、
MOUSE_MODE_CONFINEDなどに変更します。
- 通常は
手順④: キー入力でロックを解除/再ロックする(任意)
たとえば、ESC キーでマウスロックを解除したい場合、Player 側から CursorLock を呼び出すだけで済みます。
# Player.gd(例)
extends CharacterBody3D
@onready var cursor_lock: CursorLock = $CursorLock
func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"):
cursor_lock.toggle_lock()
このように、プレイヤーのロジックとカーソル制御のロジックが完全に分離されているのがポイントですね。
メリットと応用
この「CursorLock」コンポーネントを使うことで、以下のようなメリットがあります。
- シーン構造がスッキリする
カーソル制御をPlayerやMainにベタ書きする必要がなくなり、「CursorLock というノードがある=ここでカーソルを制御している」と一目で分かります。 - 再利用性が高い
新しいシーン(例: チュートリアル、別モード、デバッグ用シーン)を作っても、CursorLockを 1 個足すだけで同じ挙動を再現できます。
継承ベースでBaseSceneを作る必要もありません。 - 設定の切り替えが簡単
タイトル画面はlock_on_ready = false、ゲーム本編はtrue、などシーンごとに設定を変えられます。
すべてのロジックを 1 つの巨大なGameManagerに押し込むより、ずっと見通しが良くなります。 - テストがしやすい
カーソル制御がうまくいかないとき、CursorLockだけを単体でシーンに置いて動作確認できます。
プレイヤーや敵 AI と切り離されているので、原因切り分けが楽になります。
改造案:タイトル画面からゲーム開始時にだけロックする
最後に、応用例として「タイトル画面ではロックしないが、ゲームスタートボタンを押した瞬間にロックしたい」場合のスニペットです。
# TitleScreen.gd(例)
extends Control
@onready var cursor_lock: CursorLock = $CursorLock
func _ready() -> void:
# タイトル画面中はロックしない
cursor_lock.unlock_cursor()
func _on_start_button_pressed() -> void:
# ゲームシーンに切り替える前にロックをかける
cursor_lock.lock_cursor()
get_tree().change_scene_to_file("res://scenes/Game.tscn")
このように、「カーソル制御」という 1 つの責務を小さなコンポーネントに切り出しておくと、
どのシーンからでも同じ API(lock_cursor() / unlock_cursor() / toggle_lock())で扱えるので、とても気持ちよく管理できます。
深いノード階層や継承ツリーにロジックを埋め込むのではなく、必要な機能を小さなコンポーネントとして合成していくスタイル、ぜひ試してみてください。
