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.

使用 InputEvent

什麼是 InputEvent?

無論在任何作業系統或平台上,管理輸入通常都很複雜。為了簡化這個過程,Godot 提供了一個特殊的內建型別 InputEvent。這個型別可以被設為包含多種輸入事件。輸入事件會在引擎中流動,並根據用途在多個位置被接收。

以下是一個簡單範例:當按下 Escape 鍵時關閉遊戲:

func _unhandled_input(event):
    if event is InputEventKey:
        if event.pressed and event.keycode == KEY_ESCAPE:
            get_tree().quit()

然而,更乾淨且靈活的做法是使用 Godot 提供的 InputMap 功能,可讓你定義輸入動作並分配多個不同按鍵。如此一來,你可以將多個按鍵(如鍵盤的 ESC 與手把的開始鍵)對應到同一個動作。你也可以在專案設定中輕鬆變更對應,無需修改程式碼,甚至可以擴充成遊戲內的自訂按鍵功能,讓玩家於執行時變更操作按鍵!

你可以在 專案 > 專案設定 > 輸入對應 下設定你的 InputMap,並像這樣使用這些動作:

func _process(delta):
    if Input.is_action_pressed("ui_right"):
        # Move right.

它的運作方式是什麼?

每個輸入事件最初來自使用者/玩家(當然,也可以自行產生 InputEvent 並餵給引擎,這在手勢等應用時很有用)。各平台的 DisplayServer 會從作業系統讀取事件,然後傳遞給根 Window

視窗的 Viewport 會依序對收到的輸入事件執行多項處理:

../../_images/input_event_flow.webp
  1. 如果 Viewport 內嵌了 Window,Viewport 會以視窗管理員的角色解讀事件(例如調整 Window 大小或移動視窗)。

  2. 接著,若有內嵌 Window 取得焦點,事件會送往該 Window 並於其 Viewport 中處理,之後視為已處理。若沒有內嵌 Window 聚焦,事件會依下列順序傳遞給目前視口的節點。

  3. 首先,會呼叫任何有覆寫 Node._input() 的節點(且未用 Node.set_process_input() 停用輸入處理)。若事件在此被消耗,可以呼叫 Viewport.set_input_as_handled() 阻止事件繼續傳播。這確保你可以在 GUI 之前就攔截感興趣的事件。對於遊戲主要輸入,通常建議用 Node._unhandled_input(),因為這讓 GUI 有機會優先處理。

  4. 其次,系統會嘗試將輸入傳遞至 GUI,看有無控制項可接收。若有,將會呼叫 Control 的虛擬方法 Control._gui_input(),並發出 "gui_input" 訊號(此方法可於腳本繼承時覆寫)。若控制項需「消耗」該事件,會呼叫 Control.accept_event(),事件便不再繼續傳遞。你可透過 Control.mouse_filter 屬性,控制 Control 是否透過 Control._gui_input() 回呼接收滑鼠事件,以及事件是否會繼續傳遞。

  5. 如果事件尚未被消耗,且有覆寫 Node._shortcut_input() 停用),則會呼叫該回呼。這僅針對 InputEventKeyInputEventShortcutInputEventJoypadButton。如事件被消耗,可呼叫 Viewport.set_input_as_handled() 阻止事件繼續傳播。此回呼非常適合處理快捷鍵相關事件。

  6. 如果事件尚未被消耗,且有覆寫 Node._unhandled_key_input() 停用),則會呼叫該回呼。這僅當事件為 InputEventKey 時才會發生。如事件被消耗,可呼叫 Viewport.set_input_as_handled() 阻止事件繼續傳播。這個回呼很適合處理鍵盤事件。

  7. 如果事件仍未被消耗,且有覆寫 Node._unhandled_input() 停用),則會呼叫該回呼。若有函式消耗事件,可呼叫 Viewport.set_input_as_handled() 阻止事件繼續傳播。這個回呼很適合處理全螢幕遊戲事件(GUI 元件啟用時不會收到)。

  8. 如果事件到現在都沒被消耗,且已啟用 物件選取,則該事件會用於物件選取。對於根視口,也可在 專案設定 啟用此功能。若是 3D 場景且 Viewport 有指派 Camera3D,則會從點擊方向發射射線到物理世界,命中物件時會呼叫該物件的 CollisionObject3D._input_event()。2D 場景則同理,呼叫 CollisionObject2D._input_event()

