Unityにおけるアセット
概要

Unityでは、AssetObject
は UnityEngine.ScriptableObject
から派生しているため、Quantumアセットは一般的に他のカスタムUnityアセットと同様に .asset
ファイルとして保存されます。しかし、AssetObjects
はシミュレーションコードに利用可能であり、いつでも AssetRef
でアクセスできる必要があるため、管理および追跡が必要です。
AssetObject
アセットがインポートされると、Quantumはそれが QuantumEditorSettings.AssetSearchPaths
のいずれかに存在するかどうかを確認します(デフォルトでは Assets
フォルダーとそのすべての子フォルダー)。存在しない場合は、無視され、シミュレーションで利用できなくなります。そうでなければ:
- アセットラベル
QuantumAsset
が適用されます。 Identifier.Guid
が決定論的かつユニークなAssetGuid
に設定されます。この値はアセットのUnityGUID
およびfileID
に基づいているため、アセットを移動またはリネームしてもAssetGuid
は変更されません。Identifier.Path
はアセットファイルのパスに設定され、Assets/
プレフィックスと拡張子は省略されます。
さらに、これが新しいアセットであるか、アセットが移動された場合、QuantumUnityDB
アセットが更新されます:
QuantumAsset
ラベルを持つすべてのAssetObject
が発見されます。- 各
AssetObject
には、AssetGuid
とランタイムでAssetObject
を読み込むために必要な情報(例えば、アドレッサブルパス、リソースパス)を含む生成されたエントリが持たれます。 - エントリは
QuantumUnityDB
アセットに保存されます(デフォルトではAssets/QuantumUser/Resources/QuantumUnityDB.qunitydb
に保存されます)。
データベースに現在含まれている AssetObjects
のリストを参照するには、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);
}
AssetGuidsの上書き
場合によっては、アセットの決定論的な AssetGuid
を上書きする必要があるかもしれません。
これは、アセットオブジェクトに移動し、Quantum Unity DB
と名付けられたドロップダウンをクリックし、Guid Override
を有効にすることで行えます。カスタムのAssetGuidを入力するためのフィールドが提供されます。
これらの上書きは QuantumEditorSettings
に保存されます。

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

マップアセットベーキングパイプライン
Quantumにおけるカスタムデータ生成の別のエントリーポイントは、マップベーキングパイプラインです。
Map
アセットはQuantumシミュレーションに必要で、NavMeshや静的コライダーなどの基本情報を含みます。追加のカスタムデータは、そのカスタムアセットスロットに配置されたアセットの一部として保存できます。これには、初期化またはランタイム中に使用される静的データを格納するためにカスタムデータアセットのインスタンスが利用できます。典型的な例としては、位置や生成されたタイプなどのスポーンポイントデータの配列が挙げられます。
Unityシーンを Map
と関連付けるためには、シーン内の GameObject
に MapData
MonoBehaviour
コンポーネントが存在する必要があります。MapData.Asset
が有効な Map
を指すと、ベーキングプロセスが実行可能になります。デフォルトでは、Quantumはシーンを保存する際やプレイモードに入るときに、NavMesh、静的コライダー、シーンプロトタイプを自動的にベイクします。この動作は 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) {
// シーンからカスタムアセットにベイクするためのデータをライブロードするためのカスタムコード
// 生成されたカスタムアセットは、data.Asset.Settings.UserAsset に割り当てることができます
}
public void OnBeforeBake(MapData data) {
}
}
アドレッサブルアセットのプリアロード
Quantumはアセットを同期的にロードできる必要があります。
WaitForCompletion
はAddressables 1.17で追加され、アセットを同期的にロードする機能が提供されました。
非同期的な読み込みも可能ですが、アセットのプリアルロードが好ましい場合もあります。QuantumRunnerLocalDebug.cs
スクリプトはこの方法を実現する方法を示しています。
ビルド内でのQuantumアセットの更新
外部のCMSがデータアセットを提供することは可能です。これは、プレイヤーが新しいビルドを更新する必要なく、リリース済みのゲームにバランス調整の更新を提供するのに特に便利です。
このアプローチにより、キャラクタースペック、マップ、NPCスペックなどのデータ駆動型の側面に関する情報を含むバランスシートが、ゲームビルド自体とは独立して更新されることができます。この場合、ゲームクライアントは常にCMSサービスに接続し、更新があるかどうかを確認し(必要に応じて)、オンラインマッチを開始または参加する前に最新バージョンにゲームデータをアップグレードします。
既存アセットの更新
アドレッサブルの使用が推奨されます。これらはすぐにサポートされるためです。アドレッサブルである AssetObject
は、ランタイム中に適切な方法を使用してロードされます。
ゲームシミュレーション中にアセットをダウンロードすることから生じる予測不可能なラグスパイクを避けるためには、ここで議論されているようにアセットをダウンロードし、プリアローディングすることを検討してください:アドレッサブルアセットのプリアロード。
実行時に新しいアセットを追加する
エディタで生成された QuantumUnityDB
には、その作成時に存在するすべてのアセットのリストが含まれます。プロジェクトの動的コンテンツに新しいQuantumアセットを追加することが含まれる場合、ビルドを新たに作成せずにデータベースを更新する方法が実装される必要があります。新しいアセットは、シミュレーションの前または実行中に、いつでも QuantumUnityDB
に追加できます。ユーザーは、新しく追加されたアセットの AssetGuids
がすべてのクライアントで同一であることを確認する必要があります。
最も簡単なアプローチは 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);
}
どちらのアプローチも、AssetObject
がメモリに読み込まれるという欠点があります - ScriptableObject.CreateInstance
によってその作成の瞬間に、シミュレーションが実際にそれを読み込むかどうかに関わらず。
もしアセットがアドレッサブルであれば、これを簡単に回避できます:
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) {
// アセットを待機前に作成する。これはメインスレッドで行う必要があります。
var asset = ScriptableObject.CreateInstance<CharacterSpec>();
asset.MaxHealth = 100;
// タスクは別スレッドで再開されるため、メインスレッドがブロックされている可能性があるため、
// メインスレッドには入らないようにします。
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
をアドレッサブルにすることで、QuantumGlobalScriptableObjectAddress
属性を使用してQuantumにアドレッサブルでロードさせることができます:
C#
[assembly: Quantum.QuantumGlobalScriptableObjectAddress(typeof(QuantumUnityDB), "QuantumUnityDBAddress")]
これにより、QuantumUnityDB.Global
プロパティまたは任意の QuantumUnityDB.Global*
メソッドがアクセスされると、その時点で "QuantumUnityDBAddress" アドレスから QuantumUnityDB
がロードされます。
あるいは、QuantumGlobalScriptableObjectSourceAttribute
から派生した属性を用いて、データベースをカスタムで読み込む手段を実装することもできます。
DynamicAssetDB を使った新しいアセットの追加
新しいアセットが決定論的に作成できる場合、DynamicAssetDB
を使用することができます。詳細については、こちらをご覧ください: ダイナミックアセット。