英文原文:
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();
}

该测试通过并显示。

Unity Assets 大资源_Unity Assets 大资源


  有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);

这就是我现在所拥有的一切。