控制器、手柄和摇杆

Godot支持数百种控制器模型,这要归功于社区提供的 SDL游戏控制器数据库

控制器支持Windows、macOS、Linux、Android、iOS和HTML5。

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

在本指南中,你将学会:

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

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

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

支持通用导出

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

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

备注

与键盘输入不同,支持鼠标和控制器输入的动作将需要不同的代码路径,例如在第一人称游戏中四处查看,因为这些必须被分开处理。

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

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

  • 当你有两个轴,如操纵杆或WASD运动,并希望两个轴都表现为单一输入时,使用 Input.get_vector() :

GDScriptC#

  1. # `velocity` will be a Vector2 between `Vector2(-1.0, -1.0)` and `Vector2(1.0, 1.0)`.
  2. # This handles deadzone in a correct way for most use cases.
  3. # The resulting deadzone will have a circular shape as it generally should.
  4. var velocity = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
  5. # The line below is similar to `get_vector()`, except that it handles
  6. # the deadzone in a less optimal way. The resulting deadzone will have
  7. # a square-ish shape when it should ideally have a circular shape.
  8. var velocity = Vector2(
  9. Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
  10. Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")
  11. ).limit_length(1.0)
  1. // `velocity` will be a Vector2 between `Vector2(-1.0, -1.0)` and `Vector2(1.0, 1.0)`.
  2. // This handles deadzone in a correct way for most use cases.
  3. // The resulting deadzone will have a circular shape as it generally should.
  4. Vector2 velocity = Input.GetVector("move_left", "move_right", "move_forward", "move_back");
  5. // The line below is similar to `get_vector()`, except that it handles
  6. // the deadzone in a less optimal way. The resulting deadzone will have
  7. // a square-ish shape when it should ideally have a circular shape.
  8. Vector2 velocity = new Vector2(
  9. Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left"),
  10. Input.GetActionStrength("move_back") - Input.GetActionStrength("move_forward")
  11. ).LimitLength(1.0);
  • 当你有一个轴可以双向移动时,比如飞行操纵杆上的油门,或者你想单独处理不同的轴时,使用 Input.get_axis() :

GDScriptC#

  1. # `walk` will be a floating-point number between `-1.0` and `1.0`.
  2. var walk = Input.get_axis("move_left", "move_right")
  3. # The line above is a shorter form of:
  4. var walk = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
  1. // `walk` will be a floating-point number between `-1.0` and `1.0`.
  2. float walk = Input.GetAxis("move_left", "move_right");
  3. // The line above is a shorter form of:
  4. float walk = Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left");
  • 对于其他类型的模拟输入,例如处理一个触发器或一次处理一个方向,使用 Input.get_action_strength() :

GDScriptC#

  1. # `strength` will be a floating-point number between `0.0` and `1.0`.
  2. var strength = Input.get_action_strength("accelerate")
  1. // `strength` will be a floating-point number between `0.0` and `1.0`.
  2. float strength = Input.GetActionStrength("accelerate");

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

GDScriptC#

  1. # `jumping` will be a boolean with a value of `true` or `false`.
  2. var jumping = Input.is_action_pressed("jump")
  1. // `jumping` will be a boolean with a value of `true` or `false`.
  2. bool jumping = Input.IsActionPressed("jump");

备注

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

在3.4之前的Godot版本,如3.3, Input.get_vector()Input.get_axis() 不可用。只有 Input.get_action_strength()Input.is_action_pressed() 在Godot 3.3中可用。

振动

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

请使用 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 节点的帮助下完成。

窗口焦点

与键盘输入不同,控制器的输入可以被操作系统中的所有窗口看到,包括未持有焦点的窗口。

虽然这对于「第三方分割画面功能<https://nucleus-coop.github.io/>`__很有用,但它也可能产生不利影响。玩家在与另一个视窗互动时可能会意外地将控制器输入传送到正在执行的项目。

如果你希望在项目窗口未聚焦时忽略事件,则需要使用以下脚本创建一个 autoload 调用``Focus``,并使用它检查所有输入:

  1. # Focus.gd
  2. extends Node
  3. var focused := true
  4. func _notification(what: int) -> void:
  5. match what:
  6. NOTIFICATION_APPLICATION_FOCUS_OUT:
  7. focused = false
  8. NOTIFICATION_APPLICATION_FOCUS_IN:
  9. focused = true
  10. func input_is_action_pressed(action: StringName) -> bool:
  11. if focused:
  12. return Input.is_action_pressed(action)
  13. return false
  14. func event_is_action_pressed(event: InputEvent, action: StringName) -> bool:
  15. if focused:
  16. return event.is_action_pressed(action)
  17. return false

然后,不要使用“Input.is_action_pressed(action)”,而是使用“Focus.input_is_action_pressed(action)”,其中“action”是输入操作的名称。另外,不要使用“event.is_action_pressed(action)”,而是使用“Focus.event_is_action_pressed(event,action)”,其中“event”是InputEvent引用,“action”是事件的名称。输入动作。

防止省电模式

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

为了解决这个问题,Godot在项目运作时预设启用节能预防。如果你注意到系统在玩游戏手把时关闭了显示屏,请检查项目设定中的**显示>视窗>节能>保持屏幕开启**的值。

在Linux上,节能预防要求引擎能够使用D-Bus。如果在Flatpak中运作项目,请检查D-Bus是否已安装且可存取,因为沙盒限制可能会导致预设无法实作此操作。

故障排除

参见

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

Godot 无法识别我的控制器。

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

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

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

首先,如果你的控制器提供某种固件更新实用程序,请确保运作它以从制造商处获取最新修复程序。例如,Xbox One和Xbox Series控制器可以使用「Xbox Accessories应用程序<https://www.microsoft.com/en-us/p/xbox-accessories/9nblggh30xj3>」更新其固件。(此应用程序仅在Windows上执行,因此你必须使用Windows电脑或支持USB的Windows虚拟机器来更新控制器的固件。)更新控制器的固件后,请取消控制器配对,然后再次将其与PC配对(如果你是这样)在无线模式下使用控制器。

如果按钮存在映射错误,可能是由于来自 SDL 游戏控制器数据库的错误的映射。你可以在链接的仓库中提交拉取请求,为下一个 Godot 版本提供映射更新。

There are many ways to create mappings. One option is to use the mapping wizard in the official Joypads demo. Once you have a working mapping for your controller, you can test it by defining the SDL_GAMECONTROLLERCONFIG environment variable before running Godot:

Linux/macOSWindows (cmd)Windows (PowerShell)

  1. export SDL_GAMECONTROLLERCONFIG="your:mapping:here"
  2. ./path/to/godot.x86_64
  1. set SDL_GAMECONTROLLERCONFIG=your:mapping:here
  2. path\to\godot.exe
  1. $env:SDL_GAMECONTROLLERCONFIG="your:mapping:here"
  2. path\to\godot.exe

要在非桌面平台上测试映射,或者用额外的控制器映射来分发你的项目,你可以通过调用 Input.add_joy_mapping() 尽早在脚本的 _ready() 函数中添加它们。

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

Linux

如果你使用自编译引擎二进位档案,请确保它是使用udev支持进行编译的。预设启用此功能,但可以透过在SCons命令列上指定「udev=no」来停用udev支持。如果你使用的是Linux发行版提供的引擎二进位档案,请仔细检查它是否是使用udev支持进行编译的。

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

HTML5

与 “本地” 平台相比,HTML5 控制器的支持通常不太可靠。各个浏览器对控制器的支持质量往往相差甚远。因此,如果玩家无法使用他们的控制器,你可能不得不指示他们使用不同的浏览器。