This document is about: QUANTUM 3
SWITCH TO

DSL (ゲームステート)

はじめに

Quantumは、コンポーネントやその他のランタイムゲーム状態データ型を独自のDSL(ドメイン特化言語)で定義することを要求します。

これらの定義は、.qtn 拡張子のついたテキストファイルに書かれます。QuantumコンパイラーはそれらをASTとして解析し、各型の部分的なC#構造体定義を生成します(定義は必要に応じて複数のファイルに分けることができ、コンパイラーが適切にマージします)。

DSLの目的は、QuantumのECSスパースセットメモリモデルによって課される複雑なメモリ整列要件から開発者を抽象化することであり、決定論的な予測/ロールバックシミュレーションアプローチをサポートするために必要です。

このコード生成アプローチは、タイプシリアライゼーション(スナップショット、ゲームセーブ、キルカムリプレイに使用)、チェックサム、およびデバッグ目的でのフレームデータの印刷/ダンプなどの他の機能のために "ボイラープレート" コードを書く必要がなくなります。

新しい .qtn ファイルをプロジェクトに作成するには、Unityのプロジェクトタブのコンテキストメニューを開いて Create/Quantum/Qtn をクリックするか、単にファイル名に .qtn 拡張子を付けて新しいファイルを作成します。

コンポーネント

コンポーネントは、エンティティにアタッチできる特別な構造体であり、それらをフィルタリング(アタッチされたコンポーネントに基づいてアクティブなエンティティのサブセットを反復処理する)するために使用されます。以下は、コンポーネントの基本的な定義の例です:

C#

component Action
{
  FP Cooldown;
  FP Power;
}

これらは通常のC#構造体に変換されます。上記のようにコンポーネントとしてラベル付けすることで、適切なコード構造(マーカーインターフェース、IDプロパティなど)が生成されます。

カスタムコンポーネントに加えて、Quantumにはいくつかの事前構築されたコンポーネントがあります:

  • Transform2D/Transform3D: 固定小数点(FP)値を使用した位置と回転。
  • PhysicsCollider、PhysicsBody、PhysicsCallbacks、PhysicsJoints(2D/3D): Quantumの状態なし物理エンジンで使用される。
  • PathFinderAgent、SteeringAgent、AvoidanceAgent、AvoidanceObstacle: NavMeshに基づくパスファインディングと移動。

構造体

構造体はDSLおよびC#の両方で定義できます。

DSLでの定義

Quantum DSLでも通常の構造体を定義することが許可されています(コンポーネントと同様、メモリ整列とヘルパー関数は自動的に管理されます):

C#

struct ResourceItem
{
  FP Value;
  FP MaxValue;
  FP RegenRate;
}

フィールドは構造体が生成されるときにアルファベット順に並べられます。フィールドが特定の順序で表示される必要がある場合は、C#で構造体を定義する必要があります(以下のセクション参照)。

これにより、「Resources」構造体をDSLの他の部分で型として使用できるようになります。たとえば、コンポーネント定義の内部で使用する場合:

C#

component Resources
{
  ResourceItem Health;
  ResourceItem Strength;
  ResourceItem Mana;
}

生成された構造体は部分的であり、必要に応じてC#で拡張できます。

C#での定義

C#でも構造体を定義できますが、この場合は手動で以下のものを定義する必要があります:

  • 構造体のメモリレイアウト(LayoutKind.Explicitを使用)
  • 構造体のサイズを定義する const int SIZE を追加
  • Serialize 関数を実装

C#

[StructLayout(LayoutKind.Explicit)]
public struct Foo {
  public const int SIZE = 12; // メンバーの合計サイズ(バイト単位)

  [FieldOffset(0)]
  public int A;

  [FieldOffset(4)]
  public int B;

  [FieldOffset(8)]
  public int C;

