ECS 是否被过度神话?社区的理性声音与批评 Is ECS Overhyped? The Rational Pushback
ECS 不是人人都该吃的营养品,它更像一笔要付利息的贷款——这篇帮你算清还款计划
为什么“泼冷水”是这个系列最该补的一块基石
先说结论:ECS 是个好东西,但它被讲成了万能药,这事得有人出来说两句。
这两年我看过二十来份写着“我们打算上 ECS”的立项文档,真正跑起来之后还觉得值的,不到一半。剩下那一半不是 ECS 错了,是他们买的其实是一张叫做“高性能”的期货,到期发现兑付的是“架构纪律”四个字——这玩意儿不能当饭吃。
前面二十四篇,我们一直在教你怎么把 Flecs 用得更好:怎么设计组件、怎么排 Phase、怎么调查询、怎么接渲染和物理。那些都是“买了之后怎么用”。这一篇反过来,先算“该不该买”。就像装修,别人给你看的是效果图,我给你看的是水电改造图和增项单——不好看,但能救你的钱包。
我把社区里真实的批评声音分成四层来拆:第一层讲 ECS 把复杂度搬到了哪儿,第二层讲 Archetype 那张看不见的账单,第三层讲可读性怎么塌的,第四层给你一把尺子,量一量你到底需不需要它。最后给一张自评表和五个上线后要盯的指标,你可以直接拿去跟合伙人吵。
态度先摆在这儿:ECS 是这十年游戏架构里最好的东西之一,但它对绝大多数项目来说是“抗生素”,不是“维生素”。没事别乱吃。
第一层:ECS 没有消灭复杂度,它把复杂度从运行时搬到了架构层
宣传口径里,ECS 的卖点是“更快”。但你要真跟一个上了 ECS 的项目组聊半小时,他们会告诉你真正的变化不是帧率,是“我再也说不清这行数据是谁改的”。
先想想 OOP 帮你兜了什么底。你有一个玩家类,碰撞、移动、血量、动画状态都在这一个文件里。这带来三件很朴素的好事:一是局部推理,我改这个字段,影响范围就在这个文件里,翻两屏就看完;二是写入归属明确,谁改的血量,代码里写着呢;三是调试栈有意义,崩了看调用栈,一眼知道是哪儿炸的,还是“某个地方把速度写成了非法值”。
换成 ECS 之后,这三件事全部被收回去,重新发给你,但没配说明书。数据是公开的,任何系统都能写;行为散在系统里,一个“跳跃”可能涉及输入系统、状态机系统、物理系统和动画系统;栈信息会告诉你“某个系统在这一帧遍历了四千个实体然后出事了”,但它不会告诉你是谁把速度字段写成了 NaN。
我给你讲个我自己翻过的车。早年做一个小平台游戏,玩家偶尔会瞬移半个屏幕。查了两天,最后发现是动画系统的根位移在某个受击状态下还在写位置,而位移能力系统也在写位置,两个 Phase 顺序一颠倒就打架。OOP 时代这种 bug 我一个下午能抓三个,因为两个写入点就在同一个文件里上下相邻。在 ECS 里,它们是两个互不相识的系统,中间隔着一个执行顺序。
这不是理论洁癖,是真实的工程成本。Unity DOTS 项目里我见过最常见的土办法,是给关键组件加一个“谁最后改的”标记字段——用最笨的方式把 OOP 的局部性花钱买回来。这说明什么?说明 ECS 的架构红利,是靠“写入路径可约束”换来的。
什么时候这笔账划算?当你能用架构把写入路径锁死:所有改 Transform 的必须走同一个 Phase、所有改血量的必须通过同一处入口、所有跨系统写数据必须有明确的所有权约定。这在 20 人的团队里靠 code review 能撑住,在 3 个人的团队里靠记性——而记性,兄弟,是最不可靠的中间件。
所以第一层批评的准确表述不是“ECS 不好”,而是:ECS 把“谁能改这份数据”从语法问题降级成了纪律问题。语法错误编译器会拦,纪律错误只有上线后玩家会拦。
第二层:Archetype 的隐性账单——结构变更从来不是免费的
要理解这一层,得先把 Archetype 讲成人话。
Flecs 这类框架把实体按“组件组合”分堆存放。同样挂着“位置、速度、碰撞体”的实体,住在同一张表里,内存挨着内存。这样遍历的时候 CPU 缓存吃得饱,速度快,这就是 ECS 的性能根基。听起来很美,代价藏在后半句:一旦某个实体的组件组合变了,它就得从这张表搬到另一张表。
搬家意味着什么?把这个实体身上所有组件的数据拷一份,从旧表删掉,插进新表,顺手把所有指向它的引用(关系、句柄、缓存索引)全部修正。单次成本不大,量级上是几十到几百纳秒,听着跟白送一样。问题是你不会只搬一次。
我拿一个具体的场景给你感受下。塔防或者建造类游戏,玩家给一座塔装了一个“燃烧”词条,如果你把词条实现成“给这个实体加一个燃烧组件”,那么玩家每一次升级、每一次换装备、每一次 buff 上下、每一次状态切换,都是一次搬家。假设场上有 3000 个会动的单位,每个单位每帧状态变化 2 次——这数字在塔防里非常正常——你每帧就是 6000 次结构变更。几毫秒的预算,就这么被“架构”花掉了,而你的帧预算是 16.6 毫秒。
更阴的是碎片化。玩家自由组合,200 种建筑搭配就是 200 种组件组合,也就是 200 张表,每张表里可能只有三五个实体。你以为你在用缓存友好的数据布局,实际上你在用 200 个巴掌大的小表格,遍历时缓存疯狂失效,性能反而不如朴素数组。这是典型的“用了 ECS 反而更慢”,而且它不会报错,只会安静地吃掉你的帧时间。
正确的做法其实很朴素,一句口诀:高频变化的东西做成数据,低频变化的东西才做成组件。词条、buff、状态,这些每帧都在变的,塞进一个已有的组件内部当数组或数值;只有那些“一局游戏发生不到一百次”的东西,才值得给它开一个新组件。判断线就是这个数量级:一百次以内,加组件;每帧都在变,塞进去。
这里必须请出一位重量级反面教材:《城市:天际线 2》。这游戏 2023 年发售,技术宣传时反复强调“模拟规模上了一个量级”——每个市民、每辆车、每栋楼都是被真实模拟的对象,城市人口能干到百万级。底座就是 Unity 的 DOTS/ECS。结果呢?发售当天性能问题直接引爆,Steam 评价一片哀嚎,主机版一路延期,官方连着发了好几篇性能说明,开发者日志写得跟检讨书似的。
但请你别急着说“ECS 不行”。CS2 的教训恰恰更精确:ECS 给你的是一块更大的天花板,它不保证你能摸到,而且它让你更难知道自己在第几层。当你的帧时间被摊到两百个系统里,Profiler 里的火焰图会变成一台地震仪——你看得出它很吵,你看不出是谁在砸墙。后来他们靠 LOD 模拟(远处的市民降低模拟频率)、批量更新、把一部分模拟挪出主循环,一点点把口碑捞回来,代价是两年。
这件事跟我们上一篇聊的学习曲线是同一个病根的两种症状:ECS 的失败案例,九成不是“架构选错了”,而是“选对了架构,但没配套的成本意识”。
第三层:可读性塌方——从“按功能找代码”到“按数据找代码”
这一层最容易被技术出身的同事忽略,因为对他们来说,“数据在哪我知道啊”。但对一个刚入职三天的策划、一个外包美术、一个三个月后的你自己来说,这是要命的。
OOP 的代码组织逻辑是“按功能分文件”。新人要改跳跃,打开玩家控制器,搜一下跳跃,五分钟上手。ECS 的组织逻辑是“按数据分文件”。你要改跳跃,得先知道跳跃涉及哪几个组件,然后在六个系统里翻——输入系统、状态系统、能力系统、物理系统、动画系统、音效系统。而这些系统分散在五个不同目录里,命名风格还各不相同。
更难的是隐式调用链。A 系统的输出是 B 系统的输入,但代码里没有任何一行字写着这层依赖,它靠的是执行顺序。你把 A 挪到 B 后面,功能就废了,而且通常不是立刻废,是三个人天之后某个特定组合下才废。
《守望先锋》是这一层最好的案例,因为他们在最风光的时候自己把话说开了。2017 年 GDC 上,暴雪的工程师讲他们那套自研 ECS 和网络架构,全场掌声。但同一场分享里他们也讲了代价:ECS 让“设计师和新人理解代码”变难,所以他们专门投入做了可视化调试工具和网络面板来补偿;ECS 还强制了一套严格到近乎教条的架构纪律,这对一个上百人的团队是收益——因为纪律可以被工具检查;但对一个三个人的团队,就是纯成本。
还有一个细节特别值得品:守望先锋为什么要上 ECS?不是因为他们嫌帧率不够。他们最核心的动机是网络同步的确定性——状态是纯粹的数据,快照和回滚都变得干净,不用到处处理“这个对象里藏了个回调”这种恶心事。换句话讲,他们是先有一个必须解决的问题,然后发现 ECS 正好是解这个问题的工具。
这个因果顺序,很多人搞反了。他们先决定用 ECS,然后回头找理由,最后找到的理由通常是“性能”。而性能,往往是 ECS 收益列表里最不该排第一的那一项。
那怎么缓解可读性?三个手段,都得上。第一,命名要有所有权意识,组件名带上语义前缀,让“这个数据归谁管”写在名字里。第二,把系统依赖显式化,Flecs 的调度里可以声明依赖关系,别全靠 Phase 顺序蒙。第三,也是最容易被跳过的——把运行时可视化当成必备工具,不是可选项。Flecs Explorer 这类工具能在运行时看实体、看系统、看表结构,这是你唯一的“现场勘查”手段。不装它,你就是闭着眼睛修水管。
说到底,ECS 的可读性不是写出来的,是命名规范和工具链一点点攒出来的。你省掉这部分投入,省下的钱会在三个月后的 bug 上翻十倍还回去。
第四层:规模判定——先把“我不需要 ECS”这句话说出口
前面三层讲的是“已经用上了的人会痛在哪”。这一层讲的是“还没上的人该不该上”。我给一把尺子,五个维度,你对着自己的项目量一量。
维度一:活跃实体峰值。同屏或同一世界里同时活跃、需要每帧处理的实体数量。五百以下,老实说,继承加对象池能撑到你游戏做完;五百到五千,看情况,看的是下面几个维度;五千以上,ECS 开始明显划算;五万以上,基本没什么好犹豫的。
维度二:实体类型的组合爆炸程度。五种敌人加一个玩家,用继承树画出来整整齐齐,很舒服。但如果你是 Roguelike,200 件装备 × 80 个词条 × 30 种状态,任何一种排列组合都得能跑通,那继承树就变成了一棵疯长的树,你每天的工作是给树剪枝。这种时候 ECS 的“组合优于继承”不是理念,是止血。
维度三:横切关注点的数量。AI、物理、渲染、音效、特效、UI、存档、网络,这八个东西都要“扫一遍所有实体”,那 ECS 的查询确实爽。但如果你的游戏只有一个玩家加几个敌人,每个系统都是“直接对那个对象做点啥”,ECS 的查询就是在给一颗螺丝配一台车床。
维度四:团队规模和项目寿命。人越多、活得越久,架构纪律的收益越大,因为 ECS 的规矩是“可以被工具和 review 检查的”,而 OOP 的规矩依赖每个人的自觉。反过来,一个人的三个月 jam 项目,ECS 的架构税可能吃掉你 20% 的时间,而这 20% 本来该拿去调手感。
维度五:你的核心难点在哪。这是最容易被忽略的一条。如果你的游戏赢在叙事、美术、氛围、关卡设计,ECS 在这些方面帮不了你一点忙。它只管“很多个东西同时被处理”这件事。
| 游戏类型 | ECS 适配度 | 真实理由 |
|---|---|---|
| 弹幕射击 / 大规模 RTS | 极高 | 实体动辄上万,每帧全员遍历,结构变化少 |
| 模拟经营 / 建造沙盒 | 高,但有陷阱 | 实体多、组合多,但部件增减频繁,最容易踩碎片化 |
| ARPG / 刷宝 Roguelike | 中高 | 词条与状态组合爆炸,但建议用数据而非组件承载 |
| 平台跳跃 / 动作小品 | 低 | 实体数少,手感调优占大头,架构税换不来收益 |
| 卡牌 / 回合制策略 | 低到中 | 难点在规则结算顺序和效果栈,ECS 帮不上,反而把“一张牌”拆碎 |
| 视觉小说 / 点击解谜 | 极低 | 用 ECS 等于用叉子喝汤 |
我还想专门说一句卡牌。很多人觉得卡牌“数据驱动、效果组合”,天然适合 ECS。我不同意。卡牌的核心难点是结算顺序、时机插入、效果嵌套——这是一个规则引擎问题,不是一个实体遍历问题。你在 ECS 里做卡牌,最后会发现“一张牌”被拆成六个组件,而你要读的其实是那六行加起来的语义。数据驱动不等于 ECS,这是两件事,别混。
参数化工具:一张适配度自评表,加五个上线后要盯的指标
光说“看情况”是废话。我给你一张能打分的表,六项,每项 0 到 3 分,拿去跟团队当众吵一架,比什么都管用。
| 维度 | 0 分 | 1 分 | 2 分 | 3 分 |
|---|---|---|---|---|
| 活跃实体峰值 | <200 | 200–1000 | 1000–10000 | >10000 |
| 实体组合类型数 | <10 种 | 10–50 | 50–200 | >200 |
| 每帧横切系统数 | ≤2 | 3–4 | 5–7 | ≥8 |
| 结构变更频率(反向) | 几乎每帧变 | 频繁 | 偶尔 | 一局内极少变 |
| 团队规模与寿命 | 1–2 人 / <6 个月 | 2–4 人 | 5–10 人 / >1 年 | >10 人 / 长线运营 |
| 是否存在必须解决的问题 | 纯粹想变快 | 有点好奇 | 有明确瓶颈 | 网络同步 / 回放 / 大规模模拟等硬需求 |
总分 18。我的建议区间是:6 分以下,别上,真的,你会后悔;6 到 11 分,可以用,但要准备好走“先朴素实现、后局部迁移”的路线;12 分以上,ECS 基本是稳的,可以放心把架构压上去。
但注意最后一项的权重:它经常是一个人决定项目要不要上 ECS 的真实原因,而它给的分数却只占 3 分。这就是为什么我把它单独拎出来讲——如果你的最后一项是 0 分或 1 分,那么哪怕总分上了 12,我也会建议你再想想。
已经上了的人,别急着走,这一张表是给你的。上线之后,这五个指标该被盯住,我给了健康区间,越界了就该动手了:
| 指标 | 怎么算 | 健康区间 | 越界说明什么 |
|---|---|---|---|
| 每帧结构变更率 | 每帧组件增删次数 ÷ 活跃实体数 | <1% | 你在拿“加组件”当状态机用 |
| 表膨胀比 | Archetype 数量 ÷ 组件类型数 | <8 | 碎片化已经开始,缓存不友好了 |
| 单帧系统数 | 一个 Phase 里跑的系统个数 | <60 | 执行顺序成了玄学,该分 Phase 分组了 |
| 平均每实体组件数 | 组件总数 ÷ 实体数 | 5–12 | 低于 5 说明你拆太碎,高于 12 说明实体在变成上帝对象 |
| 每帧查询重建次数 | 因结构变化而被重新构建的查询数 | 接近 0 | 你每次重构都在给全局交税 |
一个反直觉的提醒:“每帧结构变更率”这个指标,我见过所有翻车的项目里至少有七个是超标的,而他们自己都觉得没问题——因为没人测。这个数字不统计就永远健康。
双路径对照:十分钟速览版,和一次“重来我该怎么排”的推演版
给赶时间的你:三句话版
第一句,先问“我的瓶颈是不是实体规模”,如果不是,别用 ECS,用组合和对象池就够了。
第二句,如果用了,所有每帧都在变的东西塞进组件内部当数据,只有一局游戏变不到一百次的东西才值得开新组件。
第三句,装个运行时可视化工具,不然你就是在黑屋子里修水管,还觉得是自己手艺不行。
给已经陷进去的你:如果重来一次,我会这么排
先写一个最朴素的版本,不需要任何架构——一堆数组、一个循环、该 new 就 new,把玩法跑通,把帧时间分布量出来。这一步大概两三天,能救你两个月。
然后看分布。如果“遍历所有实体”这一项占了 30% 以上,那才轮到 ECS 出场。不到 30%,你的瓶颈在别处,去优化别处。
真上了,按系统切,别一次重写。先迁一两个“实体多、逻辑简单、结构稳定”的系统过去——比如特效、比如粒子、比如单位寻路。那种“手感调优要到死”的战斗核心,先留在外面,别动它。
最后,永远留一条逃生通道:渲染层和 UI 层不要跟 ECS 绑死。这两层是变更最频繁的,绑死了之后你会为了换一个 UI 方案而痛苦一整周。架构的价值在于它允许你反悔,不在于它设计得多漂亮。
顺便补个反例给自己打脸。前面我说“小团队别上 ECS”,但《Tiny Glade》就打了我的脸——两个人做的程序化造城堡小游戏,Rust 加 Bevy,ECS 用得挺开心。为什么他们行?因为他们的数据形状太适合了:一堵墙、一扇窗、一段路,每个东西都带二三十个可调参数,玩家一直改,批量更新和查询顺手得不得了。而且他们自己写了渲染,没被框架的渲染层拖着走。
所以“团队小”从来不是绝对门槛,数据形状才是。这句话我希望你记住:判断该不该上 ECS,不要问“我的团队多大”,要问“我的数据长得像不像一堆表格”。
争议与常见坑:五句最常听到的抬杠,和我的回答
抬杠一:“我实测快十倍。”通常你测的对照组,是一个写得不太讲究的 OOP 实现——基类加虚函数,对象在堆上散得到处都是,指针追来追去。那不是 OOP 的上限,那是 OOP 的下限。拿合理设计的、数据紧凑的结构体数组去比,差距常常缩到两倍以内。ECS 真赢的地方是“规模上去之后依然稳定”,不是“小规模下快十倍”。
抬杠二:“ECS 自带多线程。”没有。Flecs 的调度系统是给你并行执行的机会,它帮你排顺序、帮你隔离写冲突,但数据依赖是你自己理的。你要是每两个系统都抢着写同一个组件,那排出来的时间线跟单线程没区别。我见过把 ECS 用成单线程的项目,不在少数。
抬杠三:“先上 ECS,规模大了不慌。”说句实话,你大概率到不了那个规模。你更可能死在玩法不够有趣上。为了三年后可能到来的性能问题,支付今天的开发速度,这是典型的本末倒置。
抬杠四:“用 ECS 但写得跟 OOP 一样,不行。”这话对,但说它的人经常自己也在犯。一个系统里塞八百行,什么逻辑都往里装;或者把行为逻辑做成函数式的东西挂在组件上——你是买了辆跑车,然后挂一档开了三年。ECS 的收益来自“数据密集、行为按阶段切分”,你不按这个来,它就是一堆更难用的结构体。
抬杠五:“ECS 能让策划自己改数值和逻辑。”不会。除非你专门做了工具链——编辑器、脚本层、声明式配置。Flecs 提供了脚本化这条路,但那是另一摊工程,不是上了 ECS 就自动送的。我见过太多项目信了这句话,最后策划还是抱着 Excel 找程序导表。
最该警惕的一句自我欺骗:“我们用 ECS 是为了将来的扩展性。”这句话在立项文档里出现超过一次,就该有人举手问:那你能不能先说出三个具体的、现在就需要扩展的场景?说不出来,就是你在为一个幻觉支付真实的成本。
FAQ
Q1:我的游戏同屏就一两百个实体,用 Flecs 是不是纯浪费?
不算浪费,但你得知道自己买的是什么。这个规模下你从 ECS 拿到的性能收益基本为零,你真正买到的是“逻辑拆得干净、加新功能不动老代码”的组织方式。如果你正好在为“加一个新怪要改五个文件”发愁,那这个钱花得值;如果你只是觉得 ECS 很酷,那这三周你会很想退货。
Q2:用了之后发现调试变难了,有解吗,还是只能认?
有解,但要花力气。三件事:给关键数据标出写入归属(哪怕只是个调试标记);把系统依赖显式声明出来,别靠 Phase 顺序蒙;把运行时可视化当必备工具装上。做完这三件,调试体验能回到 OOP 的八成——注意,是八成,剩下两成是 ECS 的固有成本,认了。
Q3:Unity 用户,DOTS 值得上吗?
先别问值不值,先问你的项目有没有“必须在这个主机上跑到这个帧率、而这个帧率靠传统方案做不到”的硬指标。有,就上,但要准备好吃它的生态代价:调试工具链、第三方插件、招聘难度,全都比传统路线差一截。没有硬指标,就用最省事的方案,把省下来的时间去打磨那件真正决定生死的事——好不好玩。城市:天际线 2 已经把“上了 DOTS 也不保证性能”这个教训演给你看了,不用自己再演一遍。
Q4:我用了 Flecs,但发现有一半系统里其实只有一个实体(比如玩家),正常吗?
太正常了,而且这恰恰说明你该反思。如果你的核心玩法只围绕一个或少数几个实体,那 ECS 的“批量遍历”优势你根本没吃到——你吃的是它的组织方式。这种情况下,把玩家相关的逻辑从“一堆系统”合并回“两三个大系统”往往更清晰,别为了架构纯洁性把自己绕进去。
Q5:怎么判断我是不是“为了 ECS 而 ECS”?
给你一个自测题:假设现在有人告诉你,用最朴素的方式也能做出同样的效果、只是代码丑一点,你会不会觉得难受?如果答案是“会”,那你就是在为审美买单,不是在为产品买单。审美不是罪,但你要清楚自己在为什么付钱——因为账单最后是项目出,不是架构出。
最后一个问题留给你自己:如果三年后回头看这个决定,你希望自己当时是因为“算清楚了”选了 ECS,还是因为“大家都在用”选了它?这两个理由在开工第一天长得一模一样,在发版那天差得很远。