news 2025/12/18 16:08:35

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进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/13 21:17:24

JavaEE进阶——SpringAOP从入门到源码全解析

目录 Spring AOP 超详细入门教程&#xff1a;从概念到源码 写给新手的话 1. AOP基础概念&#xff08;先理解思想&#xff09; 1.1 什么是AOP&#xff1f;&#xff08;生活化理解&#xff09; 1.2 AOP核心术语&#xff08;必须掌握&#xff09; 2. Spring AOP快速入门&…

作者头像 李华
网站建设 2025/12/17 7:38:37

SolidWorks装配体与装配图区别介绍

SolidWorks中的“装配体”和“装配图”是两个核心但常被混淆的概念&#xff0c;它们分别处于三维设计流程和二维工程制图两个不同但紧密关联的阶段。深入理解其区别与联系&#xff0c;是掌握现代机械设计流程的关键。 一、核心区别概览 特性维度 装配体​ 装配图​ 本质​ …

作者头像 李华
网站建设 2025/12/13 21:15:29

常用软件工具的使用(2) ---- git 命令进阶 和 github

目录git branchgit branch creategit 查看分支git cherry-pickgit blamegit patchgit rebasegit submodulegithubgithub 创建远程代码仓库github clone 远程仓库到本地github 修改文件提交到本地仓库github push 到远程分支git branch git 分支可以理解为代码的平行世界&#…

作者头像 李华
网站建设 2025/12/13 21:13:40

数据库事务、并发控制与安全机制全解析:原理、实践与避坑指南

数据库事务、并发控制与安全机制全解析&#xff1a;原理、实践与避坑指南 在现代多用户数据库系统中&#xff0c;事务一致性、并发控制、故障恢复和安全访问构成了核心支柱。无论是开发高并发业务系统&#xff0c;还是设计高可用数据架构&#xff0c;深入理解这些机制都至关重要…

作者头像 李华
网站建设 2025/12/13 21:12:05

B样条曲线拟合能量约束方法介绍

B样条曲线拟合中的能量约束方法&#xff08;Unicode公式版&#xff09;1. B样条曲线基本形式B样条曲线由控制点 Pᵢ 和基函数 Nᵢ,ₖ(u) 定义&#xff0c;其表达式为&#xff1a;C(u) Σᵢ₌₀ⁿ Pᵢ Nᵢ,ₖ(u), u ∈ [uₖ, uₘ₋ₖ]其中&#xff1a;k 为阶数&#xff08;次…

作者头像 李华
网站建设 2025/12/13 21:11:23

「旅行商问题 TSP 动态规划 贪心算法 数据结构 Java 代码」

旅行商问题&#xff08;TSP&#xff09;—— 从问题建模到经典算法实现&#xff08;数据结构视角&#xff09;旅行商问题&#xff08;Traveling Salesman Problem, TSP&#xff09;是组合优化领域的经典NP难问题&#xff0c;核心目标是找到一条经过所有城市且仅经过一次、最终回…

作者头像 李华