マテリアライゼーション
はじめに
Component Prototype
やEntity Prototype
からエンティティやコンポーネントのインスタンスを作成するプロセスは、__マテリアライズ__と呼ばれます。
マップアセットにベイクされたシーンプロトタイプのマテリアライズは、Frame.Create
APIを使用してコードによって作成されたインスタンスのマテリアライズと同じルールと実行フローに従います。
プロトタイプとインスタンス
コンポーネントインスタンスとエンティティインスタンスはゲームの状態の一部であり、言い換えれば、実行時に操作可能です。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プロトタイプアダプタセクションを参照してください。
例
以下の例は、アーケードレーシングテンプレートに見られるVehicle
コンポーネントのマテリアライズを示しています。
Vehicle
コンポーネントは、主に実行時に計算される動的値を保持します。設計選択により、これらの変数はUnityエディターで初期化されるべきではないため、DSLでのコンポーネント定義ではこれらのパラメータにExcludeFromPrototype
属性を使用して、Unityエディターでデザイナーが操作できるVehiclePrototype
アセットから除外しています。Nitro
パラメータのみが編集可能で、デザイナーが特定のVehicle
をどの程度のニトロで初期化するかを決定できるようにしています。
C#
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
パラメータは、AssetRef
に対するコンポーネント特定の同等物であるComponentPrototypeRef
タイプです。それを populated するために、部分的な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
のマテリアライズ、シーンプロトタイプを含む、は次の手順を順番に実行します:
- 空のエンティティが作成されます。
Entity Prototype
に含まれる各Component 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 Prototype Adapters
コンポーネントの任意のフィールドが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
メソッドを実装することを確認してください。