Assets in Unity
概述

由於在 Unity 中,AssetObject
繼承自UnityEngine.ScriptableObject
,Quantum資產通常存儲在.asset
文件中,就像其他自定義 Unity 資產一樣。然而,因為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 編輯器 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
中。

資源與可尋址資產
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 檢查器中,並可以通過將資產拖放到插槽中來填充。

地圖資產烘焙管道
另一個在 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需要資產能夠同步加載。
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
,如*動態資產*中所述。