开源项目
bit-ec:不想上重型 ECS?试试这个 Cocos 专用轻量 EC
面向 Cocos Creator 的轻量实体组件框架,支持组件更新顺序控制和多世界隔离,适合中小型项目在不引入重型 ECS 的情况下做数据与逻辑分离。
bit-ec:不想上重型 ECS?试试这个 Cocos 专用轻量 EC
它解决什么问题
bit-ecs 那种稀疏集合加密集数组的重型 ECS,是为了应付大批量实体高频增删设计的,但不是每个项目都有这个量级的需求。中小型项目里更常见的诉求其实是”数据和逻辑分开写,组件之间的更新顺序能精确控制,最好还能配个可视化编辑器直接拖属性”,上一套完整 ECS 有点杀鸡用牛刀。
bit-ec 就是为这类场景做的:一个专为 Cocos Creator 优化的轻量实体组件框架,组件区分”数据组件”和”逻辑组件”,更新顺序你说了算,还支持多个世界(比如同时存在的多个战斗场景)互不干扰。
安装
bit-event 是 peer 依赖:
npm install @gongxh/bit-ec @gongxh/bit-event
核心用法
定义组件
组件继承 Component,用装饰器注册,属性装饰器支持的类型比较全,包括 Cocos 常用类型(spriteframe、prefab、vec3、color 等),方便配套编辑器直接可视化配置:
import { Component, _ecdecorator } from '@gongxh/bit-ec';
const { ecclass, ecprop } = _ecdecorator;
enum ComponentType {
Health,
Movable,
}
@ecdecorator.ecclass('Health', ComponentType.Health)
class HealthComponent extends Component {
@ecprop({ type: 'int' }) maxHp: number = 100;
@ecprop({ type: 'int' }) curHp: number = 100;
}
组件生命周期
class MoveComponent extends Component {
onAdd(): void {
// 组件被添加到实体的那一刻
}
onEnter(): void {
// 实体正式进入场景,这时候可以安全地拿同实体的其他组件
const health = this.getComponent<HealthComponent>(ComponentType.Health);
}
update(dt: number): void {
// 需要先把 needUpdate 设为 true 才会被调用
}
onRemove(): void {
// 组件从实体移除
}
}
onAdd 和 onEnter 分开,是因为组件刚被添加时,同实体上的其他组件可能还没就位,这时候去 getComponent 拿别的组件容易拿到 undefined。等到 onEnter 触发,实体上该有的组件都已经齐了,这时候拿组件互相引用才靠得住。
创建世界和实体
import { ECManager } from '@gongxh/bit-ec';
ECManager.init(); // 全局只调用一次
// componentUpdateOrderList 决定了各组件类型 update 的执行先后顺序
const world = ECManager.createECWorld('battle-1', battleNode, [
ComponentType.Movable,
ComponentType.Health,
], 300, 10);
const entity = ECManager.createEntity('battle-1', 'Monster_01');
// 每帧
world.update(dt);
多个 createECWorld 可以同时存在,比如 PVP 场景里两个互相独立的小战场,各自的实体、组件互不干扰,一个世界 update 出问题不会影响另一个。
组件更新顺序为什么要精确控制
游戏逻辑里经常有依赖顺序的更新:移动组件先算完新位置,碰撞组件再基于新位置判断是否命中,血量组件最后根据命中结果扣血。如果更新顺序是随意的(比如按组件添加顺序或者哈希顺序),同样的代码在不同时候跑出来的行为可能不一致,调试起来很痛苦。
createECWorld 的 componentUpdateOrderList 参数就是让你显式声明这个顺序,框架严格按这个顺序跑每种组件类型的 update,行为可预测。
避坑提醒
- 别在
onAdd()里调用getComponent()去拿同实体的其他组件,大概率拿不到,要拿组件互相引用的逻辑放到onEnter()里。 update(dt)默认不会被调用,组件需要显式把needUpdate设为true,纯数据组件不需要每帧更新的话别设,省一点遍历开销。- 多世界场景下,
createEntity第一个参数是世界名,同一个实体配置在不同世界里创建出来的是完全独立的两份实例,互相不共享状态。
项目信息
- GitHub: https://github.com/gongxh0901/bit-framework/tree/main/bit-ec
- npm: @gongxh/bit-ec
- 可视化编辑器: Cocos Store - kunpocc-ec(付费)
- 许可证: MIT License
组件之间跨系统通信、或者要通知 UI 层刷新,配合 bit-event 一起用最顺手,这也是它唯一的 peer 依赖。