ECS 五 工作流程

一:Conversion Workflow

要使用 DOTS 工作流,需要创建 entities, components and systems.

用 GameObjects (authoring data可以编辑数据) 来生成entities 和 components (runtime data 运行时数据) 的过程,称为conversion.

  • 这个过程是编写ECS 的首选方式
  • 它是DOTS的基本,未来不会变的
  • Conversion 转换只针对数据,不对对编码转换

整体的工作流程类似于:

  1. 用户在Unity Editor下,编辑数据 
  2. 然后在Unity Editor 模式下转换成 runtime data 运行时数据
  3. 游戏在运行时,只需要处理运行时数据就可以了 

1.1 Fundamental principles

Authoring data and runtime data为了不同的目标进行了不同的优化:

  • Authoring data 优化了灵活性
    • 更易理解,更易操作
    • 版本控制,不需要复制
    • 团队合作
  • Runtime data 优化了性能
    • 缓存效率Cache efficiency
    • Loading time and streaming
    • 分布大小Distribution size

Note:

GameObjects 和 entities之间不是1:1的映射关系

  • 单个GameObject可以变成一组实体,例如程序生成
  • 多个 GameObjects 可以聚合成单个实体,比如 LOD baking
  • 有些gameobject在运行时可能没有任何目的,例如关卡编辑标记. level editing markers

组件也是如此. 一个转换系统可以读取任意数量的Unity组件,并向任意数量的实体添加任意数量的ECS组件。

1.2 Key concepts 关键概念

  • Authoring scene
    普通的 Unity scene, 包括GameObjects, 注定要转换为运行时数据
  • Subscene
    一个简单的 GameObject component 引用了 authoring scene ,当Subscene 处于编辑模式下,会加载 authoring scene ,当subscence 被关闭的时候,以stream 的形式存在于转换后的 entity scene 中
  • Entity scene
    authoring scene转换后的结果,因为 entity scenes 是asset import的一个输出结果, 它们存在于 Library 文件夹下. Entity scenes 可以由多个部分组成,每个部分都可以独立加载。
  • LiveConversion
    当authoring scene在edit 模式下以 GameObjects 的形式加载出来,每一次改变都会触发entity scene更新,看起来就像是在直接编辑 Entity Scene,这个过程称之为 LiveConversion.

1.3 Scene conversion

Conversion systems 在整个场景中同时运行。

除了ConvertToEntity MonoBehaviour 能够转换外,还有一些其它的方法,比如 GameObjectConversionUtility,但是应该尽量避免,因为在将来会被移除。

下面是 authoring scene 转成 entity scene的步骤:

  1. 设置一个 conversion world,为每一个gameobject 创建一个 entity 
  2. 收集内部的引用 references (e.g. prefabs)
  3. 为这些在conversion world 中的gameobject  在destination world 创建主要的实体。
  4. 更新主要的 conversion system groups
  5. 标记Tag entity prefabs
  6. 为混合组件创建伴生的的gameobject。Create companion GameObjects for hybrid components
  7. 创建相关联的Create linked entity groups
  8. 更新Update the export system group (only for Unity Tiny)

所有引用的prefab 会在同一时间转换成entity ,没有额外专门处理.

1.4 Large scale performance

由于以下原则,转换工作流允许高效地处理大型场景:

  • 在Unity Editor 模式下, 一个场景可以被分解成几个子场景. 它们可以在运行时数据和创作数据之间来回切换, 这取决于场景的哪些部分需要处理
  • Conversion 可以作为 v2  版本的asset database 运行, 它可以精准定位依赖项,导入和转换是在后台进程进行的,不会阻塞Editor.
  • 改变 authoring data 会根据情况更新相应的运行时数据,这个过程称之为 incremental conversion.增量转换

1.5 The Subscene Monobehaviour

实体场景的转换通常通过使用子场景来完成。 它引用一个  authoring scene ,然后转换成entity scene ,并加载出来.

