* refactor: 编辑器/运行时架构拆分与构建系统升级 * feat(core): 层级系统重构与UI变换矩阵修复 * refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题 * fix(physics): 修复跨包组件类引用问题 * feat: 统一运行时架构与浏览器运行支持 * feat(asset): 实现浏览器运行时资产加载系统 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误 * test: 补齐核心模块测试用例,修复CI构建配置 * fix: 修复测试用例中的类型错误和断言问题 * fix: 修复 turbo build:npm 任务的依赖顺序问题 * fix: 修复 CI 构建错误并优化构建性能
12 KiB
12 KiB
层级系统
在游戏开发中,实体间的父子层级关系是常见需求。ECS Framework 采用组件化方式管理层级关系,通过 HierarchyComponent 和 HierarchySystem 实现,完全遵循 ECS 组合原则。
设计理念
为什么不在 Entity 中内置层级?
传统的游戏对象模型(如 Unity 的 GameObject)将层级关系内置于实体中。ECS Framework 选择组件化方案的原因:
- ECS 组合原则:层级是一种"功能",应该通过组件添加,而非所有实体都具备
- 按需使用:只有需要层级关系的实体才添加
HierarchyComponent - 数据与逻辑分离:
HierarchyComponent存储数据,HierarchySystem处理逻辑 - 序列化友好:层级关系作为组件数据可以轻松序列化和反序列化
基本概念
HierarchyComponent
存储层级关系数据的组件:
import { HierarchyComponent } from '@esengine/ecs-framework';
// HierarchyComponent 的核心属性
interface HierarchyComponent {
parentId: number | null; // 父实体 ID,null 表示根实体
childIds: number[]; // 子实体 ID 列表
depth: number; // 在层级中的深度(由系统维护)
bActiveInHierarchy: boolean; // 在层级中是否激活(由系统维护)
}
HierarchySystem
处理层级逻辑的系统,提供所有层级操作的 API:
import { HierarchySystem } from '@esengine/ecs-framework';
// 获取系统
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
快速开始
添加系统到场景
import { Scene, HierarchySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
// 添加层级系统
this.addSystem(new HierarchySystem());
// 添加其他系统...
}
}
建立父子关系
// 创建实体
const parent = scene.createEntity("Parent");
const child1 = scene.createEntity("Child1");
const child2 = scene.createEntity("Child2");
// 获取层级系统
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
// 设置父子关系(自动添加 HierarchyComponent)
hierarchySystem.setParent(child1, parent);
hierarchySystem.setParent(child2, parent);
// 现在 parent 有两个子实体
查询层级
// 获取父实体
const parentEntity = hierarchySystem.getParent(child1);
// 获取所有子实体
const children = hierarchySystem.getChildren(parent);
// 获取子实体数量
const count = hierarchySystem.getChildCount(parent);
// 检查是否有子实体
const hasKids = hierarchySystem.hasChildren(parent);
// 获取在层级中的深度
const depth = hierarchySystem.getDepth(child1); // 返回 1
API 参考
父子关系操作
setParent
设置实体的父级:
// 设置父级
hierarchySystem.setParent(child, parent);
// 移动到根级(无父级)
hierarchySystem.setParent(child, null);
insertChildAt
在指定位置插入子实体:
// 在第一个位置插入
hierarchySystem.insertChildAt(parent, child, 0);
// 追加到末尾
hierarchySystem.insertChildAt(parent, child, -1);
removeChild
从父级移除子实体(子实体变为根级):
const success = hierarchySystem.removeChild(parent, child);
removeAllChildren
移除所有子实体:
hierarchySystem.removeAllChildren(parent);
层级查询
getParent / getChildren
const parent = hierarchySystem.getParent(entity);
const children = hierarchySystem.getChildren(entity);
getRoot
获取实体的根节点:
const root = hierarchySystem.getRoot(deepChild);
getRootEntities
获取所有根实体(没有父级的实体):
const roots = hierarchySystem.getRootEntities();
isAncestorOf / isDescendantOf
检查祖先/后代关系:
// grandparent -> parent -> child
const isAncestor = hierarchySystem.isAncestorOf(grandparent, child); // true
const isDescendant = hierarchySystem.isDescendantOf(child, grandparent); // true
层级遍历
findChild
根据名称查找子实体:
// 直接子级中查找
const child = hierarchySystem.findChild(parent, "ChildName");
// 递归查找所有后代
const deepChild = hierarchySystem.findChild(parent, "DeepChild", true);
findChildrenByTag
根据标签查找子实体:
// 查找直接子级
const tagged = hierarchySystem.findChildrenByTag(parent, TAG_ENEMY);
// 递归查找
const allTagged = hierarchySystem.findChildrenByTag(parent, TAG_ENEMY, true);
forEachChild
遍历子实体:
// 遍历直接子级
hierarchySystem.forEachChild(parent, (child) => {
console.log(child.name);
});
// 递归遍历所有后代
hierarchySystem.forEachChild(parent, (child) => {
console.log(child.name);
}, true);
层级状态
isActiveInHierarchy
检查实体在层级中是否激活(考虑所有祖先的激活状态):
// 如果 parent.active = false,即使 child.active = true
// isActiveInHierarchy(child) 也会返回 false
const activeInHierarchy = hierarchySystem.isActiveInHierarchy(child);
getDepth
获取实体在层级中的深度(根实体深度为 0):
const depth = hierarchySystem.getDepth(entity);
扁平化层级(用于 UI 渲染)
// 用于实现可展开/折叠的层级树视图
const expandedIds = new Set([parent.id]);
const flatNodes = hierarchySystem.flattenHierarchy(expandedIds);
// 返回 [{ entity, depth, bHasChildren, bIsExpanded }, ...]
完整示例
创建游戏角色层级
import {
Scene,
HierarchySystem,
HierarchyComponent
} from '@esengine/ecs-framework';
class GameScene extends Scene {
private hierarchySystem!: HierarchySystem;
protected initialize(): void {
// 添加层级系统
this.hierarchySystem = new HierarchySystem();
this.addSystem(this.hierarchySystem);
// 创建角色层级
this.createPlayerHierarchy();
}
private createPlayerHierarchy(): void {
// 根实体
const player = this.createEntity("Player");
player.addComponent(new Transform(0, 0));
// 身体部件
const body = this.createEntity("Body");
body.addComponent(new Sprite("body.png"));
this.hierarchySystem.setParent(body, player);
// 武器(挂载在身体上)
const weapon = this.createEntity("Weapon");
weapon.addComponent(new Sprite("sword.png"));
this.hierarchySystem.setParent(weapon, body);
// 特效(挂载在武器上)
const effect = this.createEntity("WeaponEffect");
effect.addComponent(new ParticleEmitter());
this.hierarchySystem.setParent(effect, weapon);
// 查询层级信息
console.log(`Player 层级深度: ${this.hierarchySystem.getDepth(player)}`); // 0
console.log(`Weapon 层级深度: ${this.hierarchySystem.getDepth(weapon)}`); // 2
console.log(`Effect 层级深度: ${this.hierarchySystem.getDepth(effect)}`); // 3
}
public equipNewWeapon(weaponName: string): void {
const body = this.findEntity("Body");
const oldWeapon = this.hierarchySystem.findChild(body!, "Weapon");
if (oldWeapon) {
// 移除旧武器的所有子实体
this.hierarchySystem.removeAllChildren(oldWeapon);
oldWeapon.destroy();
}
// 创建新武器
const newWeapon = this.createEntity("Weapon");
newWeapon.addComponent(new Sprite(`${weaponName}.png`));
this.hierarchySystem.setParent(newWeapon, body!);
}
}
层级变换系统
结合 Transform 组件实现层级变换:
import { EntitySystem, Matcher, HierarchySystem, HierarchyComponent } from '@esengine/ecs-framework';
class HierarchyTransformSystem extends EntitySystem {
private hierarchySystem!: HierarchySystem;
constructor() {
super(Matcher.empty().all(Transform, HierarchyComponent));
}
public onAddedToScene(): void {
// 获取层级系统引用
this.hierarchySystem = this.scene!.getEntityProcessor(HierarchySystem)!;
}
protected process(entities: readonly Entity[]): void {
// 按深度排序,确保父级先更新
const sorted = [...entities].sort((a, b) => {
return this.hierarchySystem.getDepth(a) - this.hierarchySystem.getDepth(b);
});
for (const entity of sorted) {
const transform = entity.getComponent(Transform)!;
const parent = this.hierarchySystem.getParent(entity);
if (parent) {
const parentTransform = parent.getComponent(Transform);
if (parentTransform) {
// 计算世界坐标
transform.worldX = parentTransform.worldX + transform.localX;
transform.worldY = parentTransform.worldY + transform.localY;
}
} else {
// 根实体,本地坐标即世界坐标
transform.worldX = transform.localX;
transform.worldY = transform.localY;
}
}
}
}
性能优化
缓存机制
HierarchySystem 内置了缓存机制:
depth和bActiveInHierarchy由系统自动维护- 使用
bCacheDirty标记优化更新 - 层级变化时自动标记所有子级缓存为脏
最佳实践
- 避免深层嵌套:系统限制最大深度为 32 层
- 批量操作:构建复杂层级时,尽量一次性设置好所有父子关系
- 按需添加:只有真正需要层级关系的实体才添加
HierarchyComponent - 缓存系统引用:避免每次调用都获取
HierarchySystem
// 好的做法
class MySystem extends EntitySystem {
private hierarchySystem!: HierarchySystem;
onAddedToScene() {
this.hierarchySystem = this.scene!.getEntityProcessor(HierarchySystem)!;
}
process() {
// 使用缓存的引用
const parent = this.hierarchySystem.getParent(entity);
}
}
// 避免的做法
process() {
// 每次都获取,性能较差
const system = this.scene!.getEntityProcessor(HierarchySystem);
}
迁移指南
如果你之前使用的是旧版 Entity 内置的层级 API,请参考以下迁移指南:
| 旧 API (已移除) | 新 API |
|---|---|
entity.parent |
hierarchySystem.getParent(entity) |
entity.children |
hierarchySystem.getChildren(entity) |
entity.addChild(child) |
hierarchySystem.setParent(child, entity) |
entity.removeChild(child) |
hierarchySystem.removeChild(entity, child) |
entity.findChild(name) |
hierarchySystem.findChild(entity, name) |
entity.activeInHierarchy |
hierarchySystem.isActiveInHierarchy(entity) |
迁移示例
// 旧代码
const parent = scene.createEntity("Parent");
const child = scene.createEntity("Child");
parent.addChild(child);
const found = parent.findChild("Child");
// 新代码
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
const parent = scene.createEntity("Parent");
const child = scene.createEntity("Child");
hierarchySystem.setParent(child, parent);
const found = hierarchySystem.findChild(parent, "Child");