Retour aux skills
gamedev-skills/awesome-gamedev-agent-skillsContrôle réussi

SKILL DETAIL

input-systems

gamedev-skills/awesome-gamedev-agent-skills/input-systems

Architect game input — action mapping (abstracting keys into named actions), rebinding with conflict detection and persistence, multi-device support (keyboard, gamepad, touch), analog deadzones, and feel features like input buffering and coyote time, plus accessibility. Engine-neutral. Use when the user mentions input mapping, rebind controls, gamepad support, deadzone, input buffering, coyote time, or accessible controls.

Installations · 269Voir la source

Installation

npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill input-systems

Fichiers du skill

SKILL.md

Dernière synchronisation · 24 oct. 2026

references/buffering-and-accessibility.md›
# Input buffering, game feel, devices, and accessibility

The action layer makes controls work; these refinements make them feel good and
reach more players.

## Buffering and coyote time, tuned

Both convert "I pressed it at almost the right moment" into success:

- **Input buffer** — when an action is pressed but can't fire yet (mid-air jump,
  attack during recovery), remember it for a short window and fire the instant it
  becomes valid. Typical window: ~100–150 ms (≈6–9 frames at 60 Hz).
- **Coyote time** — allow a ground action (jump) for a brief window *after*
  leaving the ledge. Typical window: ~80–120 ms. Players perceive the edge as
  generous, not buggy.

Tuning notes:

- Express windows in **seconds**, not frames, so they're frame-rate independent.
- **Consume** the buffer when it fires, and reset coyote on a successful jump, so
  one press = one action (no double jumps from a lingering buffer).
- Too-long windows feel mushy or cause unintended actions; too-short feel
  punishing. Tune by playtest, per game.

## Jump feel (where input meets physics)

Input handling and `physics-tuning` together create jump feel:

- **Variable height**: full gravity while rising only if the button is held; if
  released early, increase downward acceleration (or cut upward velocity) so a tap
  is a short hop. Read the *held* state for this, the *edge* for the initial jump.
- **Apex hang**: reduce gravity near the top of the arc for a brief float — gives
  air control and reads as responsive.
- **Fast fall / increased fall gravity**: heavier gravity on the way down than up
  makes jumps feel snappy rather than floaty.

These are physics tweaks driven by input state; keep the input layer reporting
edge/held cleanly so the controller can apply them.

## Device detection and prompt swapping

- Track the **last-used device**: when an event arrives, note whether it was
  keyboard/mouse, a specific gamepad type, or touch. Switch on-screen button
  glyphs and tutorials to match.
- Don't assume a single gamepad layout — Xbox, PlayStation, and Switch differ in
  face-button labels. Map to abstract glyphs (South/East/West/North) and localize
  the icon set.
- Support **hot-swapping**: a player may start on keyboard and pick up a
  controller mid-session. Re-resolve prompts on device change without a restart.

## Touch controls

- Provide on-screen controls sized for thumbs (virtual stick / buttons) or
  gesture mappings, routed through the **same action layer** as physical input.
- A virtual stick should use a **floating origin** (anchor where the thumb lands)
  and a radial deadzone, just like a physical stick.
- Avoid relying on precise multi-touch chords; offer larger hit areas and
  forgiving timing. Test on a range of screen sizes.

## Accessibility checklist

Input is one of the highest-impact accessibility surfaces. Aim for:

- **Full remapping** of every action, for every device, with conflict detection
  and reset-to-default.
- **Toggle vs hold** options for actions that default to hold (aim, crouch,
  sprint) — sustained presses are painful or impossible for some players.
- **No required simultaneous inputs** for essential actions; offer single-input
  alternatives or sequential equivalents.
- **Adjustable sensitivity and deadzone** for sticks and aim, plus optional
  aim assist.
- **Adjustable timing windows** where feasible (extend buffer/coyote, slow QTEs),
  or a "hold instead of mash" option for repeated-press prompts.
- **Stick/axis inversion** (X and Y independently) for camera and movement.
- **Input latency**: process discrete presses promptly and avoid adding
  artificial delay; perceived lag harms both feel and accessibility.

## Loop placement

- Read **held/axis** state in the fixed/physics step so movement is consistent
  with the simulation (see `physics-tuning`).
- Capture **discrete presses** as events (or via a just-pressed flag latched each
  frame) so a press between physics ticks is never lost.
- Save custom bindings and input settings through `save-systems` and reload them
  at startup before the first frame of gameplay.
SKILL.md›
---
name: input-systems
description: >
  Architect game input — action mapping (abstracting keys into named actions),
  rebinding with conflict detection and persistence, multi-device support
  (keyboard, gamepad, touch), analog deadzones, and feel features like input
  buffering and coyote time, plus accessibility. Engine-neutral. Use when the
  user mentions input mapping, rebind controls, gamepad support, deadzone, input
  buffering, coyote time, or accessible controls.
---

# Input systems

Never wire gameplay to raw keys. Map physical inputs (a key, a button, a touch)
to named **actions** (`jump`, `interact`, `move`), and let gameplay read actions.
That one indirection gives you rebinding, multi-device support, and accessibility
almost for free. This skill is the engine-neutral architecture; bind it to
`unity-input-system`, `unreal-enhanced-input`, or Godot's `InputMap`.

## When to use

- Use to design an input layer: actions, bindings, multiple devices, and a
  rebinding UI with conflict detection and saved bindings.
- Use to add analog handling (deadzones, sensitivity) and game-feel features
  (input buffering, coyote time).
- Use to make controls accessible (full remapping, hold-vs-toggle, sensitivity,
  no required simultaneous presses).

