マテリアライズ
はじめに
Component Prototype
/Entity Prototype
から、コンポーネント/エンティティのインスタンスを生成するプロセスは、マテリアライズと呼ばれます。
マップアセットにベイクされたシーンプロトタイプのマテリアライズは、Frame.Create
APIを使用したコードからのマテリアライズと同じルールと実行フローに従います。
プロトタイプとインスタンス
コンポーネントインスタンスとエンティティインスタンスはゲームステートの一部で、実行時に操作可能なものです。DSLで宣言されたコンポーネントは、対応するComponent Prototype
を生成するために使用されます。生成されたプロトタイプのコードは、命名規則MyComponentPrototype
に従います。
Component Prototype
とEntity Prototype
はどちらもアセットで、ゲームステートの一部ではなく、実行時に不変であり、すべてのクライアント上で常に同一である必要があります。各Component Prototype
はComponentPrototypeRef
を持ち、Frame.FindPrototype<MyComponentNamePrototype>(MyComponentPrototypeRef)
を使用して、対応するアセットを検索するために使用されます。
コンポーネントプロトタイプ
Component Prototype
を拡張して、マテリアライズには直接使用されないデータを含めることが可能です。これによって、例えば、特定のコンポーネントインスタンス間で共有データを持たせたり、フレームから読み取り専用データを除外してゲームステートをスリムに保つことができます。
コード生成されたComponent Prototype
は、簡単に拡張できる部分クラスです。
MyComponentNamePrototype.Partial.cs
というC#ファイルを作成する- スクリプト本体は
Quantum.Prototypes
名前空間に配置する
ここからComponent Prototype
アセットにデータを追加したり、MaterializeUser()
部分メソッドを実装してマテリアライズの独自ロジックを追加したりすることができます。
コンポーネントプロトタイプが追加でUnityプロトタイプアダプターを生成する必要がある場合は、デフォルトでは部分クラスでは生成されません。回避策については、Unityプロトタイプアダプターセクションをご覧ください。
例
以下の例は、アーケードレースゲームにおけるVehicle
コンポーネントのマテリアライズを示しています。
Vehicle
コンポーネントは、実行時に計算される動的な値を主に保持していますが、設計上、これら変数をUnityエディターから初期化したくないため、DSLのコンポーネント定義ではパラメーターにExcludeFromPrototype
属性を使用し、VehiclePrototype
アセットから除外することで、Unityエディター上でデザイナーは操作できないようにしています。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
と同等です。VehiclePrototype
の参照の代入には、MaterializeUser()
部分メソッドが使用されます。
C#
using Photon.Deterministic;
using Quantum.Inspector;
using System;
namespace Quantum.Prototypes
{
public unsafe partial class VehiclePrototype
{
// publicメソッド
[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 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プロトタイプアダプター
コンポーネントのフィールドがReplaceTypeHintAttribute
を使用している場合、または以下の型のいずれかである場合、Quantumは追加でコンポーネントプロトタイプアダプター型を生成します。
EntityRef
EntityPrototypeRef
ComponentPrototypeRef
ComponentPrototypeRef<T>
アダプター型はQuantum.Prototypes.Unity
名前空間に配置され、元のプロトタイプと同じフィールドを持ちます。ただし、以下の型では置換が行われます。
EntityRef
->QuantumEntityPrototype
EntityPrototypeRef
->QUnityEntityPrototypeRef
ComponentPrototypeRef
->QUnityComponentPrototypeRef
ComponentPrototypeRef<T>
->QUnityComponentPrototypeRef<T>
[ReplaceTypeHintAttribute]
引数が、フィールドの型のかわりに使用される
UnityのMonoBehaviour
ベースのプロトタイプラッパーは、生成されたアダプターをプロトタイプのかわりに使用します。これによって、プロトタイプをUnityオブジェクト参照で扱いながら、シミュレーションとの互換性を保つことができます。
ほとんどの場合、これはユーザーにとっては完全に透過的です。ただしこのプロセスには副作用があり、コンポーネントプロトタイプは部分クラスで生成されなくなります。その理由は、アダプターのフィールドは元のプロトタイプと同期するように変換する必要がありますが、コード生成時には追加された部分クラスを知る方法がないからです。[CodeGen(ForcePartialPrototype)]
属性を使用して、コンポーネントプロトタイプの部分クラス化を強制できますが、その場合はアダプターのConvertUser
メソッドを実装する必要があります。