This document is about: QUANTUM 3
SWITCH TO

Materialization

簡介

從「元件原型 (Component Prototype)」或「實體原型 (Entity Prototype)」建立實體或元件實例的過程稱為 具現化

烘焙到地圖資產中的場景原型的具現化,與透過程式碼使用 Frame.Create API 建立的實例的具現化,遵循相同的規則和執行流程。

原型 vs 實例

元件實例和實體實例是遊戲狀態的一部分;換句話說,它們可以在執行時被操作。在 DSL 中宣告的元件用於生成其對應的Component Prototypes。程式碼生成的原型遵循命名慣例MyComponentPrototype

Component PrototypesEntity Prototypes都是 資產;這意味著它們不是遊戲狀態的一部分,在執行時是不可變的,並且必須在所有客戶端上始終保持一致。每個Component Prototype都有一個ComponentPrototypeRef,可使用Frame.FindPrototype<MyComponentNamePrototype>(MyComponentPrototypeRef)來查找對應的資產。

元件原型

可以擴展Component Prototype以包含可能不直接用於具現化的數據。例如,這允許在特定元件的實例之間共享數據,或從幀中排除只讀數據以保持遊戲狀態精簡。

程式碼生成的Component Prototypes是部分類,可以輕鬆擴展:

  1. 建立一個名為MyComponentNamePrototype.Partial.cs的 C# 檔案;
  2. 將腳本主體放入Quantum.Prototypes命名空間;

然後,可以向Component Prototype資產添加額外數據,並實現部分方法MaterializeUser()以添加自定義具現化邏輯。

如果元件原型需要生成額外的 Unity 原型適配器,則預設情況下 會將其作為部分類發出。有關解決方法,請查看Unity 原型適配器部分。

示例

以下示例展示了在 Arcade Racing 範例 中找到的Vehicle元件的具現化。

Vehicle元件主要包含在執行時計算的動態值。根據設計選擇,這些變量不應在 Unity 編輯器中初始化,因此 DSL 中的元件定義對這些參數使用ExcludeFromPrototype屬性,以將它們從設計師可以在 Unity 編輯器中操作的VehiclePrototype資產中排除。Nitro參數是唯一可以編輯的部分,以允許設計師決定特定Vehicle的初始硝基數量。

Qtn

component Vehicle
{
    [ExcludeFromPrototype]
    ComponentPrototypeRef Prototype;

    [ExcludeFromPrototype]
    Byte Flags;
    [ExcludeFromPrototype]
    FP Speed;
    [ExcludeFromPrototype]
    FP ForwardSpeed;
    [ExcludeFromPrototype]
    FPVector3 EngineForce;
    [ExcludeFromPrototype]
    FP WheelTraction;

    [ExcludeFromPrototype]
    FPVector3 AvgNormal;

    [ExcludeFromPrototype]
    array<Wheel>[4] Wheels;

    FP Nitro;
}

VehiclePrototype資產被擴展以為設計師提供可自定義的只讀參數。因此,VehiclePrototype資產可以為特定車輛實體原型「類型」的所有實例保存共享值。Vehicle元件中的Prototype參數類型為ComponentPrototypeRef,它是等同於AssetRef的元件特定參考。為了填充它,使用部分方法MaterializeUser()來分配VehiclePrototype的參考。

C#

using Photon.Deterministic;
using Quantum.Inspector;
using System;

namespace Quantum.Prototypes
{
public unsafe partial class VehiclePrototype
{
    // PUBLIC METHODS

    [Header("Engine")]
    public FP EngineForwardForce = 130;
    public FP EngineBackwardForce = 120;
    public FPVector3 EngineForcePosition;
    public FP ApproximateMaxSpeed = 20;

    [Header("Hand Brake")]
    public FP HandBrakeStrength = 10;
    public FP HandBrakeTractionMultiplier = 1;

    [Header("Resistances")]
    public FP AirResistance = FP._0_02;
    public FP RollingResistance = FP._0_10 * 6;
    public FP DownForceFactor = 0;
    public FP TractionGripMultiplier = 10;
    public FP AirTractionDecreaseSpeed = FP._0_50;

    [Header("Axles")]
    public AxleSetup FrontAxle = new AxleSetup();
    public AxleSetup RearAxle = new AxleSetup();

