第1部分:Unity ECS-简要介绍ECS

本文详细解析了Unity引擎中ComponentSystem的设计,包括Group的作用、长度字段的用途、索引管理、系统生命周期控制以及EntityArray的使用场景。同时讨论了EntityManager与ComponentData的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经验法则解释。
ComponentSystem通常是什么样子的:

public class SomeSystem : ComponentSystem
{
    private struct Group
    {
        public int Length;
        public EntityArray Entities;
        public ComponentDataArray<SomeComponent> SomeComponents;
    }
    [Inject] private Group m_group; // 注入具有给定组件的Group实体

    protected override void OnUpdate()
    {
        float dt = Time.deltaTime; // 缓存得很好的deltaTime,我们在主线程上,所以我们可以使用 Unity's API
        for (int i = 0; i < m_group.Length; i++)
        {
            // 对数据进行操作
        }
    }
    
    // System was enabled (ComponentSystemBase.Enabled = true) 
    protected override void OnStartRunning() 
    {
        // 可能会有更多缓存用于优化,为Update做准备
    }
    
    // System was disabled (ComponentSystemBase.Enabled = false) 
    protected override void OnStopRunning()
    {
        // 可能是一些清理工作
    }
}

我们先来看一下这个:

struct Group
{
    public int Length;
    public EntityArray Entities;
    public ComponentDataArray<Foo> Foos;
    public ComponentDataArray<Bar> Bars;
}

什么是Group?

  Group是活动世界中具有Foo和Bar组件的所有实体的过滤器,它类似于引用所有特定给定组件的匹配实体数组。因此,group类似于EntityArray,但是引用组件时,EntityArray本身只是实体的数组(而实体只是一个索引)。
  Group由一组必需的组件、减法组件构成。它也是同步的。

为什么我们有长度字段?Foos.Length不是一样的吗?

  是的,你是对的!它们是一样的。Length是EntityArray的长度,也是Foo和Bar的长度,因为每个实体都有Foo和Bar组件。
  在这种情况下,这一点更为明显:

for(int i; i < m_group.Length; i++) 
{
	//在这种情况下,使用m_group.Foos.Length或m_group.Bar.Length会有点混乱
	//因为我们遍历所有实体,并且可以访问它们的任何组件
}
//总而言之-注入组中的每个数组都有相同的长度,因为它是实体的长度,
//并且每个实体都有每个给定的组件。长度字段的分隔只是为了方便起见

如何管理索引?注射魔法。

for(int i; i < m_group.Length; i++) // 迭代group中的所有实体
{
    // 这样迭代是安全的,因为组中的每个数组都有相同的长度
    // 索引也是以这种方式注入(同步)以完全像这样使用的:
    var actualEntity = m_group.Entities[i];  // 实际迭代实体
    var actualFoo    = m_group.Foos[i];      // Foo component "attached" to actualEntity
    var actualBar    = m_group.Bards[i];     // Bar component "attached" to actualEntity
}

如何管理系统的生命周期?
  嗯,你真的不需要这么做!
  Unity负责处理这件事。系统跟踪注入的组,如果没有匹配的实体,系统将被禁用,如果也出现匹配的实体,系统将被启用。但如果你真的想这样做,抬头看看,看看OnStartRunning和OnStopRunning?我提到了ComponentSystemBase.Enabled=true,这可能就是您要找的。该属性允许您手动激活/禁用系统。

系统没有按我想要的顺序更新

  方便救援的属性!因为所有系统都是在主线程上更新的,所以您需要考虑更新的顺序,有一些属性可以帮助您解决这个问题:[UpdateAfter(typeof(OtherSystem)]、[UpdateBeforee(typeof(OtherSystem))]和[UpdateInGroup(typeof(UpdateGroup))],其中UpdateGroup是空类。你甚至可以通过typeof(UnityEngine.Experimental.PlayerLoop.FixedUpdate)或同一命名空间中的其他阶段来控制在Unity的阶段之前/之后的更新。

我如何才能访问我想要的系统?

  你可以简单地注入它,就像[注入]你的私有系统你的系统一样;就这么简单。您还可以使用World.Active.GetExistingManager(),或者如果您不确定它是否存在但应该存在,则使用World.Active.GetOrCreateManager()

为什么要在系统中使用EntityArray?我能用这个索引做什么?

  由于系统最常在组件上运行,而不是直接在实体上运行,这就提出了一个问题:“我为什么需要这个索引”。说“这只是AND索引”并不意味着它完全不可用。这是非常重要的整数。如果您还没有尝试过Unity的ECS实现,那么您可能不知道在哪里需要它。EntityManager包含EntityData和控制向给定实体添加/删除(以及更多)组件的功能需要它。但实际上您并不想通过EntityManager添加/删除组件。为了不破坏组,您宁愿在更新之后进行(您将在访问已释放的nativearray时出错),因此您希望使用PostUpdateCommands(EntityCommandBuffer)。有关这一点的更多信息,请参见文章的接下来的部分。

World vs EntityManager

  EntityManager不是奇怪而神奇的类,它是ScriptBehaviourManager并“合并”实体及其组件。在ECS,我们有很多manager。ComponentSystem也是ScriptBehaviourManager!这个世界把所有的管理者都融为一体。我们可以通俗地说,它管理着managers。我知道你的问题是什么-是的,我们可以创造多个世界,听起来很有趣,不是吗?也许我们将来会看看这个。

ComponentData和SharedComponentData。有什么区别?

  区别很小,ComponentData只是一个组件,SharedComponentData正如它所说的,是在不同实体之间共享的组件。你可以在文档中读到非常好的解释:

IComponentData适用于实体之间不同的数据,例如存储世界位置。当许多实体有共同之处时,ISharedComponentData非常有用,例如,在Boid演示中,我们实例化来自同一预置的多个实体,因此多个Boid实体之间的MeshInstanceRenender完全相同

  因此,使用相同的IComponentData(例如,位置)从Entity0更改不会更改Entity1的位置,但具有相同的SharedComponent(例如。渲染器)如果更改Entity0中的材质,也会更改Entity1中的材质。然而,您并不是真的想要对SharedComponents进行大量更改,实际上很少。更多细节请看这里。还有一个区别–ComponentData必须是Blittable式的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值