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 プレイヤーにカーソルロックを付けるパターンで説明します。

手順①: スクリプトファイルを用意する

  1. Godot エディタで res://components/ など、コンポーネント用フォルダを作成します。
  2. CursorLock.gd という名前で新規スクリプトを作成し、上記のコードをコピペします。
  3. 保存すると、class_name CursorLock によって、インスペクタの「ノードを追加」画面から CursorLock を直接選べるようになります。

手順②: シーンにコンポーネントを追加する

例えば、以下のような 3D プレイヤーシーンがあるとします:

Player (CharacterBody3D)
 ├── Camera3D
 ├── CollisionShape3D
 └── CursorLock (Node)
  1. Player シーンを開きます。
  2. シーンツリーで Player を右クリック → 「子ノードを追加」。
  3. 検索欄に「CursorLock」と入力し、CursorLock を追加します。
  4. 特に親子関係に依存しないので、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_CAPTUREDunlock_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」コンポーネントを使うことで、以下のようなメリットがあります。

  • シーン構造がスッキリする
    カーソル制御を PlayerMain にベタ書きする必要がなくなり、「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())で扱えるので、とても気持ちよく管理できます。

深いノード階層や継承ツリーにロジックを埋め込むのではなく、必要な機能を小さなコンポーネントとして合成していくスタイル、ぜひ試してみてください。