使用 AnimationTree

前言

通過 AnimationPlayer,Godot 擁有你在所有遊戲引擎中能找到的最靈活的動畫系統之一。幾乎可以在任何節點或資源中對任何屬性進行動畫處理,以及專門的變換、貝塞爾、函式呼叫、音訊和子動畫軌道,這樣的能力相當獨特。

然而, 通過 AnimationPlayer 混合這些動畫的支援相對有限, 只能設定固定的交叉漸變過渡時間.

建立 AnimationTree

在開始之前,需要明確說明 AnimationTree 節點本身不包含動畫,而是使用 AnimationPlayer 節點中的動畫。如此一來,你可以像往常一樣編輯你的動畫(或從 3D 場景匯入),再透過這個額外的節點來控制播放。

AnimationTree 最常見的用法是在 3D 場景中。當你從 3D 格式匯入場景時,通常會帶有動畫(可能有多個動畫,或是從一個大動畫分割而來)。最終,匯入的 Godot 場景會把動畫放在一個 AnimationPlayer 節點中。

你很少會直接在 Godot 中使用匯入的場景(通常是產生實例或繼承),你可以在包含匯入場景的新場景裡放置一個 AnimationTree 節點。然後,將這個 AnimationTree 指向匯入場景中的 AnimationPlayer 節點。

舉例來說,在 第三人稱射擊遊戲展示 就是這樣設置的:

../../_images/animtree1.png

這裡為玩家建立了一個以 CharacterBody3D 為根節點的新場景。場景中產生實體原本的 .dae``(Collada)檔案,並新增一個 ``AnimationTree 節點。

建立樹狀結構

可以在 AnimationTree 中使用三種主要節點型別:

  1. 動畫節點,會從已連結的 AnimationPlayer 取得動畫來使用。

  2. 動畫根節點,用來混合子節點。

  3. 動畫混合節點,會在 AnimationNodeBlendTree 內部做單一圖表的多輸入混合。

你可以在 AnimationTree 設定下列幾種型別之一作為根節點:

../../_images/animtree2.png
  • AnimationNodeAnimation :從動畫列表中選擇並播放一個動畫。這是最簡單的根節點,一般不會直接用作根節點。

  • AnimationNodeBlendTree :包含許多*混合*型節點,例如 mix、blend2、blend3、one shot 等。這是最常見的根節點之一。

  • AnimationNodeStateMachine :在圖表中包含多個根節點作為子節點。每個節點都代表一個*狀態*,並提供多種切換狀態的功能。

  • AnimationNodeBlendSpace2D :可以將根節點放在二維混合空間中,透過 2D 空間控制混合位置來混合多個動畫。

  • AnimationNodeBlendSpace1D :上述功能的一維簡化版本。

混合樹

AnimationNodeBlendTree 可包含用於混合的根節點和常規節點。節點從選單新增到圖中:

../../_images/animtree3.webp

所有混合樹預設都包含一個 Output (輸出)節點,為了讓動畫播放,必須有個東西與其相連。

測試此功能最簡單的方法是直接連接一個 Animation (動畫)節點:

../../_images/animtree4.png

這會簡單地重播動畫. 確保 AnimationTree 節點對實際發生的事情是啟動的.

以下是可用節點的簡短描述:

混合2/混合3

這些節點將通過使用者指定輸入的兩個或三個混合值之間進行混合:

../../_images/animtree5.gif

若需要更複雜的混合,建議改用混合空間 (Blend Space)。

混合也可以使用過濾器,你可以單獨控制哪些軌道會經過混合函式。這對於動畫分層(Layering)非常有用。

../../_images/animtree6.png

OneShot

此節點將執行子動畫, 並在完成後返回. 可以用於定制淡入淡出時間, 以及篩檢程式.

../../_images/animtree6b.gif

在設定請求並改變動畫播放後,OneShot 節點會在下個處理影格自動清除請求,做法是將其 request 屬性設為 AnimationNodeOneShot.ONE_SHOT_REQUEST_NONE

