news 2026/8/25 21:21:26

Unity EasyECS 五分钟快速上手:只加一个 [ECS]

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity EasyECS 五分钟快速上手:只加一个 [ECS]

前几篇一直在讲 EasyECS 为什么存在、AoS、SoA 和 CPU Cache。

从这一篇开始,不再只讲原理。

我们直接把 EasyECS 跑起来。

如果已经有一个普通的struct,最基本的接入实际上只需要:

[ECS]

然后剩下的大量代码都由 Source Generator 自动生成。


项目地址

EasyECS 本身就是MyFramework仓库中的一个独立 Unity Package。

MyFramework 主仓库:

https://github.com/ZHOURUIH/MyFramework

EasyECS 的 Unity Package Manager 地址:

https://github.com/ZHOURUIH/MyFramework.git?path=/Packages/com.zhourui.easyecs

当前版本:

EasyECS 1.1.0

第一步:安装 EasyECS

打开 Unity:

Window → Package Manager → + → Add package from git URL

填入:

https://github.com/ZHOURUIH/MyFramework.git?path=/Packages/com.zhourui.easyecs

安装完成以后,就可以:

using EasyECS;

第二步:定义一个普通 Struct

先写一个最普通的角色数据:

public struct RoleData { public int mHP; public float mSpeed; public float mPositionX; public float mPositionY; public int mID; public int mModelID; public int mCamp; }

如果按照传统方式使用,通常就是:

List<RoleData> roles = new();

现在我们只做一个修改:

using EasyECS; [ECS] public struct RoleData { public int mHP; public float mSpeed; public float mPositionX; public float mPositionY; public int mID; public int mModelID; public int mCamp; }

增加:

[ECS]

以后,这个 Struct 就会被 EasyECS 的 Source Generator 处理。


第三步:创建 ECSList

编译完成以后,EasyECS 会根据:

RoleData

自动生成:

RoleDataECSList

所以可以直接写:

RoleDataECSList roles = new RoleDataECSList();

也可以提前指定 Capacity:

RoleDataECSList roles = new RoleDataECSList(1024);

如果大概知道数据量,我一般更建议提前给 Capacity。

原因和List<T>类似:

避免不断 Resize。


第四步:Add 数据

添加数据仍然是非常熟悉的写法:

roles.Add(new RoleData { mHP = 100, mSpeed = 5.0f, mPositionX = 10.0f, mPositionY = 20.0f, mID = 1001, mModelID = 2001, mCamp = 1, });

还可以继续:

roles.Add(new RoleData { mHP = 200, mSpeed = 6.0f, mID = 1002, });

使用体验和:

List<RoleData>.Add()

非常接近。

但底层存储已经不再是简单的:

RoleData[]

第五步:访问数据

普通List<struct>有一个经常让人觉得麻烦的地方。

例如:

RoleData role = roles[index]; role.mHP -= 10; roles[index] = role;

因为普通 struct 是值类型,取出来以后得到的是副本。

EasyECS 生成的 ECSList Indexer 返回的则是:

RoleDataRef

所以可以直接:

RoleDataRef role = roles[0]; role.mHP -= 10; role.mPositionX += role.mSpeed;

修改会直接作用到底层 Storage。

甚至可以写:

roles[0].mHP -= 10;

不需要:

RoleData temp = ... ... roles[0] = temp;

这也是 EasyECS 希望保留的使用体验之一。


[ECS]背后发生了什么?

代码中只写了:

[ECS] public struct RoleData

但 Source Generator 实际会生成一整套相关类型。

其中比较重要的包括:

RoleDataStorage RoleDataRef RoleDataECSList RoleDataECSDictionary<TKey> Direct Column

例如RoleDataECSList负责:

Add Insert RemoveAt RemoveAtSwapBack Clear Indexer Capacity Dispose

所以开发者不需要自己去维护:

int[] hp; float[] speed; float[] positionX; float[] positionY;

也不需要自己处理这些数组之间的同步。


默认情况下,字段会进入 SoA

如果 Struct 使用:

[ECS]

那么字段默认按照 ECS 字段处理。

例如:

[ECS] public struct RoleData { public int mHP; public float mSpeed; public float mPositionX; public float mPositionY; }

从数据布局上可以简单理解成:

mHP[] mSpeed[] mPositionX[] mPositionY[]

也就是前面几篇一直讲的 SoA。


不想拆开的字段怎么办?

实际项目中,并不是所有字段都需要 SoA。

