Assets in Simulation
資料資產類別
Quantum資產是正常C#類別,其在運行階段將作為不可變的資料容器。一些規則定義了這些資產在Quantum中必須被設計、執行及使用的方式。
這是一個附有一些簡單的確定性屬性的資產類別(針對一個角色規格)的一個最小的定義:
C#
namespace Quantum {
partial class CharacterSpec {
public FP Speed;
public FP MaxHealth;
}
}
請注意,資產類別定義必須是 部分,並且被內含在Quantum命名空間之中。
本章節後面將討論建立及載入資產類別的執行個體到資料庫(從Unity編輯)。
使用及連接資產
為了告訴Quantum這是一個資產類別(透過使內部中繼資料繼承基本 資產物件 類別,並且準備資料庫來內含內部中繼資料的執行個體,來新增內部中繼資料):
C#
// this goes into a DSL file
asset CharacterSpec;
資產執行個體是不可變的物件,其必須以參照來被執行。因為正常C#物件參照不允許被包含在我們的記憶體對齊的ECS架構中,因此必須在DSL中使用 asset_ref 特殊類型,以在遊戲狀態中宣告屬性(從實體、元件或任何其他暫時性資料架構):
C#
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");
資產的基本使用是在運行階段讀取資料,並且將其應用到系統之中的任何計算。以下實例從被指派的 角色規格 使用 速度 值,以計算相應的角色速度(物理引擎):
C#
// consider cd a CharacterData*, and body a PhysicsBody2D* (from a component filter, for example)
var spec = frame.FindAsset<CharacterSpec>(cd->Spec.Id);
body->Velocity = FPVector2.Right * spec.Speed;
關於確定性的注意事項
請注意,上述程式碼只 讀取 速度 屬性,以在運行階段針對角色計算所需的速度,但是它的值(速度)將永不改變。
在運行階段從一個更新中轉換一個遊戲狀態資產參照,是完全安全且有效的(因為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;
資產物件組態屬性
您可以透過AssetObjectConfig
屬性來微調資產連結指令碼生成。
C#
[AssetObjectConfig(GenerateLinkingScripts = false)]
partial class CharacterSpec {
// ...
}
- 生成連結指令碼 (預設=真)——防止生成任何讓資產在Unity中成為可編輯的指令碼。
- 生成資產建立選單 (預設=真)——針對此資產,防止Unity
CreateAssetMenu
屬性的生成。 - 生成資產重新設定方法 (預設=真)——防止Unity可指令碼物件
Reset()
方法的生成(其中當建立資產時,一個Guid被自動地生成)。 - 自訂建立資產選單名稱 (預設=空)——覆寫
CreateAssetMenu
名稱。如果設定為null
,將使用繼承圖表來自動地生成一個選單路徑。 - 自訂建立資產選單順序 (預設=-1)——覆寫
CreateAssetMenu
順序,如果設定為-1,將使用字母的順序。
覆寫資產指令碼位置及停用AOT檔案生成
建立檔案 tools\codegen_unity\quantum.codegen.unity.dll.config
。警示:升級時需小心,因為檔案在該過程中可能遺失。
XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="RelativeLinkDrawerFilePath" value="Quantum/Editor/PropertyDrawers/Generated/AssetLinkDrawers.cs"/>
<add key="RelativeAssetScriptFolderPath" value="Quantum/AssetTypes/Generated"/>
<add key="RelativeGeneratedFolderPath" value="Quantum/Generated"/>
<add key="GenerateAOTFile" value="true"/>
<add key="EndOfLineCharacters" value=" "/>
</appSettings>
</configuration>
資產繼承
在資料資產中可以使用繼承,其提供開發者非常多的彈性(特別是當與多型方法一起使用的時候)。
繼承的基本步驟是建立一個抽象基礎資產類別(我們將繼續使用我們的 角色規格 實例):
C#
namespace Quantum {
partial abstract class CharacterSpec {
public FP Speed;
public FP MaxHealth;
}
}
角色規格 的實體子類別可能會新增它們自己的自訂資料屬性,並且必須被標記為 可序列化的 類型:
C#
namespace Quantum {
[Serializable]
public partial class MageSpec : CharacterSpec {
public FP HealthRegenerationFactor;
}
[Serializable]
public partial class WarriorSpec : CharacterSpec {
public FP Armour;
}
}
在DSL中
當您已經在DSL中宣告一個資產,您可以在DSL中使用asset_ref<T>
類型,以參照到基礎類別和其任何子類別。
C#
component CharacterData {
// Accepts asset references to CharacterSpec base class and its sub-classes(MageSpec and WarriorSpec).
asset_ref<CharacterSpec> ClassDefinition;
FP CooldownTimer;
}
如果您希望保留一個特定到一個子類別的參照,首先需要在DSL中使用asset import
來宣告被衍生的資產:
C#
asset CharacterSpec;
asset import MageSpec;
如果在DSL中已經宣告被衍生的資產,只需使用asset_ref<T>
作為基礎類別。舉例而言,為了使用直接地在DSL中MageSpec
而非CharacterSpec
,我們需要寫下列程式碼:
C#
component MageData {
// Only accepts asset references to MageSpec class.
asset_ref<MageSpec> ClassDefinition;
FP CooldownTimer;
}
由資料驅動的多型
讓遊戲遊玩邏輯直接地評估(在假如或轉換陳述式中)實體 角色規格 類別,將是一個非常糟糕的設計,所以當和多型方法搭配時,資產繼承更有道理。
請注意,新增邏輯到資料資產,意味著在quantum.state專案中執行邏輯,而這個邏輯仍然必須考慮以下限制:
- 在暫時性遊戲狀態資料上操作:這意味著在資料資產中的邏輯方法必須收到暫時性資料作為參數(實體指標或是幀物件本身);
- 只讀取,永不修改資產上的資料:資產必須仍然被對待為 不可變的 唯讀執行個體;
以下實例新增一個虛擬方法到基礎類別,並且新增一個自訂執行方式到子類別中的一個(請注意,我們在此文檔頂部使用了更多的針對 角色 實體所定義的 健康 欄位):
C#
namespace Quantum {
partial unsafe abstract class CharacterSpec {
public FP Speed;
public FP MaxHealth;
public virtual void Update(Frame f, EntityRef e, CharacterData* cd) {
if (cd->Health < 0)
f.Destroy(e);
}
}
[Serializable]
public partial 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 Update(Frame f, EntityRef e, CharacterData* cd) {
cd->Health += HealthRegenerationFactor * f.DeltaTime;
base.Update(f, e, cd);
}
}
}
為了獨立地使用這個指派到各個 角色資料 的實體資產的有彈性的方法執行方式,這可以從任何系統被執行:
C#
// Assuming cd is the pointer to a specific entity's CharacterData component, and entity is the corresponding EntityRef:
var spec = frame.FindAsset<CharacterSpec>(cd->Spec.Id);
// Updating Health using data-driven polymorphism (behavior depends on the data asset type and instance assigned to character
spec.Update(frame, entity, cd);
在資產中使用DSL生成的架構
在DSL中定義的Structs
也可以在資產上被使用。DSL架構必須以[Serializable]
屬性來標註,不然資料在Unity中無法被檢查。
[Serializable]
struct Foo {
int Bar;
}
asset FooUser;
在一個Quantum資產中使用DSL struct
。
C#
namespace Quantum {
public partial class FooUser {
public Foo F;
}
}
如果一個架構不是[Serializable]
——友善地(比如,因為它是一個聯集或含有一個Quantum集合),可取而代之地使用原型:
C#
using Quantum.Prototypes;
namespace Quantum {
public partial class FooUser {
public Foo_Prototype F;
}
}
在需要時,原型可以被具體化到模擬架構:
C#
Foo f = new Foo();
fooUser.F.Materialize(frame, ref f, default);
動態資產
在運行階段可以透過模擬來建立資產。這個功能稱為 動態資產DB。
C#
var assetGuid = frame.AddAsset(new MageSpec() {
Speed = 1,
MaxHealth = 100,
HealthRegenerationFactor = 1
});
該資產可以如同任何其他資產一樣被載入和處置:
C#
MageSpec asset = frame.FindAsset<MageSpec>(assetGuid);
frame.DisposeAsset(assetGuid);
動態資產不在同儕節點間同步。取而代之地,建立新資產的程式碼需要是確定性,並且確保各個同儕節點將使用相同的值來處理一個資產。
針對上述規則的唯一的例外是當有一個延遲加入的時候——新的客戶端將收到一個 動態資產DB 的快照以及最新的幀資料。不同於幀的序列化,動態資產的序列化及取消序列化是在模擬之外被委派到IAssetSerializer
介面。當在Unity中運行時,預設使用QuantumUnityJsonSerializer
:可以序列化/取消序列化任何可Unity序列化的類型。
初始化動態資產DB
可透過預先存在的動態資產來初始化模擬。類似於在模擬時新增資產,在客戶端間,這些需要是確定性。
首先,需要建立及以資產填入DynamicAssetDB
的一個執行個體:
C#
var initialAssets = new DynamicAssetDB();
initialAssets.AddAsset(new MageSpec() {
HealthRegenerationFactor = 10
});
initialAssets.AddAsset(new WarriorSpec() {
Armour = 100
});
...
第二,需要使用QuantumGame.StartParameters.InitialDynamicAssets
來傳遞執行個體到一個新的模擬。在Unity中,因為管理一個QuantumGame
的是QuantumRunner
行為,因此取而代之地使用QuantumRunner.StartParamters.InitialDynamicAssets
。