Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

使用 InputEvent

它是什麼?

無論是在作業系統或平臺上, 管理輸入通常很複雜. 為了簡化輸入管理, 引擎提供了一個特殊的內建型別 InputEvent. 此型別可被設定成包含多種型別的輸入事件. 輸入事件通過引擎傳遞, 可在多個位置接收, 具體位置取決於目的.

這裡有一個簡單的範例,按下 ESC 鍵時關閉您的遊戲:

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

但是,使用所提供的 InputMap 功能更簡潔靈活,它允許您定義輸入操作並分配不同的鍵。這樣,您可以定義多個鍵的相同動作,例如鍵盤ESC鍵和遊戲手柄上的啟動按鈕。然後,你可以在不更新程式碼的情況下輕鬆更改專案設定中的此對應,甚至在上面建構鍵對應功能,以便您的遊戲在運作時更改鍵值對應!

您可以在**專案 > 專案設定 > 按鍵對應**下設定您的輸入對應,這些動作的使用方法如下:

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. First of all, the standard Node._input() function will be called in any node that overrides it (and hasn't disabled input processing with Node.set_process_input()). If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. This ensures that you can filter all events of interest, even before the GUI. For gameplay input, Node._unhandled_input() is generally a better fit, because it allows the GUI to intercept the events.

  4. Second, it will try to feed the input to the GUI, and see if any control can receive it. If so, the Control will be called via the virtual function Control._gui_input() and the signal "gui_input" will be emitted (this function is re-implementable by script by inheriting from it). If the control wants to "consume" the event, it will call Control.accept_event() and the event will not spread any more. Use the Control.mouse_filter property to control whether a Control is notified of mouse events via Control._gui_input() callback, and whether these events are propagated further.

  5. If so far no one consumed the event, the Node._shortcut_input() callback will be called if overridden (and not disabled with Node.set_process_shortcut_input()). This happens only for InputEventKey, InputEventShortcut and InputEventJoypadButton. If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. The shortcut input callback is ideal for treating events that are intended as shortcuts.

  6. If so far no one consumed the event, the Node._unhandled_key_input() callback will be called if overridden (and not disabled with Node.set_process_unhandled_key_input()). This happens only if the event is a InputEventKey. If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. The unhandled key input callback is ideal for key events.

  7. If so far no one consumed the event, the Node._unhandled_input() callback will be called if overridden (and not disabled with Node.set_process_unhandled_input()). If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. The unhandled input callback is ideal for full-screen gameplay events, so they are not received when a GUI is active.

  8. If no one wanted the event so far, and Object Picking is turned on, the event is used for object picking. For the root viewport, this can also be enabled in Project Settings. In the case of a 3D scene if a Camera3D is assigned to the Viewport, a ray to the physics world (in the ray direction from the click) will be cast. If this ray hits an object, it will call the CollisionObject3D._input_event() function in the relevant physics object (bodies receive this callback by default, but areas do not. This can be configured through Area3D properties). In the case of a 2D scene, conceptually the same happens with CollisionObject2D._input_event().

將事件發送到場景中的所有偵聽節點時, 視口將以反向深度優先循序執行: 從場景樹底部的節點開始, 到根節點結束。

../../_images/input_event_scene_flow.png

This order doesn't apply to Control._gui_input(), which uses a different method based on event location or focused Control.

由於視窗不會將事件傳送到其他 SubViewports <class_SubViewport>`,因此必須使用下列方法之一:

  1. Use a SubViewportContainer, which automatically sends events to its child SubViewports after Node._input() or Control._gui_input().

  2. 根據個人需求實作事件傳播。

GUI事件也在場景樹上傳播,但由於這些事件針對的是特定的控制項,所以只有目標控制項節點的第一個父節點才會收到該事件。

根據Godot基於節點的設計, 這使得專門的子節點能夠處理和消費特定的事件, 而它們的父級節點, 以及最終的場景根節點, 可以在需要時提供更通用的行為.

InputEvent 剖析

InputEvent 只是一個基本的內建型別, 它不代表任何東西, 只包含一些基本資訊, 如事件ID(每個事件增加), 裝置索引等.

InputEvent有幾種專門的型別, 如下表所述:

事件

說明

InputEvent

空輸入事件.

InputEventKey

包含一個鍵盤掃描碼和Unicode值, 以及修飾鍵.

InputEventMouseButton

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

InputEventMouseMotion

包含運動資訊, 例如相對位置, 絕對位置和速度.

InputEventJoypadMotion

包含操縱桿/ Joypad類比軸資訊.

InputEventJoypadButton

包含操縱桿/ Joypad按鈕資訊.

InputEventScreenTouch

包含多點觸控按下/釋放資訊. (僅適用於移動裝置)

InputEventScreenDrag

包含多點觸控拖動資訊. (僅適用於移動裝置)

InputEvent

包含一個鍵盤掃描碼和Unicode值, 以及修飾鍵.

InputEvent

包含一個鍵盤掃描碼和Unicode值, 以及修飾鍵.

int

包含操縱桿/ Joypad按鈕資訊.

InputEvent

容器佈局。

InputEventAction

包含一般動作. 這些事件通常由程式師作為回饋生成. (以下更多內容)

動作

動作是將零個或多個輸入事件群組為通常理解的標題(例如,預設的「ui_left」動作將手柄左輸入和鍵盤左箭頭鍵群組)。它們不需要表示輸入事件,但很有用,因為它們在編寫遊戲邏輯時抽象化了各種輸入。

這樣的做法有許多優點:

  • 相同的程式碼可以在具有不同輸入的不同裝置上工作(例如,PC上的鍵盤, 控制台上的Joypad).

  • 輸入要在運作時重新配置.

  • 輸入要在運作時重新配置.

動作可以在“專案設定”功能表的“動作”分頁中建立。

任何事件都有方法 InputEvent.is_action(), InputEvent.is_pressed() and InputEvent.

或者, 可能希望從遊戲程式碼中向遊戲提供一個動作, 一個很好的例子是偵測手勢.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

經常需要的從程式碼中定制和重新對應輸入. 如果你的整個運作流程依賴於動作, 那麼 InputMap 單例是在運作時重新分配或建立不同動作的理想選擇. 這個單例不被保存(必須手動修改), 其狀態從專案設定進行(project.godot). 所以任何這種型別的動態系統, 都需要以程式師認為最合適的方式來儲存設定.