有一个按钮可以控制是否自动在运行时加载entity scene , 和一个手动加载/卸载 的按钮 ,也可以重新导入。

WARNING

强制重新导入会掩盖一些问题,这个选项是用来测试用的,如果重新导入没有自动进行,一般是因为一些依赖或者版本信息丢失了

更重要的是,可以在  edit mode (authoring) 和 closed (runtime)之间切换. scene 的内容,

1. Live Conversion in Edit Mode

在Edit模式下,是否发生author data  到 runtime data  的转变

2. SceneView: Editing State / Live Game State

Editing State 模式可以编辑 author data,Live Game State不可以

1.6 Conversion systems 101

每一个组件系统 只转换一次,这些系统和常规DOTS系统的一个很大的区别是,转换系统跨越两个世界,它们从一个世界读取,并向另一个世界写入。

Conversion systems 继承自 GameObjectConversionSystem ,负责从author world 读取数据,写入 destination world。

下面 使用 GetPrimaryEntity 访问destination world的实体. 给ForEach  lambda 表达式添加一个Entity参数,来访问来自author  world 的实体,是没有意义的,因为转换系统不应该修改转换世界,只写入目标世界。

Here's the "hello world" of conversion systems, that does a 1:1 conversion of all authoring components of a certain type to their ECS equivalent.

// Authoring component
class FooAuthoring : MonoBehaviour
{
    public float Value;
}

// Runtime component
struct Foo : IComponentData
{
    public float SquaredValue;
}

// Conversion system, running in the conversion world
class FooConversion : GameObjectConversionSystem
{
    protected override void OnUpdate()
    {
        // Iterate over all authoring components of type FooAuthoring
        Entities.ForEach((FooAuthoring input) =>
        {
            // Get the destination world entity associated with the authoring GameObject
            var entity = GetPrimaryEntity(input);

            // Do the conversion and add the ECS component
            DstEntityManager.AddComponentData(entity, new Foo
            {
                SquaredValue = input.Value * input.Value
            });
        });
    }
}

GameObjectConversionSystemForEach 不会创建job. 它运行在 main thread, 不需要Burst, 可以访问unity数据。

1.7 Conversion World (the input)

转换开始时,先在 conversion world 为每一个 GameObject 创建一个实体,包括它引用的prefab。GameObjects 上面的每一个component  稍后被添加到相应的实体上, 这是一种在DOTS中很少使用的机制,因为使用经典的Unity组件是无法伸缩的. 这些组件是引用类型,ECS的每次访问都以低效的方式访问内存。

这样做的唯一原因是允许转换系统使用实体查询访问创作组件。

请注意,禁用author 组件,则它不会添加到conversion world 中,在conversion world 中查询不到它们。但是inactive的GameObjects会变成disable的实体,但转换会正常进行。

1.8 Destination World (the output)

在conversion system 运行之前,每一个conversion world,里的gameobject 都会自动在destination world  中生成一个实体. 可以通过 GameObjectConversionSystem.GetPrimaryEntity.访问

一旦author data 变了,就要追踪相应的依赖,然后更新

基于conversion setting ,转换后的实体会含有一下组件:

  • Static, to bake transforms.
  • EntityGuid, for LiveConversion.
  • Disabled, to mark entities coming from disabled GameObjects as disabled.
  • SceneSection, for streaming sections.

更改该组件集将破坏转换逻辑,因此应该小心(例如,在转换期间不应使用SetArchetype)。

On top of that, the name of the GameObject will al除此之外,GameObject的名称也会被复制为实体名称,用来debug

直接通过 (via CreateEntityInstantiate, etc.) 在destination world  中添加实体,会绕过设置,并造成一些问题,所以当添加实体时,需要通过 GameObjectConversionSystem.CreateAdditionalEntity.

1.9 Conversion Systems Ordering

