【Godot】InputMapによるキーバインド管理

作成: 2025-06-20最終更新: 2025-12-16

Godot EngineのInputMap機能を解説。基本的な使い方から、動的なキーコンフィグ、設定の保存、ベストプラクティスまで、柔軟な入力システムを構築する方法を紹介します。

概要

ゲームをプレイするとき、多くのプレイヤーは自分の好みに合わせてキーボードやゲームパッドのボタン配置(キーバインド)を変更したいと考えるものです。Godotでは、このキーバインドを非常に簡単かつ効率的に管理するための「InputMap」という強力な機能が提供されています。

多くの開発者が直面するのが、キーバインド管理の煩雑さです。スクリプト内に if Input.is_key_pressed(KEY_W): のようなコードを散りばめてしまうと、後からキーを変更するのが困難な作業になります。

この記事では、InputMapの基本的な使い方から応用テクニックまでを解説します。

InputMapとは?入力を抽象化する仕組み

InputMapは、物理的なキーやボタン(Wキー、マウスの左クリック、ゲームパッドのAボタンなど)と、ゲーム内の具体的なアクション(「前進」「攻撃」「ジャンプ」など)とを結びつける中間層の役割を果たします。

コード内では物理キーを直接参照するのではなく、定義したアクション名を呼び出します。これにより、以下のようなメリットが生まれます。

  • 柔軟性: プレイヤーはゲーム内で自由にキーを再設定できます。あなたのコードは一切変更する必要がありません。
  • 保守性: 「攻撃キーをスペースからエンターに変更したい」といった要望も、InputMapの設定を1箇所変更するだけで完了します。
  • マルチデバイス対応: キーボード、マウス、ゲームパッドといった異なる入力デバイスを、同じアクションに簡単に割り当てることができます。

基本的な設定方法(Godotエディタ)

まずはGodotエディタ上でInputMapを設定する基本的な手順を学びましょう。

  1. プロジェクト設定を開く: メニューバーから「プロジェクト」→「プロジェクト設定」を選択します。
  2. InputMapタブに移動: 上部のタブから「InputMap」を選択します。
Input Map設定画面
  1. アクションの追加: 上部の「アクション」入力欄に、新しいアクション名(例: player_attack)を入力し、「追加」ボタンを押します。アクション名は、その名の通り「何をするか」を表す分かりやすい名前をつけましょう。

  2. イベント(キー)の割り当て: 追加されたアクションの右側にある「+」アイコンをクリックし、表示されるメニューから「キー」や「マウスボタン」などを選択します。ダイアログが表示されたら、割り当てたい物理キーを押すだけで登録が完了します。例えば、player_attackにマウスの左クリックとキーボードのXキーの両方を割り当てておく、といったことも可能です。

スクリプトからの基本的な使い方

InputMapで設定したアクションは、グローバルシングルトンである Input を通じて簡単に利用できます。

# Player.gd

const SPEED = 300.0

func _process(delta):
    # --- アクションの単発入力 ---
    # "player_jump" アクションが押された瞬間にジャンプ処理を呼び出す
    if Input.is_action_just_pressed("player_jump"):
        jump()

    # --- アクションの継続入力 ---
    # "player_attack" アクションが押されている間、魔法を詠唱する
    if Input.is_action_pressed("player_attack"):
        cast_magic()

    # --- アナログ入力(方向) ---
    # "move_left" と "move_right" から水平方向の入力を取得 (-1.0 から 1.0)
    var direction = Input.get_axis("move_left", "move_right")
    velocity.x = direction * SPEED

このコードの美しさは、具体的なキー名が一切登場しない点にあります。プレイヤーがキー設定を変更しても、このスクリプトは一行も変更する必要がありません。

実践的な応用例

基本をマスターしたら、次はより実践的で高度な使い方を見ていきましょう。

1. 8方向移動の実装 (get_vector)

上下左右の4つのアクションを組み合わせることで、簡単に2Dの8方向移動を実装できます。get_axisを2回呼ぶ代わりに get_vector を使いましょう。

# InputMapに move_left, move_right, move_up, move_down を設定しておく

func _physics_process(delta):
    # 4つのアクションから2Dベクトルを取得
    var input_vector = Input.get_vector("move_left", "move_right", "move_up", "move_down")

    # ベクトルに沿って移動
    velocity = input_vector * SPEED
    move_and_slide()

get_vectorは自動的にベクトルを正規化(長さを1に)してくれるため、斜め移動が速くなりすぎる問題も防いでくれます。

2. 動的なキーコンフィグの実装

ゲーム内でプレイヤーがキー設定を変更できる機能は、今や必須とも言えます。InputMapシングルトンを使えば、これも実装できます。

# KeyConfigScreen.gd

# キー入力待機中かどうかを管理するフラグ
var waiting_for_input: bool = false
var target_action: String = ""

# 特定のアクション(例: player_jump)のキー割り当てを変更するUIボタンの処理
func _on_change_jump_key_button_pressed():
    waiting_for_input = true
    target_action = "player_jump"
    # プレイヤーに新しいキー入力を待機していることを伝える
    print("新しいジャンプキーを入力してください...")

