DSL (ゲームステート)
はじめに
Quantumでは、コンポーネントや他のランタイムのゲームステートのデータ型を、独自のDSL(ドメイン固有言語)で定義する必要があります。
これらの定義は、.qtn
拡張子のテキストファイルで記述します。Quantumコンパイラは、それらを解析して抽象構文木にし、各型の部分的なC#構造体定義を生成します(必要に応じて複数のファイルに定義を分けて、コンパイラが適切にマージします)。
DSLの目的は、Quantum ECSのスパースセットメモリモデルによって課される複雑なメモリアライメント要件を抽象化することで、シミュレーションが決定論的な予測/ロールバックのアプローチをサポートするために必要です。
このコード生成アプローチによって、型のシリアライゼーション(スナップショット・ゲームの保存・キルカムのリプレイに使用される)、チェックサムや、デバッグ目的でのフレームデータの出力/ダンプなどの機能のために「ボイラープレート」コードを記述する必要がなくなります。
プロジェクトで新しい.qtn
ファイルを作成するには、UnityのProjectタブのコンテキストメニューを開いてCreate/Quantum/Qtn
をクリックするか、単に.qtn
拡張子の新しいファイルを作成してください。
コンポーネント
コンポーネントは、エンティティにアタッチできる特別な構造体で、エンティティをフィルタリング(アタッチされたコンポーネントに基づくアクティブなエンティティのサブセットのみを反復)するために使用されます。コンポーネントの定義の基本的な例は以下の通りです。
Qtn
component Action
{
FP Cooldown;
FP Power;
}
これらは通常のC#構造体に変換されます。(上記のように)コンポーネントとしてラベル付けすると、適切なコード構造(マーカーインターフェース、IDプロパティなど)が生成されます。
独自コンポーネントとは別に、Quantumにはいくつかの組み込みコンポーネントがあります。
Transform2D
/Transform3D
:固定小数点数(FP)を使用した位置と回転の値です。PhysicsCollider
/PhysicsBody
/PhysicsCallbacks
/PhysicsJoints
(2D/3D):Quantumのステートレスな物理エンジンで使用されます。PathFinderAgent
/SteeringAgent
/AoidanceAgent
/AvoidanceObstacle
:ナビメッシュに基づく経路探索と移動です。
構造体
構造体は、DSLでもC#でも定義できます。
DSLでの定義
Quantum DSLでも、通常の構造体を定義することができます(コンポーネントと同様、メモリアライメントやヘルパー関数の処理が行われます)。
Qtn
struct ResourceItem
{
FP Value;
FP MaxValue;
FP RegenRate;
}
生成される構造体のフィールドは宣言された順に並びますが、メモリのオフセットはパディングを避けるように調整され、最適なパッキングで提供されます。
これによって「Resources」構造体で、DSLの他の部分すべての型を使用できるようになります。コンポーネント定義内で使用する場合は、以下の例のようになります。
Qtn
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; // すべてのメンバーのサイズ(byte)
[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);
}
}
C#で定義された構造体をDSL内(例:コンポーネント内)で使用する場合は、構造体の定義を手動でインポートする必要があります。
import struct Foo(12);
注意: importはサイズ定数をサポートしていないので、毎回正確な数値を指定する必要があります。
コンポーネントと構造体の比較
通常の構造体のかわりに、いつどのような理由でコンポーネント使用するのかは重要な疑問になるでしょう(つまるところ、コンポーネントも構造体だからです)。
コンポーネントは、生成されたメタデータを含み、以下の特徴を持つ特別な型に変換します。
- エンティティに直接アタッチできる
- ゲームステートを走査する際に、エンティティをフィルタリングするために使用される(次のチャプターで、フィルターAPIを詳しく説明します)
コンポーネントは通常の構造体と同様に、ポインタや値型として、アクセスして使用したりパラメーターに渡したりすることができます。
動的コレクション
Quantum独自のアロケーターは、ロールバック可能なゲームステートの一部として、blittableなコレクションを公開します。コレクションは、blittableな型(つまり、プリミティブ型と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
を使用すると、動的リストを定義できます。
Qtn
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 frame)
{
foreach (var (entity, component) in frame.GetComponentIterator<Targets>()) {
// リストを使用するには、最初にポインタを解決する必要がある
var list = frame.ResolveList(component.Enemies);
// 何かする
}
}
public void OnAdded(Frame frame, EntityRef entity, Targets* component)
{
// 新しいリストを割り当てる(blittable参照型QListPtrが返される)
component->Enemies = frame.AllocateList<EntityRef>();
}
public void OnRemoved(Frame frame, EntityRef entity, Targets* component)
{
// コンポーネントは、frameからすべてのコレクションを解放しないと、メモリリークを引き起こす
// リストのQListPtr参照を渡す
frame.FreeList(component->Enemies);
// コンポーネントが持つすべての動的コレクションは、その参照が外部のみだとしても、
// コンポーネントのOnRemovedでnull化する必要がある
// これによって、同期ズレを引き起こすシリアライズ問題を回避できる
component->Enemies = default;
}
}
}
ディクショナリー
ディクショナリーは、DSLでdictionary<key, value> MyDictionary
のように宣言できます。
Qtn
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
のように宣言できます。
Qtn
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
の定義と、それを構造体のフィールドとして使用する方法を示しています。
Qtn
enum EDamageType {
None, Physical, Magic
}
struct StatsEffect {
EDamageType DamageType;
}
コード生成時にデフォルトでは、列挙型は0から始まる整数の定数として扱われます。ただし必要に応じて、列挙型メンバーに明示的な整数値を割り当てることが可能です。また、メモリ使用量が懸念される場合は、byte
などの異なる型を使用することで、列挙値のメモリフットプリントを削減することが可能です。以下はその例です。
Qtn
enum EModifierOperation : Byte
{
None = 0,
Add = 1,
Subtract = 2
}
flags
キーワードは、各列挙値が異なるビットフラグを表すことを示すために使用されます。これにより、ビット演算を使用して複数の列挙値を組み合わせて、関連するオプションや状態のセットを表現できます。
Qtn
flags ETeamStatus : Byte
{
None,
Winning,
SafelyWinning,
LowHealth,
MidHealth,
HighHealth,
}
flags
キーワードを使用すると、通常のSystem.Enum
より効率的なユーティリティメソッドが自動生成されます。例えば、IsFlagSet()
はSystem.Enum.HasFlag()
よりも効率的で、値型のボクシングを避けることができます。
共用体
C言語のような共用体も生成できます。共用体では、構造体に含まれるすべてのデータレイアウトがメモリ上で重複します。以下はDSLで共用体を宣言する方法の例です。
Qtn
struct DataA
{
FPVector2 Foo;
}
struct DataB
{
FP Bar;
}
union Data
{
DataA A;
DataB B;
}
共用体はコンポーネントの一部として宣言できます。
Qtn
component ComponentWithUnion {
Data ComponentData;
}
共用体Data
には、アクセス時に型を切り替えるために必要なロジックが内部的に含まれます。以下にいくつかの使用例を示します。
C#
private void UseWarriorAttack(Frame frame)
{
var character = frame.Unsafe.GetPointer<Character>(entity);
character->Data.Warrior->ImpulseDirection = FPVector3.Forward;
}
private void ResetSpellcasterMana(Frame frame)
{
var character = frame.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;
}
ビットセット
ビットセットは、希望する目的(例えば、戦場の霧や、ピクセルパーフェクトなゲームメカニクスでのグリッド構造など)のために、固定サイズのメモリブロックを宣言するために使用できます。
Qtn
struct FOWData
{
bitset[256] Map;
}
入力
Quantumでは、クライアント間で交換されるランタイムの入力もDSLで宣言します。以下の例では、ゲームの入力としてシンプルな移動方向と射撃ボタンを定義しています。
Qtn
input
{
FPVector2 Movement;
button Fire;
}
入力構造体は、ティック毎にポーリングされ、サーバーに送信されます(オンラインプレイ時)。
入力に関する詳細、ベストプラクティスや最適化の推奨アプローチについては、入力のページをご覧ください。
シグナル
シグナルは、分離されたシステム間通信API(Publisher/Subscriber形式)として使用される関数シグネチャです。シンプルなシグナルの定義は以下の通りです(特別な型 entity_ref については、このチャプターの後半で説明します)。
Qtn
signal OnDamage(FP damage, entity_ref entity);
これによって、以下のインターフェースが生成され、任意のシステムで実装可能です。
C#
public interface ISignalOnDamage
{
public void OnDamage(Frame frame, FP damage, EntityRef entity);
}
シグナルでのみ、QuantumのDSL内でポインタを直接宣言できます。以下のような実装で、元のデータを直接変更できるように、データを参照で渡すことができます。
Qtn
signal OnBeforeDamage(FP damage, Resources* resources);
これにより、コンポーネントのポインタを渡すこともできます(エンティティ参照型のかわりに)。
イベント
イベントは、シミュレーション内で起こったことをレンダリングエンジン/ビュー(ゲームステートを修正/更新するために決して使用すべきではない所)に伝える詳細な方法です。「event」キーワードを使用して、名前とデータを定義します。
イベントについての詳細情報は、マニュアルのフレームイベントをご覧ください。
QuantumのDSLを使用したイベントの定義は以下の通りです。
Qtn
event MyEvent{
int Foo;
}
イベントはシミュレーションからトリガーできます。
C#
frame.Events.MyEvent(2022);
イベントを購読/消費はUnityで行います。
C#
QuantumEvent.Subscribe(listener: this, handler: (MyEvent e) => Debug.Log($"MyEvent {e.Foo}"));
グローバル変数
DSLでグローバル変数を定義することができます。グローバル変数はglobal
スコープを使用して、任意の.qtn
ファイルで宣言できます。
Qtn
global {
// Any type that is valid in the DSL can also be used.
FP MyGlobalValue;
}
DSLで定義されたものと同様に、グローバル変数は状態の一部であり、予測ロールバックシステムと完全に互換性があります。
グローバル変数は、フレームAPIから利用可能になっていて、フレームにアクセスできる任意の場所からアクセス(読み取り/書き込み)できます。詳細は、ドキュメントの「システム」をご覧ください。
注意: グローバル変数はシングルトンコンポーネントで代替できます。詳細は、マニュアルの「コンポーネント」ページをご覧ください。
特殊な型
複雑な概念(エンティティの参照やプレイヤーのインデックスなど)を抽象化したり、アンマネージドコードに対する一般的なミスから保護したりするため、Quantumにはいくつか特殊な型があります。以下の特殊な型は、他のデータ型(コンポーネント・イベント・シグナルなど)内で使用できます。
player_ref
:ランタイムのプレイヤーインデックス(Int32
にキャスト可能)を表します。コンポーネント内で定義された場合、関連するエンティティを制御しているプレイヤーを格納するために使用できます(Quantumのプレイヤーインデックスに基づいた入力と組み合わせて)。entity_ref
:Quantumでは、各フレーム/ティックのデータは、別々のメモリ領域/ブロックに存在する(Quantumはロールパックをサポートするために、いくつかのコピーを保持する)ため、ポインタはフレーム間(ゲームステート間でも、Unityスクリプト間でも)でキャッシュできません。エンティティの参照は、エンティティのインデックスとバージョンプロパティ(開発者が参照が古い破棄されたエンティティスロットに間違ってアクセスすることを防ぐ)を抽象化します。asset_ref<AssetType>
:Quantumアセットデータベースのデータアセットインスタンスのロールバック可能な参照です(「データアセット」のチャプターをご覧ください)。list<T>
,dictionary<K,T>
:動的コレクションの参照(Quantumのフレームヒープに格納)です。blittableな型(プリミティブ型とDSLで定義された型)のみをサポートします。array<Type>[size]
:データコレクションを表す固定サイズの「配列」です。通常のC#配列はオブジェクト参照(プロパティなどを持つ)がヒープに割り当てられますが、これはQuantumのメモリ要件に違反します。特殊な配列型はポインタベースのシンプルなAPIを生成して、ゲームステート内でロールバック可能なデータコレクションを保持します。
アセットに関する注意点
アセットはQuantumの特別な機能で、データ駆動型コンテナ(通常のクラス・継承・ポリモーフィズムなど)を定義して、最終的にインデックスデータベース内の不変インスタンスになります。「asset」キーワードは、ゲームステート内から参照を割り当てられるデータアセットとして、既存クラスを割り当てるために使用されます(この機能や制限については「データアセット」チャプターをご覧ください)。
Qtn
asset CharacterData; // the CharacterData class is partially defined in a normal C# file by the developer
以下の構造体は、上記の型(と以前に定義した型)の有効な例を示します。
Qtn
struct SpecialData
{
player_ref Player;
entity_ref Character;
entity_ref AnotherEntity;
asset_ref<CharacterData> CharacterData;
array<FP>[10] TenNumbers;
}
有効な型
DSLでは様々な型が使用できます。一部はパーサーによってインポート済みになっていますが、他は手動でインポートする必要があります。
デフォルト
QuantumのDSLパーサーは、ゲームステートの定義に使用できるクロスプラットフォームの決定論的な型のリストを持っています。
- Boolean / bool - internally gets wrapped in QBoolean which works identically (get/set, compare, etc...)
- Byte
- SByte
- UInt16 / Int16
- UInt32 / Int32
- UInt64 / Int64
- FP
- FPVector2
- FPVector3
- FPMatrix
- FPQuaternion
- PlayerRef / player_ref in the DSL
- EntityRef / entity_ref in the DSL
- LayerMask
- NullableFP / FP? in the DSL
- NullableFPVector2 / FPVector2? in the DSL
- NullableFPVector3 / FPVector3? in the DSL
- QString
is for UTF-16 (aka Unicode in .NET) - QStringUtf8
is always UTF-8 - Hit
- Hit3D
- Shape2D
- Shape3D
- Joint, DistanceJoint, SpringJoint and HingeJoint
QStringに関する備考: N
は、文字列の合計サイズ(バイト単位)からbookkeepingに使用される2バイトを引いたものを表します。言い換えればQString<64>
は、最大62バイトの文字列(つまり、UTF-16で最大31文字)のために64バイトを使用します。
手動インポート
前のセクションでリストされていない型が必要な場合は、.qtn
ファイルで使用する際に手動でインポートする必要があります。
Quantum以外の名前空間/型
型のインポート
他の名前空間で定義されている型をインポートして、DSLのコンポーネントやグローバル変数で直接使用するには、次の構文を使用します。
Qtn
import MyInterface;
or
import MyNameSpace.Utils;
列挙型の場合、構文は次のようになります。
Qtn
import enum MyEnum(underlying_type);
// This syntax is identical for Quantum specific enums
import enum Shape3DType(byte);
ネームスペースの追加
生成されたクラスにネームスペースを含めたいようなケースでは、QTNファイルにusing MyNamespace;
を追加する必要があります。
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;
}
}
Qtn
#define FOO_SIZE 8 // Define a constant value with the known size of the struct
import struct Foo(8);
構造体の期待するサイズと実際のサイズが等しくなることを保証するため、1システム内でAssert
を追加することを推奨します。
C#
public unsafe class MyStructSizeCheckingSystem : SystemMainThread{
public override void OnInit(Frame frame)
{
Assert.Check(Constants.FOO_SIZE == Foo.SIZE);
}
}
組み込み構造体のサイズがアップグレードで変更された場合は、このAssert
が投げられるので、DSL内の値を更新することができます。
属性
Quantumは、インスペクター上でパラメーターを表示するために、いくつかの属性をサポートしています。
これらの属性はQuantum.Inspector
名前空間に含まれています。
属性 | パラメーター | 説明 |
---|---|---|
AllocateOnComponentAdded |
動的にコレクションに適用できます。
コレクションを保持するコンポーネントがエンティティに追加される時に、コレクションのメモリが割り当てられていなければ割り当てを行います。 |
|
ArrayLength ONLY FOR CSharp |
int length
|
lengthを使用して、配列のサイズを定義できます。 |
ArrayLength ONLY FOR CSharp |
int minLength
int maxLength
|
minLengthとmaxLengthを使用して、インスペクター上でのサイズの範囲を定義できます。
最終的なサイズはインスペクター上で設定できます。 (minLengthとmaxLengthは範囲に含まれます) |
DrawIf |
string fieldName
long value
CompareOperator compare
HideType hide
|
条件がtrueの場合のみ、プロパティを表示します。
fieldName = 評価するプロパティ名 value = 比較に使用される値 compare = 実行する比較操作( Equal ・NotEqual ・Less ・LessOrEqual ・GreaterOrEqual ・Greater )
hide = False 時のフィールドの動作:Hide or ReadOnly
compareとhideについての詳細は下記を参照してください。 |
FreeOnComponentRemoved |
動的コレクションとPtr に適用できます。
コンポーネントが削除された時に、関連するメモリ割り当てを解除し、フィールドが保持する Ptr をnullにします。
重要:この属性は、相互参照されたコレクションと組み合わせて使用しないでください。これは指定のフィールドに保持された Ptr のみをnullにするため、他のフィールドは無効なメモリを指したままになります。
|
|
ExcludeFromPrototype | コンポーネントかコンポーネントのフィールドに適用できます。
フィールド:コンポーネントのために生成されるプロトタイプからフィールドを除外します。 コンポーネント:コンポーネントのプロトタイプは生成されません。 | |
Header | string header |
プロパティ上部にヘッダーを追加します。
header = 表示するヘッダーのテキスト |
HideInInspector | フィールドをシリアライズして、Unityのインスペクター上ではプロパティを隠します。 | |
Layer |
int型のみ適用できます。
フィールドで EditorGUI.LayerField を呼び出します。
|
|
OnlyInPrototype | フィールドに適用すると、オブジェクトの状態から無視されて、プロトタイプにのみ追加されます。 | |
PreserveInPrototype |
型に追加すると、プロトタイプで使用できるようにマークし、プロトタイプクラスから除外されることを防ぎます。 フィールドに追加すると、特定のフィールドにのみ影響します。シンプルな [Serializable] の構造体は、Unity側で_Prototype 型を使用する必要がなくなるため便利です。
|
|
Optional | string enabledPropertyPath |
プロパティの表示のオン/オフを切り替えられます。
enabledPropertyPath = トグルに使用される bool 値へのパス
|
Range | (0, 10) |
int で動作する、Unity標準の範囲スライダーをインスペクターに追加します。 |
RangeEx | (0.1, 9.9) |
FP とlong で動作する、範囲スライダーをインスペクターに追加します。
値はQuantumマップと同様に、 float を使用してエディター上で丸められるため、ビルド時/エクスポート時のみ決定論的になります。
|
Space | プロパティ上部にスペースを追加します。 | |
Tooltip | string tooltip |
プロパティ上にマウスをホバーした時にツールチップを表示します。
tooltip = 表示するヒント |
「属性」は特に指定されていない限り、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#と同じです。
Qtn
[Header("Example Array")] array<FP>[20] TestArray;
複数の属性を組み合わせる場合は、必ず連結する必要があります。
Qtn
[Header("Example Array"), Tooltip("min = 1\nmax = 20")] array<FP>[20] TestArray;
コンパイラオプション
QuantumのDSLファイル内では、現時点で以下のコンパイラオプションが使用可能です(今後さらに追加される予定です)。
Qtn
// pre defining max number of players (default is 6, absolute max is 64)
#pragma max_players 16
// increase the component count from 256 to 512
#pragma max_components 512
// numeric constants (useable inside the DSL by MY_NUMBER and useable in code by Constants.MY_NUMBER)
#define MY_NUMBER 10
// overriding the base class name for the generated constants (default is "Constants")
#pragma constants_class_name MyFancyConstants
独自FP定数
QuantumのDSLファイル内で、独自のFP
定数を定義することもできます。
Qtn
// in a DSL file
#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