Skip to content
Development
Skill

/input-systems

Engine-neutral input architecture with Godot InputMap code: named actions, stick deadzones, buffered presses and rebinding with conflict checks. Use for controller support or a rebind menu, or a drifting stick, WASD on AZERTY, or a dropped press. Not for coyote time

From plugin
awesome-gamedev-agent-skills
8.6k79 skills

Claude Code

$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill input-systems --agent claude-code

SKILL.md

input-systems.SKILL.md
name: input-systems
description: >
  Engine-neutral input architecture with Godot InputMap code: named actions, stick deadzones,
  buffered presses and rebinding with conflict checks. Use for controller support or a rebind
  menu, or a drifting stick, WASD on AZERTY, or a dropped press. Not for coyote time
  (`platformer`) or Unity and Unreal input assets (`unity-input-system`, `unreal-enhanced-input`).

Input systems

Gameplay reads named **actions** (`jump`, `attack`, `move`); bindings from keys, buttons and touch to those actions are data. Rebinding, multi-device support and most input accessibility fall out of that one indirection. This skill is the engine-neutral architecture with Godot 4.7 code. For the engine's own input assets use `unity-input-system` or `unreal-enhanced-input`; Godot's `InputMap` is covered here.

When to use

  • Designing an input layer: actions, per-device bindings, device detection and prompts.
  • Stick handling: deadzones, response curves, sensitivity.
  • Buffering discrete presses (attack during recovery, dodge, combo links, menu confirm).
  • A rebinding screen with conflict handling, saved bindings and reset to defaults.
  • Accessible controls: full remap, toggle vs hold, no required chords, adjustable windows.

**Not this skill:** coyote time, jump buffering and variable jump height belong to `platformer`, which owns the jump controller. Hit-stop and other juice that interact with buffers are `game-feel`. Writing the bindings file is `save-systems`. Menu focus and navigation rules are `game-ui-ux`.

Core workflow

1. **Inventory before editing.** List the existing actions (Godot: the `[input]` section of `project.godot`; Unity: the `.inputactions` file; Unreal: `IA_`/`IMC_` assets) and grep gameplay code for raw key reads (`is_key_pressed`, `KEY_`, `Input.GetKey`). Raw reads are the first thing to replace. 2. **Name actions by intent**, not by key, and give each one a keyboard and a gamepad binding. Touch routes through the same actions. 3. **Match the read to the action.** Edge (pressed this frame or tick) for discrete actions, held or axis for continuous ones. 4. **Filter analog input** with a radial deadzone and a separate response curve. 5. **Buffer discrete presses** that arrive a little before they are legal, measured in game time and consumed when they fire. 6. **Rebinding:** capture the next input, bind the physical key, reject or swap conflicts, save, and offer reset to defaults. Keep a way back to the pause menu that cannot be unbound. 7. **Check it yourself, then ask for the device test** (see "Check it" below).

Patterns

1. Actions over raw keys; edge vs held

if Input.is_action_just_pressed("jump"):      # edge: true on the press frame or tick only
    try_jump()
var dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
# get_vector applies a radial deadzone, rescales past it and caps length at 1,
# so diagonals are not faster. Use get_axis for a single axis.
# WRONG: Input.is_key_pressed(KEY_SPACE): unrebindable, keyboard-only, and a held
# check, so jump re-fires every frame.

Engine equivalents: Unity Input System `InputAction` and action maps; Unreal Enhanced Input Actions plus Mapping Contexts.

2. Analog deadzone and sensitivity

# Raw sticks never rest at exactly zero. Apply a RADIAL deadzone (on the vector
# length), not per-axis, so diagonals aren't clipped into the axes.
func apply_deadzone(stick: Vector2, dead := 0.2, curve := 1.0) -> Vector2:
    # dead == 1 cannot be rescaled; report invalid settings before stopping movement.
    if not (dead >= 0.0 and dead < 1.0 and curve > 0.0):
        push_error("apply_deadzone requires 0 <= dead < 1 and curve > 0.")
        return Vector2.ZERO
    var mag := stick.length()
    if mag <= dead:
        return Vector2.ZERO                      # inside deadzone -> no movement
    # Rescale so motion ramps from 0 at the edge of the deadzone, not from `dead`.
    var scaled := minf((mag - dead) / (1.0 - dead), 1.0)  # some pads report > 1 on diagonals
    return stick / mag * pow(scaled, curve)      # curve > 1 = finer aim near center
# WRONG: clamping each axis separately — it carves a square hole and snaps to axes.

Keep the response curve (exponent) separate from sensitivity (a gain on the result, usually a camera turn rate) and expose both in settings. Invalid settings report an error and return zero movement. Godot's `get_vector` already does this deadzone and rescale, using the average of the four actions' deadzones (0.2 per action by default) unless you pass one.

3. Action buffer for discrete presses

# action_buffer.gd: remember discrete presses briefly so an early press still fires.
extends RefCounted

var window := 0.15                        # seconds; 0.1-0.2 suits attacks and dodges
var _pressed_at := {}                     # action -> game time of its latest press

func press(action: StringName, now: float) -> void:
    _pressed_at[action] = now

func consume(action: StringName, now: float) -> bool:
    if not _pressed_at.has(action) or now - _pressed_at[action] > window:
        return false
    _pressed_at.erase(action)             # one press fires one action
    return true

func clear() -> void:                     # on death, pause, cutscene, scene change
    _pressed_at.clear()

Feed it from the input read (`if Input.is_action_just_pressed("attack"): buffer.press(&"attack", clock)`) and call `consume` where the action becomes legal, such as the first frame after attack recovery. `clock` is game time you advance in `_physics_process`, so the window freezes during pause and hit-stop. When two buffered actions become legal on the same tick, choose by a fixed priority, not by dictionary order. Jump buffering and coyote time use the same idea but belong to `platformer`.

4. Rebinding with conflict detection

# Godot 4.7. Capture the next physical i
Read more
@gamedev-skills
Ships withawesome-gamedev-agent-skills

<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.

Stars on GitHub

Momentum

stars in total
8.6k
Star history of gamedev-skills/awesome-gamedev-agent-skills on GitHub, a chart by star-history.com

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills13d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react3d252k
@facebook@facebookInstall Skill

Questions about input-systems.

Which coding agents can use input-systems?
input-systems installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.