[Unity ECS] 游戏对象转换和子场景 [2]

IConvertGameObjectToEntity

  制作转换系统(子类化 GameObjectConversionSystem)确实允许您处理转换世界中的所有事情。但是如果你想要每个类型的行为怎么办?每当它被转换时,就会发生一些事情。

  Unity 有另一个内置转换系统,称为 ConvertGameObjectToEntitySystem。该系统将遍历从转换世界返回的所有游戏对象。然后使用 GetComponents 并查看是否有 IConvertGameObjectToEntity。然后调用 .Convert 就可以了。您可以在此处放置任何类型的逻辑。

using Unity.Entities;
using UnityEngine;

public class BlinkingButton : MonoBehaviour, IConvertGameObjectToEntity
{
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
    }
}

  entity 是由该组件所在的 GameObject 产生的主要实体。回想一下,“主要实体”与一个 GameObject 或其任何 MonoBehaviour 组件相关联。 dstManager 是目的地世界的管理者。你可以做任何你喜欢的与转换完全无关的事情。您想要为每个 BlinkingButton 转换创建 500 个无用的实体吗?你可以!

  您可以利用这个机会使用 dstManager.Add / SetComponentData 初始化更多数据。在使用 dstManager 的同时在转换世界中工作时,您可以“远程控制”目标世界,这一点很重要。从字面上理解这个名字,因为在转换世界中还有另一个经理。

  编写一个转换系统 Entities.ForEach 并初始化相同的东西可能更有效,因为如果初始化相似,您可以使用 Burst 和Job。但是通过这种方式,您可以使用在 MonoBehaviour 上序列化的字段来自定义随意生成的每个单独的实体。

  转换系统实际上是首先调用此转换的 ConvertGameObjectToEntitySystem,但转换回基本类型 GameObjectConversionSystem(名称容易混淆,请注意)。目的是让您可以使用映射系统来执行工作。从这里开始,您将在映射系统上遇到许多方法。

关联实体组(LinkedEntityGroup)

  LinkedEntityGroup 是 Entity 的动态缓冲区。通常它会导致:

  • 在一个实体上实例化将能够根据该缓冲区中的实体列表一次实例化多个实体,并再次为您设置类似的 LinkedEntityGroup。 (根据新实例化的实体,但表示与旧实体相同的关系。)请注意,即使在我们了解名为 Prefab 的组件之前,实例化也是可能的。

  • 如果销毁目标是具有 LinkedEntityGroup 的父对象,则 DestroyEntity 也会一起销毁它们。所以就像在编辑器中有children的gameobject上删除一样。

  • entityManager.SetEnabled 附加/分离禁用的组件,这样它就不会再被查询,也会对缓冲区中的所有内容执行此操作。所以它更像是在编辑器中禁用游戏对象树。

  如果在对所有链接的实体执行实例化/销毁/禁用时找到另一个 LinkedEntityGroup,它将不会递归地工作。

  它对所有链接实体执行操作的方式似乎各不相同。一旦检测到缓冲区,Instantiate 和 SetEnabled 就会有新的行为,并且一次对所有成员执行操作,仅此而已。这意味着缓冲区必须将自身作为“链接实体”之一包含在内。 (链接到… 本身)但是,即使缓冲区不包含自身,DestroyEntity 也能工作,它首先销毁请求的实体,然后遍历缓冲区以添加更多要销毁的实体。

  请注意,这与用于计算有意义的 LocalToWorld 的 Parent 不同。 (但它们经常一起使用。)一个是递归的,循环依赖是不允许的。

从非预制转换中获取 LinkedEntityGroup

  到目前为止,我们在任何一种模式下都没有使用 ConvertToEntity 获得 LinkedEntityGroup。因此,销毁生成的实体(比方说前面的 A)不会链式销毁所有内容。也许你期待或不期待。但他们认为这应该是默认行为。 (与 Parent 不同,在默认情况下,Unity 根据层次结构链接起来,同时转换 Transform。)

  如果要添加和填充缓冲区,请在转换时在映射系统上使用:

 public void DeclareLinkedEntityGroup(GameObject gameObject)

  该对象的主要实体将获得缓冲区,其中包含目标世界中的所有子项(递归、线性化)。