  public static unsafe void Serialize(void* ptr, FrameSerializer serializer)
  {
    var foo = (Foo*)ptr;
    serializer.Stream.Serialize(&foo->A);
    serializer.Stream.Serialize(&foo->B);
    serializer.Stream.Serialize(&foo->C);
  }
}

DSL内でC#で定義された構造体を使用する場合(たとえば、コンポーネント内で)、構造体定義を手動でインポートする必要があります。

import struct Foo(12);

注意: import はサイズに定数をサポートしていないため、毎回正確な数値を指定する必要があります。

コンポーネントと構造体の比較

コンポーネントを通常の構造体の代わりに使用する理由とタイミングは重要な質問です(結局のところ、コンポーネントも構造体です)。

コンポーネントは生成されたメタデータを含んでおり、それを特別なタイプに変え、以下の特徴を持ちます:

  • エンティティに直接アタッチすることができる;
  • ゲーム状態をトラバースする際にエンティティをフィルタリングするために使用される(次の章ではフィルターAPIについて詳しく説明します)。

コンポーネントは、通常の構造体と同様に、ポインタまたは値型としてアクセス、使用、またはパラメータとして渡すことができます。

動的コレクション

Quantumのカスタムアロケーターは、ロールバック可能なゲーム状態の一部としてビット可なコレクションを公開します。コレクションはビット可な型(すなわち、プリミティブ型およびDSLで定義された型)だけをサポートします。

コレクションを管理するために、Frame APIは各コレクションのために3つのメソッドを提供します:

  • Frame.AllocateXXX:ヒープ上にコレクションのためのスペースを割り当てる。
  • Frame.FreeXXX:コレクションのメモリを解放/デアロケートする。
  • Frame.ResolveXXX:ポインタを解決してコレクションにアクセスする。

注意: コレクションを解放した後、必ず default に設定して null にする必要があります。これはゲーム状態のシリアル化が正しく機能するために必要です。null化を省略すると、非決定論的な動作や非同期問題が発生します。コレクションを解放してそのポインタを手動で null にする代わりに、問題のフィールドに FreeOnComponentRemoved 属性を使用することができます。

注意事項

  • 複数のコンポーネントが同じコレクションインスタンスを参照できます。
  • 動的コレクションはコンポーネントや構造体の内部に参照として保存されます。そのため、初期化時に 必ず アロケートされ、さらに重要なのは、もはや必要なくなったときに 必ず 解放される必要があります。もしコレクションがコンポーネントの一部である場合、2つのオプションがあります:
    • リアクティブコールバック ISignalOnAdd<T>ISignalOnRemove<T> を実装し、コレクションをそこでアロケート/解放する。 (これらの特定のシグナルについての情報は、マニュアルのコンポーネントページを参照してください)。
    • [AllocateOnComponentAdded][FreeOnComponentRemoved] 属性を使用して、Quantumがコンポーネントが追加されるときと削除されるときにそれぞれアロケーションとデアロケーションを処理できるようにします。
  • Quantumはプロトタイプからコレクションを__事前に__ アロケートしません。少なくとも値が存在する場合を除いて。コレクションが空の場合、メモリは手動でアロケートする必要があります。
  • コレクションを複数回 解放 しようとすると、エラーがスローされ、内部的にヒープが無効な状態になります。

リスト

動的リストは、DSLを使用して list<T> MyList として定義できます。

component Targets {
  list<EntityRef> Enemies;
}

これらのリストを操作するための基本的なAPIメソッドは次の通りです:

  • Frame.AllocateList<T>()
  • Frame.FreeList(QListPtr<T> ptr)
  • Frame.ResolveList(QListPtr<T> ptr)

リストが解決された後、以下のようにAdd、Remove、Contains、IndexOf、RemoveAt、[]などの期待されるAPIメソッドを使用してリストを反復処理または操作できます。

コンポーネントの型 Targets 内で上記のコードスニペットで定義されたリストを使用するには、次のようなシステムを作成できます:

C#

