【DOTS】Unity DOTS Entity常用组件类型说明

总览

在ECS中,Component包含System可读或者可写的数据。
使用 IComponentData 接口,将struct标记为组件类型,该组件类型只能包含非托管数据(非引用类型?),并且可以包含方法,但最好是让它作为纯数据。

一、常见组件类型

组件 描述
Unmanaged components 最常用的组件类型,只能存储某些类型的字段
Managed components 可以存储任何字段类型的托管组件类型
Shared components 根据Entity的值将Entity分组到块中的组件
Cleanup components 销毁包含清理组件的实体时,Unity会删除所有非清理组件。这对于标记摧毁时需要清理的Entity非常有用。
Tag components 不存储数据,且不占用空间的非托管组件,可以在实体查询中用来筛选Entity
Buffer components 充当可调整大小的数组的组件
Chunk components 存储与整个块(而不是单个Entity)关联的值的组件
Enableable components 可以运行时在Entity上启用或者禁用的组件,而无需进行代价高昂的结构更改
Singleton components 在给定环境中只有一个实例的组件

1. Unmanaged components (非托管组件)

最常用的存储数据的数据类型。
非托管组件可以存储以下类型的字段:

  • BlittableType
  • bool
  • char
  • BlobAssetReference (对Blob数据结构的引用)
  • Collections.FixedString (固定大小的字符缓冲区)
  • Collections.FixedList
  • Fixed array (固定数组,仅在不安全的上下文中允许)
  • 符合这些相同限制的其他结构

1.1 Create an unmanaged component 创建非托管组件

要创建非托管组件,只需要继承 IComponentData 即可:

public struct ExampleUnmanagedComponent : IComponentData
{
    public int Value;
}

将使用兼容类型的属性添加到结构中,以定义组件的数据。

如果不向组件添加任何属性,它将充当标记组件。(标记组件:标记组件是非托管组件,不存储任何数据,也不占用空间。)

2. Managed components (托管组件)

与非托管组件不同,托管组件可以存储任何类型的属性。但是,它们的存储和访问会占用更多资源,并且具有以下限制:

  • 无法在jobs中访问它们
  • 不能再Burst编译代码中使用它们
  • 它们需要垃圾回收
  • 它们必须包含一个无参构造,以便进行序列化
2.1 Managed type properties 托管类型属性

如果托管组件中的属性使用托管类型(引用类型?),则可能需要手动添加clone、compare、serialize该属性的功能。

2.2 Create a managed component 创建托管组件

创建一个类,该类继承 IcomponentData

public class ExampleManagedComponent : IComponentData
{
    public int Value;
}

2.3 Manage the lifecycle of external resources 管理外部资源的生命周期

如果引用外部资源,最佳的做法是实现 ICloneableIDisposable

例如,对于存储对 ParticleSystem 的引用的托管组件:

  • 如果你复制这个托管组件的实体,默认情况下会创建两个托管组件,他们都引用相同的粒子系统,如果你为托管组件实现ICloneable,就可以直接为第二个托管组件复制粒子系统。

  • 如果你销毁了托管组件,默认情况下粒子系统会保留,如果你为托管组件实现IDisposable,就可以在组件被销毁的时候,同时销毁粒子系统。
    示例代码如下:

public class ManagedComponentWithExternalResource : IComponentData, IDisposable, ICloneable
{
    public ParticleSystem ParticleSystem;

    public void Dispose()
    {
        UnityEngine.Object.Destroy(ParticleSystem);
    }

    public object Clone()
    {
        return new ManagedComponentWithExternalResource { ParticleSystem = UnityEngine.Object.Instantiate(ParticleSystem) };
    }
}

2.4 Optimize managed components 优化托管组件

与非托管组件不同,Unity 不会将托管组件直接存储在区块中,相反,Unity 将它们存储在一个大数组中。然后,块存储相关托管组件的数组索引。这意味着,当您访问实体的托管组件时,Unity 会处理额外的索引查找。这使得托管组件不如非托管组件优化。

托管组件的性能影响意味着应尽可能改用非托管组件

3. Shared components (共享组件)

共享组件根据其共享组件的值将实体分组到块中,这有助于重复数据消除。

3.1 Shared components 简介

共享组件根据其共享值,将Entity分组到相同区块中存储,这有助于消除重复数据。
Unity将具有相同共享组件值的所有Entity存储到一起,这将会删除Entity之间的重复值。
可以创建托管或非托管的共享组件,他们有着同样的限制与优点。

3.1.1 Shared components 值存储

对于每个World,Shared components将值存储在独立于ECS区块的数组中, 而该world中的原来的ECS区块,会存储句柄,用来查找对应在数组中的值。多个区块可以存储相同的共享组件句柄,也就是说可以使用相同共享组件的实体数量没有限制。
如果要更改Entity的共享组件的值,那么Unity就会将该Entity移动到使用新共享组件值的区块。这意味着修改实体的共享组件值,是一种结构性更改。