public class CubeConvert : MonoBehaviour, IConvertGameObjectToEntity
{
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        conversionSystem.DeclareLinkedEntityGroup(this.gameObject);
    }
}

[Unity ECS] 游戏对象转换和子场景 [2]
他们在这里。它包括自身以支持 Instantiate 和 SetEnable,正如解释的那样。

[Unity ECS] 游戏对象转换和子场景 [2]
  Unity还有其他地方默认执行链接处理,但是ConvertToEntity这里没有。例如,在转换预制资产时。 (下一个)

大小提醒!

  您可能认为每个块 16kB 很多,您可以在其中容纳多达 2000 个实体。但是,一旦您使用涉及层次结构的转换,LinkedEntityGroup 缓冲区及其系统状态朋友(Child 等)就会真正占用每个实体的空间,因为每个缓冲区元素都是一个实体(8 字节)。

  这 12 个块每个只能容纳 45 个实体(容量),尽管它只是一个简单的层次结构,很少有自定义组件!现在这比 1000 范围低了很多。

[Unity ECS] 游戏对象转换和子场景 [2]
  从 0.5.1 版本开始,TypeManager.cs 源代码表示任何没有 [InternalBufferCapacity] 的缓冲区类型(LinkedEntityGroup 就是这样)的容量为 128/大小。 LinkedEntityGroup 是每个实体 8 字节的缓冲区,因此容量为 128/8 = 16。

[Unity ECS] 游戏对象转换和子场景 [2]
这意味着 :

  • LinkedEntityGroup(或任何没有容量规范的缓冲区)从一开始就为每个实体占用 128 个字节。 (相当于 32 个整数/浮点数)这就是块中的容量变得如此小的原因。

  • 在您的任何 GameObject 中拥有超过 16 个子项(递归计算,而不是向下一级)进行转换是不好的,否则记住的链接实体必须从精心打包的块中移出到堆内存中。也许 Unity 认为 16 是不太可能的数字,而 8 可能太可能了。

  • LinkedEntityGroup 仅用于预制转换,除非您在正常转换时声明链接。因此,通常情况下,您只需要监视转换后的预制件有多少 GameObject。

  • 请记住,在运行时,所有嵌套的预制件和变体工作流程都无关紧要,系统会将其视为单个预制件。并不是说如果你有一个嵌套的预制件,它们会被排除在 LinkedEntityGroup 之外并开始它们自己的。

  最后一个 16kB 的块意味着你可以在 1 MB 中拥有大约 60 个块,以供透视。因此,如果容量像上面的例子一样减少到大约 45,那么你可以在 1 MB 中容纳大约 2700 个转换后的游戏对象。也许45的容量毕竟没什么好担心的。 (这取决于)

创建Additional Entity

  转换的一个主题是一个 GameObject 返回一个实体,即它的“primary entity主要实体”。在映射系统上,调用 CreateAdditionalEntity(gameObject) 来创建更多应该来自任何游戏对象的实体。这些实体现在是“该游戏对象的次要实体”,等等。

  假设新设计是这样,每当带有 CubeMultiple 的 GameObject 被转换时,它就会获得 2 个额外的实体。

public class CubeMultiple : MonoBehaviour, IConvertGameObjectToEntity
{
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        Entity additional1 = conversionSystem.CreateAdditionalEntity(this.gameObject);
        dstManager.SetName(additional1, $"{this.name}_Add1");
        Entity additional2 = conversionSystem.CreateAdditionalEntity(this.gameObject);
        dstManager.SetName(additional2, $"{this.name}_Add2");
    }
}

请注意,Convert 调用以递归方式工作,ConvertToEntity 位于 CubeHead 的顶部。

[Unity ECS] 游戏对象转换和子场景 [2]
  结果。请注意,附加实体实际上是新创建的。与从组件对象(如 Transform)转换而来的组件不同,根本没有组件。 (请参阅具有 2008 年容量的块。)

