英文原文:
https://coffeebraingames.wordpress.com/2020/11/29/getting-started-with-blob-asset/
Blob资产是Unity的DOTS中使用的一个概念。基本上,它是一种存储在非托管内存中的资产,可以在组件中访问。这里的 "资产 "并不是指传统意义上的资产,如预制件、纹理、3D模型、音频剪辑等。这个术语是指数据。它可以是简单的整数,也可以是复杂的结构数组,或者NativeHashMap形式的查找表。只允许非空类型,所以没有类和委托等引用类型。我们建议使用blob资产来保存不可变的数据…目前是这样。
一个明显的用法是存储一个静态值的数据库,它的引用可以被添加到一个组件中。(你不能在组件中存储普通的集合。你可以有集合,但它们与本地集合不同,而且有限制。)
基本用法
让我们来做一个blob Asset的基本用法。让我们制作一个存储单一整数值的blob资产,通过Entites.ForEach()在组件中访问它。下面是拥有引用的组件和准备blob Asset和实体的系统的代码。
private struct TestComponent : IComponentData {
public BlobAssetReference<int> reference;
}
private class BlobAssetSystem : SystemBase {
private const int DATA_VALUE = 111;
private BlobAssetReference<int> reference;
protected override void OnCreate() {
base.OnCreate();
// Prepare the blob asset
BlobBuilder builder = new BlobBuilder(Allocator.TempJob);
ref int data = ref builder.ConstructRoot<int>();
data = DATA_VALUE;
this.reference = builder.CreateBlobAssetReference<int>(Allocator.Persistent);
builder.Dispose();
Entity entity = this.EntityManager.CreateEntity(typeof(TestComponent));
this.EntityManager.SetComponentData(entity, new TestComponent() {
reference = this.reference
});
}
protected override void OnDestroy() {
this.reference.Dispose();
}
protected override void OnUpdate() {
// Used non burst here so that Assert will work
// Rest assured that ScheduleParallel() works here when Assert.IsTrue()
// is removed
this.Entities.ForEach(delegate(in TestComponent component) {
Debug.Log($"value: {component.reference.Value}");
Assert.IsTrue(component.reference.Value == DATA_VALUE);
}).WithoutBurst().Run();
}
public void CreateNewEntity() {
Entity entity = this.EntityManager.CreateEntity(typeof(TestComponent));
this.EntityManager.SetComponentData(entity, new TestComponent() {
reference = this.reference
});
}
}
创建blob Asset实际上只意味着拥有指向此类资产或数据的BlobAssetReference。要创建一个,我们要创建一个BlobBuilder。使用ConstructRoot()并通过引用获得一个变量。将数值存储到该变量中,然后调用CreateBlobAssetReference(),该变量已经返回BlobAssetReference。
在BlobAssetSystem.OnCreate()中,我们创建了一个实体,添加了一个测试组件,该组件使用我们创建的BlobAssetReference。在OnUpdate()中,我们显示来自BlobAssetReference的值,同时断言它仍然等于我们为其设置的值(DATA_VALUE)。
我添加了一个方法CreateNewEntity(),所以我们可以看到一个BlobAssetReference可以被多个实体使用。下面是测试代码:
[Test]
public void BasicUsageTest() {
BlobAssetSystem system = this.World.GetOrCreateSystem<BlobAssetSystem>();
system.Update();
system.CreateNewEntity();
system.Update();
}
该测试通过并显示。
有3条调试日志,因为在系统创建时已经创建了一个实体。在调用第一个Update()时,它显示了第一行。然后我们调用CreateNewEntity(),创建一个新的类似实体。现在有两个了。在第二次调用Update()时,它显示Debug.Log()的后两行。
Blob Asset 查询表
现在让我们尝试一下半真实的游戏用法。假设我们有一个武器统计的数据库。一个单一的武器数据条目看起来像这样:
public readonly struct WeaponData {
public readonly int damage;
public readonly float projectileSpeed;
public WeaponData(int damage, float projectileSpeed) {
this.damage = damage;
this.projectileSpeed = projectileSpeed;
}
}
一个WeaponData的集合将被存储在NativeHashMap<FixedString64, WeaponData>中,其中FixedString64是武器的ID。换句话说,它是一个WeaponData的查询表。我们可以为这个哈希图创建一个blob Asset并在组件中使用它。假设这个组件看起来像这样:
public readonly struct Weapon : IComponentData {
public readonly FixedString64 weaponId;
private readonly BlobAssetReference<NativeHashMap<FixedString64, WeaponData>> weaponMapReference;
public Weapon(FixedString64 weaponId, BlobAssetReference<NativeHashMap<FixedString64, WeaponData>> weaponMapReference) {
this.weaponId = weaponId;
this.weaponMapReference = weaponMapReference;
}
public int Damage {
get {
return this.weaponMapReference.Value[this.weaponId].damage;
}
}
}
这是个很有创意的例子,但请听我说。我们有这样一个组件,它有一个weaponId,用来作为使用BlobAssetReference查询WeaponData的关键。例如,属性Damage的值是通过使用weaponMapReference从查询表中获取的。
这里有一个系统,把这一切放在一起。
private class WeaponSystem : SystemBase {
private NativeHashMap<FixedString64, WeaponData> weaponMap;
private BlobAssetReference<NativeHashMap<FixedString64, WeaponData>> weaponMapReference;
protected override void OnCreate() {
// 填充武器Map
// 你可以想象,这里的数据可能来自于一个ScriptableObject。
// 或JSON或XML或来自另一个服务器
this.weaponMap = new NativeHashMap<FixedString64, WeaponData>(2, Allocator.Persistent);
this.weaponMap["Bow"] = new WeaponData(1, 5.0f);
this.weaponMap["Gun"] = new WeaponData(2, 100.0f);
// 准备 BlobAssetReference
BlobBuilder builder = new BlobBuilder(Allocator.Persistent);
ref NativeHashMap<FixedString64, WeaponData> blobData =
ref builder.ConstructRoot<NativeHashMap<FixedString64, WeaponData>>();
blobData = this.weaponMap;
this.weaponMapReference = builder.CreateBlobAssetReference<NativeHashMap<FixedString64, WeaponData>>(Allocator.TempJob);
builder.Dispose();
// 创建实体
Entity bowEntity = this.EntityManager.CreateEntity(typeof(Weapon));
this.EntityManager.SetComponentData(bowEntity, new Weapon("Bow", this.weaponMapReference));
Entity gunEntity = this.EntityManager.CreateEntity(typeof(Weapon));
this.EntityManager.SetComponentData(gunEntity, new Weapon("Gun", this.weaponMapReference));
}
protected override void OnUpdate() {
// This is needed so it can be used inside the ForEach()
NativeHashMap<FixedString64, WeaponData> localMap = this.weaponMap;
this.Entities.ForEach(delegate(in Weapon weapon) {
Debug.Log($"{weapon.weaponId}: {weapon.Damage}");
Assert.IsTrue(localMap[weapon.weaponId].damage == weapon.Damage);
}).WithoutBurst().Run();
}
protected override void OnDestroy() {
this.weaponMapReference.Dispose();
this.weaponMap.Dispose();
}
}
在OnCreate()中,我们填充了武器Map,这将是我们的查询表。然后我们为该Map创建一个BlobAssetReference。之后,我们用武器组件创建两个实体,一个是弓,一个是枪。
在OnUpdate()中,我们显示每个武器的伤害,并将它们与原始的NativeHashMap进行比较,以验证它们是否仍然相同。
如果我们运行这个测试:
[Test]
public void NativeCollectionInSystemUsage() {
WeaponSystem weaponSystem = this.World.GetOrCreateSystem<WeaponSystem>();
weaponSystem.Update();
}
它将通过,并将显示每个实体的武器伤害。
实用方法
在写这篇文章时,我意识到我可以用这个方法来做一个实用的方法。
public static class BlobAssetUtils {
public static BlobAssetReference<T> CreateReference<T>(T value, Allocator allocator) where T : struct {
BlobBuilder builder = new BlobBuilder(Allocator.TempJob);
ref T data = ref builder.ConstructRoot<T>();
data = value;
BlobAssetReference<T> reference = builder.CreateBlobAssetReference<T>(allocator);
builder.Dispose();
return reference;
}
}
这样,之前系统中的blob资产创建部分就可以简化为:
// Basic usage system
this.reference = BlobAssetUtils.CreateReference(DATA_VALUE, Allocator.Persistent);
// Weapon example system
this.weaponMapReference = BlobAssetUtils.CreateReference(this.weaponMap, Allocator.Persistent);
这就是我现在所拥有的一切。