進階物理插值

前述方法在多數遊戲中都能得到令人滿意的效果,但有些情況下你可能會希望進一步調整,以獲得最佳和最流暢的體驗。

自動物理插值的例外情境

即使啟用了物理插值,某些情況下你可能會希望為某個 Node 分支)關閉自動插值,改由自己手動控制插值行為。

你可以透過每個節點都具備的 Node.physics_interpolation_mode 屬性來達成。例如,如果你對一個節點關閉插值,所有子節點預設也會繼承這個設定,這樣就能輕鬆為整個子場景關閉物理插值。

最常見需要手動插值的情境就是攝影機。

攝影機

在許多情況下,Camera3D 可以像其他節點一樣使用自動插值。但為了獲得最佳效果(尤其在低物理更新率下),建議你對攝影機手動進行插值。

這是因為玩家對攝影機移動極為敏感。例如,若 Camera3D 每 1/10 秒才微調一次(10 TPS 時),這種突兀跳動很容易被察覺。你可以在 _process 中每個畫面更新時手動移動攝影機,並自行插值追蹤目標,這樣效果會流暢許多。

手動攝影機插值

確保攝影機使用全域座標空間

你要手動插值攝影機時,第一步是確保 Camera3D 的變換是在*全域座標* 下進行,而不是繼承自移動的父節點。否則,父節點移動與攝影機本身的移動會互相干擾,導致插值出現異常。

有兩種方式可以這樣做:

  1. 將 Camera3D 移到自己的分支,使其不再是移動物件的子節點。

../../../_images/fti_camera_worldspace.webp
  1. 呼叫 Node3D.top_level 並設為 true,這樣攝影機就會忽略父節點的變換。

常見範例

常見的自訂方式是,每個畫面更新時在 Camera3D 的 _process() 中呼叫 look_at,讓攝影機持續盯著目標節點(例如玩家)。

但這裡有個問題:如果我們對 Camera3D 的目標節點直接用傳統的 get_global_transform(),那麼攝影機只會對準目標在*當前物理更新*的座標。這不是我們想要的,因為目標每次物理更新才變動一次,攝影機看起來還是會跳躍。即使攝影機每個畫面都更新,若目標只在物理刻度才移動,還是無法獲得平滑畫面。

get_global_transform_interpolated()

我們其實希望攝影機追蹤的,是目標的*插值座標*,也就是畫面上實際算繪時的座標,而非僅是物理更新時的座標。

可以透過 Node3D.get_global_transform_interpolated 來取得插值後的變換。這個方法和 Node3D.global_transform 類似,但會在 _process() 期間取得*插值* 變換。

重要

get_global_transform_interpolated() 僅建議用於像攝影機這類特殊情境,一般遊戲邏輯不應大量使用(不論是考量效能還是確保遊戲行為正確)。

備註

除了攝影機等極少數例外,大部分遊戲邏輯都應該寫在 _physics_process() 裡,並使用 get_global_transform()get_transform() 取得物理更新時的變換(全域/區域),這才是遊戲邏輯通常需要的資訊。

手動攝影機腳本範例

以下是一個簡易的固定攝影機範例,會追蹤插值過的目標:

extends Camera3D

# Node that the camera will follow
var _target

# We will smoothly lerp to follow the target
# rather than follow exactly
var _target_pos : Vector3 = Vector3()

func _ready() -> void:
    # Find the target node
    _target = get_node("../Player")

    # Turn off automatic physics interpolation for the Camera3D,
    # we will be doing this manually
    set_physics_interpolation_mode(Node.PHYSICS_INTERPOLATION_MODE_OFF)

func _process(delta: float) -> void:
    # Find the current interpolated transform of the target
    var tr : Transform = _target.get_global_transform_interpolated()

    # Provide some delayed smoothed lerping towards the target position
    _target_pos = lerp(_target_pos, tr.origin, min(delta, 1.0))

    # Fixed camera position, but it will follow the target
    look_at(_target_pos, Vector3(0, 1, 0))

滑鼠視角

滑鼠視角(Mouse look)是很常見的攝影機控制方式。但它有個問題:鍵盤輸入可以每個物理更新取樣,但滑鼠移動事件是連續發生的,玩家預期攝影機能即時跟隨滑鼠移動,而不是等到下個物理更新才反應。

在這種情況,建議針對攝影機節點關閉物理插值(透過 Node.physics_interpolation_mode),直接在每幀將滑鼠輸入應用於攝影機旋轉,而非僅在 _physics_process 處理。

有時候(尤其是攝影機)你可能會想混合插值與非插值操作:

攝影機型態有許多變化,不過可以看出很多情境下,手動管理插值、關閉自動插值能得到更好的結果。

關閉其他節點的插值

雖然攝影機是最常見的例子,但有時你也會希望其他節點自行管理插值、甚至完全不插值。舉例來說,上視角遊戲的玩家角色旋轉是由滑鼠視角控制的,此時將物理旋轉插值關閉,就能讓角色旋轉即時對應滑鼠操作。

MultiMeshes(多重網格)

多數視覺節點都是一個節點對應一個可見實例,但 MultiMeshes 可以在單一節點下控制多個實例。因此,它額外提供了針對*各個實例*控制插值的功能。如果你使用插值功能的 MultiMeshes,建議深入了解這些 API。

詳細資訊請參閱 MultiMesh 文件。