[Unity ECS] 游戏对象转换和子场景 [2]
  您可能会意识到您可以通过命令目标实体管理器 dstManager.CreateEntity() 来做同样的事情。但这不仅仅是使事情正确的问题。

查询“来自”单一来源的所有实体

  在映射系统上,调用 GetEntities(gameObject/component)。因为我们创建了额外的实体并使转换世界知道它们来自同一特定事物,所以现在可以要求所有这些实体。例如,如果我引用 GameObject CubeMultiple 并使用 GetEntities,我将能够获得其主要实体和 2 个附加实体。

  你不必担心它们是否已经被创建/担心转换顺序,因为在这种调用中,它有一个例程来确保尚未转换。 (这也适用于 GetPrimaryEntity)

影响链接实体组

  回想一下 DeclareLinkedEntityGroup。通过以正确的方式创建附加实体,转换系统知道它们与转换中的某些游戏对象的关联。结果是它们包含在从声明生成的链接实体组中。

  前面的示例 CubeHead 自身具有 DeclareLinkedEntityGroup,结果现在包括从 CubeMultiple 生成的其他实体。
[Unity ECS] 游戏对象转换和子场景 [2]
  如果您只是欺骗自己并使用 dstManager 创建实体,那将不起作用。通过这样做,听起来更像是转换 CubeMultiple 会导致向目标世界添加更多实体的“副作用”,而不是 CubeMultiple 真正变成多个实体。看起来它也应该使实时链接能够很好地发挥作用。 (稍后解释)

Declaration

  也许您还希望为每个Asset生成一个实体,而不仅仅是每个场景中的事物。好消息,因为主要实体也可以与资产相关联,而不仅仅是 GameObject(或 MonoBehaviour)换句话说,如果您 GetPrimaryEntity 将这些资产输入到映射系统,您将获得资产的代表性实体。

  因为您不能将 ConvertToEntity 放在project 面板上的资产文件上,所以您需要某种方式在转换后“声明”您希望它们作为实体。

你什么时候可以声明他们?

  在任何转换发生之前。这被称为 discovering 阶段。否则,如果您在转换时这样做,API 将出错。这意味着在转换时,您可以通过使用输入原始资产的映射系统的 GetPrimaryEntity 来获取转换资产的主要实体,以分配给非资产内容的实体中的任何组件。否则,如果您现在不进行转换,则查询它们以供以后使用可能会有点困难。

IDeclareReferencedPrefabs

  在发现阶段,首先将扫描所有参与的 GameObject,如果其任何组件获得 IDeclareReferencedPrefabs。在这里,您可以通过将预制资产(还不是其他资产)添加到给定列表来进行声明。您可以通过序列化公开的 GameObject 字段连接到项目面板中的预制资产来引入预制资产。

在转换系统中声明

  扫描该接口后,调用 5 个转换组。第一个 GameObjectDeclareReferencedObjectsGroup 中的所有转换系统都将得到更新。在这里,您还有一次机会可以随心所欲地声明事物,预制件,这次是任何其他资产。

public class GameObjectDeclareReferencedObjectsGroup : ComponentSystemGroup { }

public class GameObjectBeforeConversionGroup : ComponentSystemGroup { }
public class GameObjectConversionGroup : ComponentSystemGroup { }
public class GameObjectAfterConversionGroup : ComponentSystemGroup { }

public class GameObjectExportGroup : ComponentSystemGroup { }

  使用转换系统意味着您手头有映射系统。映射系统中有多种方法可用于声明。但是,您可以从系统代码中获得 Project 面板的内容吗?例如,您可以在转换世界中查询可能具有您想要连接到公开字段的资产的任何组件对象。但是此时您可能会开始想知道为什么当您无论如何都必须制作一些资产“持有者”时,为什么不首先使用 IDeclareReferencedPrefabs。在更多定制的情况下使用基于系统的声明,并在大多数情况下坚持使用 IDeclareReferencedPrefabs。

  然后,当需要进行正常转换时,您可以与 IConvertGameObjectToEntity 组合,通过获取其主要实体来获取您想要的资产/预制件的刚刚转换的实体,使用相同的转换前预制件引用作为获取它的键.

