Materialization
簡介
從「元件原型 (Component Prototype
)」或「實體原型 (Entity Prototype
)」建立實體或元件實例的過程稱為 具現化。
烘焙到地圖資產中的場景原型的具現化,與透過程式碼使用 Frame.Create
API 建立的實例的具現化,遵循相同的規則和執行流程。
原型 vs 實例
元件實例和實體實例是遊戲狀態的一部分;換句話說,它們可以在執行時被操作。在 DSL 中宣告的元件用於生成其對應的Component Prototypes
。程式碼生成的原型遵循命名慣例MyComponentPrototype
。
Component Prototypes
和Entity Prototypes
都是 資產;這意味著它們不是遊戲狀態的一部分,在執行時是不可變的,並且必須在所有客戶端上始終保持一致。每個Component Prototype
都有一個ComponentPrototypeRef
,可使用Frame.FindPrototype<MyComponentNamePrototype>(MyComponentPrototypeRef)
來查找對應的資產。
元件原型
可以擴展Component Prototype
以包含可能不直接用於具現化的數據。例如,這允許在特定元件的實例之間共享數據,或從幀中排除只讀數據以保持遊戲狀態精簡。
程式碼生成的Component Prototypes
是部分類,可以輕鬆擴展:
- 建立一個名為
MyComponentNamePrototype.Partial.cs
的 C# 檔案; - 將腳本主體放入
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
的具現化(包括場景原型)按以下順序執行步驟:
- 建立一個空實體。
- 對於
Component Prototype
中包含的每個Entity Prototype
:- 在堆疊上建立元件實例;
- 將
Component Prototype
具現化到元件實例中; - 呼叫
MaterializeUser()
(儘管實現此方法是 可選的);並且, - 將元件添加到實體,這會觸發
ISignalOnComponentAdded<MyComponent>
信號。
- 為每個具現化的實體調用
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
方法。