namespace Quantum
{
  public unsafe class HandleTargets : SystemMainThread, ISignalOnComponentAdded<Targets>, ISignalOnComponentRemoved<Targets>
  {
    public override void Update(Frame f)
    {
      foreach (var (entity, component) in f.GetComponentIterator<Targets>()) {
        // リストを使用するには、まずフレームを介してそのポインタを解決する必要があります
        var list = f.ResolveList(component.Enemies);

        // 何かを行う
      }    
    }

    public void OnAdded(Frame f, EntityRef entity, Targets* component)
    {
      // 新しいリストをアロケート(ビット可リファレンスタイプを返します - QListPtr)
      component->Enemies = f.AllocateList<EntityRef>();
    }

    public void OnRemoved(Frame f, EntityRef entity, Targets* component)
    {
      // コンポーネントは、所有しているすべてのコレクションをフレームデータから解放する必要があります。さもなければ、メモリリークに至ります。
      // リストの QListPtr リファレンスを受け取ります。
      f.FreeList(component->Enemies);

      // コンポーネントが指しているすべての動的コレクションは、コンポーネントの OnRemoved で必ず null 化される必要があります
      // 外部のものを参照している場合であっても!
      // これは、そうでなければ非同期問題に至るシリアル化問題を防ぐためです。
      component->Enemies = default;
    }
  }
}

辞書

辞書はDSLで次のように宣言できます dictionary<key, value> MyDictionary

C#

component Hazard {
  dictionary<EntityRef, Int32> DamageDealt;
}

これらの辞書を操作するための基本的なAPIメソッドは次の通りです:

  • Frame.AllocateDictionary<K,V>()
  • Frame.FreeDictionary(QDictionaryPtr<K,V> ptr)
  • Frame.ResolveDictionary(QDictionaryPtr<K,V> ptr)

他の動的コレクションと同様に、使用する前に必ずアロケートし、フレームデータから解放し、辞書をもはや使用しない場合はnullにする必要があります。リストのセクションで提供された例を参照してください。

ハッシュセット

ハッシュセットは、DSLで次のように宣言できます hash_set<T> MyHashSet

C#

component Nodes {
  hash_set<FP> ProcessedNodes;
}

これらのハッシュセットを操作するための基本的なAPIメソッドは以下の通りです:

  • Frame.AllocateHashSet(QHashSetPtr<T> ptr, int capacity = 8)
  • Frame.FreeHashSet(QHashSetPtr<T> ptr)
  • Frame.ResolveHashSet(QHashSetPtr<T> ptr)

他の動的コレクションと同様に、使用する前に必ずハッシュセットをアロケートし、使用しなくなったらフレームデータから解放し、nullにする必要があります。これは、上記のリストに関するセクションで提供された例を参照してください。

列挙型、ユニオンとビットセット

列挙型

列挙型は、命名された定数のセットを定義するために使用できます。"enum" キーワードを使用してその名前とデータを定義します。以下の例はシンプルな列挙型 EDamageType の定義と、それを構造体のフィールドとして使用する方法を示しています:

C#

enum EDamageType {
    None, Physical, Magic
}

struct StatsEffect {
    EDamageType DamageType;
}

列挙型はデフォルトでコード生成時に整数定数として扱われ、0から始まります。ただし、必要に応じて列挙型メンバーに整数値を明示的に割り当てることも可能です。さらに、メモリ使用量が懸念されるシナリオでは、列挙型の値のメモリフットプリントを、byte などの異なる型を使用することで削減することも可能です。以下はその例です:

C#

enum EModifierOperation : Byte {
    None = 0,
    Add = 1,
    Subtract = 2
}

flags キーワードは、列挙型の各値が異なるビットフラグを表すことを示すために使用されます。これにより、ビット演算を使用して複数の列挙型の値を組み合わせることができ、関連するオプションや状態のセットを表現する方法となります。

C#