3.1.2 Override the default comparison behavior 重载比较行为

如果需要修改ECS比较共享组件实例的方式,可以为共享组件实现 IEquatable,如果执行此操作,ECS 将使用您的实现来检查共享组件的实例是否相等。如果共享组件是非托管的,则可以将 [BurstCompile] 属性添加到共享组件结构、Equals方法和GetHashCode方法中以提高性能

3.2 Create a shared component 创建共享组件

可以创建托管和非托管共享组件。

3.2.1 Create an unmanaged shared component 创建非托管共享组件

要创建非托管共享组件,请创建一个实现标记接口的结构。ISharedComponentData

下面的代码示例演示了一个非托管共享组件:

public struct ExampleUnmanagedSharedComponent : ISharedComponentData
{
    public int Value;
}

3.2.2 Create a managed shared component 创建托管共享组件

若要创建托管共享组件,请创建一个实现ISharedComponentData标记接口和 IEquatable<>的结构,并确保实现public override int GetHashCode()。相等方法对于确保在使用默认 Equals 和 GetHashCode 实现时不会由于隐式装箱而不必要地生成托管分配是必需的。
以下代码示例演示了一个托管共享组件:

public struct ExampleManagedSharedComponent : ISharedComponentData, IEquatable<ExampleManagedSharedComponent>
{
    public string Value; // A managed field type

    public bool Equals(ExampleManagedSharedComponent other)
    {
        return Value.Equals(other.Value);
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }
}

3.3 Optimize shared components 优化共享组件

3.3.1 Use unmanaged shared components 使用非托管共享组件

如果可能,请使用非托管共享组件,而不是托管共享组件。这是因为 Unity 将非托管共享组件存储在 Burst 编译代码可通过非托管共享组件 API(如 SetUnmanagedSharedComponentData)访问的位置。与托管组件相比,这提供了性能优势。

3.3.2 Avoid frequent updates 避免频繁更新

更新实体的共享组件值是一种结构更改,这意味着 Unity 将实体移动到另一个块。出于性能原因,请尽量避免频繁执行此操作。

3.3.3 Avoid lots of unique shared component values 避免使用大量唯一的共享组件值

块中的所有实体必须共享相同的共享组件值。这意味着如果您为大量实体提供唯一的共享组件值,它会将这些实体分割成许多几乎为空的块。

例如,如果一个原型有500个实体和一个共享组件,每个实体都有一个唯一的共享组件值,Unity将每个实体存储在一个单独的块中。这浪费了每个块中的大部分空间,也意味着要循环遍历原型的所有实体,Unity必须循环遍历所有500个块。这否定了ECS块布局的好处,并降低了性能。为了避免这个问题,尽量少使用唯一的共享组件值。如果500个示例实体只共享10个唯一的共享组件值,Unity可以将它们存储在10个块中。

要小心使用具有多个共享组件类型的原型。原型块中的所有实体必须具有相同的共享组件值组合,因此具有多个共享组件类型的原型容易受到碎片的影响。

4. Cleanup components (清理组件)

清理组件与常规组件类似,但当您销毁包含一个实体的实体时,Unity 会删除所有非清理组件。该实体仍然存在,直到您从中删除所有清理组件。这对于标记销毁时需要清理的实体非常有用。

4.1 使用清理组件

4.1.1 清理组件生命周期

下面的代码示例说明了包含清理组件的实体的生命周期:

// Creates an entity that contains a cleanup component.
// 创建包含清理组件的实体。
Entity e = EntityManager.CreateEntity(
    typeof(Translation), typeof(Rotation), typeof(ExampleCleanup));

// Attempts to destroy the entity but, because the entity has a cleanup component, Unity doesn't actually destroy the entity. Instead, Unity just removes the Translation and Rotation components.
// 尝试销毁实体,但是,因为实体有一个清理组件,Unity实际上不会销毁实体。相反,unity只是移除平移和旋转组件。
EntityManager.DestroyEntity(e);

// The entity still exists so this demonstrates that you can still use the entity normally.
// 该实体仍然存在,所以这表明你仍然可以正常使用该实体。
EntityManager.AddComponent<Translation>(e);

// Removes all the components from the entity. This destroys the entity.
// 从实体中移除所有组件。这会破坏实体。
EntityManager.DestroyEntity(e, new ComponentTypes(typeof(ExampleCleanup), typeof(Translation)));

// Demonstrates that the entity no longer exists. entityExists is false. 
// 显示实体不再存在。entityExists为false。
bool entityExists = EntityManager.Exists(e);

::: info
注意
清理组件是非托管组件,并且具有与非托管组件相同的所有限制。
:::

4.2 创建清理组件

要创建清理组件,请创建一个继承自ICleanupComponentData的结构。

下面的代码示例显示了一个空的清理组件:

public struct ExampleCleanupComponent : ICleanupComponentData
{
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值