Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

控制器、手柄和摇杆

Godot 原生支持数百种控制器型号。控制器支持 Windows、macOS、Linux、Android、iOS 和 Web 平台。

备注

自 Godot 4.5 起,引擎在 Windows、macOS 和 Linux 平台上依赖 SDL 3 实现控制器支持。这意味着支持的控制器列表及其行为应与使用 SDL 3 的其他游戏和引擎高度一致。请注意,SDL 仅用于输入处理,不涉及窗口管理或音频功能。

在 Godot 4.5 版本之前,引擎使用的是自己编写的手柄支持代码。这可能会导致某些手柄出现运行异常(比如按键错乱或无法识别)。不过,这套自定义代码目前仍然被用于支持 Android 和 Web 平台上的手柄,因此相关问题可能只会在这两个平台上出现。

请注意,诸如方向盘、踏板和 HOTAS 等更专业的设备测试较少,可能并不总是按照预期工作。目前尚未实现在这些设备上的力反馈覆盖。如果你有机会使用这些设备,请不要犹豫,在 GitHub 上报告错误

在本指南中,你将学会:

  • 如何编写你的输入逻辑,从而支持键盘和控制器输入。

  • 控制器的行为如何与键盘/鼠标输入不同。

  • 解决 Godot 中控制器的问题。

支持通用输入

得益于 Godot 的输入动作系统,Godot 可以同时支持键盘和控制器输入,而不需要编写单独的代码路径。你不应该在脚本中对控制器的按键进行硬编码,应该在项目设置中创建输入动作,这些动作引用按键和控制器输入。

输入动作在 使用 InputEvent 页面上有详细解释。

备注

与键盘输入不同,支持鼠标和控制器输入的动作(例如在第一人称游戏中环顾四周)将需要不同的代码路径,因为它们必须分开处理。

我应该使用哪个输入单例方法?

有 3 种方式可以以模拟感知的方式获得输入:

  • 当你有两个轴(例如摇杆或 WASD 运动)并且希望两个轴都作为单个输入时,请使用 Input.get_vector()

# `velocity` will be a Vector2 between `Vector2(-1.0, -1.0)` and `Vector2(1.0, 1.0)`.
# This handles deadzone in a correct way for most use cases.
# The resulting deadzone will have a circular shape as it generally should.
var velocity = Input.get_vector("move_left", "move_right", "move_forward", "move_back")

# The line below is similar to `get_vector()`, except that it handles
# the deadzone in a less optimal way. The resulting deadzone will have
# a square-ish shape when it should ideally have a circular shape.
var velocity = Vector2(
        Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
        Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")
).limit_length(1.0)
  • 当你有一个轴可以双向移动时(比如飞行摇杆上的油门),或者你想单独处理不同的轴时,使用 Input.get_axis()

# `walk` will be a floating-point number between `-1.0` and `1.0`.
var walk = Input.get_axis("move_left", "move_right")

# The line above is a shorter form of:
var walk = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
  • 对于其他类型的模拟输入,例如处理一个触发器或一次处理一个方向,使用 Input.get_action_strength()

# `strength` will be a floating-point number between `0.0` and `1.0`.
var strength = Input.get_action_strength("accelerate")

对于非模拟数字/布尔输入(只有“按下”或“未按下”的值),如控制器按钮、鼠标按钮或键盘按键,使用 Input.is_action_pressed()

# `jumping` will be a boolean with a value of `true` or `false`.
var jumping = Input.is_action_pressed("jump")

备注

如果你想要知道上一帧是否刚刚按下了某个输入,请使用 Input.is_action_just_pressed(),不要使用 Input.is_action_pressed()Input.is_action_pressed() 是只要输入处于按下的状态就会返回 true,而 Input.is_action_just_pressed() 只会在按下按钮后的一帧内返回 true

振动

振动(也叫触觉反馈)可以用来提升游戏手感。比如在赛车游戏中,可以通过振动来体现车辆当前所处的路面,也可以在撞车时进行突然的振动。

请使用 Input 单例的 start_joy_vibration 方法开启游戏手柄的振动。要提前结束振动,请使用 stop_joy_vibration(尤其适用于启动时未指定时长的情况)。

在移动设备上,你还可以使用 vibrate_handheld 来振动设备本身(与游戏手柄的振动是分开的)。在 Android 上,这个功能需要在导出项目前启用 Android 导出预设中的 VIBRATE 权限。

备注

振动可能造成某些玩家的不适。请确保在游戏中提供滑块,用来禁用振动或降低振动强度。

键盘/鼠标和控制器输入之间的差异

如果你习惯于处理键盘和鼠标输入,可能会对控制器处理特定情况的方式感到惊讶。

死区

与键盘和鼠标不同,控制器提供带有模拟输入的轴。模拟输入的好处是它们为动作提供了额外的灵活性。不像数字输入只能提供 0.01.0 的强度,模拟输入可以提供 0.01.0 之间的任何强度。缺点是没有死区系统,由于控制器的物理结构,模拟轴的强度永远不会等于 0.0。相反,它将徘徊在一个低值,如 0.062。这种现象被称为漂移,在旧的或有问题的控制器上会更加明显。

以赛车游戏作为一个现实世界的例子。多亏模拟输入,我们可以将车慢慢地转向某个方向。然而,如果没有死区系统,即使玩家不接触摇杆,车也会自己慢慢转向。这是因为轴的方向强度不总在我们期望的时候等于 0.0。因为我们不希望车在这种情况下自动转向,我们定义了一个“死区”值 0.2,它将忽略所有强度低于 0.2 的输入。死区的理想值是既足够高,可以忽略摇杆漂移引起的输入;又足够低,不会忽略玩家的实际输入。