flags ETeamStatus : Byte {
    None,
    Winning,
    SafelyWinning,
    LowHealth,
    MidHealth,
    HighHealth,
}

flags キーワードを使用すると、通常の System.Enum メソッドよりもパフォーマンスが向上するユーティリティメソッドが自動生成されます。例えば、IsFlagSet()System.Enum.HasFlag() よりもパフォーマンスが高く、値型のボクシングが不要になるためです。

ユニオン

C言語のようなユニオンも生成できます。ユニオン型は、関与するすべての構造体のデータレイアウトをメモリで重複させます。以下はDSLでユニオンを宣言する方法の例です:

C#

struct DataA {
    FPVector2 Foo;
}

struct DataB {
    FP Bar;
}

union Data {
    DataA A;
    DataB B;
}

ユニオンはコンポーネントの一部として宣言できます:

C#

component ComponentWithUnion {
    Data ComponentData;
}

内部的に、ユニオン型 Data には、アクセスされる際にユニオンタイプを切り替えるために必要なロジックが含まれます。以下にいくつかの使用例を示します:

C#

private void UseWarriorAttack(Frame f) {
    var character = f.Unsafe.GetPointer<Character>(entity);
    character->Data.Warrior->ImpulseDirection = FPVector3.Forward;
}

private void ResetSpellcasterMana(Frame f) {
    var character = f.Unsafe.GetPointer<Character>(entity);
    character->Data.Spellcaster->Mana = FP._10;
}

ユニオンを使用する際には、その内部に含まれている複数の型のうち1つのみを使用することができるか、または特定の内部型にアクセスすることで動的に切り替えることができます。上記のコードスニペットでは、WarriorおよびSpellcasterポインタにアクセスすることで、内部的にユニオンタイプが変更されます。

現在使用されているユニオン型を確認するには、Field プロパティといくつかのコード生成された定数を使用できます:

C#

private bool IsWarrior(CharacterData data) {
    return data.Field == CharacterData.WARRIOR;
}

ビットセット

ビットセットは、任意の目的のために固定サイズのメモリブロックを宣言するために使用できます(たとえば、フォグ・オブ・ウォーやピクセル完璧なゲームメカニクスのためのグリッド構造など):

C#

struct FOWData {
    bitset[256] Map;
}

入力

Quantumでは、クライアント間で交換されるランタイム入力もDSLで宣言されます。この例では、ゲームの入力としてシンプルな移動ベクターと火のボタンを定義しています:

C#

input
{
    FPVector2 Movement;
    button Fire;
}

入力構造体は、毎ティックでポーリングされ、サーバーに送信されます(オンラインプレイ時)。

入力に関する詳細情報、ベストプラクティスや最適化の推奨アプローチについては、このページを参照してください: Input

シグナル

シグナルは、デカップリングされたインターシステム通信API(パブリッシャー/サブスクライバーAPIの形式)として使用される関数シグネチャです。これにより、シンプルなシグナルが定義されます(特別な型 entity_ref に注意してください - これらはこの章の終わりでリストされます):

C#

signal OnDamage(FP damage, entity_ref entity);

これにより、次のインターフェースが生成されます(任意のシステムで実装可能):

C#

public interface ISignalOnDamage
{
    public void OnDamage(Frame f, FP damage, EntityRef entity);
}

シグナルは、QuantumのDSL内でポインタの直接宣言を許可する唯一の概念であるため、実際の実装で元のデータを直接変更できるように参照でデータを渡すことができます:

C#

signal OnBeforeDamage(FP damage, Resources* resources);

これにより、コンポーネントポインタを渡すことができます(エンティティ参照型の代わりに)。

イベント

イベントは、シミュレーション内で何が起こったのかをレンダリングエンジン / ビューに伝えるための細かい解決策です(ゲーム状態の一部を修正/更新するために使用されるべきではありません)。"event" キーワードを使用してその名前とデータを定義します:

イベントをQuantum DSLで定義:

