This document is about: QUANTUM 2
SWITCH TO

Animation

概述

在Quantum中,有兩個不同的方式以處理動畫:

  • 從Unity輪詢遊戲狀態;以及
  • 使用自訂動畫器的確定性動畫。

基於輪詢的動畫

大多數遊戲使用動畫以通信一個物件的狀態到玩家。舉例而言,當可遊玩的角色正在行走或跳躍,動畫實際上是在原處的動畫,並且認知到的動作是由程式碼來驅動。

換句話說,管理角色的相關(Unity)動畫器的指令碼是無狀態的,並且只需基於從遊戲模擬(在Quantum中)輪詢而來的資料,衍生值以作為動畫參數來傳遞。

請注意: 如果遊戲遊玩系統依賴於根運動或必須注意動畫狀態,則請跳到下一章節。

基於動畫概念的輪詢已經在API範例中被執行。下列程式碼片段是從PlayerAnimations指令碼中擷取,並且驅動動作動畫。

  1. 當實體被具現化,其Initialize()方法將被調用,其快取實體的EntityRef及目前的QuantumGame;後者是純粹為了方便性。
  2. 每個Unity Update()MovementAnimation()功能被調用。
  3. MovementAnimation()功能使用先前快取的EntityRef,從CharacterController3D來輪詢資料。
  4. 相關的動畫器參數從被輪詢的資料中被衍生。
  5. 被計算的資料被傳遞到Unity動畫器。

C#

// This snippet is extracted from the Quantum API Sample.

public unsafe class PlayerAnimation : MonoBehaviour
{
    [SerializeField] private Animator _animator = null;

    private EntityRef _entityRef = default;
    private QuantumGame _game = null;

    // Animator Parameters
    private const string FLOAT_MOVEMENT_SPEED = "floatMovementSpeed";
    private const string FLOAT_MOVEMENT_VERTICAL = "floatVerticalMovement";
    private const string BOOL_IS_MOVING = "boolIsMoving";

    // This method is called from the PlayerSetup.cs Initialize() method which is registered
    // to the EntityView's OnEntityInstantiated event located on the parent GameObject
    public void Initialize(PlayerRef playerRef, EntityRef entityRef){
        _playerRef = playerRef;
        _entityRef = entityRef;
        _game = QuantumRunner.Default.Game;
    }

    // Update is called once per frame
    void Update(){
        MovementAnimation();
    }

    private void MovementAnimation() {
        var kcc = _game.Frames.Verified.Unsafe.GetPointer<CharacterController3D>(_entityRef);
        bool isMoving = kcc->Velocity.Magnitude.AsFloat > 0.2f;

        _animator.SetBool(BOOL_IS_MOVING, isMoving);

        if (isMoving) {
            _animator.SetFloat(FLOAT_MOVEMENT_SPEED, kcc->Velocity.Magnitude.AsFloat);
            _animator.SetFloat(FLOAT_MOVEMENT_VERTICAL, kcc->Velocity.Z.AsFloat);
        }
        else {
            _animator.SetFloat(FLOAT_MOVEMENT_SPEED, 0.0f);
            _animator.SetFloat(FLOAT_MOVEMENT_VERTICAL, 0.0f);
        }
    }

觸發事件

有些動畫是基於一個遊戲中發生的特定事件;比如,一個玩家按下跳躍或被一個敵人擊中。在這些案例中,通常而言,最好從模擬中引發一個事件,並讓檢視來接聽它。這確保解耦,並與基於輪詢的動畫方法協同工作。

為了取得針對事件及回調的一個完整的解釋,請參見操作手冊中的Quantum ECS > Game Callbacks頁面。

當準時的行動發生,API範例使用事件以通信,其應產生一個視覺的回應;比如,可遊玩的角色跳躍。

在Quantum中的MovementSystem讀取玩家輸入,並且在傳遞移動值到CharacterController3D之前,計算移動值。系統也接聽跳躍鍵按鈕。如果按鍵WasPressed,它引發一個PlayerJump事件,並且調用CharacterController3DJump()方法。

C#

using Photon.Deterministic;
namespace Quantum
{
    public unsafe struct PlayerMovementFilter
    {
        public EntityRef EntityRef;
        public PlayerID* PlayerID;
        public Transform3D* Transform;
        public CharacterController3D* Kcc;
    }

