Manual
動畫視圖更新器
QuantumEntityViewUpdater
指令碼負責觸發帶有AnimatorComponent
的每個實體的Animate
回調。該實體必須有一個實現IAnimatorEntityViewComponent
介面的QuantumEntityViewComponent
。動畫器附加元件包含AnimatorMecanim
和AnimatorPlayables
,可以直接使用或擴展以實現更加自定義的工作流程。
動畫器 Mecanim
提供 Quantum 動畫器與 Unity 的 Mecanim 系統之間的整合,實現平滑的過渡和動畫的幀同步。它提供幀率控制,並支援使用動畫圖層,這是AnimatorPlayables
不支援的功能。

為防止 Unity 中自動觸發過渡,請確保在Animator Graph
設定中勾選Mute Graph Transitions On Export
。
動畫器 Playables
它使用 Playable Graphs 來處理動畫,而非 Mecanim,提供更加自定義的動畫更新方式。然而,並非所有 Mecanim 功能都支援此設定,例如動畫圖層。
動畫器事件
Quantum 動畫器支援Instant
事件和Time-Window
事件。事件回調會在指定的剪輯播放時觸發,並在事件創建時配置的時刻執行。這意味著兩個使用相同剪輯的狀態將共享事件。
即時事件
C#
public abstract class AnimatorInstantEventAsset : AnimatorEventAsset, IAnimatorEventAsset
{
/// <inheritdoc cref="AnimatorEventAsset.OnBake"/>
public new AnimatorEvent OnBake(AnimationClip unityAnimationClip, AnimationEvent unityAnimationEvent)
{
var quantumAnimatorEvent = new AnimatorInstantEvent();
quantumAnimatorEvent.AssetRef = Guid;
quantumAnimatorEvent.Time = FP.FromFloat_UNSAFE(unityAnimationEvent.time);
return quantumAnimatorEvent;
}
}
即時事件只會在動畫器播放包含該事件的剪輯時觸發一次Execute()
函數。一個好的模式是繼承基礎AnimatorInstantEventAsset
並以不同程序覆寫基礎類別的方法,例如自定義Frame.Siganls
。
可以使用Create->Quantum->Assets...
創建事件資產

時間窗口事件
過程與即時事件創建類似,但在這種情況下,必須在 Unity 剪輯中創建第二個相同類型的事件來標記事件執行的結束。對於此事件類型,會在 Unity 剪輯中定義的開始和結束幀之間的每一幀觸發 Execute 回調。

如果想創建具有類似行為的新事件,例如有OnEnter()
、Execute()
及OnExit()
方法,透過繼承AnimatorTimeWindowEventAsset
類別:
C#
/// <summary>
/// This is a sample of how to use SampleTimeWindowEvent events. Use it as a base to create a new class inheriting from AnimatorInstantEventAsset and
/// implement a custom logic on Execute method
/// </summary>
[Serializable]
public class ExampleTimeWindowEventAsset : AnimatorTimeWindowEventAsset
{
public override unsafe void OnEnter(Frame f, AnimatorComponent* animatorComponent, LayerData* layerData)
{
Debug.Log($"[Quantum Animator ({f.Number})] OnEnter animator time window event.");
}
public override unsafe void Execute(Frame f, AnimatorComponent* animatorComponent, LayerData* layerData)
{
Debug.Log($"[Quantum Animator ({f.Number})] Execute animator time window event.");
}
public override unsafe void OnExit(Frame f, AnimatorComponent* animatorComponent, LayerData* layerData)
{
Debug.Log($"[Quantum Animator ({f.Number})] OnExit animator time window event.");
}
}
事件設置
按照以下步驟在動畫剪輯上定義事件:
- 在
QuantumEntityPrototype
中包含Animator
元件的 GameObject 上新增AnimationEventData
元件:

- 在動畫窗口中選擇剪輯並新增一個新的
Unity AnimationEvent
:

- 選擇創建的事件,並按照下圖指定
Function
及Object
:

動畫器狀態信號
動畫器狀態信號現在會在透過過渡或FadeTo
的更改狀態時被調用。這些信號由AnimatorBehaviourSystem
實現,也可以由其他系統實現。
C#
// Called in the first update when the Animator begins entering a state.
void OnAnimatorStateEnter(Frame f, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
// Called every frame while the Animator is updating a state.
void OnAnimatorStateUpdate(Frame f, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time, AnimatorStateType stateType);
// Called when the Animator updates for the last time, before fading to another state.
void OnAnimatorStateExit(Frame f, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
這些信號提供以下內容:
Frame
: 調用信號時的當前Frame
EntityRef
: 與提供給信號的AnimatorComponent
相關聯的EntityRef
- 指向
AnimatorComponent
的指標 - 當前
AnimatorGraph
和AnimatorState
的參考 - 指向
LayerData
的指標 - 調用信號時
LayerData
的當前對應時間
OnAnimatorStateUpdate
還提供AnimatorStateType
,這是一個包含以下內容的enum
:
None
FromState
CurrentState
ToState
在過渡期間,ToState
和FromState
都會發送OnAnimatorStateUpdate
信號,因此提供AnimatorStateType
以幫助區分正在更新的是哪一個。例如,這可以用於防止AnimatorStateBehaviours
在被過渡出去時執行其更新。
動畫器狀態行為
與動畫器事件不同,Animator State Behaviour
可以在使用相同剪輯的狀態之間獨立,並支援OnStateEnter
、OnStateUpdate
及OnStateExit
。
設置
1- 在 Unity 動畫器窗口中,選擇要新增行為的狀態,然後將AnimatorStateBehaviourHolder
指令碼附加到該狀態。

2- 使用動畫器套件中包含的DebugBehavior
範例,向AnimatorStateBehaviourAssets
列表新增新元素。

3- 使用選單選項內嵌圖形資產:Tool->Quantum Animator->Bake All Graph Assets
。
4- 將AnimatorBehaviourSystem
新增到Default Config Systems
資產。
5- 每次播放狀態時,應該會記錄調試訊息。
AnimatorStateBehaviour
是抽象的,因此繼承它的類別必須實現以下方法:
C#
bool OnStateEnter(Frame f, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
bool OnStateUpdate(Frame f, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time, AnimatorStateType stateType);
bool OnStateExit(Frame f, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
這些與之前提到的動畫器狀態信號幾乎相同;然而,它們返回一個bool
。如果使用提供的AnimatorBehaviourSystem
,返回true
將阻止系統檢查可能屬於AnimatorState
的剩餘AnimatorStateBehaviours
。例如,如果一個AnimatorStateBehaviour
使用FadeTo
過渡到新狀態,返回true
將阻止剩餘的AnimatorStateBehaviours
執行它們的OnAnimatorStateUpdate
調用。
FadeTo
FadeTo可以用於過渡到獨立於條件的另一個狀態。啟用AllowFadeToTransitions
以啟用此功能。
C#
// public void FadeTo(Frame frame, AnimatorComponent* animatorComponent, string stateName, bool setIgnoreTransitions, FP deltaTime)
//usage example
if (input->Run.WasPressed)
{
graph.FadeTo(frame, filter.AnimatorComponent, "Running", true, 0);
}
根運動
Quantum 動畫器使用 Unity 的AnimationClip
資訊將運動曲線資料內嵌到AnimatorGraph
資產中。內嵌的資訊稍後可以在模擬中用於根據當前播放的剪輯修改實體的位置和旋轉。

使用方法
需要啟用Root Motion
選項以允許處理運動資料。還需要正確設置源模型的Animation
分頁中的根運動配置,更多資訊請參考Unity的根運動文檔。

禁用狀態根運動
預設下,只要啟用了Root Motion
選項,AnimatorGraph
中所有狀態的運動資料都會啟用。將Animator Disable Root Motion Behaviour
新增到 Mecanim 狀態可以確保該狀態被排除在根運動計算之外。

使用範例
QuantumAnimation 僅負責計算每個 Quantum 幀的當前運動增量,允許用戶使用生成的資料來更新實體的變換。以下程式碼展示如何應用OnAnimatorRootMotion3D
和OnAnimatorRootMotion2D
的範例。
C#
// Handles 3D root motion
public void OnAnimatorRootMotion3D(Frame f, EntityRef entity, AnimatorFrame deltaFrame, AnimatorFrame currentFrame)
{
//Return in case there is no motion delta
if (deltaFrame.Position == FPVector3.Zero && deltaFrame.RotationY == FP._0) return;
if (f.Unsafe.TryGetPointer<Transform3D>(entity, out var transform))
{
// Create a quaternion representing the inverse of the current frame's Y-axis rotation
var currentFrameRotation = FPQuaternion.CreateFromYawPitchRoll(currentFrame.RotationY, 0, 0);
currentFrameRotation = FPQuaternion.Inverse(currentFrameRotation);
// Rotate the delta position by the inverse current rotation to align movement
var newPosition = currentFrameRotation * deltaFrame.Position;
// Apply the transform's rotation to the new position to get the world displacement
var displacement = transform->Rotation * newPosition;
var kccSettings = f.FindAsset<KCCSettings>(f.Unsafe.GetPointer<KCC>(entity)->Settings);
// Compute an adjusted target hit position for raycasting
var targetHitPosition =(displacement.XOZ.Normalized * FP._0_33 * 2 ) + displacement;
// Perform a raycast in the direction of the intended motion to detect potential collisions with statics
var hits = f.Physics3D.RaycastAll(transform->Position, targetHitPosition.XOZ, targetHitPosition.Magnitude, -1,
QueryOptions.HitStatics);
if (hits.Count <= 0)
{
// If no collision, disable the character controller temporarily
if (f.Unsafe.TryGetPointer<KCC>(entity, out var kcc))
{
kcc->SetActive(false);
}
// Apply the motion and rotation to the transform
transform->Position += displacement;
transform->Rotate(FPVector3.Up, deltaFrame.RotationY * FP.Rad2Deg);
}
else
{
// If there is collision, enable the character controller
if (f.Unsafe.TryGetPointer<KCC>(entity, out var kcc))
{
kcc->SetActive(true);
}
}
}
}
public void OnAnimatorRootMotion2D(Frame f, EntityRef entity, AnimatorFrame deltaFrame, AnimatorFrame currentFrame)
{
//Return in case there is no motion delta
if (deltaFrame.Position == FPVector3.Zero && deltaFrame.RotationY == FP._0) return;
if (f.Unsafe.TryGetPointer<Transform2D>(entity, out var transform))
{
// Calculate new rotation by applying delta
FP newRotation = transform->Rotation + deltaFrame.RotationY;
// Normalize rotation to keep it within [-π, π]
while (newRotation < -FP.Pi) newRotation += FP.PiTimes2;
while (newRotation > FP.Pi) newRotation += -FP.PiTimes2;
// Rotate delta movement vector based on new orientation
var deltaMovement = FPVector2.Rotate(deltaFrame.Position.XZ, newRotation);
// Apply movement and rotation to the transform
transform->Position += deltaMovement;
transform->Rotation = newRotation;
}
}
Back to top