event MyEvent {
    int Foo;
}

シミュレーションからイベントをトリガーします:

C#

f.Events.MyEvent(2022);

Unity内でイベントをサブスクライブして消費します:

C#

QuantumEvent.Subscribe(listener: this, handler: (MyEvent e) => Debug.Log($"MyEvent {e.Foo}"));

グローバル

DSLでグローバルにアクセス可能な変数を定義することもできます。グローバルは、global スコープを使って任意の .qtn ファイルで宣言できます。

C#

global {
    // DSLで有効な任意の型も使用できます。
    FP MyGlobalValue;
}

DSLで定義されたすべてのものと同様に、グローバル変数は状態の一部であり、予測ロールバックシステムと完全に互換性があります。

グローバルスコープで宣言された変数は、フレームAPIを介して利用可能になります。フレームにアクセスできる任意の場所から(読み取り/書き込み)アクセスできます - ECSセクションの Systems ドキュメントを参照してください。

注意: グローバル変数の代替はシングルトンコンポーネントです。詳細については、マニュアルの ECS セクションの Components ページを参照してください。

特殊タイプ

Quantumには、複雑な概念(エンティティ参照、プレイヤーインデックスなど)を抽象化するため、または管理されていないコードに対する一般的なミスから保護するために使用されるいくつかの特殊タイプがあります。以下の特殊タイプは、他のデータ型(コンポーネント、イベント、シグナルなど)内で使用できます:

  • player_ref:ランタイムプレイヤーインデックスを表します(Int32にキャスト可能)。コンポーネントで定義された場合、関連エンティティを制御しているプレイヤーを格納するために使用できます(Quantumのプレイヤーインデックスベースの入力と組み合わせて)。
  • entity_ref:Quantumでは、各フレーム/ティックデータが別々のメモリ領域/ブロックに存在するため(Quantumはロールバックをサポートするためにいくつかのコピーを保持しています)、ポインタはフレーム間(ゲーム状態やUnityスクリプト内)でキャッシュすることはできません。エンティティ参照はエンティティのインデックスとバージョンプロパティを抽象化します(開発者が古い参照を持つ破棄されたエンティティスロットにアクセスすることを防ぎます)。
  • asset_ref<AssetType>:Quantumアセットデータベースからのデータアセットインスタンスへのロールバック可能な参照(データアセット章を参照してください)。
  • list<T>, dictionary<K,T>:動的コレクションへの参照(Quantumのフレームヒープに格納)。ビット可な型(プリミティブ型およびDSLで定義された型)のみをサポートします。
  • array<Type>[size]:データコレクションを表す固定サイズの "配列"。通常のC#配列はヒープにアロケートされたオブジェクト参照(プロパティなどを持ちます)となり、Quantumのメモリ要件に違反するため、特殊な配列型は、ロールバック可能なデータコレクションをゲーム状態内に保持するためにポインタベースのシンプルなAPIを生成します。

アセットに関する注意点

アセットは、開発者がデータ駆動型コンテナ(通常のクラス、継承、ポリモーフィックメソッドなど)を定義できるQuantumの特別な機能です。これにより、インデックスデータベース内に不変インスタンスとして最終的に生成されます。 "asset" キーワードを使用して、ゲーム状態内で参照を割り当てることができるデータアセットとして既存のクラスを指定します(機能および制限についてはデータアセット章を参照してください):

C#

asset CharacterData; // CharacterData クラスは、開発者によって通常の C# ファイルで部分的に定義されています

以下の構造体は、上記のタイプのいくつかの有効な例を示しています(以前に定義したタイプを参照することもあります):

C#

struct SpecialData
{
  player_ref Player;
  entity_ref Character;
  entity_ref AnotherEntity;
  asset_ref<CharacterData> CharacterData;
  array<FP>[10] TenNumbers;
}

利用可能な型

DSLで作業する際には、さまざまな型を使用できます。一部はパーサーによって事前にインポートされていますが、他は手動でインポートする必要があります。

