ECS 二 System 概述

本文深入探讨Unity ECS(Entity Component System)框架,解析其核心概念,包括系统(System)、组件(Component)和实体(Entity)的运作机制。阐述了Unity ECS中不同类型的系统,如ComponentSystem和JobComponentSystem的作用,以及如何利用系统事件函数OnCreate、OnUpdate等进行游戏逻辑的高效执行。此外,还介绍了系统更新顺序的控制方法,以及Job依赖性和EntityCommandBuffer的使用技巧。

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

Systems

1.1 简介

System  ECS三要素之一,负责把组件当中的数据从一个状态转换到另一个状态的逻辑, 比如,一个system可以可能会根据所有移动实体的速度乘以自上次更新以来的时间间隔来更新它们的位置。

Unity ECS 提供了多种不同的systems. 在 main systems 主系统中实现转换实体数据的系统是 ComponentSystemJobComponentSystem.这两种系统类型都有助于根据实体上的组件类型来选择或者是遍历实体

Systems 提供了事件类型event-style callback的回调函数 ,比如OnCreate() 、 OnUpdate() ,在系统生命周期内地的正确时间段来运行你的代码,执行逻辑,这些函数是在主线程中进行的,在Job Component System中, 通常在OnUpdate() 函数内执行逻辑代码. Jobs 本身在工作线程上运行 worker threads; 通常, Job Component Systems 能够提供最佳性能,因为它充分利用了 CPU的多核结构. 甚至当你的jobs通过Burst Compiler来编译时,性能也能得到很大的改善

Unity ECS 会自动发现项目中的系统类 system classes,并在运行时实例化它们. Systems 在一个 World里面的分为一个组. 你可以控制system被添加到哪个组里面,也可以通过 system attributes来决定system在组内的顺序. 默认,所有的systems 都被添加到 default world的Simulation System Group内, 但是顺序不确定, 你可以使用system attribute 来禁用自动创建,一个系统的 system's update 循环是通过它的父组件Component System Group驱动的 . Component System Group 是一种专门来负责更新它的子系统的系统,就是它本身是一个系统,作用更新它的子系统,Groups 之间可以嵌套, Systems 从它所在的world里面继承时间值,时间通过  UpdateWorldTimeSystem 更新

你可以查看正在运行的系统的配置 system configuration ,通过 Entity Debugger 窗口(menu: Window > Analysis > Entity Debugger).

1.1.1  System types:系统类型

Unity ECS 提供了几种类型的系统:通常用于实现游戏行为和数据转换的system继承于SystemBase;其他系统类有专门的用途,通常会使用 Entity Command Buffer System和 Component System Group 类的实例.​​​​​​​​​​​​​​

  • SystemBase -- 创建系统时的基类
  • EntityCommandBufferSystem -- 为其它系统提供一个 EntityCommandBuffer 实例,每一个默认的 system groups 在它子系统列表的开始和结尾都有一个该系统的实例,也就是在开始或者结束的时候执行,减少系统之间的冲突
  • ComponentSystemGroup -- 为其它系统提供一个嵌套的组织和更新顺序, Unity ECS 会默认创建几个Component System Groups .
  • GameObjectConversionSystem -- 基于GameObject 转换, 把gameobject转换成实体. Game conversion systems 可以在 Unity Editor下运行

1.2  System event functions:系统事件函数

在创建系统时,继承于SystemBase,可以实现一组系统生命周期事件函数. Unity ECS 按照以下顺序执行:

  • OnCreate() -- 在系统创建时调用
  • OnStartRunning() -- 在第一次调用OnUpdate()之前以及每次System Resume  Running时,类似与unity的 start函数
  • OnUpdate() -- 当system 是Enabled时,每一帧都执行 (see ShouldRunSystem()) 。
  • OnStopRunning() --  当 system  Enabled 设置为false 时,或者在queries中找不到匹配的实体,就是停止更新.
  • OnDestroy() -- 当系统销毁时调用destroyed.

所有这些函数都是在主线程上执行的. 但是你可以在OnUpdate(JobHandle) 中定义一个Job ,来让它在子线程中执行.

1.2 System Update Order

在system 类上面声明以下属性,用来表示在group中更新的顺序:

UpdateInGroup :在哪个group里

UpdateBefore :

UpdateAfter 

ECS framework 默认创建了一些group default system groups 。

1.2.1 Component System Groups

