news 2026/3/26 10:02:50

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/3/16 18:35:21

Flutter 应用保活与后台任务:在 OpenHarmony 上实现定时上报

前言 在 OpenHarmony 生态中&#xff0c;许多应用场景&#xff08;如健康监测、设备状态上报、位置追踪&#xff09;要求应用即使在退到后台或屏幕关闭后&#xff0c;仍能周期性执行任务。然而&#xff0c;出于系统资源与电池优化的考虑&#xff0c;OpenHarmony 对后台进程有严…

作者头像 李华
网站建设 2026/3/15 15:36:49

【RL】verl 数据处理

您的 Eurus-2-RL-Data 数据集需要做两个主要适配&#xff1a;文件格式转换和字段映射配置。 快速解决方案 1. 转换文件格式&#xff08;推荐&#xff09; 将 arrow 文件转换为 parquet 格式&#xff1a; from datasets import load_dataset import os# 加载原始数据 ds lo…

作者头像 李华
网站建设 2026/3/21 14:46:26

Product Hunt 每日热榜 | 2025-12-13

1. Gemini Deep Research Agent 标语&#xff1a;最优秀的研究助手现已向开发者开放&#xff01; 介绍&#xff1a;Gemini深度研究助手现在可以通过互动API提供给开发者使用。它由Gemini 3.0 Pro驱动&#xff0c;能够自主规划、执行和综合多步骤的研究任务。 产品网站&#…

作者头像 李华
网站建设 2026/3/24 7:56:25

Python内置函数:你以为你很熟,但这些用法90%的人不知道

你好&#xff0c;我是你的技术朋友。今天我想和你聊聊那些每天都在用&#xff0c;却可能只用了十分之一功能的Python内置函数。 想象一下&#xff0c;你家厨房有一套顶级厨刀&#xff0c;但平时只用它切切西红柿。直到有天看到大厨用同一把刀雕出一朵萝卜花&#xff0c;你才恍然…

作者头像 李华
网站建设 2026/3/15 13:48:22

python_基于主视频删减片段并插入镜头视频

python_基于主视频删减片段并插入镜头视频 import pyJianYingDraft as draft from pyJianYingDraft import trange, ClipSettings,timdef create_jianying_draft_from_clips(draft_name,main_video_path,delete_ranges,lens_info_dict,draft_folder_path):# 时间格式转换函数(处…

作者头像 李华