Quantum DSL(ゲーム状態)
イントロダクション
Quantumでは、すべてのゲーム状態データを独自のDSL(ドメイン固有言語)で宣言する必要があります。
これらの定義は、拡張子が ".qtn"のテキストファイルに書き込まれます(定義は必要に応じて分割でき、Quantumコンパイラはそれらをマージして1つのゲーム状態を生成します)。
DSLの目的はQuantumの予測/ロールバックモデルによって課された複雑なメモリアライメントの要件から開発者を開放することです。
DSLで定義されたゲーム状態は、カスタムのQuantumコンパイラによって解析され、通常のC#(メモリ制限が設定された状態)に変換されます。
この変換には、管理されていないC#ポインタと、コード生成関数が使用され、高性能ポインターベースの手法を開発者が扱わないで良いよう、作業を簡素化します。
エンティティ
エンティティは、一時的なゲームプレイの概念(動的に作成/破棄する必要があるインスタンス)に使用するべきです。通常はコンポーネントのセットを含みますが、プロパティとして宣言されたタイプ(DSLの特殊タイプのコンポーネントではない)を含む場合があります。
エンティティの最小限の有効な定義は、その名前とインスタンスの最大数を提供する必要があります(quantumはすべてのゲーム状態データを事前に割り当てます)。
C#
entity Character[19]
{
}
これは、Characterという名前のエンティティのC#構造体を生成し、ゲーム状態で19個のインスタンスを事前に割り当てます。 "field"スコープを持つエンティティに直接プロパティを追加することが可能です:
C#
entity MyEntity[19]
{
fields
{
FP Health;
FP Mana;
}
}
生成された "MyEntity"構造体のインスタンスへのポインタは、次のようなフィールドへの直接アクセスを可能にします(生成された状態APIの詳細については、次のシステムに関する章を参照してください)。
C#
var mana = myEntity->Mana;
myEntity->Health = 10;
コンポーネント
コンポーネントは、エンティティに添付できる特別な再利用可能なデータコンテナ/グループです。これは、コンポーネントの基本的な定義です。
C#
component Weapon
{
FP Cooldown;
FP Power;
}
「use」キーワードは、コンポーネントをエンティティに添付します。
C#
entity MyEntity[19]
{
use Weapon;
fields
{
FP Health;
FP Mana;
}
}
生成されたエンティティ構造体では、コンポーネントの名前/型もプロパティ名として使用されます。
C#
myEntity->Weapon.Power = 4;
Quantumにはあらかじめ組み込まれたコンポーネントがいくつかあります。
- Transform2D - 位置(FPVector2)と回転(FP)をラジアンで示します。
- DynamicBody - 現在の速度、質量、形状、素材データおよびその他の物理エンジン関連データ(位置および回転は、エンティティに物理を使用する場合に必要なTransform2Dから取得されます)。
- Prefab - 実行時にUnityプレハブにエンティティをリンクするための簡単なコンポーネント(Unityでエンティティをレンダリングするため)。
- Animator - Quantumの決定論的アニメーションシステムのシミュレーション部分(Unityのmecanimと統合されています)。
このセクションで取り上げるトピックのDSLは次のとおりです:
C#
component Weapon
{
FP Cooldown;
FP Power;
}
entity MyEntity[19]
{
use Weapon;
use Transform2D;
use DynamicBody;
fields
{
FP Health;
FP Mana;
}
}
構造体
Quantum DSLは、アライメント不能なデータの定義から保護をしながら、メモリアラインメントに注意を払う必要がなく、通常の構造体の定義を可能にします。
C#
struct Resources
{
FP Health;
FP Mana;
}
これは、 "Resources"構造体をDSLの他のすべての部分の型として使うことを可能にします。例えば、それを使って以前にエンティティ定義で使われていたフィールドを置き換えます:
C#
entity MyEntity[19]
{
use Weapon;
use Transform2D;
use DynamicBody;
fields
{
Resources Resources;
}
}
コンポーネント対構造体
正規の構造体の代わりにコンポーネントを使用すべき理由とタイミングが重要です(最後のコンポーネントも構造体です)。
コンポーネントには、生成されたメタデータが含まれ、特殊なタイプに変換されます。
- エンティティメタデータには、実行時に定義内のコンポーネントの存在をすばやくフィルタリングするためのデータが含まれています。
- コンポーネントタイプの有効なインスタンスのゲーム状態をトラバースすることは可能です(これは、Systems APIの一部である高性能エンティティフィルタの中で最も簡単です。次の章で説明をします)。
コンポーネントは、他の構造体と同様にポインタまたは値のタイプとして(他の関数やイベントに。後に説明します。)含まれるエンティティを含まずに渡すことができます(本章の後半に説明)。しかし、上記で説明した機能はコンポーネント専用です。
共用体、列挙型、ビットセット
Cのような共用体と列挙型も生成できます。以下の例は、相互排他的ないくつかのデータ型/値を結合し、オーバーラップさせてデータメモリを保存する方法を示します。enumを使用して、格納されるデータを指定します。
C#
enum DataType
{
typeA, typeB
}
struct DataA
{
FPVector2 Something;
FP Anything;
}
struct DataB
{
FPVector3 SomethingElse;
Int32 AnythingElse;
}
union Data
{
DataA A;
DataB B;
}
struct DataContainer
{
DataType Type;
Data Data;
}
ビットセットは、任意の目的(例えば、戦場の霧、ピクセル・パーフェクトなゲームの仕組みの格子状の構造など)で固定サイズのメモリブロックを宣言するために使用できます。
C#
struct FOWData
{
bitset[256] Map;
}
入力
Quantumでは、クライアント間で交換されるランタイム入力もDSLで宣言されます。この例では、シンプルな動きのベクターをゲームの入力として定義しています。
C#
input
{
FPVector2 Movement;
}
ボタンのような入力の場合、Quantumは状態の変更を安全に計算できます(これについては次のシステムに関する章で説明します)。特殊なタイプの「button」を使用してください。
C#
input
{
FPVector2 Movement;
button Fire;
}
あらかじめ定義された入力構造体が必要ですが、共用排他的入力型や同じゲームプレイセッションで使用されるデータを定義するために共用体を使用できます。この例では、同じ入力定義に通常の移動/発射、または「何かを買う」場合のコマンドデータを格納する方法を示します。
C#
enum InputType
{
BuyCommand, MovementAndFire
}
struct BuyData
{
Int32 ItemID;
Int32 UnitID;
Boolean ApplyImmediately;
}
struct MovementAndFireData
{
FPVector2 Movement;
button Fire;
}
union InputData
{
BuyData BuyCommand;
MovementAndFireData MovementAndFire;
}
input
{
InputType Type;
InputData Data;
}
(Systems)によって入力がどのように消費され、UnityからQuantumに注入されるか(Bootstrap Unity Project)を理解するには、次の章を参照してください。
イベント
イベントは、シミュレーション内で発生したことをレンダリングエンジンに伝えるために使用されます(ゲーム状態を変更/更新するためには使用しないでください)。名前とデータを定義するには、「event」キーワードを使用します。
C#
event TriggerSound
{
FPVector2 Position;
FP Volume;
}
継承はイベントで使用できます(任意で抽象としてマークできるイベントがあります。そのため、イベントを直接トリガーすることを防ぎ、具体的なサブクラスのみを許可します)。
C#
abstract event TriggerSound
{
FPVector2 Position;
Int32 SoundID; // this would be better handled with unity-side asset linking extensions, but this is out of the scope of this chapter
}
event TriggerShot : TriggerSound
{
FP Power;
}
サーバから入力データが確認されたとき(ロールバックによる誤検出を避けて)、特定のイベントが(Unityに)送信されることを保証するには、「synced」キーワードを使用します。
C#
synced event TriggerDeathSound : TriggerSound
{
}
不要な重複ディスパッチを避けるために、Quantumはすべてのイベントタイプ(イベントデータをソースとして使用する)のハッシュコード関数を自動生成します。特定の状況では、開発者はキー候補データ(イベントインスタンスを一意にする)が何であるかを厳密に制御する必要があります。 例えば、ロールバックによって引き起こされる位置のわずかな変化により、同じイベントの2つのインスタンスが2つの実際に異なるイベントとして誤って解釈されるケースです。これを避けるために、 "nothashed"キーワードを使用して、ハッシュ関数がいくつかのイベントデータを強制的に無視するように設定することができます。
C#
abstract event TriggerSound
{
nothashed FPVector2 Position;
Int32 SoundID;
}
次の章では、イベントがQuantum(システム)からどのようにトリガされ、Unityコールバック(ブートストラップUnityプロジェクト)に送られるかを説明します。
シグナル
シグナルは、デカップリングされたシステム間通信API(パブリッシャ/サブスクライバAPIまたはオブザーバパターンのようなビット)として使用されるファンクションシグネチャです。これは簡単なシグナルを定義します:
C#
signal OnDamage(FP Damage);
これにより、次のインタフェースが生成されます(これは、どのシステムでも実装できます):
C#
public interface ISignalOnDamage
{
public void OnDamage(Frame f, FP Damage);
}
QuantalsのDSLでポインタを直接宣言できる唯一の概念はシグナルであるため、参照によってデータを渡すことで、具体的な実装で直接元のデータを変更できます。
C#
signal OnDamageBefore(FP* Damage);
特殊な型
Quantumには、複雑な概念(結合されたポインタオフセット、プレインヤーインデックスなど)を抽象化するため、または、管理されていないコードで一般的な誤りの対策をするため、またはその両方に使用される特殊なタイプがいくつかあります。他のデータ型(コンポーネント、エンティティフィールド、イベントなど)内で使用できる特殊な型は次のとおりです。
- player_ref - ランタイムプレイヤーのインデックスを表します(これらはInt32との間でキャストされます)。エンティティに添付されている場合、プレイヤーによって制御されるエンティティインスタンスをマークするために使用することができます(Quantumのプレイヤーインデックスベースの入力と組み合わせる)。
- entity_ref - (ジェネリック)Quantumの各フレーム/ティックデータが別々のメモリリージョン/ブロックにあるため(Quantumは高速なロールバックのためにこれらのバッファを保持します)、ポインタはフレーム間にキャッシュされません(Unityではゲーム状態でもない) 。Entity refは、エンティティタイプ、インデックスおよびバージョンを抽象化します(破棄または再利用されたエンティティスロット上の廃止予定のデータへの誤ったアクセスを防止します)。
- asset_ref - Quantumアセットデータベースからのデータアセットインスタンスへのロールバック可能な参照(データアセットの章を参照してください)
- array [size] - データコレクションを表す固定サイズの「配列」。通常のC#配列は、Quantumのメモリ要件に違反するヒープ割り当てのオブジェクト参照(プロパティなどを持つ)なので、特殊な配列型はポインタを使った簡単なAPIを生成し、ロールバック可能なデータコレクションをゲーム状態に保ちます。
アセットに関する注記
アセットは、インデクシングされたデータベース内で不変のインスタンスとなるデータ駆動型コンテナ(継承、多態的メソッドなどを含む通常のクラス)を開発者が定義できるようにする、Quantumの特別な機能です。"asset"キーワードは、(既存の)クラスをゲームの状態内で割り当てた参照を持つことができるデータアセットとして割り当てるために使用されます(機能と制限の詳細については、データアセットの章を参照してください):
C#
asset CharacterData; // the CharacterData class is partially defined in a normal C# file by the developer
以下の構造体は、上記の型のいくつかの有効な例を示します(以前に定義された型を参照することもあります)。
C#
struct SpecialData
{
player_ref Player;
entity_ref<Character> Character;
entity_ref AnyEntity;
asset_ref<CharacterData> CharacterData;
array<FP>[10] TenNumbers;
array<entity_ref<Character>>[4] FourCharacterRefs;
}
利用可能な型とインポート
QuantumのDSLパーサには、ゲーム状態定義で使用できる、事前にインポートされたクロスプラットフォームの確定的タイプのリストがあります。
- Boolean
- Char
- 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 - NullableFPVector2 - DSLでFPVector2?を使用
- NullableFPVector3 - uNullableFPVector3 - DSLでFPVector3?を使用
コンパイラオプション
QuantumのDSLファイルでは、現在以下のコンパイラオプションを使用できます(今後さらに追加されます)。
C#
// pre defining max number of players (default is 8)
#pragma max_players 16
// 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
Back to top