经验法则解释。
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式的。