概要
ゲームをプレイするとき、多くのプレイヤーは自分の好みに合わせてキーボードやゲームパッドのボタン配置(キーバインド)を変更したいと考えるものです。Godotでは、このキーバインドを非常に簡単かつ効率的に管理するための「InputMap」という強力な機能が提供されています。
多くの開発者が直面するのが、キーバインド管理の煩雑さです。スクリプト内に if Input.is_key_pressed(KEY_W): のようなコードを散りばめてしまうと、後からキーを変更するのが困難な作業になります。
この記事では、InputMapの基本的な使い方から応用テクニックまでを解説します。
InputMapとは?入力を抽象化する仕組み
InputMapは、物理的なキーやボタン(Wキー、マウスの左クリック、ゲームパッドのAボタンなど)と、ゲーム内の具体的なアクション(「前進」「攻撃」「ジャンプ」など)とを結びつける中間層の役割を果たします。
コード内では物理キーを直接参照するのではなく、定義したアクション名を呼び出します。これにより、以下のようなメリットが生まれます。
- 柔軟性: プレイヤーはゲーム内で自由にキーを再設定できます。あなたのコードは一切変更する必要がありません。
- 保守性: 「攻撃キーをスペースからエンターに変更したい」といった要望も、InputMapの設定を1箇所変更するだけで完了します。
- マルチデバイス対応: キーボード、マウス、ゲームパッドといった異なる入力デバイスを、同じアクションに簡単に割り当てることができます。
基本的な設定方法(Godotエディタ)
まずはGodotエディタ上でInputMapを設定する基本的な手順を学びましょう。
- プロジェクト設定を開く: メニューバーから「プロジェクト」→「プロジェクト設定」を選択します。
- InputMapタブに移動: 上部のタブから「InputMap」を選択します。

-
アクションの追加: 上部の「アクション」入力欄に、新しいアクション名(例:
player_attack)を入力し、「追加」ボタンを押します。アクション名は、その名の通り「何をするか」を表す分かりやすい名前をつけましょう。 -
イベント(キー)の割り当て: 追加されたアクションの右側にある「+」アイコンをクリックし、表示されるメニューから「キー」や「マウスボタン」などを選択します。ダイアログが表示されたら、割り当てたい物理キーを押すだけで登録が完了します。例えば、
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_keyboard と jump_gamepad のように分ける。 | 同じ意味のアクションは1つにまとめるjump アクションにキーボードのキーとゲームパッドのボタンの両方を割り当てます。 |
パフォーマンスと代替パターンとの比較
InputMapは非常に軽量で、パフォーマンスへの影響はほとんどありません。Inputシングルトンを介したチェックは、C++レベルで最適化されており非常に高速です。
もしInputMapを使わずに物理キーを直接チェックした場合、以下のような問題が発生します。
- 柔軟性の欠如: キーを変更したい場合、コード内のすべての参照箇所を修正する必要があります。
- 可読性の低下:
KEY_Aが「左移動」なのか「決定」なのか、コードを読むだけでは分かりにくくなります。 - 拡張性の問題: ゲームパッドや他のデバイスに対応する際、
if文が大量にネストし、複雑なコードになりがちです。
_input() と _unhandled_input() の使い分けも重要です。_input() はGUI要素を含む全てのノードに入力イベントを伝播しますが、_unhandled_input() はGUI要素がイベントを処理しなかった場合にのみ呼び出されます。キャラクターの移動など、ゲームプレイ固有の入力は _unhandled_input() で処理することで、UIボタンを押したときにキャラクターが動いてしまう、といった問題を避けることができます。
まとめ
InputMapは、単なるキー割り当て機能ではありません。それは、あなたのゲームの入力処理全体を整理し、柔軟性と保守性を向上させるための設計思想です。最初は少し手間に感じるかもしれませんが、プロジェクトの初期段階でInputMapをしっかりと設計しておくことは、将来の自分への最高の投資となります。
ハードコーディングを避け、アクションという抽象レイヤーを挟む習慣をつけましょう。そうすれば、あなたのゲームはより多くのプレイヤーに受け入れられ、長期的な開発もずっと快適になるはずです。まずはプロジェクト設定でレイヤーに名前を付けることから始めてみてください。