進階物理插值
前述方法在多數遊戲中都能得到令人滿意的效果,但有些情況下你可能會希望進一步調整,以獲得最佳和最流暢的體驗。
自動物理插值的例外情境
即使啟用了物理插值,某些情況下你可能會希望為某個 Node 分支)關閉自動插值,改由自己手動控制插值行為。
你可以透過每個節點都具備的 Node.physics_interpolation_mode 屬性來達成。例如,如果你對一個節點關閉插值,所有子節點預設也會繼承這個設定,這樣就能輕鬆為整個子場景關閉物理插值。
最常見需要手動插值的情境就是攝影機。
攝影機
在許多情況下,Camera3D 可以像其他節點一樣使用自動插值。但為了獲得最佳效果(尤其在低物理更新率下),建議你對攝影機手動進行插值。
這是因為玩家對攝影機移動極為敏感。例如,若 Camera3D 每 1/10 秒才微調一次(10 TPS 時),這種突兀跳動很容易被察覺。你可以在 _process 中每個畫面更新時手動移動攝影機,並自行插值追蹤目標,這樣效果會流暢許多。
手動攝影機插值
確保攝影機使用全域座標空間
你要手動插值攝影機時,第一步是確保 Camera3D 的變換是在*全域座標* 下進行,而不是繼承自移動的父節點。否則,父節點移動與攝影機本身的移動會互相干擾,導致插值出現異常。
有兩種方式可以這樣做:
將 Camera3D 移到自己的分支,使其不再是移動物件的子節點。
呼叫 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 處理。
有時候(尤其是攝影機)你可能會想混合插值與非插值操作:
第一人稱攝影機可以用插值方式對齊玩家位置(例如用 Node3D.get_global_transform_interpolated),但攝影機旋轉則直接用滑鼠控制,不進行插值。
第三人稱攝影機類似,可以透過 Node3D.get_global_transform_interpolated 取得目標座標(look at),但攝影機本身的位置則用滑鼠視角直接控制,不進行插值。
攝影機型態有許多變化,不過可以看出很多情境下,手動管理插值、關閉自動插值能得到更好的結果。
關閉其他節點的插值
雖然攝影機是最常見的例子,但有時你也會希望其他節點自行管理插值、甚至完全不插值。舉例來說,上視角遊戲的玩家角色旋轉是由滑鼠視角控制的,此時將物理旋轉插值關閉,就能讓角色旋轉即時對應滑鼠操作。
MultiMeshes(多重網格)
多數視覺節點都是一個節點對應一個可見實例,但 MultiMeshes 可以在單一節點下控制多個實例。因此,它額外提供了針對*各個實例*控制插值的功能。如果你使用插值功能的 MultiMeshes,建議深入了解這些 API。
詳細資訊請參閱 MultiMesh 文件。