アセットリンキング
データアセットクラス
Quantumアセットは、実行時に不変のデータコンテナとして機能する通常のC#クラスです。
いくつかのルールでこれらのアセットをQuantumでどのように設計、実装、使用する必要があるかを定義します。
ここでは、単純な決定論的プロパティを持つアセットクラス(文字仕様用)の最小定義を示します。
C#
namespace Quantum
{
partial class CharacterSpec
{
public FP Speed;
public FP MaxHealth;
}
}
アセットクラスの定義は 部分的 で、Quantum名前空間に含まれている必要があります。
アセットクラスのインスタンスを作成してデータベース読み込む(Unityから編集する)方法については、この章の後半で説明します。
アセットのリンキングと使用
Quantumにこれがアセットクラスであることを伝えるため(基本的な AssetObject クラスを継承し、そのインスタンスを含むようにデータベースを準備することで内部メタデータを追加):
C#
// this goes into a DSL file
asset CharacterSpec;
アセットインスタンスは不変オブジェクトであり、参照として保持する必要があります。
通常のC#オブジェクト参照は、メモリにアライメントされたECS構造体に含めることができないため、asset_ref特殊型をDSL内で使用して、(エンティティ、コンポーネントまたはその他の一時データ構造から)ゲーム状態内のプロパティを宣言できます。
C#
entity Character[8]
{
use Transform2D;
use DynamicBody;
fields
{
// reference to an immutable instance of CharacterSpec (from the Quantum asset database)
asset_ref<CharacterSpec> Spec;
FP Health;
}
}
Characterエンティティの作成時にアセット参照を割り当てるには、アセットデータベースからインスタンスを直接取得し、それをプロパティに設定します。
C#
var c = f.CreateCharacter();
c->CharacterSpec = DB.FindAsset<CharacterSpec>("mage");
アセットの基本的な使用は、実行時にデータを読み取り、システム内のあらゆる計算に適用することです。
次の例では、割り当てられた CharacterSpec の Speed 値を使用して、対応するキャラクター速度(物理エンジン)を計算します。
C#
// consider c a Character* (from an iterator, for example)
c->DynamicBody.Velocity = FPVector2.Right * c->CharacterSpec.Speed;
決定論に関する注釈
上記のコードでは、実行時に文字の速度を計算するSpeedプロパティが読み込まれますが、その値(速度)は変更されません。
実行時のゲーム状態アセット参照をUpdate内から切り替えることは安全であり、有効です(asset_refはロールバック可能なタイプであり、したがってゲーム状態の一部にすることができるため)。
ただし、データアセットのプロパティの 値 を変更することは決定的ではありません(アセットの内部データはゲーム状態の一部とみなされないため、ロールバックされることはありません)。
次のスニペットは、実行時に安全(スイッチの切り替え)と安全ではない(内部データの変更)の例を示しています。
C#
// c is a Character*
// this is VALID and SAFE, as the CharacterSpec asset ref is part of the game state
c->CharacterSpec = DB.FindAsset<CharacterSpec>("anotherCharacterSpec");
// this is NOR valid NEITHER deterministic, as the internal data from an asset is NOT part of the transient game state:
// DON'T do this:
c->CharacterSpec.Speed = 10;
AssetObjectConfig属性
アセットリンキングスクリプト生成をAssetObjectConfig
属性で微調整できます。
C#
[AssetObjectConfig(GenerateLinkingScripts = false)]
partial class CharacterSpec
{
// ...
}
- GenerateLinkingScripts (default=true) - Unityで編集可能なすべてのスクリプトの生成を回避します。
- GenerateAssetCreateMenu (default=true) - このアセットのUnity
CreateAssetMenu
属性の生成を回避します。 - GenerateAssetResetMethod (default=true) - (アセットが作成されるときに自動的にGUIDが生成される)Unityのスクリプタブルオブジェクト
Reset()
メソッドとの生成を回避します。 - CustomCreateAssetMenuName (default=null) -
CreateAssetMenu
名前を上書きします。null
に設定した場合、メニューパスが、継承グラフを使用して自動的に生成されます。 - CustomCreateAssetMenuOrder (default=-1) -
CreateAssetMenu
命令を上書きします。-1に設定した場合、アルファベット順になります。
アセットスクリプトロケーションを上書きし、aotファイル生成を無効にする
tools\codegen_unity\quantum.codegen.unity.dll.config
を作成します。注意:手順の中でファイルが紛失する恐れがありますので更新する際は注意してください。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="RelativeLinkDrawerFilePath" value="Quantum/Editor/PropertyDrawers/Generated/AssetLinkDrawers.cs"/>
<add key="RelativeAssetScriptFolderPath" value="Quantum/AssetTypes/Generated"/>
<add key="RelativeGeneratedFolderPath" value="Quantum/Generated"/>
<add key="GenerateAOTFile" value="true"/>
</appSettings>
</configuration>
アセットの継承
データアセットで継承を使用することは可能です。これにより、開発者の作業に柔軟性がもたらされます(特にポリモーフィックメソッドと併用する場合)。
継承の基本的なステップは、抽象基本アセットクラスを作成することです( CharacterSpec の例を続けます)。
C#
namespace Quantum
{
partial abstract class CharacterSpec
{
public FP Speed;
public FP MaxHealth;
}
}
CharacterSpec の具体的なサブクラスは、独自のカスタムデータプロパティを追加することができ、Serializabletypes としてマークする必要があります。
C#
namespace Quantum
{
[Serializable]
public partial class MageSpec : CharacterSpec
{
public FP HealthRegenerationFactor;
}
[Serializable]
public partial class WarriorSpec : CharacterSpec
{
public FP Armour;
}
}
注意:CharacterSpec だけがDSLからアセットとしてマークされる必要があり、asset_refsもベースクラスを指します(継承の柔軟性を最大限に利用するにはポリモルフィズムに関する次のセクションを参照)。
データ駆動ポリモーフィズム
具体的な CharacterSpec クラスを直接評価する(if文またはswitch文で)ゲームプレイロジックを使用すると、デザインが非常に悪くなります。ポリモーフィックメソッドと組み合わせるとアセットの継承が有効になります。
データアセットにロジックを追加するということは、quantum.stateプロジェクトにロジックを実装することを意味します。このロジックでは、次の制限を考慮する必要があります。
- 一時的なgamestateデータで操作:データアセットのロジックメソッドは、一時的なデータをパラメータとして受け取る必要があります(エンティティポインタまたはFrameオブジェクト自体)。
- 読み込みのみ。アセット自体のデータは変更しないでください。アセットは 変更不可能 な読み取り専用インスタンスとして扱われなければなりません。
次の例では、基本クラスに仮想メソッドを追加し、サブクラスのいずれかにカスタム実装を追加します(このドキュメントの先頭にある Character エンティティ用に定義された Health フィールドを使用しています)。
C#
namespace Quantum
{
partial unsafe abstract class CharacterSpec
{
public FP Speed;
public FP MaxHealth;
public virtual void Update(Frame f, Character* c)
{
if (c->Health < 0)
f.DestroyCharacter(c);
}
}
[Serializable]
public partial unsafe class MageSpec : CharacterSpec
{
public FP HealthRegenerationFactor;
// reads data from own instance and uses it to update transient health of Character pointer passed as param
public virtual void Update(Frame f, Character* c)
{
c->Health += HealthRegenerationFactor * f.DeltaTime;
base.Update(f, c);
}
}
}
各 キャラクタ に割り当てられた具体的なアセットとは独立してこの柔軟なメソッドの実装を使用することは、どのシステムからでも実行できます。
C#
// All currently active Character entities
var all = f.GetAllCharacters();
// looping through the iterator
while (all.Next())
{
// retrieving the Character pointer
var c = all.Current;
// Updating Health using data-driven polymorphism (behavior depends on the data asset type and instance assigned to character
c->CharacterSpec.Update(c, f);
}
サブクラスに特定のレファレンスを保持する場合は、qtn内でasset import
を使用し宣言します。
C#
asset CharacterSpec;
asset import MageSpec;
DSL内でサブクラスが宣言されたら、ベースクラスと同様にasset_ref<T>
が使用できます。例えば、CharacterSpec
の代わりにDSL内で直接MageSpec
を使用するには、以下の様に記述します:
C#
component MageData {
asset_ref<MageSpec> ClassDefinition; // Only accepts asset references to MageSpec class.
FP CooldownTimer;
}
Unityでのアセットの編集
アセットデータベース(Quantumシミュレーションシステム内のDBクラス)は、アセットインスタンスで初期化する必要があります。
そのためのデフォルトのメカニズムは、Scriptable Objectsの生成されたミラー階層の形でブートストラップUnityプロジェクトに含まれています。
新しいアセットクラスが作成されるたびに、Quantum/Generate Asset Linking Script をUnityからクリックすると、Unityミラーアセットコンテナが生成されます。
利用可能なアセットタイプのリストは、プロジェクトタブ内のコンテキストメニューから利用できます(Assets/Resources/DB フォルダ内を右クリックし、コンテキストメニューを Create/Quantum/Assets/... にナビゲートします)。
生成された ScriptableObjects アセットは、オブジェクトインスペクタから直接編集することができます。