例如:

HP Speed Position

可能每帧都要处理。

但:

ID ModelID Camp

可能很少参与批量计算。

那么可以写成:

[ECS] public struct RoleData { public int mHP; public float mSpeed; public float mPositionX; public float mPositionY; [NotECS] public int mID; [NotECS] public int mModelID; [NotECS] public int mCamp; }

这样:

HP / Speed / Position

走 SoA。

而:

ID / ModelID / Camp

继续保留 AoS。

这就是 EasyECS 的:

SoA + AoS Hybrid Storage。

具体规则下一篇会专门讲。


用完记得 Dispose

ECSList实现了:

IDisposable

所以生命周期结束以后需要:

roles.Dispose();

例如组件自己持有:

private RoleDataECSList mRoleList; private void Awake() { mRoleList = new RoleDataECSList(1024); } private void OnDestroy() { mRoleList?.Dispose(); }

这是因为 EasyECS 在合适的 Backend 下会使用 Native Storage。

它和普通:

List<T>

只依赖 GC 回收不是一回事。


一个最小完整例子

把前面的内容放在一起:

using EasyECS; using UnityEngine; [ECS] public struct RoleData { public int mHP; public float mSpeed; public float mPositionX; public float mPositionY; [NotECS] public int mID; } public class EasyECSTest : MonoBehaviour { private RoleDataECSList mRoles; private void Start() { mRoles = new RoleDataECSList(1024); mRoles.Add(new RoleData { mHP = 100, mSpeed = 5.0f, mID = 1001, }); RoleDataRef role = mRoles[0]; role.mHP -= 10; role.mPositionX += role.mSpeed; Debug.Log($"HP:{role.mHP},X:{role.mPositionX}"); } private void OnDestroy() { mRoles?.Dispose(); } }

到这里,其实已经完成 EasyECS 最基本的接入了。


写在最后

EasyECS 最基础的使用流程可以压缩成四步:

安装 Package ↓ struct 添加 [ECS] ↓ 使用生成的 xxxECSList ↓ 结束时 Dispose

最重要的是,开发者仍然维护:

public struct RoleData

而不是手工维护一堆:

int[] float[] float[] float[]

EasyECS 把这些重复又容易出错的工作交给 Source Generator。

不过这里马上会出现一个问题:

是不是加了[ECS]以后所有字段都必须变成 SoA?

当然不是。

下一篇:

[ECS][NotECS]不只是两个标签:EasyECS 的数据布局规则详解

我们会正式进入 EasyECS 的 Hybrid Storage,看看一个 Struct 里怎么同时存在 AoS 和 SoA。

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

电商自动化作图工作流实战:从环境搭建到批量处理避坑指南

这类工具最值得先看的不是功能列表&#xff0c;而是能不能在普通环境里稳定跑起来&#xff0c;以及它到底解决了电商作图流程里的哪个具体痛点。是批量套模板&#xff0c;还是智能生成文案&#xff0c;或者是自动调整尺寸和格式&#xff1f;很多教程一上来就列一堆名词&#xf…

作者头像 李华
网站建设 2026/8/25 21:11:47

Python列表精讲

列表推导式列表推导式的本质就是通过一个循环体来构建一个列表对象&#xff0c;分为两个部分&#xff1a;计算部分循环部分&#xff0c;for前面的是计算部分&#xff0c;for后面的是循环部分。语法格式&#xff1a;[ 计算部分 for循环部分] 计算部分&#xff1a;是一个计算表格…

作者头像 李华
网站建设 2026/8/25 21:10:27

AI应用部署实战:从模型封装到生产服务的全链路技能

这次我们来看一个对AI开发者、算法工程师和项目管理者都至关重要的话题&#xff1a;构建和部署AI应用的核心技能。这不仅仅是写几行模型推理代码&#xff0c;而是涉及从模型选择、环境适配、服务封装到运维监控的全链路能力。如果你关心如何将一个AI模型从实验阶段的Jupyter No…

作者头像 李华
网站建设 2026/8/25 21:06:04

2026届论文降重工具实测:五款软件怎么选

每年三四月&#xff0c;图书馆通宵区的灯总是亮到最晚。查重报告上那个刺眼的红色百分比&#xff0c;把无数毕业生按在电脑前一个字一个字地磨。市面上号称能"一键降重"的工具越来越多&#xff0c;但真正经得起推敲的没几个。这篇测评从实际使用出发&#xff0c;把五…

作者头像 李华