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を使用することができます。詳細は動的アセットをご覧ください。