This document is about: QUANTUM 3
SWITCH TO

Unityにおけるアセット

概要

Editing a data asset
Editing properties of a data asset from Unity.

Unityでは、AssetObjectUnityEngine.ScriptableObject から派生しているため、Quantumアセットは一般的に他のカスタムUnityアセットと同様に .asset ファイルとして保存されます。しかし、AssetObjects はシミュレーションコードに利用可能であり、いつでも AssetRef でアクセスできる必要があるため、管理および追跡が必要です。

AssetObject アセットがインポートされると、Quantumはそれが QuantumEditorSettings.AssetSearchPaths のいずれかに存在するかどうかを確認します(デフォルトでは Assets フォルダーとそのすべての子フォルダー)。存在しない場合は、無視され、シミュレーションで利用できなくなります。そうでなければ:

  • アセットラベル QuantumAsset が適用されます。
  • Identifier.Guid が決定論的かつユニークな AssetGuid に設定されます。この値はアセットのUnity GUID および 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 に保存されます。

AssetGuid Override
Quantum 2から移行されたアセットは、`Guid Overrides` を使用してその非決定論的な `AssetGuids` を保持します。

リソースとアドレッサブル

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インスペクターに表示され、アセットをスロットにドラッグ&ドロップすることで入力できます。

Drag & Drop Asset
アセットのリファレンスプロパティは、Quantumスクリプタブルオブジェクトのための型安全なスロットとして表示されます。

マップアセットベーキングパイプライン

Quantumにおけるカスタムデータ生成の別のエントリーポイントは、マップベーキングパイプラインです。

Map アセットはQuantumシミュレーションに必要で、NavMeshや静的コライダーなどの基本情報を含みます。追加のカスタムデータは、そのカスタムアセットスロットに配置されたアセットの一部として保存できます。これには、初期化またはランタイム中に使用される静的データを格納するためにカスタムデータアセットのインスタンスが利用できます。典型的な例としては、位置や生成されたタイプなどのスポーンポイントデータの配列が挙げられます。

Unityシーンを Map と関連付けるためには、シーン内の GameObjectMapData 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 3.0+ では、コールバッククラスの上に `[assembly: QuantumMapBakeAssembly]` 属性が必要です。

アドレッサブルアセットのプリアロード

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 を使用することができます。詳細については、こちらをご覧ください: ダイナミックアセット

Back to top