# Play child animation connected to "shot" port.
animation_tree.set("parameters/OneShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
# Alternative syntax (same result as above).
animation_tree["parameters/OneShot/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE

# Abort child animation connected to "shot" port.
animation_tree.set("parameters/OneShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT)
# Alternative syntax (same result as above).
animation_tree["parameters/OneShot/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT

# Get current state (read-only).
animation_tree.get("parameters/OneShot/active"))
# Alternative syntax (same result as above).
animation_tree["parameters/OneShot/active"]

時間縮放

這個節點可以用來對動畫樹內的任何子節點發出「尋找」(seek)指令。你可以用這個節點類型,從頭或指定位置播放 AnimationNodeBlendTree 裡的動畫。

在設定時間和改變動畫播放後,尋找節點通過設定其 seek_position 值為 -1.0,在下一個程序影格自動進入睡眠模式。

# Play child animation from the start.
animation_tree.set("parameters/TimeSeek/seek_request", 0.0)
# Alternative syntax (same result as above).
animation_tree["parameters/TimeSeek/seek_request"] = 0.0

# Play child animation from 12 second timestamp.
animation_tree.set("parameters/TimeSeek/seek_request", 12.0)
# Alternative syntax (same result as above).
animation_tree["parameters/TimeSeek/seek_request"] = 12.0

時間縮放

允許你用 scale 參數調整連到 in 輸入的動畫速度(或反轉),設為 0 時會暫停動畫。

轉場效果

這是一個非常簡易的狀態機(當你不想用 StateMachine 節點時可用)。動畫可以連接到輸出,也能指定過渡時間。設定請求並改變動畫播放後,過渡節點會在下個處理影格自動清除請求,做法是將其 transition_request 設為空字串("")。

# Play child animation connected to "state_2" port.
animation_tree.set("parameters/Transition/transition_request", "state_2")
# Alternative syntax (same result as above).
animation_tree["parameters/Transition/transition_request"] = "state_2"

# Get current state name (read-only).
animation_tree.get("parameters/Transition/current_state")
# Alternative syntax (same result as above).
animation_tree["parameters/Transition/current_state"]

# Get current state index (read-only).
animation_tree.get("parameters/Transition/current_index"))
# Alternative syntax (same result as above).
animation_tree["parameters/Transition/current_index"]

二維混合空間

BlendSpace2D 是一個在二維空間進行高級混合的節點. 將點新增到一個二維空間, 然後可以控制位置來確定混合:

../../_images/animtree7.gif

可以控制X和Y的範圍(為方便起見, 還可以標記它們). 預設情況下, 可以在任何位置放置點(只需按右鍵坐標系統或使用 新增點 按鈕)將自動生成德洛內三角形.

../../_images/animtree8.gif

也可以通過禁用 自動三角形 選項來手動繪製三角形, 雖然基本上沒必要這麼做:

../../_images/animtree9.png

最後, 可能會更改混合模式. 預設情況下, 混合是通過在最近的三角形內插點來實作的. 當處理二維動畫(逐影格)時, 您可能希望切換到 離散 模式. 此外, 如果您想在離散動畫之間切換時保持目前播放位置, 請使用 進位元 模式. 此模式可在 混合 功能表中更改:

../../_images/animtree10.png

一維混合空間

這類似於二維混合空間, 但在一維空間中(所以不需要三角形).

狀態機

這個節點是一個狀態機,根節點都是狀態。根節點可以建立並通過線路連接。狀態通過*轉換*連接,它們是具有特殊性質的連接。轉換是單向的,但是可以用兩個來達到雙向連接。

../../_images/animtree11.gif

轉場有很多種類型:

../../_images/animtree12.png
  • Immediate (立即):將立即切換到下一個狀態。目前狀態將結束,並與新狀態的開頭相混合。

  • Sync (同步):立即切換到下一個狀態,但會將新狀態快進並到舊狀態的播放位置。

  • At End (末尾):將等待目前狀態播放結束,然後切換到下一個狀態動畫的開頭。

過渡也有一些屬性。按一下任何過渡,它就會顯示在“屬性檢視器”面板中:

../../_images/animtree13.png
  • Switch Mode (切換模式)為過渡型別(見上文),可以在此處建立後修改。

  • Auto Advance*(自動前進)當達到此狀態時將自動開啟轉換。最適合 *At End 切換模式。

  • Advance Condition (前進條件)會在條件成立時打開自動前進。這是一個可以用變數名填充的自訂文字欄位。可以從程式碼中修改變數(稍後將對此進行詳細介紹)。

  • Xfade Time (疊化時間)是在這個狀態和下一個狀態之間交叉漸變的時間。

  • Priority (優先順序)與程式碼中的 travel() 函式一起使用(後述)。當從一個狀態到另一個狀態時,會優先使用優先順序較低的過渡。

  • Disabled (禁用)允許禁用此轉換(它不會在行程或自動前進期間使用)。

為了更好的混合

在 Godot 4.0+ 中,為了使混合結果具有確定性(可重現且始終一致),混合屬性值必須具有特定的初始值。例如,在要混合兩個動畫的情況下,如果一個動畫具有屬性軌跡而另一個動畫沒有,則計算混合動畫時就好像後一個動畫具有具有初始值的屬性軌跡一樣。

當對 Skeleton3D 骨骼使用位置/旋轉/縮放 3D 軌跡時,初始值為 Bone Rest。對於其他屬性,初始值為“0”,如果軌道存在於“RESET”動畫中,則使用其第一個關鍵影格的值。

例如,下面的AnimationPlayer有兩個動畫,但其中一個缺少Position的Property軌道。

../../_images/blending1.webp

這意味著缺少的動畫會將這些位置視為「Vector2(0, 0)」。

../../_images/blending2.webp

這個問題可以透過加入一個位置屬性軌跡作為「RESET」動畫的初始值來解決。

../../_images/blending3.webp ../../_images/blending4.webp

備註

請注意,「RESET」動畫的存在是為了定義最初載入物件時的預設姿勢。假設它只有一影格,並且預計不會使用時間軸進行播放。

另請記住,插值型別設定為線性角度或立方角的旋轉 3D 軌道和 2D 旋轉的屬性軌道將防止混合動畫從初始值旋轉超過 180 度。

這對於 Skeleton3D 很有用,可以在混合動畫時防止骨頭穿透身體。因此,Skeleton3D 的 Bone Rest 值應盡可能接近可移動範圍的中點。 這意味著對於人形模型,最好以 T 形姿勢匯入它們

../../_images/blending5.webp

您可以看到,優先考慮骨骼休息的最短旋轉路徑,而不是動畫之間的最短旋轉路徑。

如果您需要透過混合動畫進行運動來將 Skeleton3D 本身旋轉超過 180 度,則可以使用 Root Motion。

根骨骼運動

處理 3D 動畫時,一種流行的技術是動畫師利用根骨骼為其餘部分骨骼製作運動動畫。這使得動畫角色的腳步與下面的地板相配對。並且允許在電影拍攝期間與物體進行精確的互動。

在 Godot 中重播動畫時,可以選擇這根骨骼作為*根運動軌跡*。這會在視覺上取消這根骨骼的變換(動畫將保持原狀)。

../../_images/animtree14.png

然後, 實際運動可以通過 AnimationTree API 作為轉換:

# Get the motion delta.
animation_tree.get_root_motion_position()
animation_tree.get_root_motion_rotation()
animation_tree.get_root_motion_scale()

# Get the actual blended value of the animation.
animation_tree.get_root_motion_position_accumulator()
animation_tree.get_root_motion_rotation_accumulator()
animation_tree.get_root_motion_scale_accumulator()

可以提供給 CharacterBody3D.move_and_slide 等函式,用來控制角色移動。

還有一個名為 RootMotionView 的工具節點,可以放置在場景中,作為你的角色和動畫的自訂地板(這個節點預設在遊戲期間禁用)。

../../_images/animtree15.gif

使用程式碼控制

建立樹和預覽之後,只剩下一個問題:“如何使用程式碼控制所有的節點?”。

要注意動畫節點就是資源,因此他們會在所有使用他們的實例之間共用。直接修改節點中的值,將會影響到場景中所有使用這個 AnimationTree 的實例。通常是不希望這樣的,不過也有一些不錯的用法,比如你可以複製貼上你的動畫樹的一部分,或者在不同的動畫樹中複用具有複雜佈局的節點(例如狀態機和混合樹)。

實際的動畫封包含在 AnimationTree 節點中, 並通過屬性存取. 檢查 AnimationTree 節點的 "參數" 部分, 查看所有可以即時修改的參數:

../../_images/animtree16.png

這很方便, 因為它可以通過 AnimationPlayer 獲得動畫效果, 甚至是 AnimationTree 本身, 允許實作非常複雜的動畫邏輯.

想要通過程式碼修改這些值, 必須獲得該屬性的路徑. 這是很容易做到的, 把滑鼠懸停在任何參數:

../../_images/animtree17.png

允許設定或讀取它們:

animation_tree.set("parameters/eye_blend/blend_amount", 1.0)
# Simpler alternative form:
animation_tree["parameters/eye_blend/blend_amount"] = 1.0

狀態機行程

Godot的“StateMachine”實現中一個不錯的功能是旅行的能力。可以指示圖形從當前狀態轉到另一個狀態,同時訪問所有中間狀態。這是通過 A* 演算法完成的。如果沒有從當前狀態開始到目標狀態結束的轉換路徑,則圖形將傳送到目標狀態。

要使用行程能力, 您應該首先從 AnimationTree 節點中檢索 AnimationNodeStateMachinePlayback 物件(其被匯出為一個屬性).

var state_machine = animation_tree["parameters/playback"]

取得之後,可以呼叫它所提供的各種函式:

state_machine.travel("SomeState")

狀態機必須正在運作才能使用行程能力。確保呼叫 start() 或選擇一個節點以**在載入時自動播放**。

../../_images/animtree18.png