ComponentSystemGroup 类也是一个system,也一样拥有onupdate 方法,它里面包含一个system 的集合,按照树结构排列,在更新的时候,按照深度优先遍历system,执行其里面的onupdate 方法

1.2.2 System Ordering Attributes

下面是一些排序的属性:

  • UpdateInGroup — 声明一个system 属于哪个group,如果没有这个属性,默认添加到 World’s SimulationSystemGroup (see below).
  • UpdateBefore 、 UpdateAfter — 相对于其它system的顺序,它相比较的system 必须是在同一个group里,因为不同group的system 的顺序,有group 决定
  • DisableAutoCreation — 在default world 初始化的时候,该system 不能被创建,你必须显式的创建和更新,但是,可以添加到一个  ComponentSystemGroup的update 列表里面,这样就会自动更新。

1.2.3 Default System Groups 

下面是一些默认创建的ComponentSystemGroup:

  • InitializationSystemGroup (updated at the end of the Initialization phase of the player loop)
    • BeginInitializationEntityCommandBufferSystem
    • CopyInitialTransformFromGameObjectSystem
    • SubSceneLiveConversionSystem
    • SubSceneStreamingSystem
    • EndInitializationEntityCommandBufferSystem
  • SimulationSystemGroup (updated at the end of the Update phase of the player loop)
    • BeginSimulationEntityCommandBufferSystem
    • TransformSystemGroup
      • ParentSystem
      • CopyTransformFromGameObjectSystem
      • TRSToLocalToWorldSystem
      • TRSToLocalToParentSystem
      • LocalToParentSystem
      • CopyTransformToGameObjectSystem
    • LateSimulationSystemGroup
    • EndSimulationEntityCommandBufferSystem
  • PresentationSystemGroup (updated at the end of the PreLateUpdate phase of the player loop)
    • BeginPresentationEntityCommandBufferSystem
    • CreateMissingRenderBoundsFromMeshRenderer
    • RenderingSystemBootstrap
    • RenderBoundsUpdateSystem
    • RenderMeshSystem
    • LODGroupSystemV1
    • LodRequirementsUpdateSystem
    • EndPresentationEntityCommandBufferSystem

请注意,此列表的具体内容可能会更改。

1.2.4 Multiple Worlds

可以创建多个world ,也可以覆盖默认的world,同一个system 可以在多个world 间创建,每一个都是一个实例,所以有可能更新顺序也不同,在不同的world 间。

目前没有方式手动更新给定world 中的每一个system,但是可以控制,system 在哪个world 中被创建,以及属于哪个group。 

通过继承于接口 new ICustomBootstrap 来做到:

public interface ICustomBootstrap
{
    // Returns the systems which should be handled by the default bootstrap process.
    // If null is returned the default world will not be created at all.
    // Empty list creates default world and entrypoints
    List<Type> Initialize(List<Type> systems);
}

当实现该接口时,在默认的world 初始化之前,会传递一个system  列表给Initialize() 方法,这里面的system 可以自定义属于的world.

比如MyCustomBootstrap.Initialize() 的实现:

  1. Create any additional Worlds and their top-level ComponentSystemGroups.
  2. For each Type in the system Type list:
    1. Traverse upward through the ComponentSystemGroup hierarchy to find this system Type’s top-level group.
    2. If it’s one of the groups created in step 1, create the system in that World and add it to the hierarchy with group.AddSystemToUpdateList().
    3. If not, append this Type to the List to return to DefaultWorldInitialization.
  3. Call group.SortSystemUpdateList() on new top-level groups.
    1. Optionally add them to one of the default world groups
  4. Return list of unhandled systems to DefaultWorldInitialization.

注意:

ECS framework 通过反射查找到自定义的 ICustomBootstrap .

1.2.5 Tips and Best Practices

  • 使用 UpdateInGroup 来标记system属于哪个group,不标记默认为SimulationSystemGroup.
  • 手动标记ComponentSystemGroups 更新systems,在 Unity player loop 的任何地方. 通过给system 添加 [DisableAutoCreation] 属性,来阻止它自动添加到group中,可以通过World.GetOrCreateSystem() 创建system,然后调用MySystem.Update() ,这样就可以控制在 Unity player loop  的调用顺序了
  • 使用已经存在的 EntityCommandBufferSystem 而不是重新创建一个. EntityCommandBufferSystem  是一个主线程等待所有工作线程完成之后的一个同步点,复用它比创建它减少碎片。
  • 避免在ComponentSystemGroup.OnUpdate写逻辑.

1.3 Job dependencies

