Unity内のアセット
概要

Unityでは、AssetObject
はUnityEngine.ScriptableObject
を継承しているため、他のUnityアセットと同様に、Quantumアセットは通常.asset
ファイルとして保存されます。ただし、AssetObject
はシミュレーションコードから利用可能かつAssetRef
からいつでも参照できる必要があるため、管理や追跡が必要です。
AssetObject
アセットがインポートされると、QuantumはそのアセットがQuantumEditorSettings.AssetSearchPaths
(デフォルトでは、Assets
フォルダー以下)に存在するかどうかをチェックします。存在しなければ、そのアセットは無視されるため、シミュレーションで利用できません。存在するなら、
- アセットにラベル
QuantumAsset
が適用されます。 Identifier.Guid
が決定論的でユニークなAssetGuid
に設定されます。この値はUnityのGUID
とfileID
に基づいているため、アセットを移動/リネームしてもAssetGuid
は変わりません。Identifier.Path
がアセットファイルのパスに設定され、Assets/
接頭辞と拡張子は省略されます。
さらに、これが新しいアセットであるか、アセットが移動したものである場合は、QuantumUnityDB
アセットが更新されます。
QuantumAsset
ラベルが付いたすべてのAssetObject
を見つけます。- 各
AssetObject
では、AssetGuid
と、実行時にAssetObject
をロードするために必要な情報(例:Addressableパス・Resourcesパス)を含むエントリーが生成されます。 - エントリーは
QuantumUnityDB
アセット(デフォルトではAssets/QuantumUser/Resources/QuantumUnityDB.qunitydb
)に保存されます。
データベースに含まれるAssetObject
のリストを閲覧するには、Quantum/Window/Quantum Unity DB
から開けるQuantumUnityDB
インスペクターウインドウを使用してください。

実行時にロードされるQuantumUnityDB
は、シミュレーションのIResourceManager
として、エントリーがアセットを動的にロードするために使用されます。
Unityスクリプト内でのQuantumアセットの検索
シミュレーション外でアセットにアクセスするには、QuantumUnityDB.GetGlobalAsset
/QuantumUnityDB.TryGetGlobalAsset
の静的メソッドを使用します。これらメソッドの呼び出しによって、QuantumUnityDB
に保存されているエントリーを利用します。これは、シミュレーションでFrame.FindAsset
/Frame.TryFindAsset
を呼び出すのと同等です。
C#
CharacterSpec characterData = QuantumUnityDB.GetGlobalAsset(myAssetRef);
FP maximumHealth = characterData.MaximumHealth;
C#
if (QuantumUnityDB.TryGetGlobalAsset(myAssetRef, out CharacterSpec characterData)) {
FP maximumHealth = characterData.MaximumHealth;
}
インスペクター内でのアセットの検索
エディタースクリプトからQuantumアセットをロードする場合には、GetGlobalAssetEditorInstance
/TryGetGlobalAssetEditorInstance
を使用することが重要です。これらメソッドは、Unity Editor APIを使用してアセットをロードします。
使用例:
C#
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
CharacterSpec characterData = QuantumUnityDB.GetGlobalAssetEditorInstance(myAssetRef);
FP maximumHealth = characterData.MaximumHealth;
// 何かする
EditorUtility.SetDirty(characterData);
}
AssetGuidの上書き
場合によっては、アセットの決定論的なAssetGuid
を上書きする必要があるかもしれません。
アセットオブジェクトを選択し、Quantum Unity DB
ドロップダウンをクリックして、Guid Override
を有効にすると、独自のAssetGuid
を入力するフィールドが編集できます。
上書きした値はQuantumEditorSettings
に保存されます。

