This document is about: QUANTUM 3
SWITCH TO

Assets in Unity

概述

Editing a data asset
在 Unity 中編輯數據資產的屬性。

由於在 Unity 中,AssetObject繼承自UnityEngine.ScriptableObject,Quantum資產通常存儲在.asset文件中,就像其他自定義 Unity 資產一樣。然而,因為AssetObjects需要對模擬代碼可用,並且需要隨時通過AssetRef訪問,所以需要對它們進行管理和追蹤。

每當導入一個AssetObject資產時,Quantum 會檢查它是否位於QuantumEditorSettings.AssetSearchPaths中的某個路徑(默認為Assets文件夾及其所有子文件夾)。如果不是,它將被忽略且不會對模擬可用。。否則:

  • 會應用QuantumAsset資產標籤。
  • Identifier.Guid會被設置為一個確定且唯一的AssetGuid。該值基於資產的 Unity GUIDfileID,因此移動/重命名資產不會改變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.GetGlobalAssetQuantumUnityDB.TryGetGlobalAsset靜態方法。這些方法的調用會使用QuantumUnityDB中存儲的條目,並等效於在模擬中調用Frame.FindAssetFrame.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 編輯器 API 來加載資產。

使用方法:

C#

public override void OnInspectorGUI()
{
    base.OnInspectorGUI();

    CharacterSpec characterData = QuantumUnityDB.GetGlobalAssetEditorInstance(myAssetRef);
    FP maximumHealth = characterData.MaximumHealth;

    // do something

    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)來避免硬引用。

C#

public class TestScript : MonoBehaviour {
  // hard reference
  public CharacterSpec HardRef;
  // soft reference
  public AssetRef<CharacterSpec> SoftRef;

  void Start() {
    // depending on the target asset's settings, this call may result in
    // any of the supported loading methods being used
    CharacterSpec characterData = QuantumUnityDB.GetGlobalAsset(SoftRef);
  }
}

在 Unity 中拖放資產

從模擬系統內部通過 類添加資產實例並搜索它們的功能有限。一個方便的解決方案是讓資產實例指向數據庫引用,並能夠在 Unity 編輯器中拖放這些引用。

一種常見的用法是擴展預構建的RuntimePlayer類,以包含玩家選擇的特定CharacterSpec資產的AssetRef。生成的類型安全的asset_ref類型用於在資產或其他配置對象之間建立引用鏈接。

C#

// this is added to the RuntimePlayer.User.cs file
namespace Quantum {
  partial class RuntimePlayer {
    public AssetRef<CharacterSpec> CharacterSpec;
  }
}

此代碼片段將生成一個asset_ref,它只接受指向CharacterSpec類型資產的鏈接。此字段將顯示在 Unity 檢查器中,並可以通過將資產拖放到插槽中來填充。

Drag & Drop Asset
資產引用屬性顯示為 Quantum 可腳本化對象的類型安全插槽。

地圖資產烘焙管道

另一個在 Quantum 中生成自定義數據的入口點是地圖烘焙管道。

Map資產是 Quantum 模擬所必需的,包含基本信息,如導航網格和靜態碰撞體;額外的自定義數據可以作為資產的一部分存儲在其自定義資產插槽中——這可以是任何自定義數據資產的實例。自定義資產可用於存儲在初始化或運行時使用的任何靜態數據。典型的例子是生成點數據的數組,例如位置、生成類型等。

為了讓 Unity 場景與Map關聯,場景中的GameObject上需要存在 MapData MonoBehaviour組件。一旦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)OnBakeBefore(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
  }
  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) {
  // create asset before the await, as this needs to be done in the main thread
  var asset = ScriptableObject.CreateInstance<CharacterSpec>();
  asset.MaxHealth = 100;

  // task will resume on a different thread; we don't want to enter the main thread as the main thread may be blocked with
  // the DB waiting
  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; // no support for editor instance
}

動態QuantumUnityDB

另一種手動添加新資產的方法是使QuantumUnityDB本身動態化。

如果QuantumUnityDB.qunitydb是可尋址的,可以使用QuantumGlobalScriptableObjectAddress屬性指示 Quantum 通過可尋址資產加載它:

C#

[assembly:Quantum.QuantumGlobalScriptableObjectAddress(typeof(QuantumUnityDB), "QuantumUnityDBAddress")]

這將導致QuantumUnityDB從可尋址資產中以 "QuantumUnityDBAddress" 地址加載,當訪問QuantumUnityDB.Global屬性或任何QuantumUnityDB.Global*方法時。

或者,可以通過派生自QuantumGlobalScriptableObjectSourceAttribute的屬性實現自定義加載數據庫的方法。

使用 DynamicAssetDB 添加新資產

如果新資產可以以確定性的方式創建,可以使用DynamicAssetDB,如*動態資產*中所述。

Back to top