一意のGUIDは、手動で挿入するか、 Quantum/Refresh Database をクリックして生成することができます。
Resources/DB フォルダ内(Unityブートストラッププロジェクト内)に作成された ScriptableObject を持つアセットデータクラスのすべてのインスタンスは、デフォルトで自動的にデータベースに読み込まれます(その他の高度なオプションについては、カスタムアセットローダの章を参照してください)。
Unityでアセットをドラッグ&ドロップ
アセットインスタンスを追加し、内部クラスから DB クラス内で検索するシステムには限界があります。
アセットインスタンスが互いのデータベース参照を指すようにする興味深い機能があります(これらの参照をUnityからドラッグ&ドロップできる)。
1つの一般的な使用法は、その特定のプレイヤーによって選択された CharacterSpec アセットへのリンクを含むように RuntimePlayer の事前ビルドされたクラスを持つことです(手続き的に割り当てることができるGUIDストリングに至る技術。この例では アセットインスタンスをUnityエディタのスロットにドラッグ&ドロップする簡単な方法を使用します。これはプロトタイプ作成時によく使用される方法です)。
一時的なゲーム状態ではないものは、asset_refを使用する代わりに、生成された CharacterSpecLink クラスを使用して、必要な機能(型の安全性を含む)を提供することをお勧めします。
C#
// this is added to the RuntimPlayer.User.cs file
namespace Quantum
{
partial class RuntimePlayer
{
public CharacterSpecLink CharacterSpec;
}
}
これはUnityインスペクタで次のように表示されます(ブートストラップのゲームプレイシーンからQuantumDebugRunnerを見てください。また、トレーニングビデオとトピックの章も参照してください)。