Godot 提供了内置的死区系统来解决这个问题。默认值是 0.5,但你可以在“项目设置”的“输入映射”选项卡中针对具体的动作进行调整。Input.get_vector() 可以在第五个参数中指定死区。如果没有指定,则会计算向量中的所有动作死区的平均值。

“回显”事件

与键盘输入不同,按住一个控制器按钮,如十字方向键,不会产生固定间隔的重复输入事件(也被称为“回显”事件)。这是因为操作系统本来就不会为控制器输入发送“回显”事件。

如果你想让控制器按钮发送回显事件,你将不得不通过代码生成 InputEvent 对象,并使用 Input.parse_input_event() 定期解析它们。这可以在 Timer 节点的帮助下完成。

窗口焦点

与键盘输入不同的是,手柄输入在默认情况下可以被操作系统中 所有 的窗口识别到,即使这些窗口当前并没有获得焦点(即不是当前激活的窗口)。

虽然这对于第三方分屏功能很有用,但也可能产生不利影响。玩家在与另一个窗口互动时可能会意外地将控制器输入传送到正在执行的项目。

如果你希望在项目(游戏)失去焦点时,忽略掉手柄的输入事件,可以将 ProjectSettings.input_devices/joypads/ignore_joypad_on_unfocused_application 设置为 true 。或者,你也可以将 Input.ignore_joypad_on_unfocused_application 设置为 true

防止省电模式

与键盘和鼠标输入不同,控制器输入不会抑制睡眠和省电措施(例如在经过一定时间后关闭屏幕)。

为了解决这个问题,Godot 在项目运行时默认启用预防省电。如果你注意到在使用游戏手柄玩游戏时系统正在关闭其显示屏,请检查项目设置中的显示 > 窗口 > 节能 > 保持屏幕开启的值。

故障排除

参见

你可以在 GitHub 上查看控制器支持的已知问题列表

Godot 无法识别我的控制器。

首先,检查你的控制器是否被其他应用程序识别。你可以使用 Gamepad Tester 网站来确认你的控制器是否被识别。

在 Windows 上,Godot 一次最多支持 4 个控制器。这是因为 Godot 使用了 XInput API,该 API 一次最多支持 4 个控制器。超过该限制的其他控制器将被 Godot 忽略。

我的控制器的按钮或轴映射不正确。

首先,如果你的控制器提供某种固件更新实用程序,请确保运行它以从制造商处获取最新修复程序。例如,Xbox One 和 Xbox Series 控制器可以使用 Xbox 附件应用 更新固件。(此应用程序仅在 Windows 上执行,因此你必须使用 Windows 电脑或支持 USB 的 Windows 虚拟机来更新控制器的固件。)更新控制器的固件后,如果你在无线模式下使用控制器,请取消配对控制器并将其重新与你的 PC 配对。

如果按钮映射不正确,这可能是由于 Godot 使用的 SDL 游戏控制器数据库或 Godot 游戏控制器数据库中的错误映射。在这种情况下,你需要为你的控制器创建自定义映射。

创建映射的方法有很多。一种选择是以“大屏幕模式”启动 Steam,配置你的控制器,然后在 Steam 安装目录下的 config/config.vdf 文件中查找 SDL_GamepadBind 条目。另一种选择是使用 SDL's testcontroller application (该链接仅提供 Windows 可执行文件)。一旦你为控制器创建了可用的映射,就可以在运行 Godot 之前通过定义 SDL_GAMECONTROLLERCONFIG 环境变量来测试它:

export SDL_GAMECONTROLLERCONFIG="your:mapping:here"
./path/to/godot.x86_64

当你对自定义映射感到满意后,可以通过在 Godot game controller database 上发起拉取请求,为下一个 Godot 版本贡献你的映射,或者在 Godot repository 中创建一个议题。

由于 Godot 使用 SDL 3 来处理手柄输入,所以也请大家考虑为 SDL 库贡献你的手柄按键映射。你可以直接在 official SDL gamepad database 上发起一个拉取请求(Pull Request),或者在 SDL repository 里创建一个议题(Issue)。

备注

请注意,市面上存在一些“通用”控制器(通常其 Input.get_joy_info(device)["raw_name"] 属性包含 "USB Gamepad" 字符串)。不同的通用控制器可能使用相同的芯片,但按钮布局却各不相同。因此,为其中某一款控制器创建映射很可能会与其它控制器产生冲突,因为引擎无法区分使用相同芯片的控制器。

我的控制器在特定的平台上工作,但在另一个平台上却不能。

Linux

如果你使用自编译引擎二进制文件,请确保它是使用 udev 支持进行编译的。默认启用该功能,但可以通过在 SCons 命令行上指定 udev=no 来禁用 udev 支持。如果你使用的是 Linux 发行版提供的引擎二进制文件,请仔细检查它是否是使用 udev 支持进行编译的。

控制器在没有 udev 支持的情况下仍然可以工作,但可靠性较差,因为必须使用定期轮询来检查游戏过程中控制器是否连接或断开连接(热插拔)。

Android

如页面顶部所述,移动平台上的控制器支持依赖于自定义实现,而非使用 SDL 处理输入。这意味着相比桌面平台,移动平台上的控制器支持可能不够稳定可靠。

对移动平台上基于 SDL 的控制器输入支持,计划在未来的版本中推出。

Web

与“原生”平台相比,Web 控制器的支持通常不太可靠。不同浏览器对控制器的支持质量往往差别很大。因此,如果玩家无法使用控制器,你可能需要指示他们使用其他浏览器。

与移动平台类似,未来版本中计划在 Web 平台上支持基于 SDL 的控制器输入。