Assets in Simulation
資料資產類別
Quantum資產是 C# 類別,在運行時充當不可變的資料容器。以下幾條規則定義了這些資產的設計、實現和使用方式。
以下是一個資產類別(用於角色規格)的最小定義,包含一些簡單的確定性屬性:
C#
public class CharacterSpec : AssetObject {
public FP Speed;
public FP MaxHealth;
}
在 Unity 中創建並將資產類別的實例加載到資料庫(從 Unity 編輯)將在本章後續部分介紹。
使用和連結資產
資產實例是不可變的物件,必須以參考形式攜帶。由於普通的 C# 物件參考不允許包含在我們記憶體對齊的 ECS 結構中,因此必須在 DSL 中使用 asset_ref 特殊類型來宣告遊戲狀態中的屬性(來自實體、組件或任何其他瞬態資料結構):
Qtn
component CharacterData {
// reference to an immutable instance of CharacterSpec (from the Quantum asset database)
asset_ref<CharacterSpec> Spec;
// other component data
}
在創建角色實體時分配資產參考的一種方法是直接從框架資產資料庫中獲取實例並將其設置為屬性:
C#
// assuming cd is a pointer to the CharacterData component
// using the SLOW string path option (fast data driven asset refs will be explained next)
cd->Spec = frame.FindAsset<CharacterSpec>("path-to-spec");
資產的基本用途是在運行時讀取資料並將其應用於系統內的任何計算。以下示例使用分配的 CharacterSpec 中的 Speed 值來計算相應的角色速度(物理引擎):
C#
// consider cd a CharacterData*, and body a PhysicsBody2D* (from a component filter, for example)
var spec = frame.FindAsset(cd->Spec);
body->Velocity = FPVector2.Right * spec.Speed;
關於確定性的說明
請注意,上述代碼僅在運行時讀取 速度 屬性來計算角色的期望速度,但其值(速度)從未被更改。
在運行時從 Update 內部切換遊戲狀態資產參考是完全安全和有效的(因為 asset_ref 是一個可回滾的類型,因此可以成為遊戲狀態的一部分)。
然而,更改資料資產屬性的 值 是非確定性的(因為資產的內部資料不被視為遊戲狀態的一部分,因此永遠不會回滾)。
以下代碼片段展示了在運行時什麼是安全的(切換參考)和不安全的(更改內部資料):
C#
// cd is a CharacterData*
// this is VALID and SAFE, as the CharacterSpec asset ref is part of the game state
cd->Spec = frame.FindAsset<CharacterSpec>("anotherCharacterSpec-path");
// this is NOR valid NEITHER deterministic, as the internal data from an asset is NOT part of the transient game state:
var spec = frame.FindAsset<CharacterSpec>("anotherCharacterSpec-path");
// (DO NOT do this) changing a value directly in the asset object instance
spec.Speed = 10;
資產繼承
可以在資料資產中使用繼承,這為開發者提供了更大的靈活性(特別是與多型方法一起使用時)。
繼承的基本步驟是創建一個抽象的基礎資產類別(我們將繼續使用 CharacterSpec 示例):
C#
public abstract class CharacterSpec : AssetObject {
public FP Speed;
public FP MaxHealth;
}
CharacterSpec 的具體子類可以添加自己的自訂資料屬性,並且必須標記為 可序列化 類型:
C#
public class MageSpec : CharacterSpec {
public FP HealthRegenerationFactor;
}
public class WarriorSpec : CharacterSpec {
public FP Armour;
}
資料驅動的多型
在遊戲邏輯中直接評估(在 if 或 switch 語句中)具體的 CharacterSpec 類別是非常糟糕的設計,因此資產繼承與多型方法結合使用更有意義。
N請注意,在資料資產中添加邏輯意味著在 quantum.state 專案中實現邏輯,並且這些邏輯仍需考慮以下限制:
- 操作瞬態遊戲狀態資料:這意味著資料資產中的邏輯方法必須接收瞬態資料作為參數(可以是實體指針或 Frame 物件本身);
- 僅讀取,永不修改資產本身的資料:資產仍必須被視為 不可變 的唯讀實例;
以下示例在基礎類別中添加了一個虛擬方法,並在其中一個子類中實現了自訂邏輯(請注意,我們使用了本文檔前面定義的 Character 實體的 Health 欄位):
C#
public unsafe abstract class CharacterSpec : AssetObject {
public FP Speed;
public FP MaxHealth;
public virtual void UpdateCharacter(Frame frame, EntityRef entity, CharacterData* data) {
if (data->Health < 0)
frame.Destroy(entity);
}
}
public unsafe class MageSpec : CharacterSpec {
public FP HealthRegenerationFactor;
// reads data from own instance and uses it to update transient health of Character pointer passed as param
public override void UpdateCharacter(Frame frame, EntityRef entity, CharacterData* data) {
data->Health += HealthRegenerationFactor * frame.DeltaTime;
base.UpdateCharacter(frame, entity, data);
}
}
要獨立於分配給每個 CharacterData 的具體資產使用此靈活的方法實現,可以從任何系統執行:
C#
// Assuming data is the pointer to a specific entity's CharacterData component, and entity is the corresponding EntityRef:
var spec = frame.FindAsset(data->Spec);
// Updating Health using data-driven polymorphism (behavior depends on the data asset type and instance assigned to character
spec.UpdateCharacter(frame, entity, data);
在資產中使用 DSL 生成的結構
在 DSL 中定義的 Structs
也可以在資產中使用。DSL 結構必須標記為 [Serializable]
屬性,否則資料在 Unity 中不可檢查。
[Serializable]
struct Foo {
int Bar;
}
在量子資產中使用 DSL struct
。
C#
public class FooUser : AssetObject {
public Foo F;
}
如果結構不是 [Serializable]
友好的(例如因為它是聯合或包含Quantum集合),則可以使用原型代替:
C#
using Quantum.Prototypes;
public class FooUser : AssetObject {
public FooPrototype F;
}
原型可以在需要時具體化為模擬結構:
C#
Foo f = new Foo();
fooUser.F.Materialize(frame, ref f, default);
在運行時添加靜態資產
可以在運行時將靜態資產添加到資產資料庫中,前提是在Quantum模擬開始之前。這對於從後端下載地圖或程序生成內容等場景非常有用。在運行時添加資產時,必須確保每個資產具有確定性的 GUID,以確保所有客戶端的一致性。
生成確定性AssetGuid
的方法有兩種:
- 使用常數:
- 這是最簡單的生成確定性
AssetGuid
的方法。 - 必須確保沒有其他資產被分配相同的
AssetGuid
。 - 只要您有固定數量的資產要添加,並且確定相同的資產始終被分配相同的
AssetGuid
,這種方法就沒問題。
- 這是最簡單的生成確定性
- 生成:
QuantumUnityDB.CreateRuntimeDeterministicGuid
方法提供了生成AssetGuid
的 API。- 它使用資產物件的名稱作為種子來生成確定性的
AssetGuid
。 - 如果您需要添加的資產數量不固定,應使用此方法。
使用方法:
C#
// create any asset
var assetObject = AssetObject.Create<MyAssetObjectType>();
// set its name
assetObject.name = "My Unique Asset Object Name";
// get a deterministic GUID
var guid = QuantumUnityDB.CreateRuntimeDeterministicGuid(assetObject);
// add the asset to the asset database
QuantumUnityDB.Global.AddAsset(assetObject);
// set the GUID
assetObject.Guid = guid;
將資產添加到資產資料庫後,它實際上與在編輯器中創建和添加的資產相同。然而,這僅在遊戲尚未開始時有效。一旦遊戲開始,靜態資產資料庫就不應被修改。
如果您需要在遊戲過程中添加或修改資產,則必須改用DynamicDB
API。
動態資產
資產可以在運行時由模擬創建。此功能稱為 DynamicAssetDB。
C#
var mageSpec = AssetObject.Create<MageSpec>();
mageSpec.Speed = 1;
mageSpec.MaxHealth = 100;
frame.AddAsset(mageSpec);
此類資產可以像任何其他資產一樣加載和釋放:
C#
MageSpec asset = frame.FindAsset<MageSpec>(assetGuid);
frame.DisposeAsset(assetGuid);
動態資產不會在節點之間同步。相反,創建新資產的代碼必須是確定性的,並確保每個節點將使用相同的值生成資產。
上述規則的唯一例外是當有延遲加入時——新客戶端將收到 DynamicAssetDB 的快照以及最新的幀數據。與幀的序列化不同,動態資產的序列化和反序列化被委託給模擬外部的IAssetSerializer
接口。在 Unity 中運行時,默認使用QuantumUnityJsonSerializer
:它能夠序列化/反序列化任何 Unity 可序列化的類型。
初始化DynamicAssetDB
模擬可以使用預先存在的動態資產進行初始化。與在模擬過程中添加資產類似,這些資產需要在客戶端之間保持確定性。
首先,需要創建一個DynamicAssetDB
實例並填充資產:
C#
var initialAssets = new DynamicAssetDB();
initialAssets.AddAsset(mageSpec);
initialAssets.AddAsset(warriorSpec);
...
其次,需要使用QuantumGame.StartParameters.InitialDynamicAssets
將實例傳遞給新的模擬。在 Unity 中,由於是QuantumRunner
行為管理QuantumGame
,因此使用QuantumRunner.StartParamters.InitialDynamicAssets
代替。
內置資產
Quantum還附帶了幾個內置資產,例如:
- SimulationConfig - 定義Quantum模擬的許多規格,從場景管理設置、堆配置、線程數和物理/導航設置;
- DeterministicConfig - 指定遊戲會話的細節,如其模擬速率、校驗和間隔以及許多與客戶端和伺服器相關的輸入配置;
- QuantumEditorSettings - 包含許多僅限編輯器的細節定義,例如資料庫應基於的文件夾、Gizmos 的顏色以及自動構建選項,用於自動烘焙地圖、導航網格等;
- BinaryData - 一種資產,允許用戶參考任意二進制信息(以
byte[]
的形式)。例如,默認情況下,物理和導航引擎使用二進制資料資產來存儲靜態三角形資料等信息。此資產還內置了使用 gzip 壓縮和解壓資料的實用工具。 - CharacterController3DConfig - 內置 3D KCC 的配置資產。
- CharacterController2DConfig - 內置 2D KCC 的配置資產。
- PhysicsMaterial - 為 Quantum 的 3D 物理引擎定義
Physics Material
。 - PolygonCollider - 為 Quantum 的 2D 物理引擎定義
Polygon Collider
。 - NavMesh - 定義 Quantum 導航系統使用的
NavMesh
。 - NavMeshAgentConfig - 為 Quantum 的導航系統定義
NavMesh Agent Config
。 - Map - 存儲許多靜態的每場景信息,例如物理設置、碰撞器、導航網格設置、鏈接、區域以及該地圖上的場景實體原型。每個地圖與單個 Unity 場景相關聯。