agentleFS
Simone-TarantinoSimone-Tarantino/godot-superpowers/skills/setup-input-map/SKILL.md

setup-input-map

Skill2 starsChanged 6 months agoMITView on GitHub

Configure Godot 4.x InputMap with standard actions (move, jump, attack, interact, pause, UI nav) for keyboard, mouse, and gamepad. Generate a runtime remap UI as a bonus. Edits project.godot.

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use physical_keycode (positional) not keycode (layout-dependent) for movement keys — works across QWERTY / AZERTY / Dvorak.

    line 215

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Setup Input Map

Configures Godot 4.x InputMap actions and provides a remap UI scaffold.

Default actions (genre-agnostic)

ActionKeyboardGamepadNotes
move_leftA, LeftLStick left, DPad left
move_rightD, RightLStick right, DPad right
move_upW, UpLStick up, DPad up
move_downS, DownLStick down, DPad down
jumpSpaceA (Xbox) / X (PS)platformer / 3d
attackLMB, JRT / R2
attack_secondaryRMB, KLT / L2block / heavy
interactEY / Triangle
dashShiftB / Circle
inventoryISelect / Share
pauseEscStart / Options
ui_acceptEnter, SpaceAalso auto-mapped by engine
ui_cancelEscBengine default
ui_left/right/up/downarrowsDPadengine default

ui_* actions exist by default — adjust as needed but don't remove (Control nodes rely on them).

project.godot — [input] section

[input]

move_left={
"deadzone": 0.5,
"events": [
  Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null),
  Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null),
  Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null),
  Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
]
}

(That's just move_left. The full block is verbose — easier to add actions through the editor's Input Map panel and let Godot serialize. Recommended: open Project Settings → Input Map, click "Add" for each action listed above, bind keys/buttons, then save.)

Physical keycode reference (physical_keycode):

  • A=65, S=83, D=68, W=87
  • Space=32
  • Left=4194319, Right=4194321, Up=4194320, Down=4194322
  • Esc=4194305, Enter=4194309
  • Shift=4194325

Gamepad button indices (Xbox layout):

  • 0=A, 1=B, 2=X, 3=Y
  • 4=LB, 5=RB
  • 6=Select, 7=Start
  • 11=DPad up, 12=DPad down, 13=DPad left, 14=DPad right

Joypad axes:

  • 0=LStick X, 1=LStick Y, 2=RStick X, 3=RStick Y
  • 4=LT, 5=RT

Reading input in code

# Movement vector (handles 8-direction + analog stick)
var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity.x = input_dir.x * MAX_SPEED

# One-shot in _unhandled_input (preferred for actions)
func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("jump"):
        _try_jump()
    elif event.is_action_pressed("pause"):
        _toggle_pause()

# Held check in _physics_process
if Input.is_action_pressed("attack"):
    _charge_attack(delta)

# Just-pressed in _physics_process (alternative to _unhandled_input)
if Input.is_action_just_pressed("dash"):
    _dash()

Genre-specific additions

Platformer

  • jump (already in defaults)
  • crouch — Ctrl, Down (modifier)
  • wall_grab — Shift held

3D action / FPS

  • look_left/right/up/down (mouse fallback for keyboard-only)
  • aim — RMB / L2
  • fire — LMB / R2
  • reload — R / X(Square)
  • zoom_in/out — scroll wheel

Top-down shooter

  • Same as 3D action, omit jump

Turn-based

  • confirm — Space, Enter, A
  • cancel — Esc, B
  • next_unit — Tab, RB
  • end_turn — F, Start

Runtime remap UI

scenes/ui/input_remap_menu.tscn skeleton. Mark ActionList and ResetButton as unique names (%ActionList, %ResetButton) in the editor so the controller can grab them:

InputRemapMenu (Control)
├── VBoxContainer
│   ├── ScrollContainer
│   │   └── ActionList (VBoxContainer, unique name %ActionList — action rows generated at runtime)
│   └── HBoxContainer
│       ├── ResetButton (Button, unique name %ResetButton, text "Reset to defaults")
│       └── BackButton (Button, optional)

Controller scripts/ui/input_remap_menu.gd:

extends Control

const REMAPPABLE := [
    "move_left", "move_right", "move_up", "move_down",
    "jump", "attack", "interact", "dash", "pause",
]
const SAVE_PATH := "user://input_map.cfg"

@onready var _list: VBoxContainer = %ActionList
@onready var _reset_button: Button = %ResetButton
var _capturing_action: StringName = &""
var _row_for_action: Dictionary[StringName, HBoxContainer] = {}

func _ready() -> void:
    _reset_button.pressed.connect(_on_reset_pressed)
    _load_overrides()
    _build_rows()

func _on_reset_pressed() -> void:
    InputMap.load_from_project_settings()
    DirAccess.remove_absolute(SAVE_PATH)
    _build_rows()

func _build_rows() -> void:
    for child in _list.get_children():
        child.queue_free()
    for action in REMAPPABLE:
        var row := HBoxContainer.new()
        var label := Label.new()
        label.text = action
        label.custom_minimum_size.x = 200
        var btn := Button.new()
        btn.text = _format_event(InputMap.action_get_events(action))
        btn.pressed.connect(_capture.bind(action, btn))
        row.add_child(label)
        row.add_child(btn)
        _list.add_child(row)
        _row_for_action[action] = row

func _capture(action: StringName, btn: Button) -> void:
    _capturing_action = action
    btn.text = "press a key..."

func _input(event: InputEvent) -> void:
    if _capturing_action == &"":
        return
    if event is InputEventKey or event is InputEventMouseButton or event is InputEventJoypadButton:
        if event.pressed:
            InputMap.action_erase_events(_capturing_action)
            InputMap.action_add_event(_capturing_action, event)
            _save_overrides()
            _build_rows()
            _capturing_action = &""
            get_viewport().set_input_as_handled()

func _format_event(events: Array[InputEvent]) -> String:
    if events.is_empty():
        return "(unbound)"
    return events[0].as_text()

func _save_overrides() -> void:
    var cfg := ConfigFile.new()
    for action in REMAPPABLE:
        var events := InputMap.action_get_events(action)
        cfg.set_value("input", action, events)
    cfg.save(SAVE_PATH)

func _load_overrides() -> void:
    var cfg := ConfigFile.new()
    if cfg.load(SAVE_PATH) != OK:
        return
    for action in REMAPPABLE:
        var events: Array = cfg.get_value("input", action, [])
        if events.is_empty():
            continue
        InputMap.action_erase_events(action)
        for ev in events:
            InputMap.action_add_event(action, ev)

Persists to user://input_map.cfg and restores at startup.

Notes

  • Use physical_keycode (positional) not keycode (layout-dependent) for movement keys — works across QWERTY / AZERTY / Dvorak.
  • Input.is_action_pressed() is fine in _physics_process; for one-shots prefer _unhandled_input so UI captures take precedence.
  • For couch co-op, set device on each InputEvent and read with Input.is_joy_known(device).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.