我们已经让角色会走会跳、会开枪、会被追、会有光有粒子有动画。但所有这些能力都挂在一个前提上:玩家的手能舒服地操控它。而 Steam 玩家和主机玩家不一样——有人用键鼠,有人用手柄,有人拿 Steam Deck 掌机。如果你的代码里写满
if Input.is_key_pressed(KEY_A),你等于把一半买家挡在门外。今天把 Godot 的输入层讲透,它是「能不能卖」级别的工程问题,不是手感调优。
今日速览
- 不要硬编码按键。所有输入都走 Input Map 的「动作(action)」抽象:代码只认
"jump",项目设置里把它绑到 Space、手柄 A、Steam Deck 的 A 都行。 - 事件 vs 轮询要分清:一次性触发(跳跃、开菜单)用
_input()+is_action_pressed();持续状态(移动、蓄力)在_physics_process里轮询Input.is_action_pressed()。 - 模拟量三件套:二维方向用
Input.get_vector()(自带圆形死区),单轴用Input.get_axis(),扳机/力度用Input.get_action_strength()。手写相减会得到「方形死区」,斜向走会偏快。 - 手柄没有 echo 事件:按住方向键不会像键盘那样自动连发,需要自己用 Timer 造
InputEvent再parse_input_event()。 - 手柄输入会发给未聚焦窗口:必须开
input_devices/joypads/ignore_joypad_on_unfocused_application,否则玩家在聊天窗打字时角色在后台乱跑。
核心学习内容
一、动作抽象:把「按了什么」翻译成「想干什么」
Godot 的 Input Map(项目设置 → 输入映射)本质是一张语义表:左边是你游戏里的意图(move_left、jump、interact、pause),右边可以挂任意多个物理输入(键、鼠标键、手柄按钮、摇杆轴)。代码只跟左边打交道。
官方文档明确建议动作名用 snake_case,并且新项目自带一批默认动作(ui_accept、ui_cancel 等),做 UI 导航时直接复用,别另起炉灶。
这一步的真正价值不是省事,而是可重构:以后加手柄支持、加按键重绑定、加 Steam Deck 布局,你只改配置表,不改一行游戏逻辑。
二、事件 vs 轮询:两种读取姿势别用错
# 一次性:按下瞬间触发
func _input(event):
if event.is_action_pressed("jump"):
jump()
# 持续性:按住就一直生效
func _physics_process(delta):
if Input.is_action_pressed("move_right"):
position.x += speed * delta
判断标准很简单:这个行为需要「每帧都发生」吗? 移动、蓄力、瞄准属于是;跳跃、暂停、交互属于否。混用会出经典 bug——把跳跃写在 _physics_process 里用 is_action_pressed,玩家按住不放就会连跳;把移动写在 _input 里,松开按键后角色还在走。
注意 is_action_just_pressed() 只在按下后一帧为 true,它适合在 _physics_process 里做一次性判定(比 _input 更好控制时序)。
三、模拟量:get_vector 与死区,手柄手感的分水岭
手柄摇杆是模拟量,物理上永远回不到精确的 0.000,静止时会残留 0.05~0.1 的漂移(drifting),老手柄更明显。不处理死区,你的角色会「自己慢慢走」。
# 推荐:二维移动,圆形死区,自动归一化
var dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
# 单轴(左右走)
var walk := Input.get_axis("move_left", "move_right")
# 扳机 / 力度
var throttle := Input.get_action_strength("accelerate")
关键是 get_vector() 的第 5 个可选参数就是死区值。不传时它会取相关动作死区的平均值。Godot 默认死区是 0.5(相当大),平台跳跃游戏通常调到 0.15~0.25;死区太高会丢失微操,太低会漂移,这是必须上手实测的参数。
而手写 get_action_strength(right) - get_action_strength(left) 的死区是方形的:斜向推摇杆时,两个轴都在死区边缘,合成向量长度会超过 1,角色斜着走比直着走快。get_vector() 做的是圆形死区 + 长度钳制,这就是官方推荐它的原因。
四、三个容易被忽略的坑
1. 手柄不产生 echo 事件。 键盘按住方向键,操作系统会以固定间隔重复发按键事件;手柄不会。做菜单/列表导航时,按住十字键不会连续移动光标——需要自己用 Timer 周期性构造 InputEventAction 并 Input.parse_input_event()。
2. 焦点问题。 手柄事件默认会被所有窗口收到(这是分屏联机的基础),但也意味着玩家切到浏览器时还在操控你的游戏。设置 input_devices/joypads/ignore_joypad_on_unfocused_application = true 即可。
3. 震动(haptics)。 Input.start_joy_vibration(device, weak, strong, duration) / stop_joy_vibration(device)。官方特别提醒:必须给玩家一个关闭/调节震动的选项,部分玩家对震动物理不适,这在 Steam 上也是无障碍(Accessibility)标签的一部分。
另外从 Godot 4.5 起,桌面端手柄支持底层换成 SDL 3,支持列表和主流引擎一致——这意味着 Steam Deck 的识别会更稳,但旧版 Godot 可能对某些手柄表现异常。
🎯 DeepSeek 提示词
提示词 1:生成「键鼠 + 手柄通用」的 2D 角色控制器
适用场景:新项目起步,或者想把已有硬编码按键的控制器改造成动作抽象版。
你是资深 Godot 4 游戏程序员,熟悉 GDScript 2.0 与官方输入最佳实践。
【任务】为 2D 平台跳跃游戏写一个 CharacterBody2D 控制器脚本,同时支持键盘和手柄,不写任何硬编码按键。
【必须满足】
1. 所有输入通过 Input Map 动作读取:move_left / move_right / jump / dash。
2. 水平移动用 Input.get_axis(),不用 is_action_pressed 判断左右。
3. 跳跃用 is_action_just_pressed() 在 _physics_process 中判定,支持「土狼时间(coyote time)」0.1s 与「跳跃缓冲(jump buffer)」0.12s。
4. 使用 move_and_slide(),包含重力、最大下落速度、可变跳跃高度(松开跳跃键提前削减上升速度)。
5. 所有可调参数用 @export 暴露,并在注释中给出推荐值范围。
【输出格式】
- 先输出「需要在 Input Map 中创建的动作名 + 建议绑定(键盘键 / 手柄按钮)」清单表格。
- 再输出完整 .gd 文件,含节点结构说明注释。
- 最后用 5 行说明这段代码在什么情况下会手感变差、该怎么调。
【约束】Godot 4.x 语法;不要用已废弃的 KinematicBody2D;不要写 C#;不要省略实现只给 TODO。
提示词 2:审计输入代码,找出「手柄上会坏掉」的地方
适用场景:功能已跑通、准备上 Steam 前做兼容性排查。
你是 Godot 4 输入系统审查员。我会贴一段 GDScript,请找出它在「只用键盘开发、但玩家用手柄玩」时会出问题的地方。
【检查清单,逐条给结论:通过 / 风险 / 失败】
1. 是否硬编码了 Key 或 MouseButton 常量,绕过 Input Map。
2. 一次性行为是否误用 is_action_pressed(会导致连跳/连发)。
3. 模拟量取值是否用了手动相减而非 get_vector/get_axis(会导致方形死区、斜向超速)。
4. 死区设置是否合理(默认 0.5 偏大),是否需要为摇杆单独指定。
5. 菜单/列表导航是否依赖键盘 echo 事件(手柄不会连发)。
6. 是否处理了手柄在窗口失焦时仍可输入的问题。
7. 震动是否提供了玩家可关闭的开关。
【输出格式】
表格:问题位置(行号/函数名) | 风险等级(高/中/低) | 手柄上的具体表现 | 修复后的代码片段。
最后给一句总评:这段代码当前「手柄可玩度」打分(0-10)及最短修复路径。
【约束】只基于我贴的代码判断,不要假设未出现的文件内容;不确定的标注「需确认」。
提示词 3:设计按键重绑定 + 设备提示切换方案
适用场景:Steam 商店页承诺了「完整手柄支持」,需要 UI 上的提示图标跟着当前设备变。
你是 Godot 4 UI 与输入架构师。请为一个 2D 独立游戏设计「输入重绑定 + 设备提示自动切换」系统。
【需求】
1. 设置界面可重绑定每个动作的键盘键与手柄按钮,冲突时提示并允许替换。
2. 绑定结果持久化到 user://settings.cfg(ConfigFile),启动时自动加载并写回 InputMap。
3. 游戏内 UI 提示图标自动切换:检测到最近一次输入来自手柄就显示手柄图标,来自键鼠就显示键位文字。
4. 提供「恢复默认」按钮。
【输出格式】
- 文件/场景结构树。
- 每个脚本的完整 GDScript(标注挂在哪个节点)。
- 说明「如何检测最后一次输入来自哪种设备」的具体实现(列出需要用的事件类型与判断条件)。
- 3 条测试用例:拔掉手柄、游戏中热插拔、重绑定后立刻生效。
【约束】Godot 4.x;重绑定期间要屏蔽游戏输入(用 set_process_input 或状态标志);不要依赖第三方插件。
学习心得
今天我们做的不是「加功能」,是拆掉一堵墙。前面六十多天我们一直在堆能力——移动、战斗、光照、存档、粒子。但所有能力最终要经过玩家的一双手才能变成体验,而输入层是唯一直接贴着那双手的代码。
对「用 DeepSeek 做一款 Steam 游戏」这个目标来说,这一篇的价值在于:输入系统是 AI 最容易写错、也最容易靠提示词规范好的模块。如果你只跟 DeepSeek 说「写个角色移动」,它十有八九给你硬编码 KEY_A 和 KEY_D;但如果你在提示词里明确「所有输入走 Input Map 动作、水平轴用 get_axis」,它写出来的就是能直接上 Steam 的版本。差别不在模型能力,在你有没有把工程约束写进提示词。
还有一个现实理由:Steam Deck 验证(Verified)会直接影响你的游戏在掌机上的曝光。而 Deck 验证里有一条硬指标就是「手柄可完整操作、UI 提示正确」。今天这三段代码,本质是在为通过验证提前铺路。
明日关注
- 实测
Input.get_vector()第 5 参数在不同死区值(0.1 / 0.2 / 0.3)下的手感差异,记录到项目调参表 - 查 Steamworks 文档里 Steam Input API 与原生输入的区别,判断小项目是否值得接入(多数 2D 独立游戏用原生输入 + Input Map 足够)
- 用提示词 2 把现有控制器脚本完整审计一遍,把「高风险」项改掉并回归测试
本文由自动化学习流水线每日生成,坚持即学即记。
评论