system 的 Dependency 属性是一个  JobHandle 类型,它表示相关的依赖关系,默认情况下,system 更新 Dependency 属性基于job 中读取的写入的组件。 

通过 Entities.ForEach 和 Job.WithCode 方法,手动传递一个dependency 过去,而不是自动合并system 的 Dependency 属性.

注意:

system Dependency 属性,并不是追踪job 中所有访问的变量,比如你在一个job 中写入,在另一个job 中读取,则你需要手动设置依赖关系 ,一般使用 JobHandle.CombineDependencies.

当调用 Entities.ForEach.Run() ,job 会完成所有job 之后,再调用,如果使用了 WithStructuralChanges() ,表示等所有structural改变的job 完成之后,Structural changes also invalidate any direct references to component data. See sync points for more information.

See JobHandle and dependencies for more information.

1.4 Entity Command Buffer

The EntityCommandBuffer 类解决了两个问题·:

  1. 当你在 job中,你不能访问EntityManager.
  2. 当你访问 EntityManager (比如创建一个entity) ,所有注入的数组和 EntityQuery 查询物体都是无效的

EntityCommandBuffer 它是一个结构体,记录了在所有job 完成之后需要进行的操作,它的目的就是在不产生同步点的情况下,进行操作 ,它包含了EntityManager 的大部分方法.

调用其中的方法Playback  来调用所有的命令。

它对一些native container 是线程安全的,会先检测有没有job 在访问它。

一些实体结构上的改变无法在job 中完成,所以需要在EntityCommandBuffer中完成。

Entity Command Buffer Systems

默认的World初始化提供了三个系统组, 分别是initialization, simulation,和 presentation, 它们在每一帧中按顺序更新.在一个组内, 有两个 entity command buffer system,一个会在组内其它系统执行之前运行 ,还有一个会在其它系统都执行完毕后运行. 更好的来说, 为了最小化同步点,您应该使用现有的 command buffer systems而不是创建一个新的. See Default System Groups for a list of the default groups and command buffer systems.

1.4.1 ECB use in a parallel job

当在并行job 中添加ECB 时,需要调用EntityCommandBuffer.ParallelWriter  方法。

EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);

// Methods of this writer record commands to 
// the ECB in a thread-safe way.
EntityCommandBuffer.ParallelWriter parallelEcb = ecb.AsParallelWriter();

注意: recording 需要对并行是安全的, playback 总是在主线程上是单线程的。

因为可以在不同的job 中调用ECB,命令的顺序取决于job 调度的顺序,所以命令的执行顺序是不确定的,但是可以让playback 的顺序是确定的:

  1. 每一条命令都有一个int 类型的'sort key' ,作为命令方法的第一个参数
  2. playback时,命令在执行之前按照排序键进行排序.

在并行job 中,entityInQueryIndex 提供了entity 的索引值。

注意:

如果是并行的ECB,最好每一个job 中都单独使用一个ECB 实例。

1.4.2 Multi-playback

默认 Playback 只调用一次,如果要调用多次需要通过 PlaybackPolicy.MultiPlayback 选项。比如多次创建实体。

1.4.3 EntityCommandBufferSystem

可以使用EntityCommandBufferSystem 来执行ECB:

  1. Get the instance of the ECB system which you want to do the playback.
  2. Create an ECB via the system.
  3. Schedule a job that will write commands to the ECB.
  4. Register the scheduled job to be completed by the system.

注意:

不用手动调用ECB 的playback 和 dispose ,system 会自动调用

In each update, an EntityCommandBufferSystem will:

  1. Complete all registered jobs (thereby ensuring that they have finished their recording).
  2. Playback all ECB's created via the system (in the same order they were created).
  3. Dispose of the ECB's.

The standard ECB systems

默认的五个ECB systems:

  • BeginInitializationEntityCommandBufferSystem
  • EndInitializationEntityCommandBufferSystem
  • BeginSimulationEntityCommandBufferSystem
  • EndSimulationEntityCommandBufferSystem
  • BeginPresentationEntityCommandBufferSystem

These update at the begin and end of the three standard system groups. See Default System Groups.

注意:

因为原型的变化不会在渲染之后,所以没有EndPresentationEntityCommandBufferSystem . BeginInitializationEntityCommandBufferSystem 是一帧的开始,同时也是上一帧的结束,这几个ECB system 可以满足大部分需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

TO_ZRG

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

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

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

打赏作者

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

抵扣说明:

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

余额充值