input-systems
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInput systems
输入系统
Never wire gameplay to raw keys. Map physical inputs (a key, a button, a touch)
to named actions (, , ), 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
, , or Godot's .
jumpinteractmoveunity-input-systemunreal-enhanced-inputInputMap切勿将游戏玩法直接绑定到原始按键。应将物理输入(按键、按钮、触控操作)映射到命名actions(、、),让游戏玩法读取动作状态。这一层间接映射几乎能让你免费获得rebinding、多设备支持和无障碍设计能力。本方案是与引擎无关的输入架构;你可以将其与、或Godot的结合使用。
jumpinteractmoveunity-input-systemunreal-enhanced-inputInputMapWhen 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
, , or Godot's InputMap. For the
movement/jump physics the buffer feeds, see and the engine
movement skill. Persisting bindings to disk is .
unity-input-systemunreal-enhanced-inputphysics-tuningsave-systems- 用于设计输入层:actions、绑定关系、多设备支持,以及带冲突检测和绑定保存功能的rebinding UI。
- 用于添加模拟输入处理(deadzone、灵敏度)和操作手感特性(input buffering、coyote time)。
- 用于实现无障碍控制(完全重绑定、按住/切换模式、灵敏度调节、无强制同时按键要求)。
不适用场景:若需使用引擎的具体输入包/API,请使用、或Godot的InputMap。若需处理input buffering所关联的移动/跳跃物理逻辑,请参考及引擎的移动相关方案。将绑定关系持久化到磁盘属于的范畴。
unity-input-systemunreal-enhanced-inputphysics-tuningsave-systemsCore workflow
核心工作流程
- Define actions, not keys. Gameplay asks "is pressed?", never "is Space pressed?". Actions are the stable contract; bindings are data.
jump - 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.
- 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.
- Filter analog input. Apply a deadzone to sticks/triggers so resting drift reads as zero, and scale sensitivity/curve to taste.
- 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).
- 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 - Verify on every device and with rebinds: keyboard, gamepad, touch; rebind an action mid-game and confirm gameplay and prompts follow.
- 定义actions,而非按键。游戏玩法应询问"是否被按下?",而非"空格键是否被按下?"。actions是稳定的约定;绑定关系是数据。
jump - 按设备绑定。每个action包含针对键盘、游戏手柄和触控设备的绑定。激活设备为最后发送输入的设备;需同步切换UI提示以匹配当前设备。
- 读取正确的输入边缘。对离散动作(jump、interact)使用"本帧按下"(边缘触发),对连续动作(move、aim)使用"持续按住"(电平触发)。混淆两种触发方式会导致重复触发或输入丢失。
- 过滤模拟输入。对摇杆/扳机施加deadzone,使静止时的漂移输入被识别为零,并根据需求调整灵敏度/曲线。
- 通过缓冲优化手感。在短时间窗口内记住已按下的action,以便稍早的按键仍能触发(input buffering);允许玩家在离开平台后短时间内仍能跳跃(coyote time)。
- 将rebinding设为一等功能。提供一个UI,用于捕获下一次物理输入、检测冲突并持久化绑定关系——同时提供重置为默认值的选项。通过实现保存。
save-systems - 在所有设备及rebinding场景下验证:键盘、游戏手柄、触控设备;在游戏中途rebind某个action,确认游戏玩法和提示能同步更新。
Patterns
设计模式
1. Actions over raw keys; edge vs held
1. 优先使用actions而非原始按键;边缘触发vs持续按住
gdscript
undefinedgdscript
undefinedGameplay reads ACTIONS. The mapping from key/button to action lives in data.
Gameplay reads ACTIONS. The mapping from key/button to action lives in data.
Discrete (edge): fire once on the press frame.
Discrete (edge): fire once on the press frame.
if Input.is_action_just_pressed("jump"):
try_jump()
if Input.is_action_just_pressed("jump"):
try_jump()
Continuous (held): read every frame as an axis.
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
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.
RIGHT: name actions ("jump"); rebinding/devices just change the binding data.
WRONG: if Input.is_key_pressed(KEY_SPACE)
— unrebindable, keyboard-only,
if Input.is_key_pressed(KEY_SPACE)WRONG: if Input.is_key_pressed(KEY_SPACE)
— unrebindable, keyboard-only,
if Input.is_key_pressed(KEY_SPACE)and is_key_pressed
is a held check that would re-fire jump every frame.
is_key_pressedand is_key_pressed
is a held check that would re-fire jump every frame.
is_key_pressed
Engine equivalents: Godot `InputMap` + `Input.is_action_just_pressed`; Unity
Input System `InputAction` / action maps; Unreal Enhanced Input `Input Actions` +
`Input Mapping Contexts`.
引擎等效实现:Godot的`InputMap` + `Input.is_action_just_pressed`;Unity Input System的`InputAction` / 动作映射;Unreal Enhanced Input的`Input Actions` + `Input Mapping Contexts`。2. Analog deadzone and sensitivity
2. 模拟输入deadzone与灵敏度
gdscript
undefinedgdscript
undefinedRaw sticks never rest at exactly zero. Apply a RADIAL deadzone (on the vector
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.
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 .
var scaled := (mag - dead) / (1.0 - dead)
return stick.normalized() * pow(scaled, sens) # sens>1 = finer near center
deadfunc 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 .
var scaled := (mag - dead) / (1.0 - dead)
return stick.normalized() * pow(scaled, sens) # sens>1 = finer near center
deadWRONG: clamping each axis separately — it carves a square hole and snaps to axes.
WRONG: clamping each axis separately — it carves a square hole and snaps to axes.
undefinedundefined3. Input buffering + coyote time (forgiving, responsive feel)
3. Input buffering + coyote time(提升操作容错性与响应感)
gdscript
undefinedgdscript
undefinedBuffer: a jump pressed slightly BEFORE landing still triggers on touchdown.
Buffer: a jump pressed slightly BEFORE landing still triggers on touchdown.
Coyote: a jump pressed slightly AFTER walking off a ledge still works.
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
undefinedconst 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
undefined4. Rebinding with conflict detection
4. 带冲突检测的rebinding
gdscript
undefinedgdscript
undefinedCapture the next physical input, reject duplicates, then persist.
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
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
Always provide "reset to defaults", and never let the player unbind a key they
need to reach the menu without an alternative.
need to reach the menu without an alternative.
undefinedundefinedPitfalls
常见陷阱
- 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.
- 硬编码按键:在游戏玩法中硬编码按键会阻碍rebinding、限制游戏手柄/触控设备的使用,并分散输入逻辑。仅读取命名actions。
- 混淆边缘触发与持续按住:对jump使用持续按住检测会导致每帧重复触发;对move使用边缘触发检测会丢失按住输入。需根据动作类型匹配检测方式。
- 按轴设置deadzone:会裁剪摇杆的斜向输入,并使移动吸附到坐标轴。应基于向量长度设置径向deadzone。
- 缺少input buffering/coyote time:即使物理逻辑正确,也会让精密平台游戏感觉不公平——玩家明明"按下了jump"。需添加短时间窗口提升容错性。
- 无冲突处理的rebinding:会导致两个action共享同一按键,或因玩家解除菜单访问按键的绑定而陷入困境。需检测冲突;确保玩家有返回菜单的途径。
- 设备切换时未更新提示:向游戏手柄玩家显示"按空格键"的提示。需跟踪最后使用的设备并切换对应的图标。
- 忽略无障碍设计:强制同时按键、不支持重绑定、固定灵敏度、仅支持按住模式的动作。应提供重绑定、切换/按住模式切换、灵敏度调节功能。
- 错误的输入读取时机:在物理步骤中轮询持续按住状态以保证移动一致性;捕获离散按键输入,避免帧间输入丢失。
References
参考资料
- — buffering/coyote tuning, jump feel (variable height, apex), device detection and prompt swapping, touch controls, and an accessibility checklist (remap, toggle/hold, sensitivity, latency).
references/buffering-and-accessibility.md
- —— input buffering/coyote time调优、跳跃手感(可变高度、顶点控制)、设备检测与提示切换、触控控制,以及无障碍设计检查表(重绑定、切换/按住模式、灵敏度、延迟)。
references/buffering-and-accessibility.md
Related skills
相关方案
- ,
unity-input-system— concrete engine input APIs (Godot usesunreal-enhanced-input+ theInputMapsingleton).Input - — persist custom key bindings and input settings.
save-systems - — the movement the buffer/coyote windows feed into.
physics-tuning - ,
platformer— genres whose feel depends on input handling.fps-shooter
- 、
unity-input-system—— 具体引擎的输入API(Godot使用unreal-enhanced-input+InputMap单例)。Input - —— 持久化自定义按键绑定和输入设置。
save-systems - —— input buffering/coyote time窗口所关联的移动物理逻辑。
physics-tuning - 、
platformer—— 手感依赖输入处理的游戏类型。fps-shooter