public class BlinkingButton : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
{
    public GameObject myPrefab;
    
    //This first
    public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    {
        referencedPrefabs.Add(myPrefab);
    }
    
    // |
    // V
    
    //Then GameObjectDeclareReferencedObjectsGroup <- last chance to declare stuff
    
    //Then GameObjectBeforeConversionGroup
    //Then GameObjectConversionGroup, which contains ConvertGameObjectToEntitySystem that run the following Convert

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        //Get primary entity of the prefab asset, to assign to primary entity converted from this component.
        Entity prefabAssetEntity = conversionSystem.GetPrimaryEntity(myPrefab);
        //Remember it, etc.
    }
    
    // |
    // V
    
    //Then GameObjectAfterConversionGroup
}

  如果您想查看违反声明时刻规则的错误,请注意在转换 IConvertToGameObjectToEntity 时刻您也有机会使用映射系统。 (如前所述,添加额外实体,前往其他主要实体等)但是如果您使用此映射系统声明更多内容,则会出现错误。太晚了!

这现在变得神秘了:

??? <- IDeclareReferencedPrefabs calls.
GameObjectDeclareReferencedObjectsGroup.Update()
??? <- End of discovery phase, no more declares to assets.
GameObjectBeforeConversionGroup.Update()
GameObjectConversionGroup.Update()
GameObjectAfterConversionGroup.Update()
???
GameObjectExportGroup.Update()

  顺便说一下,声明截止日期仅适用于声明资产和预制资产。在转换过程中,您仍然可以 DeclareLinkedEntityGroup。

声明任何资产

  在映射系统上,使用 DeclareReferencedAsset。

  在转换世界中,您将获得一个带有标记组件 Asset 的实体,以及一个与该资产完全相同的 Unity 资产类型的组件对象。 (不是目的地世界)

  例如,我可以通过声明 DeclareReferencedAsset(mySpriteAtlas) 创建我的实体 + 资产 + SpriteAtlas 实体。然而,它在目的地世界的主要实体是完全空的。目前尚不清楚该系统的目的是什么。也许它希望您自己执行 Asset 到任何转换,否则您会得到空实体。

声明Prefab资产

  将其作为 IDeclareReferencedPrefabs 的一部分添加到列表中。或者在GameObjectDeclareReferencedObjectsGroup组内声明的情况下,使用映射系统,使用DeclareReferencedPrefab。

  但这一次并不像用它来获取 Asset 和 GameObject 作为组件对象那样愚蠢。这一次,您真的在目的地世界上获得了一个预制实体。

  什么是实体预制件的入门。如果您将 Prefab 附加到任何实体,除非明确要求,否则它将停止显示在查询中,工作方式几乎与 Disabled 类似,除非使用 Instantiate 时,它​​会去除 Prefab 标签。 (您不希望实例化生成另一个预制件…)

  目的不同,我们将它们从查询中隐藏不是因为我们想让它们“不活动”,而是因为它们不应该是活动的。它们只是快速创建新数据的蓝图。在创建查询时,有 2 种分离模式“包括disabled”和“包括disabled”。所以请按照库的意图使用它。 (不要使用 Prefab 隐藏查询等)

  与前面解释过的影响 EntityManager.Instantiate 的 LinkedEntityGroup 一起,您真的可以感觉像在 ECS 中实例化一个预制件!

  因此,通过声明一个预制资产,您添加了一个与该资产相关的新主要实体,并在输出中正确设置了 Prefab 和 LinkedEntityGroup。

  请注意,通过将 ConvertToEntity 放在 Hierarchy 上,它们只会变成我们目前所见的常规实体,因为层次结构上的东西不是资产。我们没有任何 Prefab 或任何 LinkedEntityGroup。无论它们是否在层次结构中被命名为蓝色。

  Prefab是资产文件,您不能粘贴 ConvertToEntity 以在“Project”面板中的资产上运行。 ECS 库通过查看它是否在scene来确定它是否是预制件。显然,所有可使用 ConvertToEntity 粘贴的东西都在scene。