ResoucesとAddressables
Quantumでは、AssetObject
アセットのハード参照をできるだけ避けることで、任意の動的コンテンツ配信が使用可能になります。標準では、以下のアセットロード方法がサポートされてます。
- Addressables:アセットに(明示的/暗黙的)アドレスがある場合
- Resources:アセットが
Resources
フォルダー内にある場合 - ハード参照:上記のいずれも適用できない場合
各アセットロード方法の詳細は、QuantumUnityDB
に保存されています。シミュレーションでFrame.FindAsset
を呼び出す時か、QuantumUnityDB.GetGlobalAsset
を呼び出す時に、この情報が参照され、適切なロード方法が使用されます。QuantumUnityDB
ロード時に、ハード参照されているすべてのアセットもロードされます。これはQuantumUnityDB
自体がAddressableである場合には、最適ではないことがあります。
アセットのリスト(QuantumUnityDB
)を動的にするには、追加実装が必要です。詳細は実行時にQuantumアセットを更新するセクションをご覧ください。
独自スクリプトは、AssetObject
を参照する(例:CharacterSpec
)かわりに、AssetRef<T>
を使用する(例:AssetRef<CharacterSpec>
)ことで、Quantumアセットのハード参照を避けることができます。
C#
public class TestScript : MonoBehaviour {
// ハード参照
public CharacterSpec HardRef;
// ソフト参照
public AssetRef<CharacterSpec> SoftRef;
void Start() {
// 対象アセットの設定に応じて、適切な読み込みメソッドが使用される
CharacterSpec characterData = QuantumUnityDB.GetGlobalAsset(SoftRef);
}
}
Unityでのアセットのドラッグ&ドロップ
追加したアセットインスタンスを、シミュレーションのシステム内のFrameクラスから検索することには限界があります。便利な解決策として、アセットインスタンスはデータベースの参照を指し示すため、これら参照をUnityエディターでドラッグ&ドロップすることができます。
一般的な使用例は、組み込みのRuntimePlayer
クラスを拡張して、プレイヤーのCharacterSpec
アセットのAssetRef
を含めることです。アセットや他のコンフィグオブジェクトの参照をリンクするには、生成された型安全なasset_ref
型を使用します。
C#
// RuntimePlayer.User.csファイルに追加される
namespace Quantum {
partial class RuntimePlayer {
public AssetRef<CharacterSpec> CharacterSpec;
}
}
このスニペットは、CharacterSpec
型のアセットへのリンクのみを受け付けるasset_ref
を生成します。フィールドはUnityのインスペクターに表示されるので、アセットをそこにドラッグ&ドロップすることができます。

