This document is about: QUANTUM 3
SWITCH TO

This page is a work in progress and could be pending updates.

Manual

動畫視圖更新器

QuantumEntityViewUpdater指令碼負責觸發帶有AnimatorComponent的每個實體的Animate回調。該實體必須有一個實現IAnimatorEntityViewComponent介面的QuantumEntityViewComponent。動畫器附加元件包含AnimatorMecanimAnimatorPlayables,可以直接使用或擴展以實現更加自定義的工作流程。

動畫器 Mecanim

提供 Quantum 動畫器與 Unity 的 Mecanim 系統之間的整合,實現平滑的過渡和動畫的幀同步。它提供幀率控制,並支援使用動畫圖層,這是AnimatorPlayables不支援的功能。

Debug.
Muting Transitions

為防止 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...創建事件資產

Events.

時間窗口事件

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

Time-Window event.

如果想創建具有類似行為的新事件,例如有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.");
    }
  }

事件設置

按照以下步驟在動畫剪輯上定義事件:

  1. QuantumEntityPrototype中包含Animator元件的 GameObject 上新增AnimationEventData元件:
EventAsset creation.
  1. 在動畫窗口中選擇剪輯並新增一個新的Unity AnimationEvent
EventAsset creation.
  1. 選擇創建的事件,並按照下圖指定FunctionObject
EventAsset creation.

動畫器狀態信號

動畫器狀態信號現在會在透過過渡或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的指標
  • 當前AnimatorGraphAnimatorState的參考
  • 指向LayerData的指標
  • 調用信號時LayerData的當前對應時間

OnAnimatorStateUpdate還提供AnimatorStateType,這是一個包含以下內容的enum

  • None
  • FromState
  • CurrentState
  • ToState

在過渡期間,ToStateFromState都會發送OnAnimatorStateUpdate信號,因此提供AnimatorStateType以幫助區分正在更新的是哪一個。例如,這可以用於防止AnimatorStateBehaviours在被過渡出去時執行其更新。

動畫器狀態行為

動畫器事件不同,Animator State Behaviour可以在使用相同剪輯的狀態之間獨立,並支援OnStateEnterOnStateUpdateOnStateExit

設置

1- 在 Unity 動畫器窗口中,選擇要新增行為的狀態,然後將AnimatorStateBehaviourHolder指令碼附加到該狀態。

State Behaviours.

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

State Behaviours.

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資產中。內嵌的資訊稍後可以在模擬中用於根據當前播放的剪輯修改實體的位置和旋轉。

Baked Root Motion data.

使用方法

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

Baked Root Motion data.

禁用狀態根運動

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

Baked Root Motion data.

使用範例

QuantumAnimation 僅負責計算每個 Quantum 幀的當前運動增量,允許用戶使用生成的資料來更新實體的變換。以下程式碼展示如何應用OnAnimatorRootMotion3DOnAnimatorRootMotion2D的範例。

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