控制器、遊戲手把與搖桿
Godot 內建支援數百種控制器型號。控制器在 Windows、macOS、Linux、Android、iOS 和 HTML5 上皆可使用。
請注意,像方向盤、方向舵踏板及 HOTAS 這類更專業的裝置測試較少,可能無法完全依預期運作。這些裝置的力回饋覆寫功能也尚未實作。如果你有這類裝置,歡迎直接 在 GitHub 上回報問題 。
在本指南中,你將學到:
如何設計你的輸入邏輯以同時支援鍵盤與控制器輸入。
控制器與鍵盤/滑鼠輸入行為上的差異。
如何在 Godot 中排查控制器相關問題。
支援通用輸入
由於 Godot 的輸入動作系統,你可以同時支援鍵盤與控制器輸入,無需撰寫額外的分支程式碼。你應在「專案設定」中建立 輸入動作 ,這些動作可同時對應指定的鍵盤與控制器按鈕,避免在腳本中硬編碼按鍵或控制器按鈕。
詳細說明請參見 使用 InputEvent 頁面。
備註
與鍵盤輸入不同,若要支援滑鼠與控制器對同一動作(如第一人稱視角的環顧四周),這兩者必須分開處理,因此需要不同的程式邏輯。
我該用哪個 Input 單例的方法?
有三種取得類比輸入的方法:
當有兩個軸(如搖桿或 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)
// `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.
Vector2 velocity = Input.GetVector("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.
Vector2 velocity = new Vector2(
Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left"),
Input.GetActionStrength("move_back") - Input.GetActionStrength("move_forward")
).LimitLength(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")
// `walk` will be a floating-point number between `-1.0` and `1.0`.
float walk = Input.GetAxis("move_left", "move_right");
// The line above is a shorter form of:
float walk = Input.GetActionStrength("move_right") - Input.GetActionStrength("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")
// `strength` will be a floating-point number between `0.0` and `1.0`.
float strength = Input.GetActionStrength("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");
備註
若你想判斷某輸入是否在上一個影格中『剛剛』被按下,請用 Input.is_action_just_pressed() 取代 Input.is_action_pressed()。後者在持續按下時會一直回傳 true,而 Input.is_action_just_pressed() 僅會在按下後的單一影格回傳 true。
振動
振動(也稱為*觸覺回饋*)能增強遊戲體驗。例如,在賽車遊戲中,可以用振動表現車輛行駛路面的不同,或在撞擊時產生強烈震動。
使用 Input 單例的 start_joy_vibration 方法可啟動手把振動。要提前停止(若啟動時未設定時長),則用 stop_joy_vibration。
在行動裝置上,可用 vibrate_handheld 讓整台裝置振動(獨立於手把)。Android 匯出前需於預設設定中勾選 VIBRATE 權限。
備註
部分玩家可能會排斥振動。請務必在遊戲中提供調整強度或關閉振動的選項。
鍵盤/滑鼠與控制器輸入的差異
如果你習慣只處理鍵盤與滑鼠輸入,控制器在某些情境下的行為可能會讓你感到驚訝。
死區
與鍵盤和滑鼠不同,控制器的軸是*類比*輸入。類比輸入的好處是能提供 0.0 到 1.0 的任意強度,數位輸入則只能有 0.0 或 1.0。但缺點是,若沒有死區設計,類比軸的值永遠不會精確等於 0.0,而會維持在如 0.062 這樣的低值,這種現象稱為*漂移*,在老舊或有缺陷的手把上更明顯。
以賽車遊戲為例:有了類比輸入,我們能讓車輛緩慢轉向。但若無死區,車輛即使玩家沒碰搖桿,也會自己慢慢轉彎,因為軸向強度不會真的歸零。因此我們通常設定死區(如 0.2),忽略小於此強度的輸入。理想的死區要高到能排除漂移影響,但又不會太高而誤判玩家的真實操作。
Godot 內建死區系統來解決這個問題。預設值為 0.5,但你可以在「專案設定」的「輸入對應」分頁中,針對每個動作調整死區。對於 Input.get_vector(),可指定第 5 個參數為死區值,若未指定則會取所有動作的平均死區值。
「重複」事件
與鍵盤不同,長按手把的按鈕(如方向鍵)**不會**產生固定間隔的重複輸入事件(即「重複」事件),因為作業系統對控制器輸入本就不會傳送這類事件。
若你要讓控制器按鈕也產生重複事件,需用程式自行產生 InputEvent 物件,並定時用 Input.parse_input_event() 解析。你可以配合 Timer 節點達成此效果。
視窗焦點
與鍵盤不同,控制器輸入會被作業系統上**所有**視窗接收到,即使該視窗未獲得焦點。
這對於 第三方分割畫面功能 很有幫助,但也可能有負面影響:玩家在操作其他視窗時,仍可能誤觸並將控制器輸入傳送到正在執行的專案。
若希望在專案視窗未獲焦點時忽略控制器事件,請建立一個名為 Focus 的 自動載入,並用以下腳本來檢查所有輸入:
# Focus.gd
extends Node
var focused := true
func _notification(what: int) -> void:
match what:
NOTIFICATION_APPLICATION_FOCUS_OUT:
focused = false
NOTIFICATION_APPLICATION_FOCUS_IN:
focused = true
func input_is_action_pressed(action: StringName) -> bool:
if focused:
return Input.is_action_pressed(action)
return false
func event_is_action_pressed(event: InputEvent, action: StringName) -> bool:
if focused:
return event.is_action_pressed(action)
return false
然後,請用 Focus.input_is_action_pressed(action)``(其中 ``action 為輸入動作名稱)取代 Input.is_action_pressed(action)。同理,請用 Focus.event_is_action_pressed(event, action)``(其中 ``event 是 InputEvent 參照,action 是動作名稱)取代 event.is_action_pressed(action)。
防止省電
與鍵盤與滑鼠不同,控制器輸入**不會**阻止作業系統進入睡眠或省電(如螢幕逾時自動關閉)。
因此 Godot 在專案執行時會自動啟用防止省電。如果你發現用手把遊玩時螢幕還是被關閉,請檢查「專案設定」裡 顯示 > 視窗 > 節能 > 保持螢幕開啟 是否已啟用。
在 Linux 上,防止省電機制需引擎能存取 D-Bus。若你在 Flatpak 沙盒下執行專案,請確認 D-Bus 已安裝且可用,因為沙盒限制預設會阻擋其存取。
疑難排解
也參考
你可以在 GitHub 上查看 控制器支援相關已知問題列表。
Godot 無法辨識我的控制器。
首先,請確認你的控制器能被其他應用程式辨識。你可以用 Gamepad Tester 網站測試控制器有無被正確辨識。
在 Windows 上,Godot 一次僅支援最多 4 支控制器。這是因為 Godot 採用 XInput API,而此 API 最多只支援 4 支控制器。超過此數量的控制器將會被忽略。
我的控制器在某些平台可用,但在其他平台卻不可用。
Linux
若你用自行編譯的引擎執行檔,請確認已啟用 udev 支援(預設為開啟),不然在 SCons 編譯時加入 udev=no 會導致關閉。若用的是 Linux 發行版自帶的執行檔,也請確認其是否有啟用 udev。
控制器即使沒 udev 支援仍有機會運作,但穩定性較差,因為無法即時偵測熱插拔,只能定時輪詢檢查連接變化。
HTML5
HTML5 平台的控制器支援通常不如「原生」平台穩定,不同瀏覽器的相容性有很大差異。如果玩家的控制器無法使用,可能需要請他們嘗試更換瀏覽器。