This document is about: QUANTUM 3
SWITCH TO

マテリアライズ

はじめに

Component Prototype/Entity Prototypeから、コンポーネント/エンティティのインスタンスを生成するプロセスは、マテリアライズと呼ばれます。

マップアセットにベイクされたシーンプロトタイプのマテリアライズは、Frame.CreateAPIを使用したコードからのマテリアライズと同じルールと実行フローに従います。

プロトタイプとインスタンス

コンポーネントインスタンスとエンティティインスタンスはゲームステートの一部で、実行時に操作可能なものです。DSLで宣言されたコンポーネントは、対応するComponent Prototypeを生成するために使用されます。生成されたプロトタイプのコードは、命名規則MyComponentPrototypeに従います。

Component PrototypeEntity Prototypeはどちらもアセットで、ゲームステートの一部ではなく、実行時に不変であり、すべてのクライアント上で常に同一である必要があります。各Component PrototypeComponentPrototypeRefを持ち、Frame.FindPrototype<MyComponentNamePrototype>(MyComponentPrototypeRef)を使用して、対応するアセットを検索するために使用されます。

コンポーネントプロトタイプ

Component Prototypeを拡張して、マテリアライズには直接使用されないデータを含めることが可能です。これによって、例えば、特定のコンポーネントインスタンス間で共有データを持たせたり、フレームから読み取り専用データを除外してゲームステートをスリムに保つことができます。

コード生成されたComponent Prototypeは、簡単に拡張できる部分クラスです。

  1. MyComponentNamePrototype.Partial.csというC#ファイルを作成する
  2. スクリプト本体は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コンポーネントの値はVehiclePrototypeMaxNitro値に制限されます。これによって、同期ズレ時のペナルティを強制したり、ゲームステートをスリムに保ったりすることができます。

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. Entity Prototypeに含まれる各Component 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を使用している場合、または以下の型のいずれかである場合、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メソッドを実装する必要があります。

Back to top