このアプローチでは、シミュレーションシステム内からハードコードされた DB ルックアップを削除することができます。
C#
public override void OnInit(Frame f)
{
for (Int32 i = 0; i < f.RuntimeConfig.Players.Length; ++i)
{
var c = f.CreateCharacter();
c->CharacterSpec = f.RuntimeConfig.Players[i].CharacterSpec; // notice CharacterSpecLink casts to CharacterSpec asset ref
}
}
Unityデータによるアセットの拡張
Unityでのみアクセス可能かつシミュレーションで使用されていない、生成したQuantumアセットスクリプトに、独自のファイル内で部分クラス宣言を追加することで、手動でフィールドを追加できます。
プレゼンテーションやGameObjectリファレンスの追加のため設定ファイルにデータをさらに追加する間、シミュレーションが使用する同じ設定ファイルのサイズを小さくしておくのに便利です。
生成したFooAsset.csをご確認ください。
C#
using Quantum;
using UnityEngine;
[CreateAssetMenu(menuName = "Quantum/Assets/Foo", order = 0)]
public partial class FooAsset : AssetBase {
public Foo Settings;
public override AssetObject AssetObject {
get { return Settings; }
}
}
Create a new file called MyFooAsset.cs
C#
public partial class FooAsset {
public int Bar;
}
カスタムアセットローダ
デフォルトのQuantumアセットローダは、 Resources/DB フォルダ内からUnityの ScriptableObject のサブクラスである AssetBase のインスタンスを検索し、アセット自体をQuantum DB クラス(シミュレーションシステムAPIから利用可能)に読み込み、Unityミラーアセットを UnityDB クラスに読み込みます(前のセクションで説明したUnityデータ拡張のために使用される)。
このデフォルトのローダの基本実装は、Assets/Quantum/UnityDB.cs にあります(LoadAll 関数をご確認ください)。
実装されているゲームによって便利な2つのオプションがあります:
- 外部CMSシステムからアセットを読み込む。
- 手順的にアセットを生成。
CMSからの読み込み
データアセットを提供する外部CMSのケースは、対象のゲームでリリース後に多くのバランシングラウンドが必要な場合には有意義です。
これにより、キャラクタ仕様、マップ、NPC仕様などのデータ駆動型のすべてのバランシングシートをゲームビルド自体と独立して更新することができます。
ゲームクライアントは、常にオンラインマッチを開始または参加する前に、CMSサービスに接続をしてゲームデータを最新バージョンにアップグレードします。
任意のカスタム・シリアライザ(バイナリ、JSONなどから)を使用して、データをQuantum Assetオブジェクトに戻すことができます。このオブジェクトは、サンプル実装と同様にデータベースに注入できます。
手順的生成
同様に、アセットは、マッチが始まる前に手順的に生成することができます(データベースの全体または一部をこのように作成することができる)。
重要:
- 手順的生成は通常、各クライアント上で独立して実行されるため、生成されたデータがすべてのクライアントで同じになるように注意しなければなりません(共有シードを使用し、決定論的アルゴリズムを使用することをお勧めします)。
- シミュレーションシステム内から(オンラインマッチング中に)手順的にデータアセットを生成する必要がある場合、DBクラスのアセットのライブ追加は、「expose verified frame status(検証済みフレームステータスの公開)」設定オプションと一緒に実行する必要があります(決定論的構成に関する章で詳しく説明します)。
マップアセットのベイキングパイプライン
Quantumでカスタムデータを生成するために使用できる別の柔軟なエントリポイントとして、マップベーキングパイプラインがあります。
Map アセットは、Quantumシミュレーションで必要とされ、Navmeshや静的コライダなどの基本情報だけでなく、カスタムアセットスロット(任意のカスタムデータアセットのインスタンスでも可)が含まれています。
このカスタムアセットは、初期化または実行時に使用される任意の静的データ(たとえば、位置、生成されたタイプなどのスポーンポイントデータの配列)を格納するために使用できます。
Unityエディタから bake ボタンが呼び出されるたびにカスタムコードを呼び出すように割り当てるには、必須の OnBake(MapDataデータ) メソッドを、クラス拡張を行う MapDataBakerCallback でオーバーライドします。
C#
public class MyCustomDataBaker: MapDataBakerCallback
{
public void OnBake(MapData data)
{
// any custom code to live-load data from scene to be baked into a custom asset
// generated custom asset can then be assigned to data.Asset.Settings.UserAsset
}
}
community-wikiでアセットパイプラインの事例をご覧ください。:
- Code Snippets/Auto Baking Map Data
- Tooling/Creating Unity MonoBehaviours which bake out assets easily