Godot 4でアクションゲームを作っていると、必ずキーバインドの問題にぶつかります。最初はInput.is_key_pressed(KEY_SPACE)のようなハードコードで済ませますが、そのままでは以下の問題が出てきます。
- キーボードとゲームパッドに対応したい
- プレイヤーに設定を変更させたい
- 設定を保存して次回起動時に読み込みたい
- 同じアクションに複数のキーを割り当てたい
Godot 4のInputMapシステムを使えば、これらすべてを綺麗に解決できます。この記事では、実際に動くコードで、InputMapの使い方を一通り紹介します。
なぜInputMapを使うのか
一般的な作り方を比較してみましょう。
ハードコード版:
func _physics_process(delta: float) -> void:
if Input.is_key_pressed(KEY_SPACE):
jump()
if Input.is_key_pressed(KEY_Z) or Input.is_joy_button_pressed(0, JOY_BUTTON_X):
attack()
この書き方だと、新しいキーを追加するたびにコードを修正しないといけません。ゲームパッドの種類ごとにボタン番号が違うので、対応が増えるほど条件式が複雑化します。
InputMap版:
func _physics_process(delta: float) -> void:
if Input.is_action_pressed("jump"):
jump()
if Input.is_action_pressed("attack"):
attack()
jumpとattackはアクション名で、実際にどのキーやボタンが対応するかはプロジェクト設定またはコードから定義できます。
プロジェクト設定でのInputMap定義
Project > Project Settings > Input Map タブから、アクション名を追加してキーバインドを設定します。標準的なプラットフォーマーの場合、move_left、move_right、jump、attack、interact、menu、pauseくらいを定義しておくと便利です。
入力の検知方法
1. ボタンが押された瞬間を検知
ジャンプや攻撃のように、押された瞬間に1回だけ反応したい場合:
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("jump"):
jump()
if event.is_action_pressed("attack"):
attack()
_unhandled_inputを使うことで、UI要素が先に入力を消費した場合はスキップされます。
2. ボタンが押されている間を検知
移動のように、押している間ずっと処理したい場合:
func _physics_process(delta: float) -> void:
var direction = Input.get_axis("move_left", "move_right")
velocity.x = direction * SPEED
2D移動ならInput.get_vectorを使うとさらに簡単です:
var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity = input_dir * SPEED
3. ボタンが離された瞬間を検知
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_released("charge"):
release_charge_attack()
コードからInputMapを変更する
プレイヤーに設定を変更させる場合、コードからアクションの内容を書き換えます。
# Hキーを「heal」アクションに追加
var event = InputEventKey.new()
event.keycode = KEY_H
InputMap.action_add_event("heal", event)
# 既存のキーバインドを置き換え
func rebind_action(action: String, new_event: InputEvent) -> void:
InputMap.action_erase_events(action)
InputMap.action_add_event(action, new_event)
キーリバインド画面の実装
プレイヤーに設定を変更させるUIを作る場合:
extends Button
@export var action_name: String
var waiting_for_input: bool = false
func _ready() -> void:
update_label()
pressed.connect(_on_pressed)
func update_label() -> void:
var events = InputMap.action_get_events(action_name)
text = events[0].as_text() if events.size() > 0 else "未設定"
func _on_pressed() -> void:
waiting_for_input = true
text = "キーを押してください..."
func _input(event: InputEvent) -> void:
if not waiting_for_input:
return
if event is InputEventKey and event.pressed:
InputMap.action_erase_events(action_name)
InputMap.action_add_event(action_name, event)
waiting_for_input = false
update_label()
get_viewport().set_input_as_handled()
as_text()はキーを人間が読める形式(SpaceやCtrl+Aなど)に変換してくれるので便利です。
設定の保存と読み込み
InputMapの変更はメモリ上だけなので、ConfigFileで保存します。
const CONFIG_PATH = "user://input_config.cfg"
func save_input_map() -> void:
var config = ConfigFile.new()
for action in InputMap.get_actions():
if action.begins_with("ui_"):
continue
var events = InputMap.action_get_events(action)
var event_data: Array = []
for event in events:
if event is InputEventKey:
event_data.append({"type": "key", "keycode": event.keycode})
elif event is InputEventJoypadButton:
event_data.append({"type": "joy", "button": event.button_index})
config.set_value("input", action, event_data)
config.save(CONFIG_PATH)
ui_で始まるアクションはGodotが自動で管理するものなので、保存対象から除外しています。
ゲームパッド対応
プロジェクト設定のInput Mapで、jumpアクションにInputEventKey (Space)とInputEventJoypadButton (Button 0 = A)を両方追加すると、どちらからでも発火します。
デッドゾーンの設定も重要です。アナログスティックで移動する場合、Input Map設定で Deadzone を 0.2 程度に設定しておくと誤入力が減ります。
よくある落とし穴
-
_inputはすべての入力を拾いますが、_unhandled_inputはUIが処理しなかった入力だけを拾います -
ui_プレフィックスのアクションを上書きすると、デフォルトUI操作が動かなくなります - カスタムアクションは
player_などのプレフィックスを付けると安全です
AIエージェントでInputMapコードを生成する
InputMapのコードはボイラープレートが多いのが悩みどころです。リバインド画面を作るときに毎回似たコードを書くことになります。
私はZivaというGodot専用のAIエージェントを使っています。「キーボードとゲームパッド両対応のリバインド画面を作って、設定を自動保存するようにして」と指示すると、ボタンとSignal、設定ファイルの読み書きまで含めた一式を生成してくれます。私自身がZivaを作っている側なのでバイアスはありますが、Godot 4固有のAPI(InputMapの正しいメソッド名など)をちゃんと使ってくれるのは便利です。通常のChatGPTはたまにGodot 3のAPIを混ぜて答えてきます。
まとめ
InputMapはGodot 4の入力系の中核です。最初のうちはInput.is_action_pressedを使うだけでも十分ですが、リバインド画面や設定保存を作ろうとするとInputMap.action_add_eventなどのAPIも必要になります。
公式のInputMap ドキュメントには全メソッドのリファレンスがあります。入力システムは後から変更しようとすると地獄なので、プロジェクトの最初の段階でInputMapを使う設計にしておくことを強くおすすめします。