    unsafe class MovementSystem : SystemMainThreadFilter<PlayerMovementFilter>
    {
        public override void Update(Frame f, ref PlayerMovementFilter filter)
        {
            var input = f.GetPlayerInput(filter.PlayerID->PlayerRef);

            // Other Logic

            if (input->Jump.WasPressed)
            {
                f.Events.PlayerJump(filter.PlayerID->PlayerRef);
                filter.Kcc->Jump(f);
            }

            // Other Logic
        }
    }
}

在Unity側,PlayerAnimation指令碼接聽PlayerJump事件,並且回應它。這些步驟是完成這件事所必要的:

  1. 定義一個可以收到事件的方法——void Jump(EventPlayerJump e)
  2. 訂閱該事件。
  3. 當事件被收到時,透過將事件中包含的PlayerRef與之前快取的PlayerRef進行比較,來檢查它是否適用於指令碼所在的遊戲物件。
  4. 觸發/設定在Unity動畫器中的參數。

C#

// This snippet is extracted from the Quantum API Sample.

public unsafe class PlayerAnimation : MonoBehaviour
{
    [SerializeField] private Animator _animator = null;

    private PlayerRef _playerRef = default;
    private QuantumGame _game = null;

    // Animator Parameters
    private const string TRIGGER_JUMP = "triggerJump";

    public void Initialize(PlayerRef playerRef, EntityRef entityRef)
    {
        _playerRef = playerRef;

        // Other Logic

        QuantumEvent.Subscribe<EventPlayerJump>(this, Jump);
    }

    private void Jump(EventPlayerJump e)
    {
        if (e.PlayerRef != _playerRef) return;
        _animator.SetTrigger(TRIGGER_JUMP);
    }

訣竅

  • 將模型及其動畫器元件放置在一個子物件上。
  • 事件不是遊戲狀態的一部分,因此不可用於延遲/重新加入的玩家。因此如果遊戲已經開始,建議首先透過輪詢最新的遊戲狀態來初始化動畫狀態。
  • 針對需要以100%的精確度被觸發的動畫,比如一個勝利慶祝,使用已同步的事件。
  • 針對需要敏捷度的動畫,比如被擊中,使用常規的非同步的事件。
  • 使用EventCanceled回調以優雅地從被一個已取消的非同步事件所觸發的動畫中退出。當事件作為一個預測的一部分被引發,但在一個已驗證幀時被復原時,這可能發生。

確定性動畫

使用一個確定性動畫系統的主要優勢是精確刷新的動畫,其在所有客戶端上都100%同步,並且在一個復原的時候貼齊到正確狀態。雖然這聽起來很理想,但它帶來一個性能影響,因為現在動畫及其狀態是被模擬的遊戲狀態的一部分。實際上,只有一些遊戲要求及受益於一個確定性動畫系統;比如對打遊戲及 一些 運動遊戲。

自訂動畫器是一個啟用確定性動畫的工具。它從Unity的Mecanim控制器來內嵌資訊,並且匯入每個組態,比如狀態、在狀態之間的轉換、動作剪輯等等。

自訂動畫器的開發已經被暫停,這是因為其所建立的,與Unity的Mecanim的相依性。然而,程式碼已開源,可在Addons > Custom Animator頁面下載。此頁面也提供針對如何匯入及使用自訂動畫器的一個概述及一個快速指引。

請記得其功能受限,並且它可能需要根據您的需要進行調整。

訣竅

  • 在使用自訂動畫器之前,想一想動畫是否繫於遊戲遊玩,或僅僅是一個遊戲遊玩的視覺效果。前者的話,自訂動畫器是一個合適的解決方案,否則基於輪詢的動畫將是好方法。
Back to top