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.

進階物理插值

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

自動物理插值的例外情境

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

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

../../../_images/physics_interpolation_mode.webp

It is worth noting that, both in 2D and 3D, physics interpolation is performed on the local transform of each instance. During rendering, interpolated local transforms are passed down to children.

This means that if a parent has physics_interpolation_mode set to On, but the child is set to Off, the child will still be interpolated if the parent is moving. Only the child's local transform is uninterpolated. Controlling the on / off behavior of nodes therefore requires some thought and planning.

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

攝影機

在許多情況下,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 文件。