**When *not* to use:** for an engine's concrete input package/API, use
`unity-input-system`, `unreal-enhanced-input`, or Godot's InputMap. For the
movement/jump *physics* the buffer feeds, see `physics-tuning` and the engine
movement skill. Persisting bindings to disk is `save-systems`.

## Core workflow

1. **Define actions, not keys.** Gameplay asks "is `jump` pressed?", never "is
   Space pressed?". Actions are the stable contract; bindings are data.
2. **Bind per device.** Each action holds bindings for keyboard, gamepad, and
   touch. The active device is whichever last sent input; swap UI prompts to match.
3. **Read the right edge.** Use *pressed-this-frame* (edge) for discrete actions
   (jump, interact) and *held* (level) for continuous ones (move, aim). Confusing
   the two causes double-fires or missed presses.
4. **Filter analog input.** Apply a deadzone to sticks/triggers so resting drift
   reads as zero, and scale sensitivity/curve to taste.
5. **Buffer for feel.** Remember a pressed action for a short window so a slightly
   early press still fires (input buffering); allow a jump shortly after leaving a
   ledge (coyote time).
6. **Make rebinding first-class.** A UI that captures the next input, detects
   conflicts, and persists bindings — and a reset-to-default. Save via
   `save-systems`.
7. **Verify on every device** and with rebinds: keyboard, gamepad, touch; rebind
   an action mid-game and confirm gameplay and prompts follow.

## Patterns

### 1. Actions over raw keys; edge vs held

```gdscript
# Gameplay reads ACTIONS. The mapping from key/button to action lives in data.
# Discrete (edge): fire once on the press frame.
if Input.is_action_just_pressed("jump"):
    try_jump()
# Continuous (held): read every frame as an axis.
var move := Input.get_axis("move_left", "move_right")   # -1..1
player.velocity.x = move * RUN_SPEED
# RIGHT: name actions ("jump"); rebinding/devices just change the binding data.
# WRONG: `if Input.is_key_pressed(KEY_SPACE)` — unrebindable, keyboard-only,
# and `is_key_pressed` is a held check that would re-fire jump every frame.
```

Engine equivalents: Godot `InputMap` + `Input.is_action_just_pressed`; Unity
Input System `InputAction` / action maps; Unreal Enhanced Input `Input Actions` +
`Input Mapping Contexts`.

### 2. Analog deadzone and sensitivity

```gdscript
# 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, sens := 1.0) -> Vector2:
    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 := (mag - dead) / (1.0 - dead)
    return stick.normalized() * pow(scaled, sens)  # sens>1 = finer near center
# WRONG: clamping each axis separately — it carves a square hole and snaps to axes.
```

### 3. Input buffering + coyote time (forgiving, responsive feel)

```gdscript
# Buffer: a jump pressed slightly BEFORE landing still triggers on touchdown.
# Coyote: a jump pressed slightly AFTER walking off a ledge still works.
const BUFFER := 0.12   # seconds an early press stays "remembered"
const COYOTE := 0.10   # seconds after leaving ground you can still jump
var _buffer_timer := 0.0
var _coyote_timer := 0.0

func _physics_process(dt):
    _buffer_timer -= dt
    _coyote_timer = COYOTE if is_on_floor() else _coyote_timer - dt
    if Input.is_action_just_pressed("jump"):
        _buffer_timer = BUFFER                  # remember the press
    if _buffer_timer > 0.0 and _coyote_timer > 0.0:
        velocity.y = JUMP_VELOCITY
        _buffer_timer = 0.0; _coyote_timer = 0.0  # consume both so it fires once
```

### 4. Rebinding with conflict detection

```gdscript
# Capture the next physical input, reject duplicates, then persist.
func rebind(action: String, event: InputEvent) -> bool:
    for other in actions:                        # conflict check across actions
        if other != action and binding_of(other) == event:
            return false                         # already used -> let UI warn/swap
    set_binding(action, event)                   # engine: erase old + add new event
    save_bindings()                              # persist (see save-systems)
    return true
# Always provide "reset to defaults", and never let the player unbind a key they
# need to reach the menu without an alternative.
```

## Pitfalls

- **Hardcoding keys** in gameplay blocks rebinding, locks out gamepad/touch, and
  scatters input logic. Read named actions only.
- **Edge vs held confusion**: using a held check for jump re-fires every frame;
  using an edge check for movement drops held input. Match the check to the action.
- **Per-axis deadzones** clip diagonal stick input and snap movement to the axes.
  Use a radial deadzone on the vector magnitude.
- **No buffering/coyote time** makes tight platformers feel unfair even when the
  physics are correct — players "clearly pressed jump". Add small windows.
- **Rebinding without conflict handling** lets two actions share a key, or strands
  the player by unbinding menu access. Detect conflicts; guarantee a way back.
- **Not swapping prompts on device change** shows "Press Space" to a gamepad
  player. Track the last-used device and switch glyphs.
- **Ignoring accessibility**: required simultaneous presses, no remap, fixed
  sensitivity, hold-only actions. Offer remap, toggle-vs-hold, and sensitivity.
- **Reading input in the wrong loop**: poll held state in the physics step for
  consistent movement; capture discrete presses so none are missed between frames.

## References

- `references/buffering-and-accessibility.md` — buffering/coyote tuning, jump feel
  (variable height, apex), device detection and prompt swapping, touch controls,
  and an accessibility checklist (remap, toggle/hold, sensitivity, latency).

## Related skills

- `unity-input-system`, `unreal-enhanced-input` — concrete engine input APIs
  (Godot uses `InputMap` + the `Input` singleton).
- `save-systems` — persist custom key bindings and input settings.
- `physics-tuning` — the movement the buffer/coyote windows feed into.
- `platformer`, `fps-shooter` — genres whose feel depends on input handling.