Prefab asset 转换

  您可以通过像解释那样声明来转换预制​​资产。我想详细说明与在Hierarchy上转换 GameObject 时的区别。是否从Prefab asset实例化,只要它们在Hierarchy上就无关紧要。只有当它们链接到您的 Project 面板的资产时,才算作资产。

自动 LinkedEntityGroup :用于实例化

在Prefab asset上进行以下设置:
[Unity ECS] 游戏对象转换和子场景 [2]
[Unity ECS] 游戏对象转换和子场景 [2]
重要的是要意识到 CubeHead 是一种资产,而不是场景对象。

[GenerateAuthoringComponent]
public struct PrefabConversion : IComponentData
{
    public Entity prefab;
}

我们会得到:
[Unity ECS] 游戏对象转换和子场景 [2]

  • 如果我们将实例化的 CubeHead 转换为场景,我们将不会得到任何 LinkedEntityGroup。此行为仅适用于预制资产,其目的当然是为实例化做好准备。 (并且您还会获得实例化实体的链破坏行为,因为实例化甚至会复制此链接的实体组。)

  • LinkedEntityGroup 包含树下的每个,也包含它本身。您可能希望它不包含 Cube 3 和 4,但事实并非如此。转换“线性化”预制件。它正在尝试所以不需要递归算法,只需在实例化时线性地通过链接的实体组。

  • 它也将自身包含在缓冲区中的原因是,大规模的 SetEnable 可以一次完成所有操作,而不是先在请求的 Entity 处执行,然后再为每个链接的实体执行此操作。

  • 包括头部在内的所有实体都获得预制件。但是我们将只使用头部进行实例化。 Prefab 实际上与 Instantiate 无关。 LinkedEntityGroup 使实例化在Group中工作。您可以实例化任何实体。

自动 LinkedEntityGroup :用于启用链

  上一节解释了只有在预制件的头部才能获得 LinkedEntityGroup 以用于实例化。

  此外,在它发现禁用 GameObject 的预制件中的任何地方,它都会递归地获取包含所有子项的 LinkedEntityGroup。

  这样做的目的是让您可以一次在实例化的实体上使用 SetEnable 并一次从多个实体中删除 Disabled。禁用转换仍遵循与转换非资产 GameObject 时相同的规则,此添加的 LinkedEntityGroup 进一步帮助您。

停不下来

  您不能将 Convert To Entity (Stop) 放在预制件中的某处,希望它不会包含在结果中。

趣味测验

  使用到目前为止的所有转换规则,看看您是否可以获得关于以下组件的正确转换结果:Parent(记住这是用于转换系统)、Prefab、Disabled、LinkedEntityGroup(及其内容)。转换系统经过一轮更新后,child和friends将根据parent出现。

[Unity ECS] 游戏对象转换和子场景 [2]

Cube : Prefab, LinkedEntityGroup (Cube,1,2,3,4)
1 : Prefab, Parent (Cube)
2 : Prefab, Parent (Cube)
3 : Prefab, Parent (2)
4 : Prefab, Parent (2)

[Unity ECS] 游戏对象转换和子场景 [2]

Cube : Prefab, Prefab, LinkedEntityGroup (Cube,1,2,3,4)
1 : Prefab, Parent (Cube)
2 : Prefab, Parent (Cube), Disabled, LinkedEntityGroup (2,3,4)
3 : Prefab, Parent (2), Disabled
4 : Prefab, Parent (2), Disabled

  请记住,您始终将自己置于 LinkedEntityGroup 的元素之一中,包括启用的元素。因此,如果您有任何禁用的 GameObject,即使它位于叶节点,您也总是会获得至少 1 个元素的 LinkedEntityGroup 本身。 (你可能认为这无关紧要,但它会更多地分割块)

[Unity ECS] 游戏对象转换和子场景 [2]

Cube :
1 : Parent (Cube)
2 : Parent (Cube)
3 : Parent (2)
4 : Parent (2)

[Unity ECS] 游戏对象转换和子场景 [2]