# キー入力を受け取るコールバック
func _input(event: InputEvent):
    # 入力待機中でなければ何もしない
    if not waiting_for_input:
        return

    # キーボード入力かつ押された瞬間かを確認
    if event is InputEventKey and event.is_pressed():
        # 既存の割り当てを一旦すべて消去
        InputMap.action_erase_events(target_action)
        # 新しいキーイベントを追加
        InputMap.action_add_event(target_action, event)
        print("ジャンプキーを %s に変更しました。" % OS.get_keycode_string(event.keycode))

        # 待機状態を解除し、このイベントを消費する
        waiting_for_input = false
        get_viewport().set_input_as_handled()

このパターンでは、フラグ変数waiting_for_inputを使って入力待機状態を管理しています。_input()関数はGodotから入力イベントが発生するたびに呼び出されるため、ボタンを押してから次のキー入力を待ち受けることができます。set_input_as_handled()を呼ぶことで、他のノードにこの入力イベントが伝播するのを防いでいます。

3. 設定の保存と読み込み

変更したキー設定は、ゲームを終了しても保持されるべきです。ConfigFileクラスを使うと、InputMapの状態を簡単にファイルに保存・復元できます。

# ConfigManager.gd

const SAVE_PATH = "user://keyconfig.cfg"

# キー設定をファイルに保存する
func save_key_config():
    var config = ConfigFile.new()
    # InputMapの全アクションをループ
    for action in InputMap.get_actions():
        # アクションに関連する全イベントを保存
        config.set_value(action, "events", InputMap.action_get_events(action))
    config.save(SAVE_PATH)

# ファイルからキー設定を読み込む
func load_key_config():
    var config = ConfigFile.new()
    # 保存ファイルが存在しなければ何もしない
    if config.load(SAVE_PATH) != OK:
        return

    # 既存のInputMapをクリア
    InputMap.load_from_project_settings() # プロジェクトデフォルトに戻す

    for action in config.get_sections():
        var events = config.get_value(action, "events")
        # 既存のイベントを消去してから新しいイベントを追加
        InputMap.action_erase_events(action)
        for event in events:
            InputMap.action_add_event(action, event)

よくある間違いとベストプラクティス

InputMapを効果的に使うためには、いくつかの注意点と推奨されるパターンがあります。

よくある間違いベストプラクティス
アクション名を文字列で直書きする
Input.is_action_pressed("player_jump")
アクション名を定数やEnumで管理する
Input.is_action_pressed(PlayerActions.JUMP)
これによりタイプミスを防ぎ、コード補完が効くようになります。
物理キーコードをスクリプトでチェックする
if event.is_action_pressed(KEY_SPACE):
常にInputMapアクションを介して入力を扱う
InputMapのメリットが失われるため、物理キーの直接参照は原則として避けます。
_process で毎回 get_axis を呼ぶ
単純な処理では問題ありませんが、入力処理が複雑化すると可読性が低下します。
_unhandled_input を活用する
GUIに吸収されなかった入力を処理するのに最適です。ゲームプレイに関する入力はここに書くと、UI操作と明確に分離できます。
デバイスごとにアクションを分割する
jump_keyboardjump_gamepad のように分ける。
同じ意味のアクションは1つにまとめる
jump アクションにキーボードのキーとゲームパッドのボタンの両方を割り当てます。

パフォーマンスと代替パターンとの比較

InputMapは非常に軽量で、パフォーマンスへの影響はほとんどありません。Inputシングルトンを介したチェックは、C++レベルで最適化されており非常に高速です。

もしInputMapを使わずに物理キーを直接チェックした場合、以下のような問題が発生します。

  • 柔軟性の欠如: キーを変更したい場合、コード内のすべての参照箇所を修正する必要があります。
  • 可読性の低下: KEY_A が「左移動」なのか「決定」なのか、コードを読むだけでは分かりにくくなります。
  • 拡張性の問題: ゲームパッドや他のデバイスに対応する際、if文が大量にネストし、複雑なコードになりがちです。

_input()_unhandled_input() の使い分けも重要です。_input() はGUI要素を含む全てのノードに入力イベントを伝播しますが、_unhandled_input() はGUI要素がイベントを処理しなかった場合にのみ呼び出されます。キャラクターの移動など、ゲームプレイ固有の入力は _unhandled_input() で処理することで、UIボタンを押したときにキャラクターが動いてしまう、といった問題を避けることができます。

まとめ

InputMapは、単なるキー割り当て機能ではありません。それは、あなたのゲームの入力処理全体を整理し、柔軟性と保守性を向上させるための設計思想です。最初は少し手間に感じるかもしれませんが、プロジェクトの初期段階でInputMapをしっかりと設計しておくことは、将来の自分への最高の投資となります。

ハードコーディングを避け、アクションという抽象レイヤーを挟む習慣をつけましょう。そうすれば、あなたのゲームはより多くのプレイヤーに受け入れられ、長期的な開発もずっと快適になるはずです。まずはプロジェクト設定でレイヤーに名前を付けることから始めてみてください。