デフォルト

QuantumのDSLパーサーは、ゲーム状態定義で使用できる事前にインポートされたクロスプラットフォームの決定論的型のリストを持っています:

  • Boolean / bool - 内部的にはQBooleanでラップされ、同一に動作します(get/set、比較など)。
  • Byte
  • SByte
  • UInt16 / Int16
  • UInt32 / Int32
  • UInt64 / Int64
  • FP
  • FPVector2
  • FPVector3
  • FPMatrix
  • FPQuaternion
  • PlayerRef / DSL内の player_ref
  • EntityRef / DSL内の entity_ref
  • LayerMask
  • NullableFP / DSL内の FP?
  • NullableFPVector2 / DSL内の FPVector2?
  • NullableFPVector3 / DSL内の FPVector3?
  • QString は UTF-16(.NET の場合、ユニコード)用です。
  • QStringUtf8 は常に UTF-8 です。
  • Hit
  • Hit3D
  • Shape2D
  • Shape3D
  • Joint、DistanceJoint、SpringJoint、および HingeJoint

QString に関する注意: N は、文字列の総サイズ(バイト単位)から、ブックキーピングに使用される2バイトを引いたものを表します。言い換えれば、QString<64> は最大62バイトの文字列のために64バイトを使用します(つまり、最大31 UTF-16 文字)。

手動インポート

前のセクションにリストされていない型が必要な場合、QTNファイルで使用する際に手動でインポートする必要があります。

Quantum以外の名前空間/型

他の名前空間で定義されている型をインポートするには、次の構文を使用します:

C#

import MyInterface;
or
import MyNameSpace.Utils;

列挙型の場合、構文は次のようになります:

C#

import enum MyEnum(underlying_type);

// この構文はQuantum特有の列挙型に対しても同じです
import enum Shape3DType(byte);

ビルトインQuantumタイプとカスタムタイプ

Quantumのビルトイン型またはカスタム型をインポートする場合、構造体のサイズはC#宣言で事前に定義されます。したがって、安全対策を追加することが重要です。

C#

namespace Quantum {
  [StructLayout(LayoutKind.Explicit)]
  public struct Foo {
    public const int SIZE = sizeof(Int32) * 2;

    [FieldOffset(0)]
    public Int32 A;

    [FieldOffset(sizeof(Int32))]
    public Int32 B;
  }
}

C#

#define FOO_SIZE 8 // 構造体の既知のサイズを持つ定数値を定義
import struct Foo(8);

構造体の期待されるサイズが実際のサイズと等しいことを確認するために、次のようにシステムの1つで Assert を追加することを推奨します。

C#

public unsafe class MyStructSizeCheckingSystem : SystemMainThread {  
  public override void OnInit(Frame f) {
    Assert.Check(Constants.FOO_SIZE == Foo.SIZE);
  }
}

ビルトイン構造体のサイズがアップグレード中に変更された場合、この Assert はスローされ、DSL内の値を更新することができます。

属性

Quantumは、インスペクターでパラメータを表示するためにいくつかの属性をサポートしています。

これらの属性は Quantum.Inspector 名前空間に含まれています。

属性 パラメータ 説明
DrawIf string fieldName
long value
CompareOperator compare
HideType hide
条件が真に評価される場合のみプロパティを表示します。

fieldName = 評価するプロパティの名前。
value = 比較に使用される値。
compare = 実行する比較操作 EqualNotEqualLessLessOrEqualGreaterOrEqual または Greater
hide = 式が False のときのフィールドの動作:Hide または ReadOnly

比較と隠れるについての詳細は下記を参照してください。
Header string header プロパティの上にヘッダーを追加します。

header = 表示するヘッダーのテキスト。
HideInInspector - フィールドをシリアル化し、Unityインスペクターでそのプロパティを隠します。
Layer - タイプ int のみ適用できます。
EditorGUI.LayerField をフィールドで呼び出します。
Optional string enabledPropertyPath プロパティの表示をオン/オフにすることができます。