和其它system 一样, conversion systems 可以通过下面属性排序:

  • [UpdateBefore]
  • [UpdateAfter]
  • [UpdateInGroup]

默认提供了以下conversion system groups 按照顺序是:

  1. [GameObjectDeclareReferencedObjectsGroup] - before the creation of entities in the destination world.
  2. [GameObjectBeforeConversionGroup] - early conversion group
  3. [GameObjectConversionGroup] - main conversion group (this is the default when no groups is explicitly specified)
  4. [GameObjectAfterConversionGroup] - late conversion group
  5. [GameObjectExportGroup] - only for Unity Tiny

IMPORTANT

在转换期间调用 GetPrimaryEntity 会返回 partially constructed entity,这个实体上的组件集将取决于系统顺序。

1.10 Prefabs

拥有一个 Prefab tag 和 LinkedEntityGroup

下面的两个组件是同等的,一个是Unity  一个是DOTS里的

// Authoring component
public class PrefabReference : MonoBehaviour
{
    public GameObject Prefab;
}

// Runtime component
public struct PrefabEntityReference : IComponentData
{
    public Entity Prefab;
}

默认情况下,转换工作流只处理创作场景的实际内容,因此还需要一个特定的机制来包括asset文件夹中的预制件。这也是 GameObjectDeclareReferencedObjectsGroup, 的目的,它在实际生成实体之前执行,并且提供了一中注册实体的方法.

比如:

[UpdateInGroup(typeof(GameObjectDeclareReferencedObjectsGroup))]
class PrefabConverterDeclare : GameObjectConversionSystem
{
    protected override void OnUpdate()
    {
        Entities.ForEach((PrefabReference prefabReference) =>
        {
            DeclareReferencedPrefab(prefabReference.Prefab);
        });
    }
}

请注意,只要游戏对象的数量持续增长,这个系统就会更新. This means that if you have a GameObject A (in an authoring scene) that references a prefab B (in the asset folder) that itself references another prefab C (in the asset folder) that doesn't reference anything, the system above will update three times.

  • The first time PrefabConverterDeclare runs, the ForEach will iterate over the set { A } and it will declare A.Prefab (this grows the set by one, it becomes { A, B }).
  • The second time PrefabConverterDeclare runs, the ForEach will iterate over the set { A, B } and it will declare A.Prefab and B.Prefab (this grows the set by one, it becomes { A, B, C }).
  • The third time PrefabConverterDeclare runs, the ForEach will iterate over the set { A, B, C } and it will declare A.Prefab and B.Prefab (there is no C.prefab, so this doesn't grow the set, it remains { A, B, C }).
  • Because the set didn't grow since the last iteration, the process stops.

同一个prefab 调用多次 DeclareReferencedPrefab方法,该prefab 只注册一次。

DeclareReferencedPrefab 只能在GameObjectDeclareReferencedObjectsGroup 该组里面的系统里调用。

Declared prefabs can be retrieved as entities by calling GetPrimaryEntity in a system that runs after the creation of those entities, in other words in a system which isn't part of GameObjectDeclareReferencedObjectsGroup.

Example: The following system will convert the components declared in the previous example.

class PrefabConverter : GameObjectConversionSystem
{
    protected override void OnUpdate()
    {
        Entities.ForEach((PrefabReference prefabReference) =>
        {
            var entity = GetPrimaryEntity(prefabReference);
            var prefab = GetPrimaryEntity(prefabReference.Prefab);

            var component = new PrefabEntityReference {Prefab = prefab};
            DstEntityManager.AddComponentData(entity, component);
        });
    }
}

prefab 在转换期间是不能实例化的,原因如下:

  • 预制件与所有其他gameobject一起转换,这意味着GetPrimaryEntity将返回一个部分转换的预制件。
  •  prefab需要一个LinkedEntityGroup,它只在转换结束时初始化。
  • 预制实例化等同于在目标世界中手动创建实体,这将中断转换,原因在本文前面已经说明。

1.11 The IConvertGameObjectToEntity interface

IConvertGameObjectToEntity 接口提供了很大的灵活性,可以通过Monobehavior 继承. 在更新 GameObjectConversionGroup,时,所有实现该接口的组件都会转换成实体。

比如:


// Authoring component
class FooAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
    public float Value;

    public void Convert(Entity entity, EntityManager dstManager,
        GameObjectConversionSystem conversionSystem)
    {
        dstManager.AddComponentData(entity, new Foo {SquaredValue = Value * Value});
    }
}

