控制器、手柄和摇杆

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

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

请注意,诸如方向盘、方向盘踏板和 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")
).clamped(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")
// `jumping` will be a boolean with a value of `true` or `false`.
bool jumping = Input.IsActionPressed("jump");

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

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

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

死区

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

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

Godot 有一个内置的死区系统来解决这个问题。默认值是 0.2,但你可以在“项目设置”的“按键映射”选项卡中根据每个动作增加或减少它。对于 Input.get_vector(),可以指定死区,否则它将从向量中的所有动作计算出平均死区值。

“回声”事件

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

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

Window focus

Unlike keyboard input, controller inputs can be seen by all windows on the operating system, including unfocused windows.

While this is useful for third-party split screen functionality, it can also have adverse effects. Players may accidentally send controller inputs to the running project while interacting with another window.

If you wish to ignore events when the project window isn't focused, you will need to create an autoload called Focus with the following script and use it to check all your inputs:

# Focus.gd
extends Node

var focused := true

func _notification(what: int) -> void:
    match what:
        NOTIFICATION_WM_FOCUS_OUT:
            focused = false
        NOTIFICATION_WM_FOCUS_IN:
            focused = true


func input_is_action_pressed(action: String) -> bool:
    if focused:
        return Input.is_action_pressed(action)

    return false


func event_is_action_pressed(event: InputEvent, action: String) -> bool:
    if focused:
        return Input.is_action_pressed(action)

    return false

Then, instead of using Input.is_action_pressed(action), use Focus.input_is_action_pressed(action) where action is the name of the input action. Also, instead of using event.is_action_pressed(action), use Focus.event_is_action_pressed(event, action) where event is an InputEvent reference and action is the name of the input action.

Power saving prevention

Unlike keyboard and mouse input, controller inputs do not inhibit sleep and power saving measures (such as turning off the screen after a certain amount of time has passed).

To combat this, Godot enables power saving prevention by default when a project is running. If you notice the system is turning off its display when playing with a gamepad, check the value of Display > Window > Energy Saving > Keep Screen On in the Project Settings.

Power saving prevention is not supported on Linux in Godot 3.x, but it is supported in Godot 4.x. As a workaround, increase the screensaver's timeout period to make it less likely to occur during gameplay.

故障排除

参见

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

Godot 无法识别我的控制器。

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

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

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

有很多方法来创建映射。一个选择是使用 官方Joypads演示 中的映射向导。一旦你有了控制器可工作的映射,你可以在运行Godot之前通过定义 SDL_GAMECONTROLLERCONFIG 环境变量来测试它:

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

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

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

Linux

在 Godot 3.3 之前,官方的 Godot 可执行文件在编译时支持 udev,但自编译的可执行文件在编译时不支持 udev,除非在 SCons 命令行中传递 udev=yes。这使得控制器的热插拔支持在自编译的可执行文件中不可用。

HTML5

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

另外,请注意,在 Godot 3.3 及以后的版本中,对控制器的支持得到了很大的改善