做到 EasyECS 1.1.0 的时候,我其实已经具备了继续往前走的条件。
现在已经有:
bash
ECSList
ECSDictionary
SoA / AoS Hybrid
Managed / Unmanaged Hybrid
Ref
Direct Column
Source Generator
多 Backend
生命周期检查
Benchmark
如果继续加:
bash
Entity
World
System
Query
Archetype
Scheduler
再往前走一步,它完全可以开始向一个"完整 ECS Framework"发展。
但我最后没有这么做。
甚至可以说:
这是我刻意停下来的地方。
因为 EasyECS 做到后面,我越来越确定:
它真正有价值的地方,恰恰不是成为另一个完整 ECS。
一开始,我真正想优化的只是一个 List
整个 EasyECS 最初的问题非常小。
项目里有:
bash
List<RoleData>
数据量越来越大。
然后这种代码开始变成热点:
bash
RoleData role = list[i];
role.mHP -= 1;
list[i] = role;
于是我开始思考:
如果底层不是 AoS,而是 SoA,会怎么样?
后来才有:
bash
[ECS]
↓
Source Generator
↓
SoA Storage
再后来,为了让它真正能用,又不得不加入:
bash
Hybrid Storage
Ref
ECSList
ECSDictionary
Direct Column
EasyECS 一点一点长到了现在。
但你会发现:
所有这些东西始终围绕"数据怎么存"展开。
它从来没有真正开始管理:
bash
游戏逻辑应该怎么组织
System 应该什么时候执行
Entity 应该属于哪个 World
这条边界我最后决定保留下来。
因为真实项目里,我并不想重写架构
如果是一个新项目,
当然可以在第一天就决定:
bash
Entity
+
Component
+
System
所有业务都按照 ECS 思路设计。
但现实情况往往不是这样。
很多项目已经存在:
bash
class Character
class Monster
class Skill
class Buff
class UIManager
class ResourceManager
周围可能已经有几十万行业务代码。
现在只是发现:
bash
怪物数据
Buff 数据
子弹数据
角色运行时属性
里面几个批量循环比较慢。
为了优化这些地方,
要求整个项目改成:
bash
World
System
Query
Archetype
我觉得代价太高了。
EasyECS 想解决的反而是:
能不能只换掉最慢的那部分?
所以我更喜欢这种状态
例如角色依然可以是:
bash
public class Character
{
public CharacterAI mAI;
public CharacterModel mModel;
public int mDataIndex;
}
AI、动画、表现、业务逻辑继续按照 OOP 写。
真正的大量运行时数据放进:
bash
RoleDataECSList mRoleDataList;
于是整个项目变成:
bash
Character
负责业务语义
+
EasyECS
负责热点数据
需要批量处理时:
bash
HP[]
Position[]
Speed[]
连续跑。
需要处理某个角色的复杂业务时:
bash
Character
照常使用。
我觉得这比"所有东西必须统一成一种架构"更符合实际项目。
EasyECS 最适合什么地方?
做到现在,我觉得它最适合的场景已经比较清楚了。
例如:
bash
大量角色运行时数据
怪物
子弹
Buff
碰撞数据
粒子状态
寻路节点
服务器实体状态
它们通常有几个共同特征:
bash
数量多
+
生命周期相对统一
+
存在批量循环
+
一次经常只访问少数字段
这时候:
bash
SoA
Dense Storage
Direct Column
SwapBack
都能真正发挥作用。
但这些地方我反而不会用 EasyECS
例如一个 UI 面板只有:
bash
20 条数据
每次打开才访问一次。
我不会为了它写:
bash
[ECS]
普通:
bash
List<T>
已经够好了。
再比如:
bash
配置数据
任务描述
剧情信息
少量临时数据
编辑器工具
它们根本不是性能热点。
这里强行换 EasyECS,只会增加理解成本。
还有一种数据:
bash
每次都需要完整读取整个 Struct
AoS 本身就非常合理。
EasyECS 也从来不是:
看见 struct 就加
[ECS]。
数据量小的时候,也不用急着优化
这一点我觉得尤其重要。
如果:
bash
100 条数据
一帧只跑一次,
就算某种方案理论上快:
bash
3 倍
最终可能只是:
bash
0.003 ms
→
0.001 ms
这种优化对游戏根本没有意义。
却可能让代码复杂很多。
所以我更推荐:
bash
先正常写
↓
Profiler 找热点
↓
确认是数据访问问题
↓
再考虑 EasyECS
而不是从第一天就把整个项目都变成"高性能代码"。
EasyECS 真正想优化的是热点,不是代码风格
这也是为什么前面一直有:
bash
Indexer
↓
Ref
↓
Direct Column
三层访问方式。
如果 EasyECS 只追求 Benchmark,
完全可以要求所有人:
bash
全部 Direct Column
肯定更漂亮。
但最后业务代码就会变成:
bash
hp[i]
speed[i]
positionX[i]
positionY[i]
那和手写 SoA 已经差不了多少了。
我更希望:
bash
普通代码 → 好写
热点代码 → 可以快
极端热点 → 可以直接到底层
这才是一个我愿意长期放进项目里的工具。
为什么没有 World?
因为 EasyECS 不需要知道:
这些 RoleData 属于哪个游戏世界。
为什么没有 System?
因为它不需要规定:
谁负责修改 HP。
为什么没有 Query?
因为它目前解决的不是:
如何动态组合 Component。
而是:
已经有一批数据,怎样让 CPU 更高效地处理它们。
这些看起来像"缺失的 ECS 功能"。
但对 EasyECS 来说,很多其实是:
有意不做
功能越多不一定越好。
一个框架开始承担越多职责,
它对项目架构的侵入就越深。
做到最后,我反而越来越喜欢"小框架"
以前做框架很容易有一种冲动:
bash
既然已经有 A
那就顺便做 B
有了 B
C 也不远了
最后:
bash
数据容器
↓
Entity
↓
System
↓
Scheduler
↓
World
↓
整个项目都必须围着框架转
框架越来越完整。
替换成本也越来越高。
EasyECS 这次我反而希望控制住这种冲动。
它解决:
bash
数据布局
高频访问
批量处理
就够了。
其他事情继续交给项目现有架构。
如果现在让我重新设计一次
有些东西我依然会保留。
比如:
bash
[ECS] / [NotECS]
Source Generator
Hybrid Storage
Indexer → Ref → Column
ECSList + ECSDictionary
Unsafe / Safe Backend
因为这些能力都围绕同一个目标:
让普通项目能够低成本使用更好的数据布局。
我也依然不会急着加入:
bash
World
System
Archetype
除非以后真的出现一个问题:
不做这些东西,现有数据层已经无法解决。
而不是因为:
一个叫 ECS 的框架"理论上应该有这些东西"。
这 19 篇文章最后其实只在讲一件事
从最开始:
bash
List<struct>
到:
bash
AoS / SoA
再到:
bash
Hybrid Storage
Managed / Unmanaged
Ref
Column
RemoveAtSwapBack
ECSDictionary
IL2CPP
Resize
Source Generator
看起来讲了很多东西。
但它们最终都围绕同一个问题:
如何让数据更适合 CPU,同时又不要让整个项目为性能付出过高的工程成本?
EasyECS 给出的答案不一定是唯一答案。
但至少是我目前比较喜欢的一种:
bash
业务层
继续保持熟悉的写法
↓
Source Generator
承担机械复杂度
↓
数据层
变成更适合 CPU 的布局
写在最后
EasyECS 1.1.0 做完以后,我越来越不想把它称为:
一个完整 ECS 框架。
它更准确的定位仍然是:
OOP-compatible SoA data layout optimizer for Unity
也就是:
一个尽量兼容现有 OOP 项目、专门优化数据布局的 Unity 工具。
它没有打算告诉你:
bash
游戏应该怎么写
架构应该怎么设计
所有逻辑应该放在哪里
它只想在真正的数据热点出现时回答:
这批数据,能不能摆得更适合 CPU 一点?
如果答案是可以,
那就加一个:
bash
[ECS]
然后让编译器去干那些本来需要我们手写的大量脏活。
我觉得做到这里,EasyECS 作为一个独立工具已经足够完整了。
至于未来会不会继续变化,
当然会。
但至少有一件事情我不准备改变:
框架应该帮业务解决问题,而不是让业务为了使用框架重新变成另一个样子。
项目地址
EasyECS 是 MyFramework 仓库中的独立 Unity Package。
MyFramework:
bash
https://github.com/ZHOURUIH/MyFramework
EasyECS UPM:
bash
https://github.com/ZHOURUIH/MyFramework.git?path=/Packages/com.zhourui.easyecs
EasyECS 1.1.0 系列,到这里正式收官。
:::