// Runtime component
struct Foo : IComponentData
{
    public float SquaredValue;
}

需要实现convert 方法,它有两个参数:

  • Entity entity -含有authoring component.的实体
  • EntityManager dstManager - the entity manager of the destination world.
  • GameObjectConversionSystem conversionSystem -转换的system,调用所有的convert 方法

Please note that:

  • entity 只存在于destination world,所有只能由 dstManager.访问
  • dstManager和 conversionSystem.DstEntityManager 等价,用起来方便
  • 没有现成的方法控制convert 执行的顺序,除非自定义conversion system. 

1.12 The IDeclareReferencedPrefabs interface

实现 IDeclareReferencedPrefabs 接口,实现自定义prefab的添加.

比如:

public class PrefabReference : MonoBehaviour, IDeclareReferencedPrefabs
{
    public GameObject Prefab;

    public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    {
        referencedPrefabs.Add(Prefab);
    }
}

它有一个参数:

  • List<GameObject> referencedPrefabs -添加到该list里面的prefab ,会生成实体,不要clear 它,因为有可能已经添加了别的prefab.

注意:

  • 它会递归访问prefab 的引用,所以 DeclareReferencedPrefabs 可能会在转换过程中调用好几次.
  • 添加同一个prefab 多次,只会注册一次

[!NOTE]

一个脚本同时继承 IDeclareReferencedPrefabs and IConvertGameObjectToEntity on 非常常用

1.13 Generated authoring components

对于简单的 runtime 组件, GenerateAuthoringComponent 属性可以用来自动为runtime 组件创建author 组件.

比如:

// Runtime component
[GenerateAuthoringComponent]
public struct Foo : IComponentData
{
    public int ValueA;
    public float ValueB;
    public Entity PrefabC;
    public Entity PrefabD;
}

// Authoring component (generated code retrieved using the DOTS Compiler Inspector)
[DisallowMultipleComponent]
internal class FooAuthoring : MonoBehaviour, IConvertGameObjectToEntity,
    IDeclareReferencedPrefabs
{
    public int ValueA;
    public float ValueB;
    public GameObject PrefabC;
    public GameObject PrefabD;

    public void Convert(Entity entity, EntityManager dstManager,
        GameObjectConversionSystem conversionSystem)
    {
        Foo componentData = default(Foo);
        componentData.ValueA = ValueA;
        componentData.ValueB = ValueB;
        componentData.PrefabC = conversionSystem.GetPrimaryEntity(PrefabC);
        componentData.PrefabD = conversionSystem.GetPrimaryEntity(PrefabD);
        dstManager.AddComponentData(entity, componentData);
    }

    public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    {
        GeneratedAuthoringComponentImplementation
            .AddReferencedPrefab(referencedPrefabs, PrefabC);
        GeneratedAuthoringComponentImplementation
            .AddReferencedPrefab(referencedPrefabs, PrefabD);
    }
}

记住一下限制:

  • 生成的author data 会覆盖同名的组件。
  • 单个 C# 文件,只能包含一个可转换的组件 ,并且不能有monobehavior
  • The file doesn't have to follow any naming convention, i.e. it doesn't have to be named after the generated authoring component.
  • ECS only reflects public fields and they have the same name as that specified in the component.
  • ECS reflects fields of an Entity type in the IComponentData as fields of GameObject types in the MonoBehaviour it generates. ECS converts the GameObjects or Prefabs you assign to these fields as referenced Prefabs.
  • There is no way to specify default values for the fields.
  • There is no way to implement authoring callbacks (e.g. OnValidate)