    [Header("Nitro")]
    public FP MaxNitro = 100;
    public FP NitroForceMultiplier = 2;

    // PARTIAL METHODS
    partial void MaterializeUser(Frame frame, ref Vehicle result, in PrototypeMaterializationContext context)
    {
        result.Prototype = context.ComponentPrototypeRef;
    }

    [Serializable]
    public class AxleSetup
    {
        public FPVector3 PositionOffset;
        public FP Width = 1;
        public FP SpringForce = 120;
        public FP DampingForce = 175;
        public FP SuspensionLength = FP._0_10 * 6;
        public FP SuspensionOffset = -FP._0_25;
    }
}
}

VehiclePrototype中的參數保存計算元件實例中動態值所需的值,這些動態值會影響附加了Vehicle元件的實體的行為。例如,當玩家獲得額外的Nitro時,Vehicle元件中存儲的值會限制在VehiclePrototype中的MaxNitro值內。這在不同步的情況下強制實施限制,並保持遊戲狀態精簡。

C#

namespace Quantum
{
    public unsafe partial struct Vehicle
    {
        public void AddNitro(Frame frame, EntityRef entity, FP amount)
        {
            var prototype = frame.FindPrototype<Vehicle_Prototype>(Prototype);
            Nitro = FPMath.Clamp(Nitro + amount, 0, prototype.MaxNitro);
        }
    }
}

具現化順序

每個Entity Prototype的具現化(包括場景原型)按以下順序執行步驟:

  1. 建立一個空實體。
  2. 對於Component Prototype中包含的每個Entity Prototype
    1. 在堆疊上建立元件實例;
    2. Component Prototype具現化到元件實例中;
    3. 呼叫MaterializeUser()(儘管實現此方法是 可選的);並且,
    4. 將元件添加到實體,這會觸發ISignalOnComponentAdded<MyComponent>信號。
  3. 為每個具現化的實體調用ISignalOnEntityPrototypeMaterialized
    • 載入地圖 / 場景:在所有場景原型具現化後,為所有實體和Entity Prototype對調用該信號。
    • Frame.Create()建立:在原型具現化後立即調用該信號。

Component Prototype具現化步驟按預先確定的順序具現化默認元件。

C#

Transform2D
Transform3D
Transform2DVertical
PhysicsCollider2D
PhysicsBody2D
PhysicsCollider3D
PhysicsBody3D
PhysicsJoints2D
PhysicsJoints3D
PhysicsCallbacks2D
PhysicsCallbacks3D
CharacterController2D
CharacterController3D
NavMeshPathfinder
NavMeshSteeringAgent
NavMeshAvoidanceAgent
NavMeshAvoidanceObstacle
View
MapEntityLink

所有默認元件具現化完成後,使用者定義的元件按字母順序具現化。

C#

MyComponentAA
MyComponentBB
MyComponentCC
...

Unity原型適配器

如果元件的任何欄位使用ReplaceTypeHintAttribute或是以下類型之一:

  • EntityRef
  • EntityPrototypeRef
  • ComponentPrototypeRef
  • ComponentPrototypeRef<T>

那麼 Quantum 將生成額外的元件原型適配器類型。適配器類型位於Quantum.Prototypes.Unity命名空間中,並且具有與其源原型相同的欄位,但以下類型會進行替換:

  • EntityRef -> QuantumEntityPrototype
  • EntityPrototypeRef -> QUnityEntityPrototypeRef
  • ComponentPrototypeRef -> QUnityComponentPrototypeRef
  • ComponentPrototypeRef<T> -> QUnityComponentPrototypeRef<T>
  • 使用[ReplaceTypeHintAttribute]的參數代替欄位類型

如果生成了適配器,基於 Unity MonoBehaviour 的原型包裝器將使用它而不是原型。這樣一來,原型看起來可以與 Unity 物件參考一起使用,同時仍然與模擬兼容。

大多數情況下,這對使用者來說是完全透明的。但是,此過程有一個副作用,即阻止元件原型作為部分類發出。原因是適配器的欄位和轉換應該與源原型同步,但一旦涉及部分類,程式碼生成就無法知道添加了什麼。可以使用[CodeGen(ForcePartialPrototype)]屬性來強制部分元件原型,但在這種情況下,請確保在適配器中實現ConvertUser方法。

Back to top