enabledPropertyPath = トグルを評価するために使用される bool へのパス。
Space - プロパティの上にスペースを追加します。
Tooltip string tooltip プロパティの上にマウスをホバーしたときにツールチップを表示します。

tooltip = 表示するヒント。
ArrayLength
ONLY FOR CSharp
int length
--------
int minLength
int maxLength
length を使用することで、配列のサイズを定義できます。
------
minLengthmaxLength を使用することで、インスペクター内でのサイズの範囲を定義できます。
最終的なサイズはインスペクターで設定できます。
(minLengthmaxLength は含まれます)
ExcludeFromPrototype - コンポーネントおよびコンポーネントフィールドの両方に適用できます。
------
- フィールド:このコンポーネントのために生成されるプロトタイプからフィールドを除外します。
- コンポーネント:このコンポーネントのプロトタイプは生成されません。
PreserveInPrototype - タイプに追加されると、それをプロトタイプで使用できるようにマークし、プロトタイプクラスの生成を防ぎます。
フィールドに追加された場合、特定のフィールドにのみ影響します。
シンプルな [Serializable] 構造体に便利で、Unity側で _Prototype タイプを使用する必要がなくなります。
AllocateOnComponentAdded - 動的コレクションに適用できます。
これは、コレクションを保持しているコンポーネントがエンティティに追加されるときに、まだアロケートされていない場合にコレクションのメモリをアロケートします。
FreeOnComponentRemoved - 動的コレクションおよび Ptrs に適用できます。
これは、コンポーネントが削除されると、関連するメモリをデアロケートしてフィールドが保持する Ptr を nullify します。
------
重要:クロスリファレンスされたコレクションとの組み合わせでこの属性を使用しないでください。これは、特定のフィールドに保持される Ptr のみを nullify し、他のフィールドは無効なメモリを指すことになります。

属性 は、特に指定されていない限り、C#および.qtnファイルの両方で使用できます。ただし、いくつかの文法の違いがあります。

C#での使用

C#ファイルでは、属性は他の属性のように使用し、連結できます。

C#

// 複数の単一属性
[Header("Example Array")][Tooltip("min = 1\nmax = 20")] public FP[] TestArray = new FP[20];

// 複数の連結属性
[Header("Example Array"), Tooltip("min = 1\nmax = 20")] public FP[] TestArray = new FP[20];

qtnでの使用

qtnファイルでは、単一の属性の使用方法はC#と同じままです。

C#

[Header("Example Array")] array<FP>[20] TestArray;

複数の属性を組み合わせる場合、これらは必ず連結される必要があります。

C#

[Header("Example Array"), Tooltip("min = 1\nmax = 20")] array<FP>[20] TestArray;

コンパイラオプション

現在、QuantumのDSLファイル内で使用できるコンパイラオプションは以下の通りです(今後さらに追加される予定です):

C#

// プレイヤーの最大数を事前に定義(デフォルトは6、絶対最大は64)
#pragma max_players 16

// コンポーネント数を256から512に増加
#pragma max_components 512

// 数値定数(DSL内では MY_NUMBER として使用可能で、コード内では Constants.MY_NUMBER で使用可能)
#define MY_NUMBER 10

// 生成される定数の基底クラス名をオーバーライドする(デフォルトは "Constants")
#pragma constants_class_name MyFancyConstants

カスタム FP 定数

QuantumのDSLファイル内でカスタム FP 定数を定義することもできます。例:

C#

// DSLファイル内
#define Pi 3.14

これにより、Quantumのコード生成が FP 構造体内に対応する定数を生成します:

C#

// 3.14
FP constant = Constants.Pi;

また、対応する生の値も生成されます:

C#

// 3.14 Raw
var rawConstant = Constants.Raw.Pi;
Back to top