マップアセットベイクのパイプライン
Quantumにおける独自データ生成の別のエントリーポイントは、マップベイクのパイプラインです。
Map
アセットはナビメッシュや静的コライダーなどの基本情報を含み、Quantumシミュレーションで必要になります。独自データアセットの追加は、カスタムアセットスロットに置かれたアセットの一部として保存されます。独自アセットは、初期化時や実行時に使用される任意の静的データを格納するために使用されます。典型的な例は、スポーンポイントのデータ(位置やタイプなど)の配列です。
UnityシーンをMap
と関連付けるためには、シーン内に存在するゲームオブジェクトにMapData
コンポーネントが付いている必要があります。MapData.Asset
が有効なMap
なら、ベイク処理が実行されます。デフォルトでQuantumは、シーン保存時やゲーム再生時に、ナビメッシュ・静的コライダー・シーン上のプロトタイプを自動的にベイクします。この動作はQuantumEditorSettings
から変更できます。
ベイクする毎に独自コードを呼び出したい場合は、抽象クラスMapDataBakerCallback
を継承したクラスを作成してください。
C#
public abstract class MapDataBakerCallback {
public abstract void OnBake(MapData data);
public abstract void OnBeforeBake(MapData data);
public virtual void OnBakeNavMesh(MapData data) { }
public virtual void OnBeforeBakeNavMesh(MapData data) { }
}
そして、OnBake(MapData data)
とOnBeforeBake(MapData data)
をオーバーライドします。
C#
[assembly: QuantumMapBakeAssembly]
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
// 独自アセットにベイクするシーンからデータをロードするコード
// 生成された独自アセットは、data.Asset.Settings.UserAssetに割り当てることができる
}
public void OnBeforeBake(MapData data) {
}
}
Addressableアセットのプリロード
Quantumは、アセットを同期的にロードできる必要があります。
Addressables 1.17ではWaitForCompletion
が追加され、アセットを同期的にロードできるようになります。
非同期ロードも可能ですが、アセットをプリロードする方が好ましく、QuantumRunnerLocalDebug.cs
スクリプトでそれを実現する方法を示しています。
ビルド内Quantumアセットの更新
外部CMSからデータアセットを提供することは可能です。これは、リリース済みのゲームのビルドを更新せずに、バランス調整の更新をプレイヤーへ提供するのに特に便利です。
このアプローチにより、キャラクター仕様・マップ・NPC仕様などのデータ駆動型の情報を含むバランスシートを、ゲームビルド自体とは独立して更新することができます。その場合、ゲームクライアントは常にCMSサービスに接続し、(必要に応じて)更新を確認して、オンラインマッチを開始/参加する前に、最新バージョンにゲームデータをアップグレードします。
既存アセットの更新
これには、標準でサポートされているAddressablesの使用が推奨されます。任意のAddressableなAssetOject
は、実行時に適切なメソッドを使用してロードされます。
ゲームシミュレーション中にアセットをダウンロードすることから生じる予測不可能なラグスパイクを避けるには、Addressableアセットのプリロードで説明されているように、アセットをプリロードすることを検討してください。
実行時に新しいアセットを追加
エディターで生成されたQuantumUnityDB
は、作成時に存在するすべてのアセットのリストを含みます。新しいビルドを作成せずに、プロジェクトの動的コンテンツに新しいQuantumアセットを追加したい場合は、データベースを更新する方法を実装する必要があります。シミュレーションの実行前や実行中のいつでも、新しいアセットをQuantumUnityDB
に追加できます。ここで、新しく追加されたアセットのAssetGuid
が、すべてのクライアント間で同一であることを確認する必要があります。
これを行う最も簡単なアプローチは、QuantumUnityDB.AddAsset
メソッドです。
C#
public void AddStaticAsset(AssetGuid guid) {
var asset = ScriptableObject.CreateInstance<CharacterSpec>();
asset.Guid = guid;
asset.Speed = 10;
asset.MaxHealth = 100;
QuantumUnityDB.Global.AddAsset(asset);
}
また、次のようにアセットを追加することもできます。
C#
public void AddStaticAsset(AssetGuid guid) {
var asset = ScriptableObject.CreateInstance<CharacterSpec>();
asset.Guid = guid;
asset.Speed = 10;
asset.MaxHealth = 100;
QuantumUnityDB.Global.AddSource(new QuantumAssetObjectSourceStatic(asset), guid);
}
どちらのアプローチも、シミュレーションが実際にアセットをロードするかどうかにかかわらず、ScriptableObject.CreateInstance
の作成時にAssetObject
がメモリにロードされるという欠点があります。
アセットがAddressableであれば、これは簡単に回避できます。
C#
public void AddAddressableAsset(AssetGuid guid, Type assetType, string address) {
var source = new QuantumAssetObjectSourceAddressable(address, assetType);
QuantumUnityDB.Global.AddSource(source, guid);
}
IQuantumAssetObjectSource
インターフェースを実装することで、完全に独自のアセットローディングを行うこともできます。以下のスニペットは、エラー処理を省略して、Task<AssetObject>
ファクトリーからアセットを非同期にロードする独自アセットソースです。
C#
public void AddCustomAsset(AssetGuid guid) {
var source = new AsyncAssetObjectSource() {
AssetType = typeof(CharacterSpec), Factory = () => LazyCreateCharacterSpec(guid)
};
QuantumUnityDB.Global.AddSource(source, guid);
}
private async Task<AssetObject> LazyCreateCharacterSpec(AssetGuid guid) {
// アセット作成はメインスレッドで実行する必要があるため、await前に実行する
var asset = ScriptableObject.CreateInstance<CharacterSpec>();
asset.MaxHealth = 100;
// DB待機でメインスレッドがブロックされないようにするため、別スレッドでタスクを再開する
await Task.Delay(1000).ConfigureAwait(false);
return asset;
}
public class AsyncAssetObjectSource : IQuantumAssetObjectSource {
private Task<AssetObject> _task;
public Func<Task<AssetObject>> Factory { get; set; }
public Type AssetType { get; set; }
public void Acquire(bool synchronous) => _task = Factory();
public void Release() => _task = null;
public AssetObject WaitForResult() => _task.Result;
public bool IsCompleted => _task?.IsCompleted == true;
public string Description => $"AsyncAssetObjectSource: {AssetType}";
public AssetObject EditorInstance => null; // エディターインスタンスは非対応
}
動的QuantumUnityDB
手動で新しいアセットを追加するかわりに、QuantumUnityDB
自体を動的にすることができます。
QuantumUnityDB.qunitydb
をAddressableにすると、QuantumGlobalScriptableObjectAddress
属性を使用して、AddressablesでQuantumにロードすることができます。
C#
[assembly: Quantum.QuantumGlobalScriptableObjectAddress(typeof(QuantumUnityDB), "QuantumUnityDBAddress")]
これによって、QuantumUnityDB.Global
プロパティか、任意のQuantumUnityDB.Global*
メソッドにアクセスした際に、"QuantumUnityDBAddress"
からQunatumUnityDB
がロードされます。
または、QuantumGlobalScriptableObjectSourceAttribute
を継承した属性から、データベースを独自にロードする手段を実装することもできます。
DynamicAssetDBによる新しいアセットの追加
新しいアセットが決定論的に作成できる場合、DynamicAssetDB
を使用することができます。詳細は動的アセットをご覧ください。