將事件傳送給所有子孫節點時,Viewport 會採用反向深度優先順序:從場景樹底層節點到根節點。Window 與 SubViewport 不參與此流程。

../../_images/input_event_scene_flow.webp

備註

這個順序不適用於 Control._gui_input(),GUI 事件的分派會依事件位置或焦點控制元件而異。GUI 滑鼠 事件也會沿著場景樹向上傳遞,受限於前述的 Control.mouse_filter 設定。不過,這類事件只會傳給目標 Control 節點的直接父系。GUI 鍵盤和手把 事件則*不會*沿場景樹傳遞,只能由收到事件的控制元件處理,否則會以非 GUI 事件形式傳遞至 Node._unhandled_input()

由於 Viewport 不會將事件傳遞給其他 SubViewport,你必須使用下列其中一種方法:

  1. 使用 SubViewportContainer,它會在 Node._input()Control._gui_input() 執行後,自動將事件傳送給其子 SubViewports

  2. 根據自身需求實作事件傳遞邏輯。

這符合 Godot 節點導向設計,使專用子節點能處理並消耗特定事件,而父級或場景根節點則可依需要提供更一般性的行為。

InputEvent 結構解析

InputEvent 只是基本的內建類型,不代表任何特定事件,只包含基本資訊,如事件 ID(每個事件遞增)、裝置索引等。

InputEvent 有多種專門型別,說明如下表所示:

事件

說明

InputEvent

空輸入事件。

InputEventKey

包含一個按鍵碼、Unicode 值,以及修飾鍵。

InputEventMouseButton

包含點擊資訊,例如按鈕、修飾鍵等。

InputEventMouseMotion

包含移動資訊,如相對/絕對位置與速度。

InputEventJoypadMotion

包含搖桿/手把類比軸資訊。

InputEventJoypadButton

包含搖桿/手把按鈕資訊。

InputEventScreenTouch

包含多點觸控的按下/釋放資訊。(僅限行動裝置)

InputEventScreenDrag

包含多點觸控的拖曳資訊。(僅限行動裝置)

InputEventMagnifyGesture

包含位置、縮放係數與修飾鍵。

InputEventPanGesture

包含位置、位移量與修飾鍵。

InputEventMIDI

包含 MIDI 相關資訊。

InputEventShortcut

包含一組快捷鍵。

InputEventAction

包含一般動作。這類事件通常由開發者作為回饋手動產生。(詳見下文)

輸入動作

輸入動作是將零個或多個 InputEvent 群組成一個容易理解的名稱(例如預設的「ui_left」可同時代表手把左鍵和鍵盤左方向鍵)。動作本身不一定要對應單一 InputEvent,但這種抽象化設計能讓你在撰寫遊戲邏輯時同時支援多種輸入裝置。

這能帶來下列好處:

  • 相同程式碼適用於不同裝置的不同輸入(如 PC 鍵盤與主機手把)。

  • 可於執行時重新設定輸入對應。

  • 可於程式中在執行時自動觸發動作。

你可以在「專案設定」的 輸入對應 分頁建立動作並分配輸入事件。

任何事件都具備 InputEvent.is_action()InputEvent.is_pressed()InputEvent.is_echo() 這些方法。

你也可以從遊戲程式碼主動送出一個動作(例如用於手勢偵測)。Input 單例物件提供此功能:Input.parse_input_event()。通常你會這樣用:

var ev = InputEventAction.new()
# Set as ui_left, pressed.
ev.action = "ui_left"
ev.pressed = true
# Feedback.
Input.parse_input_event(ev)

也參考

關於如何在專案設定中新增輸入動作,請參閱 建立圖示 教學。

輸入映射表

從程式碼自訂與重映射輸入是常見需求。如果你的系統完全依賴動作,InputMap 單例非常適合於執行時重新分配或建立不同動作。這個單例的狀態不會自動儲存(需自行處理),其預設值來自專案設定(project.godot)。因此,任何這類動態系統都須自行決定設定儲存方式。