Cube :
1 : Parent (Cube)
2 : Parent (Cube)
3 : Parent (2)
4 : Parent (2)

  蓝色名称(与预制资产相关联)与否无关紧要,它不是资产。它不会遵循 LinkedEntityGroup 转换规则。

[Unity ECS] 游戏对象转换和子场景 [2]

Cube :
1 : Parent (Cube)
2 : Parent (Cube), Disabled
3 : Parent (2), Disabled
4 : Parent (2), Disabled

  您不能从 Cube (2) 的主要实体开始启用链,因为没有为您生成 LinkedEntityGroup。您必须手动声明链接。

prefab 实例化的重映射行为

  只有在使用 LinkedEntityGroup 的实体调用 Instantiate 时,才会发生实体重映射。 (在此处阅读更多信息:https://gametorrahod.com/entity-remapping/) 您知道转换预制资产使 LinkedEntityGroup 可用。

  因此,可以在您从转换中获得的预制实体中的某处烘焙一个实体值,并且在实例化时,该烘焙值将恢复生机,在实例化的上下文中是有意义的。

  从前面的情况来看,如果我向 SpecialCube 添加一个新的转换脚本,以便它的 Convert 被调用作为制作 CubeHead 预制实体的一部分,目的是记住将从其 Cube (3) 生成的实体。

[Unity ECS] 游戏对象转换和子场景 [2]

public class SpecialCube : MonoBehaviour, IConvertGameObjectToEntity
{
    public GameObject itsChild;
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        dstManager.AddComponent<LinkedEntityGroup>(entity);
        var leg = dstManager.GetBuffer<LinkedEntityGroup>(entity);
        leg.Add(conversionSystem.GetPrimaryEntity(itsChild));
    }
}

  检查从转换中获得的预制实体,预计这个手动 LinkedEntityGroup(我可以只烘焙了一个实体,但实体的任何缓冲区也可以工作。)有对其预制对等体的引用。

[Unity ECS] 游戏对象转换和子场景 [2]
  重映射将发生在这个隐藏在预制件中的实体上,只要它可以在顶部的 LinkedEntityGroup 中找到要重映射到的东西,在那里你使用 Instantiate。因此,在将其转换为预制件之前在转换过程中“连接”实体引用很有用,因此当您实例化它时,您就可以使用关系。

  如果实例化为没有 LinkedEntityGroup 的东西,则不会发生重新映射,因为在这种情况下,没有对等点来查找重新映射实体值。

GameObjectExportGroup呢?

  在介绍了5个组中的4个组及其目的后,这是最后一个组。因此,看起来,转换可以重新调整,不是为了使用,而是为了出口。通常情况下,出口组不包括在转换管道中。只有当被要求出口时,才会被扫描并收集在最后。

  如果您的转换系统在这里,不仅所有转换都已完成,您还可以看到 LinkedEntityGroup 可用并填充缓冲区,并且在此阶段将 Prefab 标记添加到所有声明的预制件中,因此您可以使用它。 (用于实例化等)当然,您不应再在名为“导出组”的组中进行转换。

  什么是“导出”仍然是个谜,也许当 Unity 团队制作了一些我可以理解的文档时。现在你可以认为这个组对你的日常转换没有用。并且它必须对资产实体从声明资产的结果做一些事情,前面解释过。

??? <- IDeclareReferencedPrefabs calls.
GameObjectDeclareReferencedObjectsGroup.Update()
??? <- End of discovery phase, no more declares to assets.
GameObjectBeforeConversionGroup.Update()
GameObjectConversionGroup.Update()
GameObjectAfterConversionGroup.Update()
??? <- LinkedEntityGroup added to declared linked, LinkedEntityGroup + Prefab added to declared prefabs back at discovery phase.
GameObjectExportGroup.Update()

什么是映射系统上的DeclareDependency

  还有一个无法解释的声明。但我不知道这件事。如果您做得正确,它看起来可以使实时链接(稍后解释)更好地工作。

上一篇:Unity案例-实现心电图效果


下一篇:UNITY实例化预制体