也可以实现 IBufferElementData的组件 生成author data

Example: The following runtime component will generate the authoring component below, the source for BufferElementAuthoring is in the entities package, it does exactly what you'd expect.

// Runtime component
[GenerateAuthoringComponent]
public struct FooBuffer : IBufferElementData
{
    public int Value;
}

// Authoring component (generated code retrieved using ILSpy)
internal class FooBufferAuthoring :
    Unity.Entities.Hybrid.BufferElementAuthoring<FooBuffer, int>
{
}

Note the following additional restrictions:

  • IBufferElementData  包含2个及以上的该组件,不能装换成authoring components。
  • IBufferElementData authoring components 对于具有显式布局的类型不能自动生成。

1.14 Asset pipeline V2 and background importing

一个scene的转换发生在一下两种情形中:

  • When a subscene is open for edit, the conversion runs in the Unity editor process every time something changes.
  • When a subscene is closed, the result of the conversion (entity scene) is loaded as an asset.

第二种情形下,entity scenes are produced on demand by the asset pipeline through the use of a scripted importer (see the asset pipeline V2 documentation for further information on scripted importers). 这个转换在后台发生在一个独立的进程中,称之为 "asset worker".

下面是需要注意的点:

  • Importing an entity scene 异步的, 首先转换可能花费很长时间,一旦开始,就会一直存在,直到进行完,再导入就会快很多
  • Exceptions, errors, warnings, logs, etc.  不会在Unity Editor展现出来,.log会在工程文件夹下的 "Log“,# 代表一个数字,每当转换崩溃或者需要重新开始时,就会自增1, So if all goes well you should only ever see AssetImportWorker0.log.
  • 当 attaching the debugger to a Unity process 时,需要注意 each process will have a child process (if at least one entity scene import happened since startup), depending if you want to debug the main process or the asset import process you'll have to pick the right one. You can rely on the process name for that purpose, or on the parenting relation between the two processes: the asset worker is the child.
  • asset pipeline v2 将根据需要导入asset,并检查依赖关系以确定资产是否最新。它还保留了上次导入的缓存,在这二者之间切换更快,但是如果缺少依赖项,导入的可能就是上次缓存的旧的资源了
  • 因为导入是有缓存的,所以撤销导入并不会触发重新导入

1.15 Type dependencies

entity scene 为每一个它引用的runtime 组件,都包含一个hash值,用来检测type的变化, component type 的变化会触发conversion process.

The ConverterVersion attribute

改变 authoring types and conversion systems不会被自动检测出来, ConverterVersion 属性可以用来检测,用在实现了IConvertGameObjectToEntity的接口上。

它包含两个参数:

  • A string identifier
  • A version number

 对这两者中的任何一个进行更改都将影响依赖关系. 

public class SomeComponentAuthoring : MonoBehaviour
{
    public int SomeValue;
}

[ConverterVersion("Fabrice", 140)]
public class SomeComponentConversion : GameObjectConversionSystem
{
    protected override void OnUpdate()
    {
        // ...
    }
}

Please note that in the example above, the attribute has to be put on the system, not on the authoring component. Because any relationship between a conversion system and any component only exists in the OnUpdate of the system, so it's not something that the dependency system can reason about automatically.

[ConverterVersion("Fabrice", 140)]
public class SomeComponentAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
    public int SomeValue;

    public void Convert(Entity entity, EntityManager dstManager,
        GameObjectConversionSystem conversionSystem)
    {
        // ...
    }
}

In the case of an authoring component that implements IConvertGameObjectToEntity, the conversion code and the definition of the authoring type are in the same class, so there's no ambiguity about the location of the ConverterVersion attribute.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TO_ZRG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值