news 2026/5/13 14:05:31

ECS系统入门手记——其一

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ECS系统入门手记——其一

观前须知

ECS是一种用于处理大量运算,性能极高的架构,在某些特定的情况下可能发挥很大作用,由于所蕴含的知识很多,而我只粗学了10多个小时,某些地方可能会有纰漏,看不懂或者讲错了直接喷

注:需要导入的一些包这里就不再赘述了,加载SubScene什么的这里也不再提及

正式开始

ECS,即"Entity(实体)",“Component(组件)”,"System(系统)"简单来说就是组件挂载在实体上,执行系统中的规则,与传统unity的区别是原本unity执行的规则放在组件上,下面,我们分开来讲这三个部分

组件Component

这里的Component,实际上充当了Data的作用,主要是用来存放一些字段数据,下面给出一个简单的例子

public struct RotateSpeed : IComponentData { public float Speed; }

注意这里使用结构体,是为了让Data为非托管类型,如果你非要用class,也不是不可以,只是会让性能下降很多,还有一点,请不要在结构体中放托管类型的字段,否则也会让它性能下降

实体Entity

实体,只负责承载Component,本身没有任何字段和方法,想要给实体上放置组件,我们不能直接把ComponentData拖到对象上面,我们可以用Baker的方法

public class RotateSpeedAuthoring : MonoBehaviour { [SerializeField] private float _speed; [SerializeField] private bool _startRotate; public float Speed => _speed; public bool StartRotate => _startRotate; private class Baker : Baker<RotateSpeedAuthoring> { public override void Bake(RotateSpeedAuthoring authoring) { Entity entity = GetEntity(TransformUsageFlags.Dynamic);//也可以用None,Dynamic是可动的,None一般是不需要它的位置的 AddComponent(entity, new RotateSpeed() { Speed = authoring.Speed, }); //先忽略 SetComponentEnabled<RotateSpeed>(entity,authoring.StartRotate); } } }

这里的Baker是Mono脚本的一个子类,可以在游戏开始是给挂载该脚本的对象实体添加一个Component。注意这里的GetEntity方法,参数可以为动态或静态,它会让世界给你返回一个实体的引用,你可以为它添加自己想要的组件,并为对象设置想要的初始值。除了用Baker在开始时添加组件,还有另外三种常用的方法添加组件,它们分别是在主线程动态增删的EntityManager和EntityCommandBuffer,还有在多线程中增删的EntityCommandBuffer.ParallelWriter,至于它们,我们会在下一段见到

系统System

系统是最核心的一部分,包含了整个世界的规则,简单举一个例子就是"让所有长翅膀的动物会飞",这就是一个简单的系统了,系统要做的事其实就这么几步,查找所有动物,看谁长了翅膀,然后让它飞起来。最多需要在开始时先查找一遍有没有长翅膀的动物,如果没有就不查找了节省性能。
至于这里的查找,主要有三种常用方法,Query,Job和Chunk.我们来以一个简单的系统来依次看看

Query

[BurstCompile] partial struct RotateCubeSystem : ISystem { private float _timer; private const float INTERVAL = 1f; [BurstCompile] public void OnCreate(ref SystemState state) { state.Enabled = false;//这里如果用false系统就不运行了,记得删!!! state.RequireForUpdate<RotateSpeed>();//找寻世界里有没有对应组件,没有就不Update } [BurstCompile] public void OnUpdate(ref SystemState state) { foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotateSpeed>>()) { transform.ValueRW = transform.ValueRO.RotateY( speed.ValueRO.Speed * DeltaTime); } } [BurstCompile] public void OnDestroy(ref SystemState state) { } }

这里就是最简单的一个Query,下面我们依次解析可能会看不懂的地方,

  1. 首当其冲的是 [BurstCompile],这个标签是为了提升性能,但是如果方法中有托管对象就会报错,比如GameObject,
  2. 第二,查询Query,前面的类是var加一个元组,元组是用来简化代码的,关键看后面.Query<RefRW, RefRO>(),查询世界中所有带LocalTransform和RotateSpeed的实体,并返回LocalTransform的读写,RotateSpeed的可读引用,用LocalTransform主要是因为unity内置的Transform用不了,只能用它,改变它的旋转
  3. 后面就是简单的改变transform的值了,没有好讲的了

这里的Query性能不高,但是理解起来容易,我们来给他改成job,性能更高,理解起来稍稍有些困难

Job

job类 [BurstCompile] public partial struct RotateCubeJob : IJobEntity { public float DeltaTime; void Execute(ref LocalTransform transform, ref PostTransformMatrix postTransform, in RotateSpeed speed) { transform = transform.RotateY(speed.Speed * DeltaTime); } } //使用:把上面一段foreach改成 RotateCubeJob job = new RotateCubeJob() { DeltaTime = SystemAPI.Time.DeltaTime }; state.Dependency= job.ScheduleParallel(state.Dependency);
  1. 这里为什么要传一个DeltaTime作为参数?因为job中不能使用SystemAPI.Time.DeltaTime,所以需要外界给他传进去

  2. Execute方法?其实和上面的Query差不多,只不过把可写参数加上ref,可读参数加上in,其他几乎就是照搬

  3. 最后的使用,只要先new一个job,然后job.Schedule()就好了

最后的chunk性能最高,当然代码也有点繁琐,注意不是困难,是繁琐

Chunk

chunk类,注意Execute参数固定 public struct RotateCubeJobChunk : IJobChunk { public ComponentTypeHandle<LocalTransform> TransformTypeHandle; [ReadOnly] public ComponentTypeHandle<RotateSpeed> RotateSpeedTypeHandle; public float DeltaTime; [BurstCompile] public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { var transforms = chunk.GetNativeArray(ref TransformTypeHandle); var speeds = chunk.GetNativeArray(ref RotateSpeedTypeHandle); for(int i=0; i<chunk.Count;i++) { transforms[i] = transforms[i].RotateY(speeds[i].Speed * DeltaTime); } } } chunk的使用 var spinningCubesQuery = SystemAPI.QueryBuilder().WithAll<RotateSpeed, LocalTransform>().Build(); RotateCubeJobChunk chunk = new RotateCubeJobChunk() { DeltaTime = SystemAPI.Time.DeltaTime, TransformTypeHandle = SystemAPI.GetComponentTypeHandle<LocalTransform>(), RotateSpeedTypeHandle=SystemAPI.GetComponentTypeHandle<RotateSpeed>(true), }; //state.Dependency= chunk.Schedule(spinningCubesQuery,state.Dependency);
  1. chunk里面所有用到的Component都要用到句柄的方式。其中只读的要加上只读标签,然后在使用是从句柄中取到引用

  2. spinningCubesQuery是取到所有有对应组件实体的队列,chunk的Schedule方法要用到这个队列,后面几乎就差不多了,把句柄通过SystemAPI的获得句柄的方式传进去

  3. 句柄最好在开始时缓存起来

附加内容

碍于篇幅限制,下面用几句简单的话解释知识点

  1. 在系统上加上[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]这个标签,它的Update函数会变成类似unity的FixedUpdate
  2. 系统除了可以继承自ISystem,还可以继承自SystemBase,在它的里面放托管类型字段方法,作为ECS世界和Mono世界的桥梁
  3. 想要大量生成预制体,用Component存Entity,再在烘焙时把Component里面的Entity=GetEntity(prefab,TransformUsageFlags.Dynamic),再state.EntityManager.Instantiate(prefab, 200, Allocator.Temp);就好,记得如果数量过多就要把第三个参数的Temp改成永久,并在完成后Dispose
  4. 可以通过SystemAPI.GetSingleton获取某个组件单例

大概基础只能写这么多,能看懂谢天谢地,希望能出第二期…………或者学HybridCLR+Addressable也好啊

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 7:52:17

SCCLIP

SCCLIP动机 “anomaly tokens emerge during the forward pass, drawing excessive attention from normal patch tokens, thereby diminishing spatial awareness” (Bai 等, 2024, p. 1) (pdf) &#x1f524;在前向传递过程中出现异常令牌&#xff0c;引起正常补丁令牌的过度…

作者头像 李华
网站建设 2026/5/13 11:56:43

用PHP8实现斗地主游戏,后端逻辑开发

核心功能模块设计 斗地主游戏的核心模块包括卡牌生成、发牌逻辑、玩家交互和出牌规则验证。以下分模块说明实现方法。 卡牌生成与初始化 使用数组生成54张标准扑克牌&#xff0c;包含大小王。示例代码展示如何初始化牌组并洗牌&#xff1a; class Deck {private $cards [];pub…

作者头像 李华
网站建设 2026/5/11 23:31:14

《深析游戏社交量化逻辑:解锁留存付费的核心传导路径》

很多时候量化社交影响的误区,不在于指标不够繁杂,而在于误将“社交行为数量”等同于“社交关系价值”,比如单纯统计好友数量、互动频次,却忽略了社交关系的双向性、协作依赖性、圈层归属感这些核心维度,反而让量化结果失去落地指导意义。真正有效的量化分析,核心是拆解社…

作者头像 李华
网站建设 2026/5/6 2:34:11

概率论与数理统计期末复习:大数定理与中心极限定理详解(扩展版)

概率论与数理统计期末复习&#xff1a;大数定理与中心极限定理详解&#xff08;扩展版&#xff09;关键词&#xff1a;概率论、数理统计、大数定律、中心极限定理、切比雪夫不等式、依概率收敛、依分布收敛、蒙特卡洛方法、期末复习引言&#xff1a;为什么这两个定理如此重要&a…

作者头像 李华
网站建设 2026/5/10 23:49:29

如何设计一门领域特定语言?

领域特定语言&#xff0c;又可以称为领域专用语言或者领域特定语言。 目前&#xff0c;计算机编程语言的种类众多&#xff0c;常见的有C、C、Java、Python等。 这些编程语言都各有特点。从应用范围上划分&#xff0c;个人大致讲它们划分成两种&#xff1a;通用型和领域型。 刚…

作者头像 李华
网站建设 2026/5/5 22:34:16

Flutter 与 OpenHarmony 深度整合:构建跨设备统一剪贴板同步系统

引言 在多设备协同场景中&#xff0c;用户频繁遇到这样的“断点”&#xff1a; 手机上复制了一段文字&#xff0c;想粘贴到平板的文档里&#xff0c;却要重新输入&#xff1b;电脑上复制了一个链接&#xff0c;想在电视上打开&#xff0c;但无法传递&#xff1b;智能手表收到…

作者头像 李华