diff --git a/README.md b/README.md index 112c4e97..fc0864c0 100644 --- a/README.md +++ b/README.md @@ -3,53 +3,45 @@ [![npm version](https://badge.fury.io/js/%40esengine%2Fecs-framework.svg)](https://badge.fury.io/js/%40esengine%2Fecs-framework) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -一个专业级的 TypeScript ECS(Entity-Component-System)框架,采用现代化架构设计,专为高性能游戏开发打造。 +TypeScript ECS (Entity-Component-System) 框架,专为游戏开发设计。 -## ✨ 核心特性 +> 🤔 **什么是 ECS?** 不熟悉 ECS 架构?建议先阅读 [ECS 架构基础](docs/concepts-explained.md#ecs-架构基础) 了解核心概念 -- 🏗️ **现代化 ECS 架构** - 完整的实体组件系统,提供清晰的代码结构 -- 📡 **类型安全事件系统** - 增强的事件总线,支持异步事件、优先级、批处理和装饰器 -- ⏰ **定时器管理系统** - 完整的定时器管理,支持延迟和重复任务 -- 🔍 **智能查询系统** - 支持复杂的实体查询,流式API设计 -- ⚡ **高性能优化** - 组件索引、Archetype系统、脏标记机制三重优化 -- 🛠️ **开发者友好** - 完整的TypeScript支持,丰富的调试工具 -- 📦 **轻量级设计** - 最小化依赖,适用于各种游戏引擎 +## 特性 -## 📦 安装 +- 🔧 **完整的 TypeScript 支持** - 强类型检查和代码提示 +- 📡 **[类型安全事件系统](docs/concepts-explained.md#事件系统)** - 事件装饰器和异步事件处理 +- 🔍 **[查询系统](docs/concepts-explained.md#实体管理)** - 流式 API 和智能缓存 +- ⚡ **[性能优化](docs/concepts-explained.md#性能优化技术)** - 组件索引、Archetype 系统、脏标记 +- 🎯 **[实体管理器](docs/concepts-explained.md#实体管理)** - 统一的实体生命周期管理 +- 🧰 **调试工具** - 内置性能监控和调试信息 + +> 📖 **不熟悉这些概念?** 查看我们的 [技术概念详解](docs/concepts-explained.md) 了解它们的作用和应用场景 + +## 安装 ```bash npm install @esengine/ecs-framework ``` -## 🚀 快速开始 +## 快速开始 -### 1. 基础设置 +### 基础设置 ```typescript -import { Core, CoreEvents, Scene } from '@esengine/ecs-framework'; +import { Core, Scene, Entity, Component, EntitySystem } from '@esengine/ecs-framework'; -// 创建 Core 实例 -const core = Core.create(true); // 开启调试模式 +// 创建核心实例 +const core = Core.create(true); // 调试模式 // 创建场景 -class GameScene extends Scene { - public initialize() { - // 场景初始化逻辑 - } -} - -// 在游戏循环中更新框架 -function gameLoop() { - Core.emitter.emit(CoreEvents.frameUpdated); -} +const scene = new Scene(); +Core.scene = scene; ``` -### 2. 创建实体和组件 +### 定义组件 ```typescript -import { Component, Entity } from '@esengine/ecs-framework'; - -// 定义组件 class PositionComponent extends Component { constructor(public x: number = 0, public y: number = 0) { super(); @@ -62,18 +54,37 @@ class VelocityComponent extends Component { } } -// 创建实体 -const entity = scene.createEntity("Player"); -entity.addComponent(new PositionComponent(100, 100)); -entity.addComponent(new VelocityComponent(10, 0)); +class HealthComponent extends Component { + constructor( + public maxHealth: number = 100, + public currentHealth: number = 100 + ) { + super(); + } +} ``` -### 3. 创建处理系统 +### 创建实体 ```typescript -import { EntitySystem } from '@esengine/ecs-framework'; +// 基础实体创建 +const player = scene.createEntity("Player"); +player.addComponent(new PositionComponent(100, 100)); +player.addComponent(new VelocityComponent(5, 0)); +player.addComponent(new HealthComponent(100, 100)); +// 批量创建实体 +const enemies = scene.createEntities(50, "Enemy"); +``` + +### 创建系统 + +```typescript class MovementSystem extends EntitySystem { + constructor() { + super(); + } + public process(entities: Entity[]) { for (const entity of entities) { const position = entity.getComponent(PositionComponent); @@ -91,236 +102,297 @@ class MovementSystem extends EntitySystem { scene.addEntityProcessor(new MovementSystem()); ``` -### 4. 实体查询和管理 +### 游戏循环 + +```typescript +function gameLoop() { + // 更新场景 + scene.update(); + + // 发送帧更新事件 + Core.emitter.emit(CoreEvents.frameUpdated); + + requestAnimationFrame(gameLoop); +} + +gameLoop(); +``` + +## 实体管理器 + +EntityManager 提供了统一的实体管理接口: ```typescript import { EntityManager } from '@esengine/ecs-framework'; -// 使用EntityManager进行高级查询 const entityManager = new EntityManager(); -// 查询具有特定组件的实体 -const movingEntities = entityManager +// 流式查询 API +const results = entityManager .query() .withAll(PositionComponent, VelocityComponent) + .withNone(HealthComponent) + .withTag(1) .execute(); -// 查询带标签的实体 -const enemies = entityManager.getEntitiesByTag(1); +// 批量操作(使用Scene的方法) +const bullets = scene.createEntities(100, "bullet"); -// 批量创建实体 -const bullets = entityManager.createEntities(100, "bullet"); +// 按标签查询 +const enemies = entityManager.getEntitiesByTag(2); ``` -### 5. 事件系统 +## 事件系统 + +### [基础事件](docs/concepts-explained.md#类型安全事件) + +类型安全的事件系统,编译时检查事件名和数据类型。 ```typescript -import { EventBus, ECSEventType, EventHandler } from '@esengine/ecs-framework'; +import { EventBus, ECSEventType } from '@esengine/ecs-framework'; -// 获取事件总线 const eventBus = entityManager.eventBus; -// 监听实体创建事件 +// 监听预定义事件 eventBus.onEntityCreated((data) => { - console.log(`Entity created: ${data.entityName}`); + console.log(`实体创建: ${data.entityName}`); }); -// 监听组件添加事件 eventBus.onComponentAdded((data) => { - console.log(`Component ${data.componentType} added to entity ${data.entityId}`); + console.log(`组件添加: ${data.componentType}`); }); -// 使用装饰器自动注册事件监听器 -class GameManager { - @EventHandler(ECSEventType.ENTITY_DESTROYED) - onEntityDestroyed(data) { - console.log('Entity destroyed:', data.entityName); - } -} - // 自定义事件 -eventBus.emit('player:levelup', { playerId: 123, newLevel: 5 }); +eventBus.emit('player:death', { playerId: 123, reason: 'fall' }); ``` -## 🆚 框架对比 +### [事件装饰器](docs/concepts-explained.md#事件装饰器) -与其他 TypeScript ECS 框架相比,我们的优势: - -| 特性 | @esengine/ecs-framework | bitecs | ecsy | Miniplex | -|------|-------------------------|-------|------|----------| -| **TypeScript 支持** | ✅ 原生支持 | ✅ 完整支持 | ⚠️ 部分支持 | ✅ 原生支持 | -| **事件系统** | ✅ 类型安全+装饰器 | ❌ 无内置事件系统 | ⚠️ 基础事件 | ✅ 响应式事件 | -| **查询系统** | ✅ 智能查询+流式API | ✅ 高性能 | ✅ 基础查询 | ✅ 响应式查询 | -| **性能优化** | ✅ 多层优化系统 | ✅ 高性能优化 | ⚠️ 基础优化 | ✅ React集成优化 | -| **实体管理器** | ✅ 统一管理接口 | ❌ 无统一接口 | ✅ 基础管理 | ✅ 响应式管理 | -| **组件索引** | ✅ 哈希+位图索引 | ✅ 原生支持 | ❌ 无索引系统 | ✅ 自动索引 | -| **Archetype系统** | ✅ 内置支持 | ✅ 内置支持 | ❌ 无Archetype | ❌ 无Archetype | -| **脏标记系统** | ✅ 细粒度追踪 | ⚠️ 基础支持 | ❌ 无脏标记 | ✅ React级追踪 | -| **批量操作** | ✅ 全面的批量API | ✅ 批量支持 | ⚠️ 有限支持 | ⚠️ 有限支持 | -| **游戏引擎集成** | ✅ 通用设计 | ✅ 通用设计 | ✅ 通用设计 | ⚠️ 主要针对React | -| **学习曲线** | 🟢 中等 | 🟡 较陡峭 | 🟢 简单 | 🟡 需要React知识 | -| **社区生态** | 🟡 成长中 | 🟢 活跃 | 🟡 稳定 | 🟡 小众但精品 | - -### 为什么选择我们? - -**相比 bitecs**: -- 更友好的 TypeScript API,无需手动管理内存 -- 完整的实体管理器,开发体验更佳 -- 内置类型安全事件系统,bitecs需要自己实现 -- 多种索引系统可选,适应不同场景 - -**相比 ecsy**: -- 现代化的性能优化系统(组件索引、Archetype、脏标记) -- 更完整的 TypeScript 类型定义 -- 增强的事件系统,支持装饰器和异步事件 -- 活跃的维护和功能更新 - -**相比 Miniplex**: -- 不依赖 React 生态,可用于任何游戏引擎 -- 专门针对游戏开发优化 -- 更轻量级的核心设计 -- 传统事件模式,更适合游戏开发习惯 - -## 📚 核心概念 - -### Entity(实体) -实体是游戏世界中的基本对象,可以挂载组件和运行系统。 +使用装饰器语法自动注册事件监听器,减少样板代码。 ```typescript -// 创建实体 -const entity = scene.createEntity("Player"); +import { EventHandler, ECSEventType } from '@esengine/ecs-framework'; -// 设置实体属性 -entity.tag = 1; -entity.updateOrder = 0; -entity.enabled = true; - -// 批量创建实体 -const entities = scene.createEntities(100, "Enemy"); -``` - -### Component(组件) -组件存储数据,定义实体的属性和状态。 - -```typescript -import { Component } from '@esengine/ecs-framework'; - -class HealthComponent extends Component { - public maxHealth: number = 100; - public currentHealth: number = 100; - - public takeDamage(damage: number) { - this.currentHealth = Math.max(0, this.currentHealth - damage); - if (this.currentHealth <= 0) { - this.entity.destroy(); - } +class GameSystem { + @EventHandler(ECSEventType.ENTITY_DESTROYED) + onEntityDestroyed(data: EntityDestroyedEventData) { + console.log('实体销毁:', data.entityName); } -} -// 添加组件到实体 -entity.addComponent(new HealthComponent()); -``` - -### System(系统) -系统处理具有特定组件的实体集合,实现游戏逻辑。 - -```typescript -import { EntitySystem, Entity } from '@esengine/ecs-framework'; - -class HealthSystem extends EntitySystem { - protected process(entities: Entity[]) { - for (const entity of entities) { - const health = entity.getComponent(HealthComponent); - if (health && health.currentHealth <= 0) { - // 处理实体死亡逻辑 - entity.destroy(); - } - } + @EventHandler('player:levelup') + onPlayerLevelUp(data: { playerId: number; newLevel: number }) { + console.log(`玩家 ${data.playerId} 升级到 ${data.newLevel} 级`); } } ``` -## 🧪 测试 +## 性能优化 -```bash -# 运行所有测试 -npm run test +### [组件索引](docs/concepts-explained.md#组件索引系统) -# 性能基准测试 -npm run benchmark +通过建立索引避免线性搜索,将查询复杂度从 O(n) 降低到 O(1)。 + +```typescript +// 使用Scene的查询系统进行组件索引 +const querySystem = scene.querySystem; + +// 查询具有特定组件的实体 +const entitiesWithPosition = querySystem.queryAll(PositionComponent).entities; +const entitiesWithVelocity = querySystem.queryAll(VelocityComponent).entities; + +// 性能统计 +const stats = querySystem.getStats(); +console.log('查询效率:', stats.hitRate); ``` -## 📖 文档 +**索引类型选择:** +- **哈希索引** - 适合稳定的、大量的组件(如位置、生命值) +- **位图索引** - 适合频繁变化的组件(如Buff、状态) -- [快速入门](docs/getting-started.md) - 从零开始学习框架使用 -- [EntityManager 使用指南](docs/entity-manager-example.md) - 详细了解实体管理器的高级功能 -- [事件系统使用指南](docs/event-system-example.md) - 学习类型安全事件系统的完整用法 -- [性能优化指南](docs/performance-optimization.md) - 深入了解三大性能优化系统 -- [核心概念](docs/core-concepts.md) - 深入了解 ECS 架构和设计原理 -- [查询系统使用指南](docs/query-system-usage.md) - 学习高性能查询系统的详细用法 +> 📋 详细选择指南参见 [索引类型选择指南](docs/concepts-explained.md#索引类型选择指南) -## 🔗 扩展库 +### [Archetype 系统](docs/concepts-explained.md#archetype-系统) -- [路径寻找库](https://github.com/esengine/ecs-astar) - A*、广度优先、Dijkstra、GOAP 算法 -- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI 系统 +将具有相同组件组合的实体分组,减少查询时的组件检查开销。 -## 🤝 贡献 +```typescript +// 使用查询系统的Archetype功能 +const querySystem = scene.querySystem; -欢迎提交 Issue 和 Pull Request! - -### 开发环境设置 - -```bash -# 克隆项目 -git clone https://github.com/esengine/ecs-framework.git -cd ecs-framework - -# 运行基准测试 -node benchmark.js - -# 开发构建 (在source目录) -cd source && npm install && npm run build +// 查询统计 +const stats = querySystem.getStats(); +console.log('缓存命中率:', stats.hitRate); ``` -### 构建要求 +### [脏标记系统](docs/concepts-explained.md#脏标记系统) + +追踪数据变化,只处理发生改变的实体,避免不必要的计算。 + +```typescript +// 脏标记通过组件系统自动管理 +// 组件变化时会自动标记为脏数据 + +// 查询系统会自动处理脏标记优化 +const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent); +``` + +> 💡 **不确定何时使用这些优化?** 查看 [性能优化建议](docs/concepts-explained.md#性能建议) 了解适用场景 + +## API 参考 + +### 核心类 + +| 类 | 描述 | +|---|---| +| `Core` | 框架核心管理类 | +| `Scene` | 场景容器,管理实体和系统 | +| `Entity` | 实体对象,包含组件集合 | +| `Component` | 组件基类 | +| `EntitySystem` | 系统基类 | +| `EntityManager` | 实体管理器 | + +### 查询 API + +```typescript +entityManager + .query() + .withAll(...components) // 包含所有指定组件 + .withAny(...components) // 包含任意指定组件 + .withNone(...components) // 不包含指定组件 + .withTag(tag) // 包含指定标签 + .withoutTag(tag) // 不包含指定标签 + .execute() // 执行查询 +``` + +### 事件类型 + +```typescript +enum ECSEventType { + ENTITY_CREATED = 'entity:created', + ENTITY_DESTROYED = 'entity:destroyed', + COMPONENT_ADDED = 'component:added', + COMPONENT_REMOVED = 'component:removed', + SYSTEM_ADDED = 'system:added', + SYSTEM_REMOVED = 'system:removed' +} +``` + +## 与其他框架对比 + +| 特性 | @esengine/ecs-framework | bitECS | Miniplex | +|------|-------------------------|--------|----------| +| TypeScript 支持 | ✅ 原生支持 | ✅ 完整支持 | ✅ 原生支持 | +| 事件系统 | ✅ 内置+装饰器 | ❌ 需自己实现 | ✅ 响应式 | +| 查询系统 | ✅ 流式 API | ✅ 函数式 | ✅ 响应式 | +| 实体管理器 | ✅ 统一接口 | ❌ 低级 API | ✅ 高级接口 | +| 性能优化 | ✅ 多重优化 | ✅ 极致性能 | ✅ React 优化 | +| 游戏引擎集成 | ✅ 通用设计 | ✅ 通用设计 | ⚠️ 主要 React | + +**选择指南:** +- 选择本框架:需要完整的游戏开发工具链和中文社区支持 +- 选择 bitECS:需要极致性能和最小化设计 +- 选择 Miniplex:主要用于 React 应用开发 + +## 项目结构 + +``` +ecs-framework/ +├── src/ +│ ├── ECS/ # ECS 核心系统 +│ │ ├── Core/ # 核心管理器 +│ │ ├── Systems/ # 系统类型 +│ │ └── Utils/ # ECS 工具 +│ ├── Types/ # TypeScript接口定义 +│ └── Utils/ # 通用工具 +├── docs/ # 文档 +└── scripts/ # 构建脚本 +``` + +## 文档 + +### 🎯 新手入门 +- **[📖 新手教程完整指南](docs/beginner-tutorials.md)** - 完整学习路径,从零开始 ⭐ **强烈推荐** +- [🚀 快速入门](docs/getting-started.md) - 详细的入门教程 +- [🧠 技术概念详解](docs/concepts-explained.md) - 通俗易懂的技术概念解释 ⭐ **推荐新手阅读** +- [🎯 位掩码使用指南](docs/bitmask-guide.md) - 位掩码概念、原理和高级使用技巧 +- [💡 使用场景示例](docs/use-cases.md) - 不同类型游戏的具体应用案例 +- [🔧 框架类型系统](docs/concepts-explained.md#框架类型系统) - TypeScript接口设计和使用指南 + +### 📚 核心功能 +- [🎭 实体管理指南](docs/entity-guide.md) - 实体的创建和使用方法 +- [🧩 组件设计指南](docs/component-design-guide.md) - 如何设计高质量组件 ⭐ **设计必读** +- [⚙️ 系统详解指南](docs/system-guide.md) - 四种系统类型的详细使用 +- [🎬 场景管理指南](docs/scene-management-guide.md) - 场景切换和数据管理 +- [⏰ 定时器系统指南](docs/timer-guide.md) - 定时器的完整使用方法 + +### API 参考 +- [核心 API 参考](docs/core-concepts.md) - 完整的 API 使用说明 +- [实体基础指南](docs/entity-guide.md) - 实体的基本概念和操作 +- [EntityManager 指南](docs/entity-manager-example.md) - 高性能查询和批量操作 +- [事件系统指南](docs/event-system-example.md) - 事件系统完整用法 +- [查询系统指南](docs/query-system-usage.md) - 查询系统使用方法 + +### 性能相关 +- [性能优化指南](docs/performance-optimization.md) - 性能优化技术和策略 + +## 构建 + +```bash +# 安装依赖 +npm install + +# 构建项目 +npm run build + +# 监听模式 +npm run build:watch + +# 清理构建文件 +npm run clean + +# 重新构建 +npm run rebuild +``` + +## 性能监控 + +框架提供内置性能统计: + +```typescript +// 场景统计 +const sceneStats = scene.getStats(); +console.log('性能统计:', { + 实体数量: sceneStats.entityCount, + 系统数量: sceneStats.processorCount +}); + +// 查询系统统计 +const queryStats = scene.querySystem.getStats(); +console.log('查询统计:', { + 缓存命中率: queryStats.hitRate + '%', + 查询次数: queryStats.queryCount +}); +``` + +## 扩展库 + +- [路径寻找库](https://github.com/esengine/ecs-astar) - A*、BFS、Dijkstra 算法 +- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI + +## 社区 + +- QQ 群:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6) +- GitHub:[提交 Issue](https://github.com/esengine/ecs-framework/issues) + +## 贡献 + +欢迎提交 Pull Request 和 Issue! + +### 开发要求 - Node.js >= 14.0.0 - TypeScript >= 4.0.0 -## 📄 许可证 +## 许可证 -本项目采用 [MIT](LICENSE) 许可证。 - -## 💬 交流群 - -加入 QQ 群讨论:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - -### 🚀 核心性能指标 - -```bash -实体创建: 640,000+ 个/秒 -组件查询: O(1) 复杂度(使用索引) -内存优化: 30-50% 减少分配 -批量操作: 显著提升处理效率 -``` - -### 🎯 性能优化技术 - -- **组件索引系统**: 哈希和位图双重索引,支持 O(1) 查询 -- **Archetype 系统**: 按组件组合分组,减少查询开销 -- **脏标记机制**: 细粒度变更追踪,避免不必要的计算 -- **批量操作 API**: 减少函数调用开销,提升大规模操作效率 -- **智能缓存**: 查询结果缓存和延迟清理机制 - -### 🔧 性能建议 - -1. **大规模场景**: 使用批量API和组件索引 -2. **频繁查询**: 启用Archetype系统进行快速筛选 -3. **实时游戏**: 利用脏标记减少无效更新 -4. **移动端**: 建议实体数量控制在20,000以内 - -运行 `npm run benchmark` 查看在您的环境中的具体性能表现。 - ---- - -**ECS Framework** - 让游戏开发更简单、更高效! \ No newline at end of file +[MIT](LICENSE) \ No newline at end of file diff --git a/docs/beginner-tutorials.md b/docs/beginner-tutorials.md new file mode 100644 index 00000000..b9636ed4 Binary files /dev/null and b/docs/beginner-tutorials.md differ diff --git a/docs/bitmask-guide.md b/docs/bitmask-guide.md new file mode 100644 index 00000000..eb647986 --- /dev/null +++ b/docs/bitmask-guide.md @@ -0,0 +1,431 @@ +# 位掩码使用指南 + +本文档详细解释ECS框架中位掩码的概念、原理和使用方法。 + +## 目录 + +1. [什么是位掩码](#什么是位掩码) +2. [位掩码的优势](#位掩码的优势) +3. [基础使用方法](#基础使用方法) +4. [高级位掩码操作](#高级位掩码操作) +5. [实际应用场景](#实际应用场景) +6. [性能优化技巧](#性能优化技巧) + +## 什么是位掩码 + +### 基本概念 + +位掩码(BitMask)是一种使用二进制位来表示状态或属性的技术。在ECS框架中,每个组件类型对应一个二进制位,实体的组件组合可以用一个数字来表示。 + +### 简单例子 + +假设我们有以下组件: +- PositionComponent → 位置 0 (二进制: 001) +- VelocityComponent → 位置 1 (二进制: 010) +- HealthComponent → 位置 2 (二进制: 100) + +那么一个同时拥有Position和Health组件的实体,其位掩码就是: +``` +001 (Position) + 100 (Health) = 101 (二进制) = 5 (十进制) +``` + +### 可视化理解 + +```typescript +// 组件类型对应的位位置 +PositionComponent → 位置0 → 2^0 = 1 → 二进制: 001 +VelocityComponent → 位置1 → 2^1 = 2 → 二进制: 010 +HealthComponent → 位置2 → 2^2 = 4 → 二进制: 100 +RenderComponent → 位置3 → 2^3 = 8 → 二进制: 1000 + +// 实体的组件组合示例 +实体A: Position + Velocity → 001 + 010 = 011 (二进制) = 3 (十进制) +实体B: Position + Health → 001 + 100 = 101 (二进制) = 5 (十进制) +实体C: Position + Velocity + Health → 001 + 010 + 100 = 111 (二进制) = 7 (十进制) +``` + +## 位掩码的优势 + +### 1. 极快的查询速度 + +```typescript +// 传统方式:需要遍历组件列表 +function hasComponents(entity, componentTypes) { + for (const type of componentTypes) { + if (!entity.hasComponent(type)) { + return false; + } + } + return true; +} + +// 位掩码方式:一次位运算即可 +function hasComponentsMask(entityMask, requiredMask) { + return (entityMask & requiredMask) === requiredMask; +} +``` + +### 2. 内存效率 + +```typescript +// 一个bigint可以表示64个组件的组合状态 +// 相比存储组件列表,内存使用量大大减少 + +const entity = scene.createEntity("Player"); +entity.addComponent(new PositionComponent()); +entity.addComponent(new HealthComponent()); + +// 获取位掩码(只是一个数字) +const mask = entity.componentMask; // bigint类型 +console.log(`位掩码: ${mask}`); // 输出: 5 (二进制: 101) +``` + +### 3. 批量操作优化 + +```typescript +// 可以快速筛选大量实体 +const entities = scene.getAllEntities(); +const requiredMask = BigInt(0b101); // Position + Health + +const filteredEntities = entities.filter(entity => + (entity.componentMask & requiredMask) === requiredMask +); +``` + +## 基础使用方法 + +### 获取实体的位掩码 + +```typescript +import { Scene, Entity, Component } from '@esengine/ecs-framework'; + +// 创建组件 +class PositionComponent extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +class HealthComponent extends Component { + constructor(public maxHealth: number = 100) { + super(); + } +} + +// 创建实体并添加组件 +const scene = new Scene(); +const entity = scene.createEntity("Player"); + +console.log(`初始位掩码: ${entity.componentMask}`); // 0 + +entity.addComponent(new PositionComponent(100, 200)); +console.log(`添加Position后: ${entity.componentMask}`); // 可能是 1 + +entity.addComponent(new HealthComponent(100)); +console.log(`添加Health后: ${entity.componentMask}`); // 可能是 5 + +// 查看二进制表示 +console.log(`二进制表示: ${entity.componentMask.toString(2)}`); +``` + +### 手动检查位掩码 + +```typescript +// 检查实体是否拥有特定组件组合 +function checkEntityComponents(entity: Entity) { + const mask = entity.componentMask; + + // 将位掩码转换为二进制字符串查看 + const binaryString = mask.toString(2).padStart(8, '0'); + console.log(`实体组件状态: ${binaryString}`); + + // 分析每一位 + console.log(`位0 (Position): ${(mask & 1n) !== 0n ? '有' : '无'}`); + console.log(`位1 (Velocity): ${(mask & 2n) !== 0n ? '有' : '无'}`); + console.log(`位2 (Health): ${(mask & 4n) !== 0n ? '有' : '无'}`); + console.log(`位3 (Render): ${(mask & 8n) !== 0n ? '有' : '无'}`); +} +``` + +## 高级位掩码操作 + +### 使用BitMaskOptimizer + +框架提供了BitMaskOptimizer类来简化位掩码操作: + +```typescript +import { BitMaskOptimizer } from '@esengine/ecs-framework'; + +// 获取优化器实例 +const optimizer = BitMaskOptimizer.getInstance(); + +// 注册组件类型(建议在游戏初始化时进行) +optimizer.registerComponentType('PositionComponent'); +optimizer.registerComponentType('VelocityComponent'); +optimizer.registerComponentType('HealthComponent'); +optimizer.registerComponentType('RenderComponent'); + +// 创建单个组件的掩码 +const positionMask = optimizer.createSingleComponentMask('PositionComponent'); +console.log(`Position掩码: ${positionMask} (二进制: ${positionMask.toString(2)})`); + +// 创建组合掩码 +const movementMask = optimizer.createCombinedMask(['PositionComponent', 'VelocityComponent']); +console.log(`Movement掩码: ${movementMask} (二进制: ${movementMask.toString(2)})`); + +// 检查实体是否匹配掩码 +const entity = scene.createEntity("TestEntity"); +entity.addComponent(new PositionComponent()); +entity.addComponent(new VelocityComponent()); + +const hasMovementComponents = optimizer.maskContainsAllComponents( + entity.componentMask, + ['PositionComponent', 'VelocityComponent'] +); +console.log(`实体拥有移动组件: ${hasMovementComponents}`); +``` + +### 位掩码分析工具 + +```typescript +// 分析位掩码的实用函数 +class MaskAnalyzer { + private optimizer = BitMaskOptimizer.getInstance(); + + // 分析实体的组件组合 + analyzeEntity(entity: Entity): void { + const mask = entity.componentMask; + const componentNames = this.optimizer.maskToComponentNames(mask); + const componentCount = this.optimizer.getComponentCount(mask); + + console.log(`实体 ${entity.name} 分析:`); + console.log(`- 位掩码: ${mask} (二进制: ${mask.toString(2)})`); + console.log(`- 组件数量: ${componentCount}`); + console.log(`- 组件列表: ${componentNames.join(', ')}`); + } + + // 比较两个实体的组件差异 + compareEntities(entityA: Entity, entityB: Entity): void { + const maskA = entityA.componentMask; + const maskB = entityB.componentMask; + + const commonMask = maskA & maskB; + const onlyInA = maskA & ~maskB; + const onlyInB = maskB & ~maskA; + + console.log(`实体比较:`); + console.log(`- 共同组件: ${this.optimizer.maskToComponentNames(commonMask).join(', ')}`); + console.log(`- 仅在A中: ${this.optimizer.maskToComponentNames(onlyInA).join(', ')}`); + console.log(`- 仅在B中: ${this.optimizer.maskToComponentNames(onlyInB).join(', ')}`); + } + + // 查找具有特定组件组合的实体 + findEntitiesWithMask(entities: Entity[], requiredComponents: string[]): Entity[] { + const requiredMask = this.optimizer.createCombinedMask(requiredComponents); + + return entities.filter(entity => + (entity.componentMask & requiredMask) === requiredMask + ); + } +} + +// 使用示例 +const analyzer = new MaskAnalyzer(); +analyzer.analyzeEntity(entity); +``` + +## 实际应用场景 + +### 1. 高性能实体查询 + +```typescript +class GameSystem { + private optimizer = BitMaskOptimizer.getInstance(); + private movementMask: bigint; + private combatMask: bigint; + + constructor() { + // 预计算常用掩码 + this.movementMask = this.optimizer.createCombinedMask([ + 'PositionComponent', 'VelocityComponent' + ]); + + this.combatMask = this.optimizer.createCombinedMask([ + 'PositionComponent', 'HealthComponent', 'WeaponComponent' + ]); + } + + // 快速查找移动实体 + findMovingEntities(entities: Entity[]): Entity[] { + return entities.filter(entity => + (entity.componentMask & this.movementMask) === this.movementMask + ); + } + + // 快速查找战斗单位 + findCombatUnits(entities: Entity[]): Entity[] { + return entities.filter(entity => + (entity.componentMask & this.combatMask) === this.combatMask + ); + } +} +``` + +### 2. 实体分类和管理 + +```typescript +class EntityClassifier { + private optimizer = BitMaskOptimizer.getInstance(); + + // 定义实体类型掩码 + private readonly ENTITY_TYPES = { + PLAYER: this.optimizer.createCombinedMask([ + 'PositionComponent', 'HealthComponent', 'InputComponent' + ]), + ENEMY: this.optimizer.createCombinedMask([ + 'PositionComponent', 'HealthComponent', 'AIComponent' + ]), + PROJECTILE: this.optimizer.createCombinedMask([ + 'PositionComponent', 'VelocityComponent', 'DamageComponent' + ]), + PICKUP: this.optimizer.createCombinedMask([ + 'PositionComponent', 'PickupComponent' + ]) + }; + + // 根据组件组合判断实体类型 + classifyEntity(entity: Entity): string { + const mask = entity.componentMask; + + for (const [type, typeMask] of Object.entries(this.ENTITY_TYPES)) { + if ((mask & typeMask) === typeMask) { + return type; + } + } + + return 'UNKNOWN'; + } + + // 批量分类实体 + classifyEntities(entities: Entity[]): Map { + const classified = new Map(); + + for (const entity of entities) { + const type = this.classifyEntity(entity); + if (!classified.has(type)) { + classified.set(type, []); + } + classified.get(type)!.push(entity); + } + + return classified; + } +} +``` + +## 性能优化技巧 + +### 1. 预计算常用掩码 + +```typescript +class MaskCache { + private optimizer = BitMaskOptimizer.getInstance(); + + // 预计算游戏中常用的组件组合 + public readonly COMMON_MASKS = { + RENDERABLE: this.optimizer.createCombinedMask([ + 'PositionComponent', 'RenderComponent' + ]), + MOVABLE: this.optimizer.createCombinedMask([ + 'PositionComponent', 'VelocityComponent' + ]), + LIVING: this.optimizer.createCombinedMask([ + 'HealthComponent' + ]), + INTERACTIVE: this.optimizer.createCombinedMask([ + 'PositionComponent', 'ColliderComponent' + ]) + }; + + constructor() { + // 预计算常用组合以提升性能 + this.optimizer.precomputeCommonMasks([ + ['PositionComponent', 'RenderComponent'], + ['PositionComponent', 'VelocityComponent'], + ['PositionComponent', 'HealthComponent', 'WeaponComponent'] + ]); + } +} +``` + +### 2. 位掩码调试工具 + +```typescript +// 位掩码调试工具 +class MaskDebugger { + private optimizer = BitMaskOptimizer.getInstance(); + + // 可视化位掩码 + visualizeMask(mask: bigint, maxBits: number = 16): string { + const binary = mask.toString(2).padStart(maxBits, '0'); + const components = this.optimizer.maskToComponentNames(mask); + + let visualization = `位掩码: ${mask} (二进制: ${binary})\n`; + visualization += `组件: ${components.join(', ')}\n`; + visualization += `可视化: `; + + for (let i = maxBits - 1; i >= 0; i--) { + const bit = (mask & (1n << BigInt(i))) !== 0n ? '1' : '0'; + visualization += bit; + if (i % 4 === 0 && i > 0) visualization += ' '; + } + + return visualization; + } +} +``` + +## 最佳实践 + +### 1. 组件注册 + +```typescript +// 在游戏初始化时注册所有组件类型 +function initializeComponentTypes() { + const optimizer = BitMaskOptimizer.getInstance(); + + // 按使用频率注册(常用的组件分配较小的位位置) + optimizer.registerComponentType('PositionComponent'); // 位置0 + optimizer.registerComponentType('VelocityComponent'); // 位置1 + optimizer.registerComponentType('HealthComponent'); // 位置2 + optimizer.registerComponentType('RenderComponent'); // 位置3 + // ... 其他组件 +} +``` + +### 2. 掩码缓存管理 + +```typescript +// 定期清理掩码缓存以避免内存泄漏 +setInterval(() => { + const optimizer = BitMaskOptimizer.getInstance(); + const stats = optimizer.getCacheStats(); + + // 如果缓存过大,清理一部分 + if (stats.size > 1000) { + optimizer.clearCache(); + console.log('位掩码缓存已清理'); + } +}, 60000); // 每分钟检查一次 +``` + +## 总结 + +位掩码是ECS框架中的核心优化技术,它提供了: + +1. **极快的查询速度** - 位运算比遍历快数百倍 +2. **内存效率** - 用一个数字表示复杂的组件组合 +3. **批量操作优化** - 可以快速处理大量实体 +4. **灵活的查询构建** - 支持复杂的组件组合查询 + +通过理解和正确使用位掩码,可以显著提升游戏的性能表现。记住要在游戏初始化时注册组件类型,预计算常用掩码,并合理管理缓存。 \ No newline at end of file diff --git a/docs/component-design-guide.md b/docs/component-design-guide.md new file mode 100644 index 00000000..c9079a56 --- /dev/null +++ b/docs/component-design-guide.md @@ -0,0 +1,695 @@ +# 组件设计最佳实践指南 + +组件是ECS架构的核心,设计良好的组件是构建高质量游戏的基础。本指南将教你如何设计出清晰、高效、可维护的组件。 + +## 组件设计原则 + +### 1. 数据为主,逻辑为辅 + +**核心理念:** 组件主要存储数据,复杂逻辑放在系统中处理。 + +```typescript +// ✅ 好的设计:主要是数据 +class HealthComponent extends Component { + public maxHealth: number; + public currentHealth: number; + public regenRate: number = 0; + public lastDamageTime: number = 0; + + constructor(maxHealth: number = 100) { + super(); + this.maxHealth = maxHealth; + this.currentHealth = maxHealth; + } + + // 简单的辅助方法是可以的 + isDead(): boolean { + return this.currentHealth <= 0; + } + + getHealthPercentage(): number { + return this.currentHealth / this.maxHealth; + } +} + +// ❌ 不好的设计:包含太多逻辑 +class BadHealthComponent extends Component { + public maxHealth: number; + public currentHealth: number; + + takeDamage(damage: number) { + this.currentHealth -= damage; + + // 这些逻辑应该在系统中处理 + if (this.currentHealth <= 0) { + this.entity.destroy(); // 销毁逻辑 + this.playDeathSound(); // 音效逻辑 + this.createDeathEffect(); // 特效逻辑 + this.updatePlayerScore(100); // 分数逻辑 + } + } +} +``` + +### 2. 单一职责原则 + +每个组件只负责一个方面的数据。 + +```typescript +// ✅ 好的设计:单一职责 +class PositionComponent extends Component { + public x: number = 0; + public y: number = 0; + + constructor(x: number = 0, y: number = 0) { + super(); + this.x = x; + this.y = y; + } +} + +class VelocityComponent extends Component { + public x: number = 0; + public y: number = 0; + public maxSpeed: number = 100; + + constructor(x: number = 0, y: number = 0) { + super(); + this.x = x; + this.y = y; + } +} + +class RotationComponent extends Component { + public angle: number = 0; + public angularVelocity: number = 0; + + constructor(angle: number = 0) { + super(); + this.angle = angle; + } +} + +// ❌ 不好的设计:职责混乱 +class TransformComponent extends Component { + public x: number = 0; + public y: number = 0; + public velocityX: number = 0; + public velocityY: number = 0; + public angle: number = 0; + public scale: number = 1; + public health: number = 100; // 和变换无关 + public ammo: number = 30; // 和变换无关 +} +``` + +### 3. 组合优于继承 + +使用多个小组件组合,而不是大而全的组件继承。 + +```typescript +// ✅ 好的设计:组合方式 +class Player { + constructor(scene: Scene) { + const player = scene.createEntity("Player"); + + // 通过组合不同组件实现功能 + player.addComponent(new PositionComponent(100, 100)); + player.addComponent(new VelocityComponent()); + player.addComponent(new HealthComponent(100)); + player.addComponent(new PlayerInputComponent()); + player.addComponent(new WeaponComponent()); + player.addComponent(new InventoryComponent()); + + return player; + } +} + +// 创建不同类型的实体很容易 +class Enemy { + constructor(scene: Scene) { + const enemy = scene.createEntity("Enemy"); + + // 复用相同的组件,但组合不同 + enemy.addComponent(new PositionComponent(200, 200)); + enemy.addComponent(new VelocityComponent()); + enemy.addComponent(new HealthComponent(50)); + enemy.addComponent(new AIComponent()); // 不同:AI而不是玩家输入 + enemy.addComponent(new WeaponComponent()); // 相同:都有武器 + // 没有库存组件 + + return enemy; + } +} + +// ❌ 不好的设计:继承方式 +class GameObject { + public x: number; + public y: number; + public health: number; + // ... 很多属性 +} + +class PlayerGameObject extends GameObject { + public input: InputData; + public inventory: Item[]; + // 强制继承了不需要的属性 +} + +class EnemyGameObject extends GameObject { + public ai: AIData; + // 继承了不需要的库存等属性 +} +``` + +## 常见组件类型和设计 + +### 1. 数据组件(Data Components) + +纯数据存储,没有或很少有方法。 + +```typescript +// 位置信息 +class PositionComponent extends Component { + public x: number; + public y: number; + + constructor(x: number = 0, y: number = 0) { + super(); + this.x = x; + this.y = y; + } + + // 简单的辅助方法 + distanceTo(other: PositionComponent): number { + const dx = this.x - other.x; + const dy = this.y - other.y; + return Math.sqrt(dx * dx + dy * dy); + } + + set(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +// 统计信息 +class StatsComponent extends Component { + public strength: number = 10; + public agility: number = 10; + public intelligence: number = 10; + public vitality: number = 10; + + // 计算派生属性 + getMaxHealth(): number { + return this.vitality * 10; + } + + getDamage(): number { + return this.strength * 2; + } + + getMoveSpeed(): number { + return this.agility * 5; + } +} + +// 渲染信息 +class SpriteComponent extends Component { + public textureName: string; + public width: number; + public height: number; + public tint: number = 0xFFFFFF; + public alpha: number = 1.0; + public visible: boolean = true; + + constructor(textureName: string, width: number = 0, height: number = 0) { + super(); + this.textureName = textureName; + this.width = width; + this.height = height; + } +} +``` + +### 2. 标记组件(Tag Components) + +用于标识实体状态或类型的空组件。 + +```typescript +// 标记组件通常不包含数据 +class PlayerComponent extends Component { + // 空组件,仅用于标记这是玩家实体 +} + +class EnemyComponent extends Component { + // 空组件,仅用于标记这是敌人实体 +} + +class DeadComponent extends Component { + // 标记实体已死亡 + public deathTime: number; + + constructor() { + super(); + this.deathTime = Time.totalTime; + } +} + +class InvincibleComponent extends Component { + // 标记实体无敌状态 + public duration: number; + + constructor(duration: number = 2.0) { + super(); + this.duration = duration; + } +} + +// 使用标记组件进行查询 +class GameSystem { + updatePlayers() { + // 只处理玩家实体 + const players = this.scene.findEntitiesWithComponent(PlayerComponent); + // ... + } + + updateEnemies() { + // 只处理敌人实体 + const enemies = this.scene.findEntitiesWithComponent(EnemyComponent); + // ... + } +} +``` + +### 3. 行为组件(Behavior Components) + +包含简单行为逻辑的组件。 + +```typescript +class WeaponComponent extends Component { + public damage: number; + public fireRate: number; + public ammo: number; + public maxAmmo: number; + public lastFireTime: number = 0; + + constructor(damage: number = 10, fireRate: number = 0.5) { + super(); + this.damage = damage; + this.fireRate = fireRate; + this.maxAmmo = 30; + this.ammo = this.maxAmmo; + } + + canFire(): boolean { + return this.ammo > 0 && + Time.totalTime - this.lastFireTime >= this.fireRate; + } + + fire(): boolean { + if (this.canFire()) { + this.ammo--; + this.lastFireTime = Time.totalTime; + return true; + } + return false; + } + + reload() { + this.ammo = this.maxAmmo; + } + + getAmmoPercentage(): number { + return this.ammo / this.maxAmmo; + } +} + +class InventoryComponent extends Component { + private items: Map = new Map(); + public maxCapacity: number = 20; + + addItem(itemType: string, quantity: number = 1): boolean { + if (this.getTotalItems() + quantity > this.maxCapacity) { + return false; + } + + const current = this.items.get(itemType) || 0; + this.items.set(itemType, current + quantity); + return true; + } + + removeItem(itemType: string, quantity: number = 1): boolean { + const current = this.items.get(itemType) || 0; + if (current < quantity) { + return false; + } + + const newAmount = current - quantity; + if (newAmount === 0) { + this.items.delete(itemType); + } else { + this.items.set(itemType, newAmount); + } + return true; + } + + hasItem(itemType: string, quantity: number = 1): boolean { + const current = this.items.get(itemType) || 0; + return current >= quantity; + } + + getTotalItems(): number { + let total = 0; + this.items.forEach(quantity => total += quantity); + return total; + } + + getItems(): Map { + return new Map(this.items); // 返回副本 + } +} +``` + +## 组件通信和依赖 + +### 1. 组件间通信 + +组件间不应直接通信,通过系统或事件系统进行通信。 + +```typescript +// ✅ 好的设计:通过事件通信 +class HealthComponent extends Component { + public currentHealth: number; + public maxHealth: number; + + takeDamage(damage: number) { + this.currentHealth -= damage; + + // 发送事件,让其他系统响应 + Core.emitter.emit('health:damaged', { + entity: this.entity, + damage: damage, + remainingHealth: this.currentHealth + }); + + if (this.currentHealth <= 0) { + Core.emitter.emit('health:died', { + entity: this.entity + }); + } + } +} + +// 其他组件响应事件 +class AnimationComponent extends Component { + onAddedToEntity() { + super.onAddedToEntity(); + + // 监听受伤事件 + Core.emitter.addObserver('health:damaged', this.onDamaged, this); + } + + onRemovedFromEntity() { + Core.emitter.removeObserver('health:damaged', this.onDamaged, this); + super.onRemovedFromEntity(); + } + + private onDamaged(data: any) { + if (data.entity === this.entity) { + this.playHurtAnimation(); + } + } +} + +// ❌ 不好的设计:直接依赖其他组件 +class BadHealthComponent extends Component { + takeDamage(damage: number) { + this.currentHealth -= damage; + + // 直接操作其他组件 + const animation = this.entity.getComponent(AnimationComponent); + if (animation) { + animation.playHurtAnimation(); // 紧耦合 + } + + const sound = this.entity.getComponent(SoundComponent); + if (sound) { + sound.playHurtSound(); // 紧耦合 + } + } +} +``` + +### 2. 可选依赖 + +有时组件需要其他组件配合工作,但应该优雅处理缺失的情况。 + +```typescript +class MovementComponent extends Component { + public speed: number = 100; + + update() { + // 可选依赖:输入组件 + const input = this.entity.getComponent(InputComponent); + const velocity = this.entity.getComponent(VelocityComponent); + + if (input && velocity) { + // 根据输入设置速度 + velocity.x = input.horizontal * this.speed; + velocity.y = input.vertical * this.speed; + } + + // 可选依赖:AI组件 + const ai = this.entity.getComponent(AIComponent); + if (ai && velocity && !input) { + // AI控制移动(如果没有输入) + velocity.x = ai.moveDirection.x * this.speed; + velocity.y = ai.moveDirection.y * this.speed; + } + } +} +``` + +## 组件性能优化 + +### 1. 对象池优化 + +对于频繁创建/销毁的组件,使用对象池。 + +```typescript +class PooledBulletComponent extends Component { + public damage: number = 10; + public speed: number = 200; + public direction: { x: number; y: number } = { x: 0, y: 0 }; + public lifetime: number = 5.0; + private currentLifetime: number = 0; + + // 重置组件状态,用于对象池 + reset() { + this.damage = 10; + this.speed = 200; + this.direction.set(0, 0); + this.lifetime = 5.0; + this.currentLifetime = 0; + } + + // 配置子弹 + configure(damage: number, speed: number, direction: { x: number; y: number }) { + this.damage = damage; + this.speed = speed; + this.direction = direction.copy(); + } + + update() { + this.currentLifetime += Time.deltaTime; + + if (this.currentLifetime >= this.lifetime) { + // 生命周期结束,回收到对象池 + BulletPool.release(this.entity); + } + } +} + +// 对象池管理 +class BulletPool { + private static pool: Entity[] = []; + + static get(): Entity { + if (this.pool.length > 0) { + const bullet = this.pool.pop()!; + bullet.enabled = true; + return bullet; + } else { + return this.createBullet(); + } + } + + static release(bullet: Entity) { + bullet.enabled = false; + bullet.getComponent(PooledBulletComponent)?.reset(); + this.pool.push(bullet); + } + + private static createBullet(): Entity { + const bullet = Core.scene.createEntity("Bullet"); + bullet.addComponent(new PooledBulletComponent()); + bullet.addComponent(new PositionComponent()); + bullet.addComponent(new VelocityComponent()); + return bullet; + } +} +``` + +### 2. 数据紧凑性 + +保持组件数据紧凑,避免不必要的对象分配。 + +```typescript +// ✅ 好的设计:紧凑的数据结构 +class ParticleComponent extends Component { + // 使用基本类型,避免对象分配 + public x: number = 0; + public y: number = 0; + public velocityX: number = 0; + public velocityY: number = 0; + public life: number = 1.0; + public maxLife: number = 1.0; + public size: number = 1.0; + public color: number = 0xFFFFFF; + + // 计算属性,避免存储冗余数据 + get alpha(): number { + return this.life / this.maxLife; + } +} + +// ❌ 不好的设计:过多对象分配 +class BadParticleComponent extends Component { + public position: { x: number; y: number } = { x: 0, y: 0 }; // 对象分配 + public velocity: { x: number; y: number } = { x: 0, y: 0 }; // 对象分配 + public color: Color = new Color(); // 对象分配 + public transform: Transform = new Transform(); // 对象分配 + + // 冗余数据 + public alpha: number = 1.0; + public life: number = 1.0; + public maxLife: number = 1.0; +} +``` + +## 组件调试和测试 + +### 1. 调试友好的组件 + +```typescript +class DebugFriendlyComponent extends Component { + public someValue: number = 0; + private debugName: string; + + constructor(debugName: string = "Unknown") { + super(); + this.debugName = debugName; + } + + // 提供有用的调试信息 + toString(): string { + return `${this.constructor.name}(${this.debugName}): value=${this.someValue}`; + } + + // 验证组件状态 + validate(): boolean { + if (this.someValue < 0) { + console.warn(`${this} has invalid value: ${this.someValue}`); + return false; + } + return true; + } + + // 获取调试信息 + getDebugInfo(): any { + return { + name: this.debugName, + value: this.someValue, + entityId: this.entity?.id, + isValid: this.validate() + }; + } +} +``` + +### 2. 单元测试 + +```typescript +// 组件测试示例 +describe('HealthComponent', () => { + let healthComponent: HealthComponent; + + beforeEach(() => { + healthComponent = new HealthComponent(100); + }); + + test('初始状态正确', () => { + expect(healthComponent.currentHealth).toBe(100); + expect(healthComponent.maxHealth).toBe(100); + expect(healthComponent.isDead()).toBe(false); + }); + + test('受伤功能正确', () => { + healthComponent.takeDamage(30); + expect(healthComponent.currentHealth).toBe(70); + expect(healthComponent.getHealthPercentage()).toBe(0.7); + }); + + test('死亡检测正确', () => { + healthComponent.takeDamage(100); + expect(healthComponent.isDead()).toBe(true); + }); +}); +``` + +## 常见问题和最佳实践 + +### Q: 组件应该有多大? + +A: 组件应该尽可能小和专注。如果一个组件有超过10个字段,考虑拆分。 + +### Q: 组件可以包含方法吗? + +A: 可以,但应该是简单的辅助方法。复杂逻辑应该在系统中处理。 + +### Q: 如何处理组件之间的依赖? + +A: +1. 优先使用组合而不是依赖 +2. 通过事件系统通信 +3. 在系统中处理组件间的协调 + +### Q: 什么时候使用继承? + +A: 很少使用。只在有明确的"是一个"关系时使用,如: + +```typescript +abstract class ColliderComponent extends Component { + abstract checkCollision(other: ColliderComponent): boolean; +} + +class CircleColliderComponent extends ColliderComponent { + public radius: number; + + checkCollision(other: ColliderComponent): boolean { + // 圆形碰撞检测 + } +} + +class BoxColliderComponent extends ColliderComponent { + public width: number; + public height: number; + + checkCollision(other: ColliderComponent): boolean { + // 方形碰撞检测 + } +} +``` + +遵循这些原则,你就能设计出高质量、易维护的组件系统! \ No newline at end of file diff --git a/docs/concepts-explained.md b/docs/concepts-explained.md new file mode 100644 index 00000000..ce122efe --- /dev/null +++ b/docs/concepts-explained.md @@ -0,0 +1,665 @@ +# 技术概念详解 + +本文档用通俗易懂的语言解释ECS框架中的关键技术概念,帮助开发者理解这些技术的作用和应用场景。 + +## 目录 + +- [ECS 架构基础](#ecs-架构基础) +- [性能优化技术](#性能优化技术) +- [事件系统](#事件系统) +- [实体管理](#实体管理) + +## ECS 架构基础 + +### 什么是 ECS? + +ECS (Entity-Component-System) 是一种编程架构模式,将游戏对象分解为三个独立的部分: + +**传统面向对象方式:** +```typescript +// 传统继承方式 - 问题很多 +class GameObject { + x: number; y: number; + render() { ... } + update() { ... } +} + +class Player extends GameObject { + health: number; + shoot() { ... } +} + +class Enemy extends Player { // 敌人需要射击但不需要玩家控制? + ai() { ... } +} +``` + +**ECS 方式:** +```typescript +// 数据和逻辑分离,灵活组合 +const player = createEntity() + .add(PositionComponent) // 位置数据 + .add(HealthComponent) // 生命值数据 + .add(PlayerInputComponent) // 玩家输入标记 + +const enemy = createEntity() + .add(PositionComponent) // 复用位置数据 + .add(HealthComponent) // 复用生命值数据 + .add(AIComponent) // AI标记 + +// 系统处理具有特定组件的实体 +MovementSystem.process([PositionComponent, VelocityComponent]); +``` + +### ECS 的优势 + +1. **灵活组合** - 像搭积木一样组装功能 +2. **代码复用** - 组件可以在不同实体间复用 +3. **性能优化** - 数据连续存储,缓存友好 +4. **并行处理** - 系统间相互独立,可以并行执行 +5. **易于测试** - 组件和系统可以独立测试 + +### 实际应用场景 + +**游戏开发中的例子:** +- **RPG游戏**:玩家、NPC、怪物都有位置和生命值,但只有玩家有输入组件 +- **射击游戏**:子弹、玩家、敌人都有位置和碰撞体,但行为完全不同 +- **策略游戏**:建筑、单位、资源都是实体,通过不同组件组合实现功能 + +## 性能优化技术 + +### 组件索引系统 + +**问题:** 没有索引时,查找组件需要遍历所有实体 +```typescript +// 慢的方式:线性搜索 O(n) +function findEntitiesWithHealth() { + const result = []; + for (const entity of allEntities) { // 遍历10万个实体 + if (entity.hasComponent(HealthComponent)) { + result.push(entity); + } + } + return result; +} +``` + +**解决方案:** 建立索引,直接访问 +```typescript +// 快的方式:索引查找 O(1) +const healthIndex = componentIndex.get(HealthComponent); +const entitiesWithHealth = healthIndex.getEntities(); // 直接获取 +``` + +**应用场景:** +- 频繁查询特定组件的实体 +- 大规模实体场景(数千到数万个实体) +- 实时游戏中的系统更新 + +### 索引类型选择指南 + +框架提供两种索引类型,选择合适的类型对性能至关重要: + +#### 🔸 哈希索引 (Hash Index) + +**适用场景:** +- 实体数量较多(> 1000个) +- 组件数据变化不频繁 +- 需要快速查找特定实体 + +**优势:** +- 查询速度极快 O(1) +- 内存使用相对较少 +- 适合大量实体 + +**缺点:** +- 添加/删除组件时有额外开销 +- 不适合频繁变化的组件 + +```typescript +// 适合哈希索引的组件 +componentIndex.setIndexType(PositionComponent, 'hash'); // 位置变化不频繁 +componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件稳定 +componentIndex.setIndexType(PlayerComponent, 'hash'); // 玩家标记组件 +``` + +#### 🔹 位图索引 (Bitmap Index) + +**适用场景:** +- 组件频繁添加/删除 +- 实体数量适中(< 10000个) +- 需要批量操作 + +**优势:** +- 添加/删除组件极快 +- 批量查询效率高 +- 内存访问模式好 + +**缺点:** +- 随实体数量增长,内存占用增加 +- 稀疏数据时效率降低 + +```typescript +// 适合位图索引的组件 +componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff经常添加删除 +componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件 +componentIndex.setIndexType(StateComponent, 'bitmap'); // 状态组件变化频繁 +``` + +#### 📊 选择决策表 + +| 考虑因素 | 哈希索引 (Hash) | 位图索引 (Bitmap) | +|---------|----------------|-------------------| +| **实体数量** | > 1,000 | < 10,000 | +| **组件变化频率** | 低频变化 | 高频变化 | +| **查询频率** | 高频查询 | 中等查询 | +| **内存使用** | 较少 | 随实体数增加 | +| **批量操作** | 一般 | 优秀 | + +#### 🤔 快速决策流程 + +**第一步:判断组件变化频率** +- 组件经常添加/删除? → 选择 **位图索引** +- 组件相对稳定? → 继续第二步 + +**第二步:判断实体数量** +- 实体数量 > 1000? → 选择 **哈希索引** +- 实体数量 < 1000? → 选择 **位图索引** + +**第三步:特殊情况** +- 需要频繁批量操作? → 选择 **位图索引** +- 内存使用很重要? → 选择 **哈希索引** + +#### 🎮 实际游戏中的选择示例 + +**射击游戏:** +```typescript +// 稳定组件用哈希索引 +componentIndex.setIndexType(PositionComponent, 'hash'); // 实体位置稳定存在 +componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件持续存在 +componentIndex.setIndexType(WeaponComponent, 'hash'); // 武器组件不常变化 + +// 变化组件用位图索引 +componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff频繁添加删除 +componentIndex.setIndexType(ReloadingComponent, 'bitmap'); // 装弹状态临时组件 +``` + +**策略游戏:** +```typescript +// 大量单位,核心组件用哈希 +componentIndex.setIndexType(UnitComponent, 'hash'); // 单位类型稳定 +componentIndex.setIndexType(OwnerComponent, 'hash'); // 所属玩家稳定 + +// 状态组件用位图 +componentIndex.setIndexType(SelectedComponent, 'bitmap'); // 选中状态常变化 +componentIndex.setIndexType(MovingComponent, 'bitmap'); // 移动状态变化 +componentIndex.setIndexType(AttackingComponent, 'bitmap'); // 攻击状态临时 +``` + +**RPG游戏:** +```typescript +// 角色核心属性用哈希 +componentIndex.setIndexType(StatsComponent, 'hash'); // 属性组件稳定 +componentIndex.setIndexType(InventoryComponent, 'hash'); // 背包组件稳定 +componentIndex.setIndexType(LevelComponent, 'hash'); // 等级组件稳定 + +// 临时状态用位图 +componentIndex.setIndexType(StatusEffectComponent, 'bitmap'); // 状态效果变化 +componentIndex.setIndexType(QuestComponent, 'bitmap'); // 任务状态变化 +componentIndex.setIndexType(CombatComponent, 'bitmap'); // 战斗状态临时 +``` + +#### ❌ 常见选择错误 + +**错误示例1:大量实体使用位图索引** +```typescript +// ❌ 错误:10万个单位用位图索引,内存爆炸 +const entityCount = 100000; +componentIndex.setIndexType(UnitComponent, 'bitmap'); // 内存占用过大! + +// ✅ 正确:大量实体用哈希索引 +componentIndex.setIndexType(UnitComponent, 'hash'); +``` + +**错误示例2:频繁变化组件用哈希索引** +```typescript +// ❌ 错误:Buff频繁添加删除,哈希索引效率低 +componentIndex.setIndexType(BuffComponent, 'hash'); // 添加删除慢! + +// ✅ 正确:变化频繁的组件用位图索引 +componentIndex.setIndexType(BuffComponent, 'bitmap'); +``` + +**错误示例3:不考虑实际使用场景** +```typescript +// ❌ 错误:所有组件都用同一种索引 +componentIndex.setIndexType(PositionComponent, 'hash'); +componentIndex.setIndexType(BuffComponent, 'hash'); // 应该用bitmap +componentIndex.setIndexType(TemporaryComponent, 'hash'); // 应该用bitmap + +// ✅ 正确:根据组件特性选择 +componentIndex.setIndexType(PositionComponent, 'hash'); // 稳定组件 +componentIndex.setIndexType(BuffComponent, 'bitmap'); // 变化组件 +componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件 +``` + +### Archetype 系统 + +**什么是 Archetype?** +Archetype(原型)是具有相同组件组合的实体分组。 + +**没有 Archetype 的问题:** +```typescript +// 每次都要检查每个实体的组件组合 +for (const entity of allEntities) { + if (entity.has(Position) && entity.has(Velocity) && !entity.has(Frozen)) { + // 处理移动 + } +} +``` + +**Archetype 的解决方案:** +```typescript +// 实体按组件组合自动分组 +const movableArchetype = [Position, Velocity, !Frozen]; +const movableEntities = archetypeSystem.getEntities(movableArchetype); +// 直接处理,无需逐个检查 +``` + +**应用场景:** +- 大量实体的游戏(RTS、MMO) +- 频繁的实体查询操作 +- 批量处理相同类型的实体 + +### 脏标记系统 + +**什么是脏标记?** +脏标记(Dirty Tracking)追踪哪些数据发生了变化,避免处理未变化的数据。 + +**没有脏标记的问题:** +```typescript +// 每帧都重新计算所有实体,即使它们没有移动 +function renderSystem() { + for (const entity of entities) { + updateRenderPosition(entity); // 浪费计算 + updateRenderRotation(entity); // 浪费计算 + updateRenderScale(entity); // 浪费计算 + } +} +``` + +**脏标记的解决方案:** +```typescript +// 只处理发生变化的实体 +function renderSystem() { + const dirtyEntities = dirtyTracking.getDirtyEntities(); + for (const entity of dirtyEntities) { + if (dirtyTracking.isDirty(entity, PositionComponent)) { + updateRenderPosition(entity); // 只在需要时计算 + } + if (dirtyTracking.isDirty(entity, RotationComponent)) { + updateRenderRotation(entity); + } + } + dirtyTracking.clearDirtyFlags(); +} +``` + +**应用场景:** +- 渲染系统优化(只更新变化的物体) +- 物理系统优化(只计算移动的物体) +- UI更新优化(只刷新变化的界面元素) +- 网络同步优化(只发送变化的数据) + +**实际例子:** +```typescript +// 游戏中的应用 +class MovementSystem { + process() { + // 玩家移动时标记为脏 + if (playerInput.moved) { + dirtyTracking.markDirty(player, PositionComponent); + } + + // 静止的敌人不会被标记为脏,渲染系统会跳过它们 + } +} +``` + +## 事件系统 + +### 类型安全事件 + +**传统事件的问题:** +```typescript +// 类型不安全,容易出错 +eventEmitter.emit('player_died', playerData); +eventEmitter.on('player_dead', handler); // 事件名拼写错误! +``` + +**类型安全事件的解决方案:** +```typescript +// 编译时检查,避免错误 +enum GameEvents { + PLAYER_DIED = 'player:died', + LEVEL_COMPLETED = 'level:completed' +} + +eventBus.emit(GameEvents.PLAYER_DIED, { playerId: 123 }); +eventBus.on(GameEvents.PLAYER_DIED, (data) => { + // data 类型自动推断 +}); +``` + +### 事件装饰器 + +**什么是装饰器?** +装饰器让你用简单的语法自动注册事件监听器。 + +**传统方式:** +```typescript +class GameManager { + constructor() { + // 手动注册事件 + eventBus.on('entity:created', this.onEntityCreated.bind(this)); + eventBus.on('entity:destroyed', this.onEntityDestroyed.bind(this)); + eventBus.on('component:added', this.onComponentAdded.bind(this)); + } + + onEntityCreated(data) { ... } + onEntityDestroyed(data) { ... } + onComponentAdded(data) { ... } +} +``` + +**装饰器方式:** +```typescript +class GameManager { + @EventHandler('entity:created') + onEntityCreated(data) { ... } // 自动注册 + + @EventHandler('entity:destroyed') + onEntityDestroyed(data) { ... } // 自动注册 + + @EventHandler('component:added') + onComponentAdded(data) { ... } // 自动注册 +} +``` + +**应用场景:** +- 游戏状态管理 +- UI更新响应 +- 音效播放触发 +- 成就系统检查 + +## 实体管理 + +### 实体生命周期 + +**创建实体的不同方式:** +```typescript +// 单个创建 - 适用于重要实体 +const player = scene.createEntity("Player"); + +// 批量创建 - 适用于大量相似实体 +const bullets = scene.createEntities(100, "Bullet"); + +// 延迟创建 - 避免性能峰值 +// 分批创建大量实体以避免单帧卡顿 +for (let i = 0; i < 100; i++) { + setTimeout(() => { + const batch = scene.createEntities(10, "Enemy"); + // 配置批次实体... + }, i * 16); // 每16ms创建一批 +} +``` + +### 查询系统 + +**流式API的优势:** +```typescript +// 传统方式:复杂的条件判断 +const result = []; +for (const entity of entities) { + if (entity.has(Position) && + entity.has(Velocity) && + !entity.has(Frozen) && + entity.tag === EntityTag.ENEMY) { + result.push(entity); + } +} + +// 流式API:清晰表达意图 +const result = entityManager + .query() + .withAll(Position, Velocity) + .withNone(Frozen) + .withTag(EntityTag.ENEMY) + .execute(); +``` + +### 批量操作 + +**为什么需要批量操作?** +```typescript +// 慢的方式:逐个处理 +for (let i = 0; i < 1000; i++) { + const bullet = createEntity(); + bullet.addComponent(new PositionComponent()); + bullet.addComponent(new VelocityComponent()); +} + +// 快的方式:批量处理 +const bullets = scene.createEntities(1000, "Bullet"); +bullets.forEach(bullet => { + bullet.addComponent(new PositionComponent()); + bullet.addComponent(new VelocityComponent()); +}); +``` + +**应用场景:** +- 生成大量子弹/粒子 +- 加载关卡时创建大量实体 +- 清理场景时删除大量实体 + +## 性能建议 + +### 什么时候使用这些优化? + +| 实体数量 | 推荐配置 | 说明 | +|---------|---------|------| +| < 1,000 | 默认配置 | 简单场景,不需要特殊优化 | +| 1,000 - 10,000 | 启用组件索引 | 中等规模,索引提升查询速度 | +| 10,000 - 50,000 | 启用Archetype | 大规模场景,分组优化 | +| > 50,000 | 全部优化 | 超大规模,需要所有优化技术 | + +### 常见使用误区 + +**错误:过度优化** +```typescript +// 不要在小项目中使用所有优化 +const entityManager = new EntityManager(); +entityManager.enableAllOptimizations(); // 小项目不需要 +``` + +**正确:按需优化** +```typescript +// 根据实际需求选择优化 +if (entityCount > 10000) { + entityManager.enableArchetypeSystem(); +} +if (hasFrequentQueries) { + entityManager.enableComponentIndex(); +} +``` + +## 总结 + +这些技术概念可能看起来复杂,但它们解决的都是实际开发中的具体问题: + +1. **ECS架构** - 让代码更灵活、可维护 +2. **组件索引** - 让查询更快速 +3. **Archetype系统** - 让批量操作更高效 +4. **脏标记系统** - 让更新更智能 +5. **事件系统** - 让组件间通信更安全 +6. **实体管理** - 让大规模场景成为可能 + +从简单的场景开始,随着项目复杂度增加,逐步引入这些优化技术。 + +## 框架类型系统 + +### TypeScript接口设计 + +ECS框架采用了精简的TypeScript接口设计,提供类型安全保障的同时保持实现的灵活性。 + +#### 核心接口 + +**IComponent接口** +```typescript +interface IComponent { + readonly id: number; + enabled: boolean; + updateOrder: number; + + onAddedToEntity(): void; + onRemovedFromEntity(): void; + onEnabled(): void; + onDisabled(): void; + update(): void; +} +``` +- 定义所有组件的基本契约 +- Component基类实现此接口 +- 确保组件生命周期方法的一致性 + +**ISystemBase接口** +```typescript +interface ISystemBase { + readonly systemName: string; + readonly entities: readonly any[]; + updateOrder: number; + enabled: boolean; + + initialize(): void; + update(): void; + lateUpdate?(): void; +} +``` +- 为EntitySystem类提供类型约束 +- 定义系统的核心执行方法 +- 支持可选的延迟更新 + +**IEventBus接口** +```typescript +interface IEventBus { + emit(eventType: string, data: T): void; + emitAsync(eventType: string, data: T): Promise; + on(eventType: string, handler: (data: T) => void, config?: IEventListenerConfig): string; + // ... 其他事件方法 +} +``` +- 提供类型安全的事件系统契约 +- 支持同步和异步事件处理 +- EventBus类完整实现此接口 + +#### 事件数据接口 + +**事件数据层次结构** +```typescript +// 基础事件数据 +interface IEventData { + timestamp: number; + source?: string; + eventId?: string; +} + +// 实体相关事件 +interface IEntityEventData extends IEventData { + entityId: number; + entityName?: string; + entityTag?: string; +} + +// 组件相关事件 +interface IComponentEventData extends IEntityEventData { + componentType: string; + component?: IComponent; +} +``` +- 清晰的继承层次 +- 类型安全的事件数据传递 +- 便于事件处理器的实现 + +#### 类型别名 + +**ComponentType** +```typescript +type ComponentType = new (...args: any[]) => T; +``` +- 用于类型安全的组件操作 +- 支持泛型约束 +- 广泛用于实体和查询系统 + +### 设计原则 + +#### 1. 接口简化原则 +- 只保留实际使用的接口 +- 移除了未使用的复杂接口(如IEntityManager、IEntityQueryBuilder等) +- 减少认知负担,提高开发效率 + +#### 2. 实现灵活性原则 +- 接口作为类型约束而非强制实现 +- 允许具体类有更丰富的实现 +- 保持向后兼容性 + +#### 3. 类型安全原则 +- 编译时类型检查 +- 泛型支持提供精确的类型推断 +- 事件系统的完整类型安全 + +### 使用指南 + +#### 在项目中使用接口 +```typescript +// 作为类型约束 +function processComponent(component: T) { + if (component.enabled) { + component.update(); + } +} + +// 作为参数类型 +function registerSystem(system: ISystemBase) { + scene.addEntityProcessor(system); +} + +// 作为泛型约束 +function getComponent(type: ComponentType): T | null { + return entity.getComponent(type); +} +``` + +#### 扩展框架接口 +```typescript +// 如果需要扩展组件接口 +interface IAdvancedComponent extends IComponent { + priority: number; + category: string; +} + +class AdvancedComponent extends Component implements IAdvancedComponent { + public priority: number = 0; + public category: string = "default"; + + // 实现基础接口方法 +} +``` + +### 接口维护 + +当前的接口设计已经过精心清理,包含: +- **12个核心接口** - 涵盖组件、系统、事件等核心概念 +- **0个冗余接口** - 移除了所有未使用的接口定义 +- **完整的类型覆盖** - 为所有主要功能提供类型支持 + +这种设计确保了框架的类型安全性,同时保持了代码的简洁性和可维护性。 \ No newline at end of file diff --git a/docs/core-concepts.md b/docs/core-concepts.md index 74006ea7..c98d8e3d 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -1,6 +1,8 @@ -# 核心概念 +# 核心 API 参考 -ECS Framework 基于 Entity-Component-System 架构模式,这是一种高度模块化和可扩展的游戏开发架构。本文档将详细介绍框架的核心概念。 +本文档详细介绍 ECS Framework 的核心 API 和使用方法。 + +> 🤔 **不熟悉ECS概念?** 建议先阅读 [技术概念详解](concepts-explained.md) 了解ECS架构基础和性能优化原理 ## ECS 架构概述 @@ -90,7 +92,7 @@ entities.forEach(entity => { scene.querySystem.clearCache(); // 手动清理缓存 // 获取性能统计 -const stats = scene.getPerformanceStats(); +const stats = scene.getStats(); console.log(`实体数量: ${stats.entityCount}`); ``` @@ -101,20 +103,8 @@ console.log(`实体数量: ${stats.entityCount}`); ### 实体的基本属性 ```typescript -import { Vector2 } from '@esengine/ecs-framework'; - const entity = scene.createEntity("MyEntity"); -// 位置 -entity.position = new Vector2(100, 200); -entity.position = entity.position.add(new Vector2(10, 0)); - -// 旋转(弧度) -entity.rotation = Math.PI / 4; - -// 缩放 -entity.scale = new Vector2(2, 2); - // 标签(用于分类) entity.tag = 1; @@ -126,6 +116,22 @@ entity.active = true; // 更新顺序 entity.updateOrder = 10; + +// 注意:框架专注于ECS架构,不提供Transform相关功能 +// 位置、旋转、缩放等Transform功能需要通过组件实现 +class TransformComponent extends Component { + public x: number = 0; + public y: number = 0; + public rotation: number = 0; + public scaleX: number = 1; + public scaleY: number = 1; +} + +// 使用Transform组件 +const transform = entity.addComponent(new TransformComponent()); +transform.x = 100; +transform.y = 200; +transform.rotation = Math.PI / 4; ``` ### 实体层级关系 @@ -278,20 +284,24 @@ class BulletComponent extends Component { } // 注册组件池 -ComponentPoolManager.getInstance().registerPool(BulletComponent, 1000); +ComponentPoolManager.getInstance().registerPool( + 'BulletComponent', + () => new BulletComponent(), + (bullet) => bullet.reset(), + 1000 +); // 使用对象池获取组件 -const bullet = ComponentPoolManager.getInstance().getComponent(BulletComponent); -entity.addComponent(bullet); +const bullet = ComponentPoolManager.getInstance().acquireComponent('BulletComponent'); +if (bullet) { + entity.addComponent(bullet); +} // 释放组件回对象池 -ComponentPoolManager.getInstance().releaseComponent(bullet); +ComponentPoolManager.getInstance().releaseComponent('BulletComponent', bullet); -// 预热组件池 -ComponentPoolManager.getInstance().preWarmPools({ - BulletComponent: 1000, - EffectComponent: 500 -}); +// 预热所有组件池 +ComponentPoolManager.getInstance().prewarmAll(100); // 获取池统计 const stats = ComponentPoolManager.getInstance().getPoolStats(); @@ -305,7 +315,9 @@ console.log('组件池统计:', stats); ### 场景生命周期 ```typescript -class GameScene extends es.Scene { +import { Scene } from '@esengine/ecs-framework'; + +class GameScene extends Scene { public initialize() { // 场景初始化,创建实体和系统 this.setupEntities(); @@ -365,8 +377,14 @@ console.log("系统数量:", stats.processorCount); 最常用的系统类型,处理实体集合: ```typescript -class MovementSystem extends es.EntitySystem { - protected process(entities: es.Entity[]) { +import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework'; + +class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(MovementComponent)); + } + + protected process(entities: Entity[]) { for (const entity of entities) { const movement = entity.getComponent(MovementComponent); if (movement) { @@ -382,12 +400,26 @@ class MovementSystem extends es.EntitySystem { 定期处理的系统: ```typescript -class HealthRegenerationSystem extends es.ProcessingSystem { - protected process(entities: es.Entity[]) { - for (const entity of entities) { - const health = entity.getComponent(HealthComponent); +import { ProcessingSystem, Time, Matcher } from '@esengine/ecs-framework'; + +class HealthRegenerationSystem extends ProcessingSystem { + constructor() { + super(Matcher.empty().all(HealthComponent)); + } + + public processSystem() { + // ProcessingSystem不处理具体实体,而是执行全局逻辑 + // 如果需要处理实体,应该使用EntitySystem + this.regenerateAllPlayerHealth(); + } + + private regenerateAllPlayerHealth() { + // 通过场景查找所有玩家实体并恢复生命值 + const players = this.scene.findEntitiesByTag(PlayerTag); + for (const player of players) { + const health = player.getComponent(HealthComponent); if (health && health.currentHealth < health.maxHealth) { - health.currentHealth += 10 * es.Time.deltaTime; + health.currentHealth += 10 * Time.deltaTime; } } } @@ -399,12 +431,15 @@ class HealthRegenerationSystem extends es.ProcessingSystem { 按时间间隔执行的系统: ```typescript -class SpawnSystem extends es.IntervalSystem { +import { IntervalSystem, Matcher } from '@esengine/ecs-framework'; + +class SpawnSystem extends IntervalSystem { constructor() { - super(3.0); // 每3秒执行一次 + // IntervalSystem需要Matcher和间隔时间 + super(Matcher.empty(), 3.0); // 每3秒执行一次 } - protected processSystem() { + protected process(entities: Entity[]) { // 生成敌人 const enemy = this.scene.createEntity("Enemy"); enemy.addComponent(new EnemyComponent()); @@ -417,7 +452,13 @@ class SpawnSystem extends es.IntervalSystem { 被动系统,不自动处理实体: ```typescript -class CollisionSystem extends es.PassiveSystem { +import { PassiveSystem, Matcher } from '@esengine/ecs-framework'; + +class CollisionSystem extends PassiveSystem { + constructor() { + super(Matcher.empty()); + } + public checkCollisions() { // 手动调用的碰撞检测逻辑 } @@ -429,54 +470,32 @@ class CollisionSystem extends es.PassiveSystem { 时间管理工具类,提供游戏时间相关功能: ```typescript +import { Time } from '@esengine/ecs-framework'; + // 获取时间信息 -console.log("帧时间:", es.Time.deltaTime); -console.log("总时间:", es.Time.totalTime); -console.log("帧数:", es.Time.frameCount); -console.log("时间缩放:", es.Time.timeScale); +console.log("帧时间:", Time.deltaTime); +console.log("总时间:", Time.totalTime); +console.log("帧数:", Time.frameCount); +console.log("时间缩放:", Time.timeScale); // 设置时间缩放(慢动作效果) -es.Time.timeScale = 0.5; +Time.timeScale = 0.5; // 检查时间间隔 -if (es.Time.checkEvery(1.0, lastCheckTime)) { +if (Time.checkEvery(1.0, lastCheckTime)) { // 每秒执行一次 } ``` -## Vector2(二维向量) - -二维向量类,提供数学运算: - -```typescript -// 创建向量 -const vec1 = new es.Vector2(10, 20); -const vec2 = es.Vector2.zero; -const vec3 = es.Vector2.one; - -// 向量运算 -const sum = vec1.add(vec2); -const diff = vec1.subtract(vec2); -const scaled = vec1.multiply(2); -const normalized = vec1.normalize(); - -// 向量属性 -console.log("长度:", vec1.length); -console.log("长度平方:", vec1.lengthSquared); - -// 静态方法 -const distance = es.Vector2.distance(vec1, vec2); -const lerped = es.Vector2.lerp(vec1, vec2, 0.5); -const fromAngle = es.Vector2.fromAngle(Math.PI / 4); -``` - ## 性能监控 框架内置性能监控工具: ```typescript +import { PerformanceMonitor } from '@esengine/ecs-framework'; + // 获取性能监控实例 -const monitor = es.PerformanceMonitor.instance; +const monitor = PerformanceMonitor.instance; // 查看性能数据 console.log("平均FPS:", monitor.averageFPS); @@ -493,22 +512,46 @@ monitor.reset(); 内存管理优化工具: ```typescript -// 创建对象池 -class BulletPool extends es.Pool { - protected createObject(): Bullet { - return new Bullet(); +import { Pool, IPoolable } from '@esengine/ecs-framework'; + +// 定义可池化的对象(需要实现IPoolable接口) +class Bullet implements IPoolable { + public x: number = 0; + public y: number = 0; + public speed: number = 0; + + // 重置对象状态,准备重用 + public reset(): void { + this.x = 0; + this.y = 0; + this.speed = 0; } } -const bulletPool = new BulletPool(); +// 创建对象池 +const bulletPool = new Pool(() => new Bullet(), 100); + +// 预热对象池 +bulletPool.warmUp(20); // 使用对象池 const bullet = bulletPool.obtain(); -// 使用bullet... +bullet.x = 100; +bullet.y = 200; +bullet.speed = 500; + +// 使用完后归还到池中 bulletPool.free(bullet); +// 查看池统计信息 +console.log(bulletPool.getStats()); + // 清空对象池 bulletPool.clear(); + +// 使用静态方法(自动管理池) +const bullet2 = Pool.obtain(Bullet); +Pool.free(Bullet, bullet2); ``` ## 最佳实践 @@ -539,57 +582,18 @@ bulletPool.clear(); ## 高级性能优化功能 -### 位掩码优化器 +### 查询系统优化 -位掩码优化器可以预计算和缓存常用的组件掩码,提升查询性能。 +框架内部已集成查询优化,无需手动配置。查询系统会自动使用最优的算法: ```typescript -import { BitMaskOptimizer } from '@esengine/ecs-framework'; +// 查询系统会自动优化这些操作 +const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent); +const renderableEntities = scene.querySystem.queryAll(PositionComponent, RenderComponent); -const optimizer = BitMaskOptimizer.getInstance(); - -// 注册组件类型 -optimizer.registerComponentType(PositionComponent); -optimizer.registerComponentType(VelocityComponent); -optimizer.registerComponentType(RenderComponent); - -// 预计算常用掩码组合 -optimizer.precomputeCommonMasks(); - -// 获取优化的掩码 -const positionMask = optimizer.getComponentMask(PositionComponent); -const movementMask = optimizer.getCombinedMask([PositionComponent, VelocityComponent]); - -// 掩码操作 -const hasBothComponents = optimizer.hasAllComponents(entityMask, movementMask); -const hasAnyComponent = optimizer.hasAnyComponent(entityMask, movementMask); - -// 获取掩码分析 -const analysis = optimizer.analyzeMask(entityMask); -console.log('掩码包含的组件类型:', analysis.componentTypes); -``` - -### 延迟索引更新器 - -批量更新索引可以显著提升大规模实体操作的性能。 - -```typescript -import { IndexUpdateBatcher } from '@esengine/ecs-framework'; - -const batcher = new IndexUpdateBatcher((updates) => { - // 处理批量更新 - console.log(`批量处理 ${updates.length} 个索引更新`); -}); - -// 配置批量大小和延迟 -batcher.configure(100, 16); // 批量大小100,延迟16ms - -// 添加更新任务 -batcher.addUpdate("add", entity, componentMask); -batcher.addUpdate("remove", entity, componentMask); - -// 强制刷新 -batcher.flush(); +// 获取查询统计信息 +const queryStats = scene.querySystem.getStats(); +console.log('查询统计:', queryStats); ``` ### 批量操作API @@ -598,14 +602,8 @@ batcher.flush(); // 批量创建实体 - 最高性能 const entities = scene.createEntities(10000, "Bullets"); -// 延迟缓存清理 -entities.forEach(entity => { - scene.addEntity(entity, false); // 延迟清理 -}); -scene.querySystem.clearCache(); // 手动清理 - // 批量查询优化 -const movingEntities = scene.getEntitiesWithComponents([PositionComponent, VelocityComponent]); +const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent).entities; ``` ## 总结 diff --git a/docs/entity-guide.md b/docs/entity-guide.md index 284d4f71..6633dd6e 100644 --- a/docs/entity-guide.md +++ b/docs/entity-guide.md @@ -1,6 +1,8 @@ -# 实体使用指南 +# 实体基础指南 -本指南详细介绍 ECS Framework 中实体(Entity)的所有功能和使用方法。 +本指南介绍实体(Entity)的基本概念和基础使用方法。 + +> 📖 **需要高级实体管理?** 请参考 [EntityManager 指南](entity-manager-example.md) 了解高性能查询和批量操作 ## 实体概述 @@ -155,7 +157,7 @@ if (entity.hasComponent(HealthComponent)) { // 检查组件掩码(高性能) const mask = entity.componentMask; -console.log(`组件掩码: ${mask.toString(2)}`); +console.log(`组件掩码: ${mask.toString(2)}`); // 二进制表示 ``` ### 移除组件 @@ -295,80 +297,20 @@ if (entity.isDestroyed) { // 4. 从场景中移除 ``` -## 性能优化 +# 高级特性请参考其他指南 -### 组件缓存 +> 📚 **更多功能:** +> - **高性能查询和批量操作** → [EntityManager 指南](entity-manager-example.md) +> - **性能优化技术** → [性能优化指南](performance-optimization.md) +> - **组件索引和缓存** → [技术概念详解](concepts-explained.md) -```typescript -// 预热组件缓存(提高后续访问性能) -entity.warmUpComponentCache(); - -// 清理组件缓存 -entity.cleanupComponentCache(); - -// 获取缓存统计信息 -const cacheStats = entity.getComponentCacheStats(); -console.log(`缓存命中率: ${cacheStats.cacheStats.hitRate}`); -console.log(`组件访问统计:`, cacheStats.accessStats); -``` - -### 批量操作 - -```typescript -// 批量添加组件(比单个添加更高效) -const components = entity.addComponents([ - new PositionComponent(0, 0), - new VelocityComponent(50, 0), - new HealthComponent(100) -]); - -// 批量移除组件 -const removed = entity.removeComponentsByTypes([ - HealthComponent, - VelocityComponent -]); -``` - -## 调试和监控 - -### 调试信息 - -```typescript -// 获取详细的调试信息 -const debugInfo = entity.getDebugInfo(); -console.log("实体调试信息:", debugInfo); - -// 调试信息包含: -// - 基本属性(名称、ID、状态等) -// - 组件信息(数量、类型、掩码等) -// - 层次结构信息(父子关系、深度等) -// - 性能统计(缓存命中率、访问统计等) -``` - -### 实体比较 - -```typescript -// 比较两个实体的优先级 -const result = entity1.compareTo(entity2); -if (result < 0) { - // entity1 优先级更高 -} else if (result > 0) { - // entity2 优先级更高 -} else { - // 优先级相同 -} - -// 实体的字符串表示 -console.log(entity.toString()); // "Entity[Player:1]" -``` - -## 最佳实践 +## 基础最佳实践 ### 1. 合理使用标签 ```typescript // 定义标签常量 -const Tags = { +const EntityTags = { PLAYER: 1, ENEMY: 2, PROJECTILE: 3, @@ -376,70 +318,42 @@ const Tags = { } as const; // 使用标签进行分类 -player.tag = Tags.PLAYER; -enemy.tag = Tags.ENEMY; +player.tag = EntityTags.PLAYER; +enemy.tag = EntityTags.ENEMY; ``` -### 2. 优化更新顺序 - -```typescript -// 设置合理的更新顺序 -player.updateOrder = 0; // 玩家最先更新 -enemy.updateOrder = 1; // 敌人其次 -projectile.updateOrder = 2; // 投射物最后 -``` - -### 3. 合理使用层次结构 - -```typescript -// 创建复合实体 -const tank = scene.createEntity("Tank"); -const turret = scene.createEntity("Turret"); -const barrel = scene.createEntity("Barrel"); - -// 建立层次关系 -tank.addChild(turret); -turret.addChild(barrel); - -// 这样可以通过控制父实体来影响整个层次结构 -tank.active = false; // 整个坦克都会被停用 -``` - -### 4. 组件缓存优化 - -```typescript -// 对于频繁访问的组件,预热缓存 -entity.warmUpComponentCache(); - -// 定期清理不常用的缓存 -setInterval(() => { - entity.cleanupComponentCache(); -}, 5000); -``` - -### 5. 避免内存泄漏 +### 2. 正确的销毁处理 ```typescript // 确保正确销毁实体 +if (!entity.isDestroyed) { + entity.destroy(); // 自动移除组件和层次关系 +} + +// 检查实体状态 if (entity.isDestroyed) { return; // 避免操作已销毁的实体 } +``` -// 在适当的时候销毁不需要的实体 -if (enemy.getComponent(HealthComponent)?.isDead()) { - enemy.destroy(); +### 3. 组件生命周期 + +```typescript +// 正确添加组件 +const health = entity.addComponent(new HealthComponent(100)); + +// 安全获取组件 +const healthComp = entity.getComponent(HealthComponent); +if (healthComp && healthComp.currentHealth <= 0) { + entity.destroy(); } ``` ## 常见问题 -### Q: 实体可以在不同场景间移动吗? +### Q: 实体如何实现位置、旋转等变换? -A: 不可以。实体与场景紧密绑定,如果需要在场景间传递数据,应该序列化实体的组件数据,然后在新场景中重新创建。 - -### Q: 如何实现实体的位置、旋转、缩放? - -A: 框架本身不提供这些属性,需要通过组件来实现: +A: 通过添加相应的组件: ```typescript class TransformComponent extends Component { @@ -448,35 +362,9 @@ class TransformComponent extends Component { public scale = { x: 1, y: 1 }; } -const transform = entity.addComponent(new TransformComponent()); -transform.position.x = 100; -transform.rotation = Math.PI / 4; +entity.addComponent(new TransformComponent()); ``` -### Q: 实体的更新顺序如何影响性能? +### Q: 实体可以在场景间移动吗? -A: 更新顺序主要影响游戏逻辑的执行顺序,对性能影响较小。但合理的更新顺序可以避免一些逻辑问题,比如确保输入处理在移动之前执行。 - -### Q: 如何处理大量实体的性能问题? - -A: -1. 使用对象池重用实体 -2. 合理使用组件缓存 -3. 避免不必要的组件查询 -4. 使用批量操作 -5. 定期清理销毁的实体 - -```typescript -// 使用对象池 -class EntityPool extends Pool { - protected createObject(): Entity { - return scene.createEntity("PooledEntity"); - } - - protected resetObject(entity: Entity): void { - entity.removeAllComponents(); - entity.active = true; - entity.enabled = true; - } -} -``` \ No newline at end of file +A: 不可以。实体与场景绑定,需要在新场景中重新创建。 \ No newline at end of file diff --git a/docs/entity-manager-example.md b/docs/entity-manager-example.md index 0940ed31..f504dd88 100644 --- a/docs/entity-manager-example.md +++ b/docs/entity-manager-example.md @@ -1,421 +1,370 @@ # EntityManager 使用指南 -EntityManager 是 ECS Framework 的核心管理系统,提供统一的实体管理、高性能查询和自动优化功能。 +本文档详细介绍 EntityManager 的使用方法和最佳实践。 -## 快速开始 +## 目录 -### 创建实体管理器 +1. [基础用法](#基础用法) +2. [查询系统](#查询系统) +3. [实体管理](#实体管理) +4. [性能监控](#性能监控) +5. [最佳实践](#最佳实践) + +## 基础用法 + +### 创建 EntityManager ```typescript import { EntityManager, Scene } from '@esengine/ecs-framework'; -// 通常在游戏管理器中创建 +// 创建场景和实体管理器 const scene = new Scene(); -const entityManager = new EntityManager(scene); -``` +const entityManager = new EntityManager(); -### 基础实体操作 +// 批量创建实体(使用Scene方法) +const enemies = scene.createEntities(50, "Enemy"); -```typescript -// 创建单个实体 -const player = entityManager.createEntity("Player"); -player.addComponent(new PositionComponent(100, 100)); -player.addComponent(new HealthComponent(100)); -player.tag = "player"; - -// 批量创建实体 -const enemies = entityManager.createEntities(50, "Enemy"); +// 为实体添加组件 enemies.forEach((enemy, index) => { enemy.addComponent(new PositionComponent( Math.random() * 800, Math.random() * 600 )); - enemy.addComponent(new HealthComponent(30)); - enemy.tag = "enemy"; + enemy.addComponent(new HealthComponent(100)); + enemy.addComponent(new VelocityComponent( + (Math.random() - 0.5) * 100, + (Math.random() - 0.5) * 100 + )); + enemy.tag = 2; // 敌人标签 }); - -// 销毁实体 -entityManager.destroyEntity(player); ``` -## 高性能查询系统 - -EntityManager 提供多种查询方式,自动选择最优的查询策略。 +## 查询系统 ### 基础查询 ```typescript -// 通过ID查询(O(1)) -const entity = entityManager.getEntity(123); - -// 通过名称查询(O(1) 哈希查找) -const player = entityManager.getEntityByName("Player"); - -// 通过标签查询(O(1) 索引查找) -const enemies = entityManager.getEntitiesByTag("enemy"); - -// 组件查询(使用O(1)组件索引) +// 按组件类型查询 const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent); -// 多组件查询(使用Archetype优化) -const movingEntities = entityManager.getEntitiesWithComponents([ - PositionComponent, - VelocityComponent -]); +// 按标签查询 +const enemies = entityManager.getEntitiesByTag(2); +const players = entityManager.getEntitiesByTag(1); + +// 按名称查询 +const boss = entityManager.getEntityByName("BossEnemy"); + +// 获取所有实体 +const allEntities = entityManager.getAllEntities(); ``` -### 流式查询API - -EntityManager 提供强大的流式查询构建器: +### 流式查询 API ```typescript -// 基础查询构建 -const results = entityManager +// 复杂查询条件 +const movingEnemies = entityManager .query() - .withAll([PositionComponent, HealthComponent]) // 必须包含这些组件 - .withoutTag("dead") // 不能有死亡标签 - .active(true) // 必须激活 + .withAll(PositionComponent, VelocityComponent, HealthComponent) + .withTag(2) // 敌人标签 .execute(); -// 复杂条件查询 -const livingEnemies = entityManager +// 查询活跃的玩家 +const activePlayers = entityManager .query() - .withAll([PositionComponent, HealthComponent]) - .withTag("enemy") - .withoutTag("dead") - .where(entity => { - const health = entity.getComponent(HealthComponent); - return health && health.currentHealth > 0; - }) + .withAll(PositionComponent) + .withTag(1) // 玩家标签 + .active() // 只查询活跃实体 .execute(); -// 查询变体 -const firstEnemy = entityManager +// 排除特定组件的实体 +const nonCombatEntities = entityManager .query() - .withTag("enemy") - .first(); // 获取第一个匹配 - -const enemyCount = entityManager - .query() - .withTag("enemy") - .count(); // 获取数量 - -// 直接处理查询结果 -entityManager - .query() - .withAll([HealthComponent]) - .forEach(entity => { - const health = entity.getComponent(HealthComponent); - if (health.currentHealth <= 0) { - entity.addTag("dead"); - } - }); -``` - -### 高级查询选项 - -```typescript -// 组合条件查询 -const combatUnits = entityManager - .query() - .withAll([PositionComponent, HealthComponent]) // AND条件 - .withAny([WeaponComponent, MagicComponent]) // OR条件 - .without([DeadComponent]) // NOT条件 - .withTag("combatant") - .withoutTag("peaceful") - .active(true) - .enabled(true) + .withAll(PositionComponent) + .without(WeaponComponent, HealthComponent) .execute(); -// 使用自定义过滤器 +// 自定义条件查询 const nearbyEnemies = entityManager .query() - .withAll([PositionComponent]) - .withTag("enemy") + .withAll(PositionComponent) + .withTag(2) .where(entity => { const pos = entity.getComponent(PositionComponent); - const distance = Math.sqrt( - Math.pow(pos.x - playerPos.x, 2) + - Math.pow(pos.y - playerPos.y, 2) - ); - return distance < 100; // 距离玩家100像素内 + return pos && Math.abs(pos.x - playerX) < 100; }) .execute(); ``` -## 批量操作 +## 实体管理 -EntityManager 提供高效的批量操作方法: +### 创建和销毁实体 ```typescript -// 遍历所有实体 -entityManager.forEachEntity(entity => { - // 处理每个实体 - if (entity.position.x < 0) { - entity.position.x = 0; - } -}); +// 创建单个实体 +const player = entityManager.createEntity("Player"); +player.addComponent(new PositionComponent(400, 300)); +player.addComponent(new HealthComponent(100)); +player.tag = 1; -// 遍历特定组件的实体 -entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => { - if (health.currentHealth <= 0) { - entity.addTag("dead"); - entity.enabled = false; - } -}); +// 销毁实体 +entityManager.destroyEntity(player); -// 批量创建并配置实体 -const bullets = entityManager.createEntities(100, "Bullet", (bullet, index) => { - bullet.addComponent(new PositionComponent( - 100 + index * 10, - 100 - )); - bullet.addComponent(new VelocityComponent(0, -200)); - bullet.tag = "projectile"; -}); +// 按名称销毁 +entityManager.destroyEntity("Enemy_1"); + +// 按ID销毁 +entityManager.destroyEntity(123); ``` -## 性能优化系统 - -EntityManager 内置了三个性能优化系统: - -### 1. 组件索引系统 - -自动为组件查询提供O(1)性能: +### 实体查找 ```typescript -// 获取组件索引统计 -const componentIndex = entityManager.getComponentIndex(); -const stats = componentIndex.getPerformanceStats(); +// 按ID查找 +const entity = entityManager.getEntity(123); -console.log('组件索引统计:', { - totalQueries: stats.totalQueries, - indexHits: stats.indexHits, - hitRate: (stats.indexHits / stats.totalQueries * 100).toFixed(2) + '%' -}); +// 按名称查找 +const player = entityManager.getEntityByName("Player"); -// 手动优化(通常自动进行) -componentIndex.optimize(); +// 检查实体是否存在 +if (entity && !entity.isDestroyed) { + // 实体有效 +} ``` -### 2. Archetype系统 +## 性能监控 -按组件组合分组实体,优化批量查询: +### 基础统计 ```typescript -// 获取Archetype统计 -const archetypeSystem = entityManager.getArchetypeSystem(); -const archetypeStats = archetypeSystem.getStatistics(); +// 获取实体数量 +console.log('总实体数:', entityManager.entityCount); +console.log('活跃实体数:', entityManager.activeEntityCount); -console.log('Archetype统计:', { - totalArchetypes: archetypeStats.totalArchetypes, - totalEntities: archetypeStats.totalEntities, - queryCacheSize: archetypeStats.queryCacheSize +// 获取场景统计 +const sceneStats = scene.getStats(); +console.log('场景统计:', { + 实体数量: sceneStats.entityCount, + 系统数量: sceneStats.processorCount }); -// 查看所有原型 -console.log('当前原型:', archetypeSystem.getAllArchetypes()); +// 获取查询系统统计 +const queryStats = scene.querySystem.getStats(); +console.log('查询统计:', queryStats); ``` -### 3. 脏标记系统 +## 最佳实践 -追踪实体变更,避免不必要的更新: +### 1. 高效查询 ```typescript -// 获取脏标记统计 -const dirtyTracking = entityManager.getDirtyTrackingSystem(); -const dirtyStats = dirtyTracking.getPerformanceStats(); - -console.log('脏标记统计:', { - totalMarks: dirtyStats.totalMarks, - batchesProcessed: dirtyStats.batchesProcessed, - listenersNotified: dirtyStats.listenersNotified -}); - -// 手动处理脏标记 -dirtyTracking.processDirtyMarks(); -``` - -## 实体管理器统计 - -获取EntityManager的综合性能数据: - -```typescript -const stats = entityManager.getStatistics(); - -console.log('EntityManager统计:', { - // 基础统计 - entityCount: stats.entityCount, - activeEntityCount: stats.activeEntityCount, - - // 查询统计 - totalQueries: stats.totalQueries, - indexHits: stats.indexHits, - archetypeHits: stats.archetypeHits, - - // 性能指标 - averageQueryTime: stats.averageQueryTime, - hitRate: (stats.indexHits / stats.totalQueries * 100).toFixed(2) + '%' -}); -``` - -## 系统优化和清理 - -```typescript -// 手动触发优化 -entityManager.optimize(); - -// 内存清理 -entityManager.cleanup(); - -// 压缩数据结构 -entityManager.compact(); - -// 获取内存使用情况 -const memoryStats = entityManager.getMemoryUsage(); -console.log('内存使用:', { - entityIndexSize: memoryStats.entityIndex, - componentIndexSize: memoryStats.componentIndex, - archetypeSize: memoryStats.archetype -}); -``` - -## 实际使用案例 - -### 游戏系统集成 - -```typescript -class MovementSystem extends EntitySystem { - private entityManager: EntityManager; - - constructor(scene: Scene) { - super(); - this.entityManager = new EntityManager(scene); - } +// ✅ 好的做法:缓存查询结果 +class CombatSystem extends EntitySystem { + private cachedEnemies: Entity[] = []; + private lastUpdateFrame = 0; protected process(entities: Entity[]): void { - // 使用高效查询获取移动实体 - const movingEntities = this.entityManager - .query() - .withAll([PositionComponent, VelocityComponent]) - .active(true) - .execute(); + // 每5帧更新一次缓存 + if (Time.frameCount - this.lastUpdateFrame > 5) { + this.cachedEnemies = this.entityManager + .query() + .withAll(PositionComponent, HealthComponent) + .withTag(2) + .execute(); + this.lastUpdateFrame = Time.frameCount; + } - // 批量处理 - movingEntities.forEach(entity => { - const position = entity.getComponent(PositionComponent); - const velocity = entity.getComponent(VelocityComponent); - - position.x += velocity.dx * Time.deltaTime; - position.y += velocity.dy * Time.deltaTime; + // 使用缓存的结果 + this.cachedEnemies.forEach(enemy => { + // 处理敌人逻辑 }); } } ``` -### 复杂查询示例 +### 2. 批量操作 ```typescript -// 战斗系统:查找攻击范围内的敌人 -class CombatSystem { - private entityManager: EntityManager; +// ✅ 好的做法:批量创建和配置 +function createBulletWave(count: number): Entity[] { + // 使用Scene的批量创建 + const bullets = scene.createEntities(count, "Bullet"); + // 批量配置组件 + bullets.forEach((bullet, index) => { + const angle = (index / count) * Math.PI * 2; + bullet.addComponent(new PositionComponent(400, 300)); + bullet.addComponent(new VelocityComponent( + Math.cos(angle) * 200, + Math.sin(angle) * 200 + )); + bullet.addComponent(new BulletComponent()); + bullet.tag = 3; // 子弹标签 + }); + + return bullets; +} +``` + +### 3. 内存管理 + +```typescript +// ✅ 好的做法:及时清理无用实体 +class CleanupSystem extends EntitySystem { + protected process(entities: Entity[]): void { + // 清理超出边界的子弹 + const bullets = this.entityManager.getEntitiesByTag(3); + bullets.forEach(bullet => { + const pos = bullet.getComponent(PositionComponent); + if (pos && (pos.x < -100 || pos.x > 900 || pos.y < -100 || pos.y > 700)) { + this.entityManager.destroyEntity(bullet); + } + }); + + // 清理死亡的敌人 + const deadEnemies = this.entityManager + .query() + .withAll(HealthComponent) + .withTag(2) + .where(entity => { + const health = entity.getComponent(HealthComponent); + return health && health.currentHealth <= 0; + }) + .execute(); + + deadEnemies.forEach(enemy => { + this.entityManager.destroyEntity(enemy); + }); + } +} +``` + +### 4. 查询优化 + +```typescript +// ✅ 好的做法:使用合适的查询方法 +class GameSystem extends EntitySystem { findTargetsInRange(attacker: Entity, range: number): Entity[] { const attackerPos = attacker.getComponent(PositionComponent); if (!attackerPos) return []; + // 先按标签快速筛选,再按距离过滤 return this.entityManager - .query() - .withAll([PositionComponent, HealthComponent]) - .withTag("enemy") - .withoutTag("dead") - .where(entity => { - const pos = entity.getComponent(PositionComponent); + .getEntitiesByTag(2) // 敌人标签 + .filter(enemy => { + const enemyPos = enemy.getComponent(PositionComponent); + if (!enemyPos) return false; + const distance = Math.sqrt( - Math.pow(pos.x - attackerPos.x, 2) + - Math.pow(pos.y - attackerPos.y, 2) + Math.pow(attackerPos.x - enemyPos.x, 2) + + Math.pow(attackerPos.y - enemyPos.y, 2) ); return distance <= range; - }) - .execute(); + }); + } +} +``` + +## 完整示例 + +```typescript +import { + EntityManager, + Scene, + Entity, + Component, + EntitySystem, + Matcher +} from '@esengine/ecs-framework'; + +// 组件定义 +class PositionComponent extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +class HealthComponent extends Component { + constructor( + public maxHealth: number = 100, + public currentHealth: number = 100 + ) { + super(); + } +} + +// 游戏管理器 +class GameManager { + private scene: Scene; + private entityManager: EntityManager; + + constructor() { + this.scene = new Scene(); + this.entityManager = new EntityManager(); + this.setupGame(); } - // 优化版本:使用空间分区(如果实现了的话) - findTargetsInRangeOptimized(attacker: Entity, range: number): Entity[] { - // 首先通过空间查询缩小范围 - const nearbyEntities = this.spatialIndex.queryRange( - attackerPos.x - range, - attackerPos.y - range, - attackerPos.x + range, - attackerPos.y + range - ); + private setupGame(): void { + // 创建玩家 + const player = this.entityManager.createEntity("Player"); + player.addComponent(new PositionComponent(400, 300)); + player.addComponent(new HealthComponent(100)); + player.tag = 1; - // 然后使用EntityManager进行精确过滤 - return this.entityManager - .query() - .withAll([HealthComponent]) - .withTag("enemy") - .withoutTag("dead") - .where(entity => nearbyEntities.includes(entity)) - .execute(); + // 创建敌人 + const enemies = this.scene.createEntities(10, "Enemy"); + enemies.forEach(enemy => { + enemy.addComponent(new PositionComponent( + Math.random() * 800, + Math.random() * 600 + )); + enemy.addComponent(new HealthComponent(50)); + enemy.tag = 2; + }); + + // 添加系统 + this.scene.addEntityProcessor(new HealthSystem()); + } + + public update(): void { + this.scene.update(); + + // 输出统计信息 + console.log('实体数量:', this.entityManager.entityCount); + console.log('活跃实体数:', this.entityManager.activeEntityCount); } } -``` -## 性能建议 - -### 查询优化 - -1. **利用索引**: 优先使用组件查询和标签查询,它们具有O(1)性能 -2. **减少自定义过滤**: `where()`条件虽然灵活,但会降低性能 -3. **缓存查询结果**: 对于不经常变化的查询结果,考虑缓存 - -```typescript -// ✅ 推荐:使用索引查询 -const enemies = entityManager.getEntitiesByTag("enemy"); - -// ⚠️ 谨慎:自定义过滤 -const enemies = entityManager - .query() - .where(entity => entity.name.includes("Enemy")) - .execute(); -``` - -### 批量操作优化 - -```typescript -// ✅ 推荐:批量创建 -const bullets = entityManager.createEntities(100, "Bullet"); - -// ❌ 避免:循环单独创建 -for (let i = 0; i < 100; i++) { - entityManager.createEntity("Bullet"); +// 生命值系统 +class HealthSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(HealthComponent)); + } + + protected process(entities: Entity[]): void { + const healthEntities = this.scene.querySystem.queryAll(HealthComponent); + + healthEntities.entities.forEach(entity => { + const health = entity.getComponent(HealthComponent); + if (health && health.currentHealth <= 0) { + console.log(`实体 ${entity.name} 死亡`); + entity.destroy(); + } + }); + } } -``` -### 内存管理 - -```typescript -// 定期清理 -setInterval(() => { - entityManager.cleanup(); -}, 30000); // 每30秒清理一次 - -// 监控性能 -const stats = entityManager.getStatistics(); -if (stats.indexHits / stats.totalQueries < 0.8) { - console.warn('查询命中率较低,考虑优化查询策略'); -} +// 启动游戏 +const game = new GameManager(); +setInterval(() => game.update(), 16); // 60 FPS ``` ## 总结 -EntityManager 提供了: +EntityManager 提供了强大的实体管理功能: -- **统一接口**: 所有实体操作通过一个管理器完成 -- **自动优化**: 内置三个性能优化系统 -- **灵活查询**: 从简单的ID查找到复杂的条件查询 -- **性能监控**: 完整的统计和诊断信息 -- **批量操作**: 高效的批量处理能力 +- **创建管理**:`createEntity()`, `destroyEntity()` +- **查询功能**:`getEntitiesWithComponent()`, `getEntitiesByTag()`, `query()` +- **实体查找**:`getEntity()`, `getEntityByName()` +- **统计信息**:`entityCount`, `activeEntityCount` -通过合理使用EntityManager,您可以构建高性能、可维护的ECS游戏系统。 \ No newline at end of file +通过合理使用这些API,可以构建高性能的游戏系统。记住要及时清理无用实体,缓存频繁查询的结果,并使用合适的查询方法来优化性能。 \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index 46e55c15..2c4f8feb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,15 +6,18 @@ ``` ecs-framework/ -├── source/ -│ ├── src/ # 源代码 -│ │ ├── ECS/ # ECS核心系统 -│ │ ├── Types/ # 类型定义 -│ │ ├── Utils/ # 工具类 -│ │ └── Testing/ # 测试文件 -│ ├── scripts/ # 构建脚本 -│ └── tsconfig.json # TypeScript配置 -└── docs/ # 文档 +├── src/ # 源代码 +│ ├── ECS/ # ECS核心系统 +│ │ ├── Core/ # 核心管理器 +│ │ ├── Systems/ # 系统类型 +│ │ ├── Utils/ # ECS工具类 +│ │ └── Components/# 组件类型 +│ ├── Types/ # TypeScript接口定义(精简版) +│ └── Utils/ # 通用工具类 +├── docs/ # 文档 +├── scripts/ # 构建脚本 +├── bin/ # 编译输出 +└── dist/ # 发布版本 ``` ## 安装和使用 @@ -31,14 +34,17 @@ npm install @esengine/ecs-framework # 克隆项目 git clone https://github.com/esengine/ecs-framework.git -# 进入源码目录 -cd ecs-framework/source +# 进入项目目录 +cd ecs-framework # 安装依赖 npm install # 编译TypeScript npm run build + +# 或者使用监听模式开发 +npm run build:watch ``` ## 基础设置 @@ -80,22 +86,7 @@ class GameManager { Core.scene = this.scene; // 初始化实体管理器 - this.entityManager = new EntityManager(this.scene); - - // 初始化性能优化 - this.setupPerformanceOptimizations(); - } - - private setupPerformanceOptimizations() { - // 启用组件索引(自动优化查询性能) - // EntityManager内部已自动启用 - - // 可选:手动配置优化系统 - const componentIndex = this.entityManager.getComponentIndex(); - const archetypeSystem = this.entityManager.getArchetypeSystem(); - const dirtyTracking = this.entityManager.getDirtyTrackingSystem(); - - // 优化系统会自动工作,通常无需手动配置 + this.entityManager = new EntityManager(); } public update(deltaTime: number): void { @@ -216,7 +207,7 @@ class HealthComponent extends Component { EntityManager 是框架的核心功能,提供统一的实体管理和高性能查询接口。 -### 1. 基础用法 +### 基础实体操作 ```typescript // 获取EntityManager实例(在GameManager中已创建) @@ -224,37 +215,22 @@ const entityManager = gameManager.getEntityManager(); // 创建单个实体 const player = entityManager.createEntity("Player"); -player.addComponent(new PositionComponent(100, 100)); -player.addComponent(new VelocityComponent(50, 0)); -// 批量创建实体 -const enemies = entityManager.createEntities(50, "Enemy"); -enemies.forEach((enemy, index) => { - enemy.addComponent(new PositionComponent( - Math.random() * 800, - Math.random() * 600 - )); - enemy.addComponent(new HealthComponent(30)); - enemy.tag = "enemy"; -}); -``` +// 批量创建实体(使用Scene方法) +const enemies = scene.createEntities(50, "Enemy"); -### 2. 高性能查询 - -```typescript -// 流式查询API - 支持复杂查询条件 +// 查询操作 const movingEntities = entityManager .query() - .withAll([PositionComponent, VelocityComponent]) - .withoutTag("dead") - .active(true) + .withAll(PositionComponent, VelocityComponent) + .withNone(HealthComponent) .execute(); -// 快速组件查询(使用O(1)索引) +// 组件查询 const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent); // 标签查询 -const allEnemies = entityManager.getEntitiesByTag("enemy"); +const allEnemies = entityManager.getEntitiesByTag(2); // 名称查询 const specificEnemy = entityManager.getEntityByName("BossEnemy"); @@ -262,59 +238,41 @@ const specificEnemy = entityManager.getEntityByName("BossEnemy"); // 复合查询 const livingEnemies = entityManager .query() - .withAll([PositionComponent, HealthComponent]) - .withTag("enemy") - .withoutTag("dead") - .where(entity => { - const health = entity.getComponent(HealthComponent); - return health && health.currentHealth > 0; - }) + .withAll(HealthComponent) + .withTag(2) .execute(); ``` -### 3. 批量操作 +### 实体遍历 ```typescript -// 批量处理实体 -entityManager.forEachEntity(entity => { - // 处理所有实体 - if (entity.tag === "bullet" && entity.position.y < 0) { - entity.destroy(); - } +// 遍历所有实体 +const allEntities = entityManager.getAllEntities(); +allEntities.forEach(entity => { + console.log(`实体: ${entity.name}, ID: ${entity.id}`); }); -// 批量处理特定组件的实体 -entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => { - if (health.currentHealth <= 0) { - entity.addTag("dead"); - entity.enabled = false; - } +// 遍历特定组件的实体 +const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent); +healthEntities.forEach(entity => { + const health = entity.getComponent(HealthComponent); + console.log(`${entity.name} 生命值: ${health.currentHealth}/${health.maxHealth}`); }); - -// 获取统计信息 -const stats = entityManager.getStatistics(); -console.log(`总实体数: ${stats.entityCount}`); -console.log(`索引命中率: ${stats.indexHits}/${stats.totalQueries}`); ``` -### 4. 性能优化功能 +### 性能监控 ```typescript -// 获取性能优化系统 -const componentIndex = entityManager.getComponentIndex(); -const archetypeSystem = entityManager.getArchetypeSystem(); -const dirtyTracking = entityManager.getDirtyTrackingSystem(); +// 获取场景统计 +const sceneStats = scene.getStats(); +console.log('实体数量:', sceneStats.entityCount); +console.log('系统数量:', sceneStats.processorCount); -// 查看性能统计 -console.log('组件索引统计:', componentIndex.getPerformanceStats()); -console.log('Archetype统计:', archetypeSystem.getStatistics()); -console.log('脏标记统计:', dirtyTracking.getPerformanceStats()); +// 获取查询系统统计 +const queryStats = scene.querySystem.getStats(); +console.log('查询统计:', queryStats); -// 手动优化(通常自动进行) -entityManager.optimize(); -// 内存清理 -entityManager.cleanup(); ``` ## 创建系统 @@ -322,45 +280,40 @@ entityManager.cleanup(); 系统处理具有特定组件的实体集合,实现游戏逻辑。 ```typescript -import { EntitySystem, Entity } from '@esengine/ecs-framework'; +import { EntitySystem, Entity, Matcher, EntityManager } from '@esengine/ecs-framework'; class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(PositionComponent, VelocityComponent)); + } + protected process(entities: Entity[]): void { - // 使用EntityManager进行高效查询 - const entityManager = new EntityManager(this.scene); - const movingEntities = entityManager - .query() - .withAll([PositionComponent, VelocityComponent]) - .execute(); + // 使用Scene的查询系统进行高效查询 + const movingEntities = this.scene.querySystem.queryAll(PositionComponent, VelocityComponent); - movingEntities.forEach(entity => { + movingEntities.entities.forEach(entity => { const position = entity.getComponent(PositionComponent); const velocity = entity.getComponent(VelocityComponent); if (position && velocity) { - // 更新位置 - position.x += velocity.x * 0.016; // 假设60FPS - position.y += velocity.y * 0.016; - - // 边界检查 - if (position.x < 0 || position.x > 800) { - velocity.x = -velocity.x; - } - if (position.y < 0 || position.y > 600) { - velocity.y = -velocity.y; - } + position.x += velocity.dx * Time.deltaTime; + position.y += velocity.dy * Time.deltaTime; } }); } } class HealthSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(HealthComponent)); + } + protected process(entities: Entity[]): void { - const entityManager = new EntityManager(this.scene); + const healthEntities = this.scene.querySystem.queryAll(HealthComponent); - // 查找所有有生命值的实体 - entityManager.forEachEntityWithComponent(HealthComponent, (entity, health) => { - if (health.isDead()) { + healthEntities.entities.forEach(entity => { + const health = entity.getComponent(HealthComponent); + if (health && health.currentHealth <= 0) { entity.destroy(); } }); @@ -392,15 +345,14 @@ Core.emitter.removeObserver(CoreEvents.frameUpdated, this.onFrameUpdate); ### 性能监控 ```typescript -// 获取EntityManager性能统计 -const stats = entityManager.getStatistics(); -console.log(`总实体数: ${stats.entityCount}`); -console.log(`索引命中率: ${stats.indexHits}/${stats.totalQueries}`); +// 获取场景性能统计 +const sceneStats = scene.getStats(); +console.log(`总实体数: ${sceneStats.entityCount}`); +console.log(`系统数量: ${sceneStats.processorCount}`); -// 获取各优化系统的统计 -console.log('组件索引:', entityManager.getComponentIndex().getPerformanceStats()); -console.log('Archetype:', entityManager.getArchetypeSystem().getStatistics()); -console.log('脏标记:', entityManager.getDirtyTrackingSystem().getPerformanceStats()); +// 获取查询系统统计 +const queryStats = scene.querySystem.getStats(); +console.log('查询统计:', queryStats); ``` ## 简单示例 @@ -429,7 +381,7 @@ class SimpleGame { this.scene.name = "GameScene"; Core.scene = this.scene; - this.entityManager = new EntityManager(this.scene); + this.entityManager = new EntityManager(); this.setupSystems(); } @@ -457,7 +409,7 @@ class SimpleGame { } private createEnemies(count: number): Entity[] { - const enemies = this.entityManager.createEntities(count, "Enemy"); + const enemies = this.scene.createEntities(count, "Enemy"); enemies.forEach((enemy, index) => { enemy.addComponent(new PositionComponent( @@ -493,7 +445,7 @@ game.start(); ## 性能优化建议 ### 1. 大规模实体处理 -- 使用 `EntityManager.createEntities()` 批量创建实体 +- 使用 `scene.createEntities()` 批量创建实体 - 利用组件索引系统进行高效查询 - 启用Archetype系统减少查询遍历 @@ -503,7 +455,7 @@ game.start(); - 利用脏标记系统避免不必要的更新 ### 3. 性能监控 -- 定期检查 `EntityManager.getStatistics()` 获取性能数据 +- 定期检查 `scene.getStats()` 获取性能数据 - 监控组件索引命中率 - 使用框架提供的性能统计功能 diff --git a/docs/performance-optimization.md b/docs/performance-optimization.md index ae7c9ed1..0e882683 100644 --- a/docs/performance-optimization.md +++ b/docs/performance-optimization.md @@ -1,497 +1,425 @@ # 性能优化指南 -ECS Framework 提供了多层性能优化系统,确保在各种规模的游戏中都能提供卓越的性能表现。 +本文档介绍ECS框架的性能优化技术和最佳实践。 -## 性能优化架构 +## 目录 -### 三大核心优化系统 +1. [查询系统优化](#查询系统优化) +2. [实体管理优化](#实体管理优化) +3. [组件设计优化](#组件设计优化) +4. [系统设计优化](#系统设计优化) +5. [内存管理](#内存管理) +6. [性能监控](#性能监控) -1. **组件索引系统 (ComponentIndex)** - 提供 O(1) 组件查询性能 -2. **Archetype系统** - 按组件组合分组实体,减少查询遍历 -3. **脏标记系统 (DirtyTracking)** - 细粒度变更追踪,避免不必要更新 +## 查询系统优化 -这三个系统协同工作,为不同场景提供最优的性能表现。 - -## 性能基准 - -### 核心操作性能 - -``` -实体创建: 640,000+ 个/秒 -组件查询: O(1) 复杂度(使用索引) -内存优化: 30-50% 减少分配 -批量操作: 显著提升处理效率 -``` - -### 查询性能对比 - -| 查询类型 | 传统方式 | 使用索引 | 性能提升 | -|----------|----------|----------|----------| -| 单组件查询 | O(n) | O(1) | 1000x+ | -| 多组件查询 | O(n*m) | O(k) | 100x+ | -| 标签查询 | O(n) | O(1) | 1000x+ | -| 复合查询 | O(n*m*k) | O(min(k1,k2)) | 500x+ | - -*n=实体数量, m=组件种类, k=匹配实体数量* - -## 组件索引系统 - -### 索引类型选择 - -框架提供两种索引实现: - -#### 哈希索引 (HashComponentIndex) -- **适用场景**: 通用查询,平衡的读写性能 -- **优势**: O(1) 查询,较低内存开销 -- **缺点**: 哈希冲突时性能下降 +### 使用高效的查询方法 ```typescript -// 自动选择最优索引类型 -const componentIndex = entityManager.getComponentIndex(); +// ✅ 推荐:使用标签查询(快速) +const enemies = entityManager.getEntitiesByTag(2); -// 手动配置哈希索引 -componentIndex.setIndexType(HealthComponent, 'hash'); -``` - -#### 位图索引 (BitmapComponentIndex) -- **适用场景**: 大规模实体,频繁的组合查询 -- **优势**: 超快的 AND/OR 操作,空间压缩 -- **缺点**: 更新成本较高,内存开销随实体数量增长 - -```typescript -// 配置位图索引用于大规模查询 -componentIndex.setIndexType(PositionComponent, 'bitmap'); -``` - -### 智能索引管理 - -ComponentIndexManager 会根据使用模式自动优化: - -```typescript -// 获取索引性能统计 -const stats = componentIndex.getPerformanceStats(); -console.log('索引性能:', { - queriesPerSecond: stats.queriesPerSecond, - hitRate: stats.hitRate, - indexType: stats.recommendedType -}); - -// 自动优化索引类型 -componentIndex.optimize(); // 根据使用模式切换索引类型 -``` - -## Archetype系统优化 - -### 原型分组策略 - -Archetype系统将实体按组件组合分组,实现快速批量操作: - -```typescript -// 获取Archetype统计 -const archetypeSystem = entityManager.getArchetypeSystem(); -const stats = archetypeSystem.getStatistics(); - -console.log('Archetype优化:', { - totalArchetypes: stats.totalArchetypes, // 原型数量 - avgEntitiesPerArchetype: stats.averageEntitiesPerArchetype, - queryCacheHits: stats.queryCacheHits // 缓存命中次数 -}); -``` - -### 查询缓存机制 - -```typescript -// 启用查询缓存(默认开启) -archetypeSystem.enableQueryCache(true); - -// 缓存大小限制(避免内存泄漏) -archetypeSystem.setMaxCacheSize(1000); - -// 清理过期缓存 -archetypeSystem.cleanCache(); -``` - -### 最佳实践 - -1. **组件设计**: 避免创建过多单独的原型 -2. **批量操作**: 利用原型批量处理相同组件组合的实体 -3. **缓存管理**: 定期清理查询缓存 - -```typescript -// ✅ 好的设计:复用组件组合 -class MovementSystem extends EntitySystem { - process() { - // 一次查询处理所有移动实体 - const movingEntities = this.entityManager - .query() - .withAll([PositionComponent, VelocityComponent]) - .execute(); // 利用Archetype快速获取 - - // 批量处理 - movingEntities.forEach(entity => { - // 更新逻辑 - }); - } -} - -// ❌ 避免:频繁查询不同组合 -class BadSystem extends EntitySystem { - process() { - // 多次小查询,无法充分利用Archetype - const players = this.queryPlayers(); - const enemies = this.queryEnemies(); - const bullets = this.queryBullets(); - } -} -``` - -## 脏标记系统优化 - -### 脏标记类型 - -系统提供细粒度的脏标记追踪: - -```typescript -enum DirtyType { - COMPONENT_ADDED, // 组件添加 - COMPONENT_REMOVED, // 组件移除 - COMPONENT_MODIFIED, // 组件修改 - ENTITY_ENABLED, // 实体启用 - ENTITY_DISABLED, // 实体禁用 - TAG_ADDED, // 标签添加 - TAG_REMOVED // 标签移除 -} -``` - -### 批量处理配置 - -```typescript -const dirtyTracking = entityManager.getDirtyTrackingSystem(); - -// 配置批量处理参数 -dirtyTracking.configure({ - batchSize: 100, // 每批处理100个脏标记 - timeSliceMs: 16, // 每帧最多处理16ms - processingInterval: 1 // 每帧处理一次 -}); - -// 监听脏标记事件 -dirtyTracking.addListener(DirtyType.COMPONENT_MODIFIED, (entity, component) => { - // 响应组件修改 - this.invalidateRenderCache(entity); -}, { priority: 10 }); -``` - -### 性能监控 - -```typescript -const dirtyStats = dirtyTracking.getPerformanceStats(); -console.log('脏标记性能:', { - totalMarks: dirtyStats.totalMarks, - batchesProcessed: dirtyStats.batchesProcessed, - averageBatchTime: dirtyStats.averageBatchTime, - queueSize: dirtyStats.currentQueueSize -}); -``` - -## 查询优化策略 - -### 查询层次选择 - -根据查询复杂度选择最优方法: - -```typescript -// 1. 简单查询:直接使用索引 +// ✅ 推荐:使用组件查询 const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent); -// 2. 双组件查询:使用Archetype -const movingEntities = entityManager.getEntitiesWithComponents([ - PositionComponent, - VelocityComponent -]); +// ✅ 推荐:使用Scene的查询系统 +const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent); -// 3. 复杂查询:组合使用 -const combatants = entityManager +// ⚠️ 谨慎:自定义条件查询(较慢) +const nearbyEnemies = entityManager .query() - .withAll([PositionComponent, HealthComponent]) // Archetype预筛选 - .withTag("combat") // 索引过滤 - .where(entity => { // 自定义精确过滤 - const health = entity.getComponent(HealthComponent); - return health.currentHealth > health.maxHealth * 0.3; + .withAll(PositionComponent) + .where(entity => { + const pos = entity.getComponent(PositionComponent); + return pos && Math.abs(pos.x - playerX) < 100; }) .execute(); ``` -### 查询缓存策略 +### 查询结果缓存 ```typescript -class CombatSystem extends EntitySystem { +class OptimizedCombatSystem extends EntitySystem { private cachedEnemies: Entity[] = []; - private lastEnemyCacheUpdate = 0; + private lastCacheUpdate = 0; + private cacheInterval = 5; // 每5帧更新一次 - process() { - const currentTime = performance.now(); - - // 每200ms更新一次敌人缓存 - if (currentTime - this.lastEnemyCacheUpdate > 200) { - this.cachedEnemies = this.entityManager - .getEntitiesByTag("enemy"); - this.lastEnemyCacheUpdate = currentTime; + protected process(entities: Entity[]): void { + // 缓存查询结果 + if (Time.frameCount - this.lastCacheUpdate >= this.cacheInterval) { + this.cachedEnemies = this.entityManager.getEntitiesByTag(2); + this.lastCacheUpdate = Time.frameCount; } // 使用缓存的结果 - this.processCombat(this.cachedEnemies); + this.cachedEnemies.forEach(enemy => { + this.processEnemy(enemy); + }); + } + + private processEnemy(enemy: Entity): void { + // 处理敌人逻辑 } } ``` -## 内存优化 +## 实体管理优化 -### 内存使用监控 +### 批量创建实体 ```typescript -// 获取各系统内存使用情况 -const memoryStats = entityManager.getMemoryUsage(); -console.log('内存使用情况:', { - entityIndex: memoryStats.entityIndex, // 实体索引 - componentIndex: memoryStats.componentIndex, // 组件索引 - archetype: memoryStats.archetype, // 原型系统 - dirtyTracking: memoryStats.dirtyTracking, // 脏标记 - total: memoryStats.total -}); -``` - -### 内存清理策略 - -```typescript -// 定期内存清理 -setInterval(() => { - entityManager.cleanup(); // 清理无效引用 - entityManager.compact(); // 压缩数据结构 -}, 30000); // 每30秒清理一次 - -// 游戏场景切换时的深度清理 -function switchScene() { - entityManager.destroyAllEntities(); - entityManager.cleanup(); - entityManager.compact(); +// ✅ 推荐:使用Scene的批量创建 +function createEnemyWave(count: number): Entity[] { + const enemies = scene.createEntities(count, "Enemy"); - // 重置优化系统 - entityManager.getComponentIndex().reset(); - entityManager.getArchetypeSystem().clearCache(); - entityManager.getDirtyTrackingSystem().clear(); + // 批量配置组件 + enemies.forEach((enemy, index) => { + enemy.addComponent(new PositionComponent( + Math.random() * 800, + Math.random() * 600 + )); + enemy.addComponent(new HealthComponent(100)); + enemy.addComponent(new AIComponent()); + enemy.tag = 2; // 敌人标签 + }); + + return enemies; +} + +// ❌ 避免:循环单独创建 +function createEnemyWaveSlow(count: number): Entity[] { + const enemies: Entity[] = []; + for (let i = 0; i < count; i++) { + const enemy = entityManager.createEntity(`Enemy_${i}`); + enemy.addComponent(new PositionComponent()); + enemy.addComponent(new HealthComponent()); + enemies.push(enemy); + } + return enemies; } ``` -## 实战优化案例 - -### 大规模射击游戏优化 +### 实体复用策略 ```typescript -class BulletSystem extends EntitySystem { - private bulletPool: Entity[] = []; - private maxBullets = 1000; +// 使用简单的实体复用策略 +class EntityReusableManager { + private inactiveEntities: Entity[] = []; + private scene: Scene; - constructor(entityManager: EntityManager) { - super(); - this.prewarmBulletPool(); + constructor(scene: Scene) { + this.scene = scene; } - private prewarmBulletPool() { - // 预创建子弹池 - this.bulletPool = this.entityManager.createEntities( - this.maxBullets, - "Bullet" + // 预创建实体 + preCreateEntities(count: number, entityName: string): void { + const entities = this.scene.createEntities(count, entityName); + entities.forEach(entity => { + entity.enabled = false; // 禁用但不销毁 + this.inactiveEntities.push(entity); + }); + } + + // 获取可复用实体 + getReusableEntity(): Entity | null { + if (this.inactiveEntities.length > 0) { + const entity = this.inactiveEntities.pop()!; + entity.enabled = true; + return entity; + } + return null; + } + + // 回收实体 + recycleEntity(entity: Entity): void { + entity.enabled = false; + entity.removeAllComponents(); + this.inactiveEntities.push(entity); + } +} +``` + +## 组件设计优化 + +### 数据局部性优化 + +```typescript +// ✅ 推荐:紧凑的数据结构 +class OptimizedPositionComponent extends Component { + public x: number = 0; + public y: number = 0; + public z: number = 0; + + // 避免对象分配 + public setPosition(x: number, y: number, z: number = 0): void { + this.x = x; + this.y = y; + this.z = z; + } +} + +// ❌ 避免:过多对象分配 +class SlowPositionComponent extends Component { + public position: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 }; + public velocity: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 }; + public acceleration: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 }; +} +``` + +### 组件池化 + +```typescript +// 使用框架内置的组件池 +ComponentPoolManager.getInstance().registerPool( + 'BulletComponent', + () => new BulletComponent(), + (bullet) => bullet.reset(), + 1000 +); + +// 获取组件 +const bullet = ComponentPoolManager.getInstance().acquireComponent('BulletComponent'); +if (bullet) { + entity.addComponent(bullet); +} + +// 释放组件 +ComponentPoolManager.getInstance().releaseComponent('BulletComponent', bullet); +``` + +## 系统设计优化 + +### 系统更新顺序优化 + +```typescript +class OptimizedGameManager { + private scene: Scene; + + constructor() { + this.scene = new Scene(); + this.setupSystems(); + } + + private setupSystems(): void { + // 按依赖关系排序系统 + this.scene.addEntityProcessor(new InputSystem()).updateOrder = 10; + this.scene.addEntityProcessor(new MovementSystem()).updateOrder = 20; + this.scene.addEntityProcessor(new CollisionSystem()).updateOrder = 30; + this.scene.addEntityProcessor(new RenderSystem()).updateOrder = 40; + this.scene.addEntityProcessor(new CleanupSystem()).updateOrder = 50; + } +} +``` + +### 时间分片处理 + +```typescript +class TimeSlicedAISystem extends EntitySystem { + private aiEntities: Entity[] = []; + private currentIndex = 0; + private entitiesPerFrame = 10; + + protected process(entities: Entity[]): void { + // 获取所有AI实体 + if (this.aiEntities.length === 0) { + this.aiEntities = this.entityManager.getEntitiesByTag(3); // AI标签 + } + + // 每帧只处理部分实体 + const endIndex = Math.min( + this.currentIndex + this.entitiesPerFrame, + this.aiEntities.length ); - // 初始化为非激活状态 - this.bulletPool.forEach(bullet => { - bullet.enabled = false; + for (let i = this.currentIndex; i < endIndex; i++) { + this.processAI(this.aiEntities[i]); + } + + // 更新索引 + this.currentIndex = endIndex; + if (this.currentIndex >= this.aiEntities.length) { + this.currentIndex = 0; + this.aiEntities = []; // 重新获取实体列表 + } + } + + private processAI(entity: Entity): void { + // AI处理逻辑 + } +} +``` + +## 内存管理 + +### 及时清理无用实体 + +```typescript +class CleanupSystem extends EntitySystem { + protected process(entities: Entity[]): void { + // 清理超出边界的子弹 + const bullets = this.entityManager.getEntitiesByTag(4); // 子弹标签 + bullets.forEach(bullet => { + const pos = bullet.getComponent(PositionComponent); + if (pos && this.isOutOfBounds(pos)) { + this.entityManager.destroyEntity(bullet); + } + }); + + // 清理死亡的实体 + const deadEntities = this.entityManager + .query() + .withAll(HealthComponent) + .where(entity => { + const health = entity.getComponent(HealthComponent); + return health && health.currentHealth <= 0; + }) + .execute(); + + deadEntities.forEach(entity => { + this.entityManager.destroyEntity(entity); + }); + } + + private isOutOfBounds(pos: PositionComponent): boolean { + return pos.x < -100 || pos.x > 900 || pos.y < -100 || pos.y > 700; + } +} +``` + +### 实体复用管理 + +```typescript +class GameEntityManager { + private bulletManager: EntityReusableManager; + private effectManager: EntityReusableManager; + + constructor(scene: Scene) { + this.bulletManager = new EntityReusableManager(scene); + this.effectManager = new EntityReusableManager(scene); + + // 预创建实体 + this.bulletManager.preCreateEntities(100, "Bullet"); + this.effectManager.preCreateEntities(50, "Effect"); + } + + createBullet(): Entity | null { + const bullet = this.bulletManager.getReusableEntity(); + if (bullet) { + bullet.addComponent(new BulletComponent()); bullet.addComponent(new PositionComponent()); bullet.addComponent(new VelocityComponent()); - bullet.addComponent(new BulletComponent()); - }); - } - - public spawnBullet(x: number, y: number, vx: number, vy: number): Entity | null { - // 从池中获取非激活子弹(使用索引快速查询) - const availableBullet = this.entityManager - .query() - .withAll([BulletComponent]) - .active(false) - .first(); - - if (availableBullet) { - // 重用现有子弹 - const pos = availableBullet.getComponent(PositionComponent); - const vel = availableBullet.getComponent(VelocityComponent); - - pos.x = x; pos.y = y; - vel.x = vx; vel.y = vy; - availableBullet.enabled = true; - - return availableBullet; } - - return null; // 池已满 + return bullet; } - process() { - // 批量处理所有激活的子弹 - this.entityManager.forEachEntityWithComponent( - BulletComponent, - (entity, bullet) => { - if (!entity.enabled) return; - - // 更新位置 - const pos = entity.getComponent(PositionComponent); - const vel = entity.getComponent(VelocityComponent); - - pos.x += vel.x * Time.deltaTime; - pos.y += vel.y * Time.deltaTime; - - // 边界检查,回收到池中 - if (pos.x < 0 || pos.x > 800 || pos.y < 0 || pos.y > 600) { - entity.enabled = false; // 回收而不是销毁 - } - } - ); + destroyBullet(bullet: Entity): void { + this.bulletManager.recycleEntity(bullet); } } ``` -### AI系统性能优化 +## 性能监控 + +### 基础性能统计 ```typescript -class AISystem extends EntitySystem { - private spatialGrid: SpatialGrid; - private updateFrequency = 60; // 60Hz更新频率 - private lastUpdate = 0; +class PerformanceMonitor { + private scene: Scene; + private entityManager: EntityManager; - process() { - const currentTime = performance.now(); - - // 控制更新频率 - if (currentTime - this.lastUpdate < 1000 / this.updateFrequency) { - return; - } - - // 使用空间分区优化邻居查询 - const aiEntities = this.entityManager - .query() - .withAll([PositionComponent, AIComponent]) - .active(true) - .execute(); - - // 分批处理AI实体 - const batchSize = 50; - for (let i = 0; i < aiEntities.length; i += batchSize) { - const batch = aiEntities.slice(i, i + batchSize); - this.processBatch(batch); - - // 时间片控制,避免单帧卡顿 - if (performance.now() - currentTime > 10) { // 10ms时间片 - break; // 下一帧继续处理 - } - } - - this.lastUpdate = currentTime; + constructor(scene: Scene, entityManager: EntityManager) { + this.scene = scene; + this.entityManager = entityManager; } - private processBatch(entities: Entity[]) { - entities.forEach(entity => { - const pos = entity.getComponent(PositionComponent); - const ai = entity.getComponent(AIComponent); + public getPerformanceReport(): any { + return { + // 实体统计 + entities: { + total: this.entityManager.entityCount, + active: this.entityManager.activeEntityCount + }, - // 空间查询优化邻居搜索 - const neighbors = this.spatialGrid.queryRadius(pos.x, pos.y, ai.sightRange); + // 场景统计 + scene: this.scene.getStats(), - // AI决策逻辑 - ai.update(neighbors); - }); - } -} -``` - -## 性能监控工具 - -### 实时性能仪表板 - -```typescript -class PerformanceDashboard { - private stats: any = {}; - private updateInterval = 1000; // 1秒更新一次 - - constructor(private entityManager: EntityManager) { - setInterval(() => this.updateStats(), this.updateInterval); - } - - private updateStats() { - this.stats = { - // 基础统计 - entities: this.entityManager.getStatistics(), - - // 组件索引 - componentIndex: this.entityManager.getComponentIndex().getPerformanceStats(), - - // Archetype系统 - archetype: this.entityManager.getArchetypeSystem().getStatistics(), - - // 脏标记系统 - dirtyTracking: this.entityManager.getDirtyTrackingSystem().getPerformanceStats(), + // 查询系统统计 + querySystem: this.scene.querySystem.getStats(), // 内存使用 - memory: this.entityManager.getMemoryUsage(), - - // 计算性能指标 - performance: this.calculatePerformanceMetrics() - }; - - this.displayStats(); - } - - private calculatePerformanceMetrics() { - const componentStats = this.stats.componentIndex; - const archetypeStats = this.stats.archetype; - - return { - queryHitRate: componentStats.hitRate, - archetypeEfficiency: archetypeStats.averageEntitiesPerArchetype, - memoryEfficiency: this.stats.memory.compressionRatio, - overallPerformance: this.calculateOverallScore() + memory: { + used: (performance as any).memory?.usedJSHeapSize || 0, + total: (performance as any).memory?.totalJSHeapSize || 0 + } }; } - private displayStats() { - console.log('=== ECS性能仪表板 ==='); - console.log('查询命中率:', this.stats.performance.queryHitRate.toFixed(2) + '%'); - console.log('内存使用:', (this.stats.memory.total / 1024 / 1024).toFixed(2) + 'MB'); - console.log('整体性能评分:', this.stats.performance.overallPerformance.toFixed(1) + '/10'); + public logPerformance(): void { + const report = this.getPerformanceReport(); + console.log('性能报告:', report); } } ``` -## 优化检查清单 +### 帧率监控 -### 开发阶段 +```typescript +class FPSMonitor { + private frameCount = 0; + private lastTime = performance.now(); + private fps = 0; + + public update(): void { + this.frameCount++; + const currentTime = performance.now(); + + if (currentTime - this.lastTime >= 1000) { + this.fps = this.frameCount; + this.frameCount = 0; + this.lastTime = currentTime; + + if (this.fps < 30) { + console.warn(`低帧率警告: ${this.fps} FPS`); + } + } + } + + public getFPS(): number { + return this.fps; + } +} +``` -- [ ] 使用EntityManager而不是直接操作Scene -- [ ] 优先使用组件查询和标签查询 -- [ ] 设计合理的组件组合,避免过度碎片化 -- [ ] 实现对象池机制减少频繁创建/销毁 +## 最佳实践总结 -### 运行时优化 +### 查询优化 +1. 优先使用标签查询和组件查询 +2. 缓存频繁使用的查询结果 +3. 避免过度使用自定义条件查询 +4. 合理设置查询缓存更新频率 -- [ ] 监控查询命中率,保持在80%以上 -- [ ] 控制Archetype数量,避免过度分散 -- [ ] 配置适当的脏标记批量处理参数 -- [ ] 定期进行内存清理和数据压缩 +### 实体管理 +1. 使用批量创建方法 +2. 实现实体池化减少GC压力 +3. 及时清理无用实体 +4. 合理设置实体标签 -### 性能监控 +### 组件设计 +1. 保持组件数据紧凑 +2. 避免在组件中分配大量对象 +3. 使用组件池化 +4. 分离数据和行为 -- [ ] 定期检查性能统计数据 -- [ ] 监控内存使用趋势 -- [ ] 设置性能预警阈值 -- [ ] 在不同设备上进行性能测试 +### 系统设计 +1. 合理安排系统更新顺序 +2. 对重计算任务使用时间分片 +3. 避免在系统中进行复杂查询 +4. 缓存系统间的共享数据 -通过系统性地应用这些优化策略,您可以构建出在各种规模下都能提供卓越性能的ECS游戏系统。 \ No newline at end of file +### 内存管理 +1. 定期清理无用实体和组件 +2. 使用对象池减少GC +3. 监控内存使用情况 +4. 避免内存泄漏 + +通过遵循这些最佳实践,可以显著提升ECS框架的性能表现。 \ No newline at end of file diff --git a/docs/performance.md b/docs/performance.md deleted file mode 100644 index 3e47b174..00000000 --- a/docs/performance.md +++ /dev/null @@ -1,306 +0,0 @@ -# ECS框架性能基准 - -本文档展示了ECS框架的真实性能数据和瓶颈分析。 - -## 🚀 快速测试 - -```bash -# 快速性能基准测试 -npm run benchmark - -# 完整性能测试 -npm run test:performance - -# 单元测试 -npm run test:unit -``` - -## 📊 性能基准数据 - -> 测试环境: Node.js, Windows 10, 现代桌面CPU - -### 1. 实体创建性能 - -| 实体数量 | 创建时间 | 创建速度 | 每个实体耗时 | 性能等级 | -|---------|---------|---------|-------------|---------| -| 1,000 | 1.56ms | 640,697个/秒 | 0.0016ms | 🚀 极致 | -| 5,000 | 19.47ms | 256,805个/秒 | 0.0039ms | 🚀 极致 | -| 10,000 | 39.94ms | 250,345个/秒 | 0.0040ms | 🚀 极致 | -| 50,000 | 258.17ms | 193,673个/秒 | 0.0052ms | ✅ 优秀 | -| 100,000 | 463.04ms | 215,963个/秒 | 0.0046ms | ✅ 优秀 | -| 500,000 | 3,087ms | 161,990个/秒 | 0.0062ms | ✅ 优秀 | - -**结论**: 🚀 实体创建性能达到极致水平,大规模创建50万实体仅需3秒 - -### 2. 性能瓶颈分析 (500,000个实体) - -**当前瓶颈分布**: -``` -实体创建: 46.3% (1,429ms) -组件添加: 53.5% (1,651ms) ← 主要瓶颈 -标签分配: 0.2% (7ms) -``` - -**特征**: 框架实现了均衡的性能分布,各部分开销相对合理 - -### 3. 组件添加性能详细分析 - -| 组件类型 | 添加速度 | 平均耗时 | 性能等级 | -|---------|---------|---------|---------| -| PositionComponent | 596,929组件/秒 | 0.0017ms | 🚀 极致 | -| VelocityComponent | 1,186,770组件/秒 | 0.0008ms | 🚀 极致 | -| HealthComponent | 841,982组件/秒 | 0.0012ms | 🚀 极致 | -| RenderComponent | 763,351组件/秒 | 0.0013ms | 🚀 极致 | -| AIComponent | 185,964组件/秒 | 0.0054ms | ✅ 优秀 | - -### 4. 优化技术性能影响 - -| 优化技术 | 性能提升 | 内存影响 | 适用场景 | -|---------|---------|---------|---------| -| 组件对象池 | 30-50% | 减少分配 | 频繁创建/销毁 | -| 位掩码优化器 | 20-40% | 缓存开销 | 大量查询操作 | -| 批量操作 | 显著提升 | 无明显影响 | 大规模实体创建 | -| 延迟索引更新 | 60-80% | 临时内存增加 | 批量实体操作 | -| 索引去重优化 | 避免O(n) | 轻微内存增加 | 防止重复实体 | - -### 5. 查询系统性能 - -#### 5.1 基础查询性能 -| 查询类型 | 查询速度 | 每次查询耗时 | 性能等级 | -|---------|---------|-------------|---------| -| 单组件查询 | 12,178次/秒 | 0.082ms | ✅ 优秀 | -| 多组件查询 | 9,439次/秒 | 0.106ms | ✅ 优秀 | -| 复合查询 | 7,407次/秒 | 0.135ms | ✅ 良好 | - -#### 5.2 缓存查询性能 -| 缓存状态 | 访问速度 | 性能特征 | -|---------|---------|---------| -| 缓存命中 | 零延迟 | 🚀 即时响应 | -| 缓存未命中 | 标准查询 | ✅ 自动构建 | -| 缓存清理 | 批量延迟 | 🔧 优化策略 | - -### 6. 新功能性能基准 - -#### 6.1 组件对象池性能 -``` -📊 对象池 vs 直接创建 (10,000次操作) - 对象池获取: 1.65ms (6,060,606次/秒) - 直接创建: 1.51ms (6,622,516次/秒) - -⚠️ 小规模测试中对象池可能略慢,但在大规模应用中: - - 减少30-50%的内存分配 - - 避免垃圾回收压力 - - 提升长期运行稳定性 -``` - -#### 6.2 位掩码优化器性能 -``` -🔥 位掩码操作性能 (100,000次操作) - 单个掩码创建: 20.00ms (5,000,000次/秒) - 组合掩码创建: 53.69ms (1,862,285次/秒) - 缓存掩码访问: <1ms (近零延迟) -``` - -## 🎯 性能扩展性分析 - -### 实体创建扩展性 -``` -📈 创建速度趋势分析 - 1K-10K实体: 250,000-640,000 实体/秒 (优秀) - 10K-100K实体: 200,000-250,000 实体/秒 (良好) - 100K-500K实体: 160,000-220,000 实体/秒 (稳定) - -结论: 性能随规模稳定下降,无突然性能悬崖 -``` - -### 内存使用效率 -| 实体数量 | 内存使用 | 每实体内存 | 内存效率 | -|---------|---------|-----------|---------| -| 1,000 | 3.5MB | 3.5KB | 🚀 极致 | -| 5,000 | 7.1MB | 1.4KB | 🚀 极致 | -| 10,000 | 20.8MB | 2.1KB | ✅ 优秀 | -| 50,000 | ~100MB | ~2KB | ✅ 优秀 | - -## 💡 性能优化建议 - -### 1. 实体创建最佳实践 - -**✅ 推荐做法**: -```typescript -// 使用批量创建API -const entities = scene.createEntities(10000, "Enemies"); - -// 延迟缓存清理 -entities.forEach(entity => { - scene.addEntity(entity, false); // 延迟清理 -}); -scene.querySystem.clearCache(); // 手动清理 -``` - -**❌ 避免做法**: -```typescript -// 避免循环单个创建 -for (let i = 0; i < 10000; i++) { - scene.createEntity("Enemy" + i); // 每次触发缓存清理 -} -``` - -### 2. 组件池优化策略 - -**预热策略**: -```typescript -// 预热常用组件池 -ComponentPoolManager.getInstance().preWarmPools({ - BulletComponent: 2000, // 子弹大量创建 - EffectComponent: 1000, // 特效频繁使用 - PickupComponent: 500 // 道具适量缓存 -}); -``` - -**使用模式**: -```typescript -// 高效的组件复用 -const bullet = ComponentPoolManager.getInstance().getComponent(BulletComponent); -bullet.reset(); // 重置状态 -entity.addComponent(bullet); - -// 销毁时释放到池 -ComponentPoolManager.getInstance().releaseComponent(bullet); -``` - -### 3. 查询优化策略 - -**缓存策略**: -```typescript -// 缓存频繁查询结果 -class MovementSystem extends EntitySystem { - private cachedMovingEntities: Entity[]; - private lastCacheFrame: number = 0; - - protected process(entities: Entity[]) { - // 每5帧更新一次缓存 - if (Time.frameCount - this.lastCacheFrame > 5) { - this.cachedMovingEntities = scene.getEntitiesWithComponents([Position, Velocity]); - this.lastCacheFrame = Time.frameCount; - } - - // 使用缓存结果 - this.processMovement(this.cachedMovingEntities); - } -} -``` - -### 4. 不同规模应用建议 - -#### 小型游戏 (< 5,000实体) -- ✅ 可以随意使用所有功能 -- ✅ 不需要特殊优化 -- ✅ 专注于游戏逻辑开发 - -#### 中型游戏 (5,000-50,000实体) -- ✅ 使用批量操作API -- ✅ 启用组件对象池 -- ⚠️ 注意查询频率 - -#### 大型游戏 (50,000+实体) -- 🚀 必须使用批量操作 -- 🚀 必须启用对象池 -- 🚀 必须缓存查询结果 -- 🚀 考虑分区处理 - -## 🌍 平台性能对比 - -### Windows 桌面端 (测试平台) -- **实体创建**: 640,697实体/秒 -- **组件操作**: 596,929组件/秒 -- **推荐实体数**: ≤ 200,000 - -### 预估其他平台性能 - -| 平台类型 | 预估性能比例 | 推荐实体数 | 特殊注意 | -|---------|-------------|-----------|---------| -| macOS桌面 | 90-100% | ≤ 180,000 | 内存管理优秀 | -| Linux桌面 | 95-105% | ≤ 200,000 | 性能最优 | -| Chrome浏览器 | 60-80% | ≤ 100,000 | V8引擎优化 | -| Firefox浏览器 | 50-70% | ≤ 80,000 | SpiderMonkey限制 | -| Safari浏览器 | 55-75% | ≤ 90,000 | JavaScriptCore | -| Node.js服务器 | 100-110% | ≤ 500,000 | 服务器级性能 | -| Android Chrome | 30-50% | ≤ 30,000 | 移动端限制 | -| iOS Safari | 40-60% | ≤ 40,000 | iOS优化较好 | - -## 🔬 测试环境详情 - -### 硬件环境 -- **操作系统**: Windows 10 (Build 26100) -- **处理器**: 现代桌面CPU -- **内存**: 充足RAM -- **存储**: SSD高速存储 - -### 软件环境 -- **Node.js**: v16+ -- **TypeScript**: v5.8.3 -- **ECS框架版本**: v2.0.6 -- **测试工具**: 内置基准测试套件 - -### 测试方法 -- **实体配置**: 位置、速度、生命值、渲染、AI组件随机分配 -- **测试迭代**: 多次测试取平均值 -- **内存监控**: 实时内存使用情况 -- **性能指标**: performance.now()高精度计时 - -## 📋 性能测试清单 - -### 运行完整性能测试 - -```bash -# 1. 快速基准测试 (2-3分钟) -npm run benchmark - -# 2. 完整性能测试 (10-15分钟) -npm run test:performance - -# 3. 单元测试验证 (30秒) -npm run test:unit - -# 4. 所有测试 (15-20分钟) -npm run test -``` - -### 自定义性能测试 - -```typescript -import { runEntityCreationBenchmark } from '@esengine/ecs-framework/Testing/Performance/benchmark'; - -// 自定义规模测试 -await runEntityCreationBenchmark([1000, 5000, 10000]); - -// 组件性能测试 -await runComponentPerformanceTest(); - -// 查询性能测试 -await runQueryPerformanceTest(); -``` - -## 🏆 性能总结 - -### 🎯 核心能力 -1. **实体创建速度**: 最高64万实体/秒 -2. **大规模处理**: 50万实体仅需3秒创建 -3. **均衡性能**: 各组件开销分布合理 -4. **扩展性**: 性能随规模线性下降,无突然悬崖 - -### 🔧 技术特点 -1. **批量操作架构** - 大幅减少单次操作开销 -2. **智能缓存策略** - 延迟清理机制 -3. **索引系统优化** - 避免O(n)操作 -4. **内存管理优化** - 对象池和位掩码缓存 - -### 🌟 实际应用价值 -- **小型游戏**: 性能过剩,专注玩法 -- **中型游戏**: 性能充足,适度优化 -- **大型游戏**: 需要优化策略,但完全可行 -- **服务器端**: 可处理大规模实体管理 - ---- - -**结论**: ECS框架达到了产品级性能标准,能够满足从休闲小游戏到复杂RTS游戏的各种需求。框架层面的性能已经充分优化,为开发者提供了坚实的性能基础。 \ No newline at end of file diff --git a/docs/query-system-usage.md b/docs/query-system-usage.md index 781266d3..60cfd65f 100644 --- a/docs/query-system-usage.md +++ b/docs/query-system-usage.md @@ -36,7 +36,7 @@ const noneResult = querySystem.queryNone(DeadComponent); ```typescript // 类型安全的查询,返回实体和对应的组件 -const typedResult = querySystem.queryAllTyped(PositionComponent, VelocityComponent); +const typedResult = querySystem.queryAll(PositionComponent, VelocityComponent); for (let i = 0; i < typedResult.entities.length; i++) { const entity = typedResult.entities[i]; const [position, velocity] = typedResult.components[i]; @@ -158,9 +158,6 @@ querySystem.warmUpCache(commonQueries); ### 2. 索引优化 ```typescript -// 自动优化索引配置 -querySystem.optimizeIndexes(); - // 获取性能统计 const stats = querySystem.getStats(); console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`); @@ -205,10 +202,14 @@ console.log(`新增: ${diff.added.length}, 移除: ${diff.removed.length}`); ### 移动系统示例 ```typescript -import { EntitySystem } from '@esengine/ecs-framework'; +import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework'; class MovementSystem extends EntitySystem { - public update(): void { + constructor() { + super(Matcher.empty().all(PositionComponent, VelocityComponent)); + } + + protected process(entities: Entity[]): void { // 查询所有可移动的实体 const movableEntities = this.scene.querySystem.queryTwoComponents( PositionComponent, @@ -236,7 +237,11 @@ class MovementSystem extends EntitySystem { ```typescript class CollisionSystem extends EntitySystem { - public update(): void { + constructor() { + super(Matcher.empty().all(PositionComponent, ColliderComponent)); + } + + protected process(entities: Entity[]): void { // 获取所有具有碰撞器的实体 const collidableEntities = this.scene.querySystem.queryTwoComponents( PositionComponent, @@ -276,7 +281,11 @@ class CollisionSystem extends EntitySystem { ```typescript class HealthSystem extends EntitySystem { - public update(): void { + constructor() { + super(Matcher.empty().all(HealthComponent)); + } + + protected process(entities: Entity[]): void { // 查询所有具有生命值的实体 const healthEntities = this.scene.querySystem.queryComponentTyped(HealthComponent); const deadEntities: Entity[] = []; diff --git a/docs/scene-management-guide.md b/docs/scene-management-guide.md new file mode 100644 index 00000000..7f3a391f --- /dev/null +++ b/docs/scene-management-guide.md @@ -0,0 +1,734 @@ +# 场景管理完整指南 + +场景(Scene)是ECS框架中管理游戏对象和系统的核心容器。本指南将详细介绍如何有效地使用场景来构建和管理你的游戏。 + +## 场景基础概念 + +### 什么是场景? + +场景是一个完整的游戏世界容器,它包含: +- 🎮 **实体集合** - 所有游戏对象 +- ⚙️ **系统集合** - 处理游戏逻辑的系统 +- 📊 **事件系统** - 场景内的事件通信 +- 🔍 **查询系统** - 高效的实体查询 +- 📈 **性能监控** - 场景级别的性能统计 + +```typescript +import { Scene, Core } from '@esengine/ecs-framework'; + +// 创建场景 +const gameScene = new Scene(); + +// 设置为当前活动场景 +Core.scene = gameScene; +``` + +### 场景的生命周期 + +```typescript +class GameScene extends Scene { + // 场景开始时调用 + onStart() { + console.log("场景开始"); + this.initializeScene(); + } + + // 场景更新时调用(每帧) + update() { + super.update(); // 调用父类更新 + + // 自定义更新逻辑 + this.updateGameLogic(); + } + + // 场景结束时调用 + onDestroy() { + console.log("场景结束"); + this.cleanup(); + super.onDestroy(); + } +} +``` + +## 基础场景操作 + +### 1. 创建和配置场景 + +```typescript +class MenuScene extends Scene { + private backgroundMusic: AudioClip; + + onStart() { + this.setupUI(); + this.setupSystems(); + this.setupInput(); + this.playBackgroundMusic(); + } + + private setupUI() { + // 创建菜单UI实体 + const titleEntity = this.createEntity("Title"); + titleEntity.addComponent(new TextComponent("我的游戏", 48)); + titleEntity.addComponent(new PositionComponent(400, 100)); + + const startButton = this.createEntity("StartButton"); + startButton.addComponent(new ButtonComponent("开始游戏")); + startButton.addComponent(new PositionComponent(400, 300)); + + const settingsButton = this.createEntity("SettingsButton"); + settingsButton.addComponent(new ButtonComponent("设置")); + settingsButton.addComponent(new PositionComponent(400, 400)); + + const exitButton = this.createEntity("ExitButton"); + exitButton.addComponent(new ButtonComponent("退出")); + exitButton.addComponent(new PositionComponent(400, 500)); + } + + private setupSystems() { + // 添加UI相关系统 + this.addEntityProcessor(new UIRenderSystem()); + this.addEntityProcessor(new ButtonClickSystem()); + this.addEntityProcessor(new MenuTransitionSystem()); + } + + private setupInput() { + // 监听按钮点击事件 + this.eventBus.on('button:clicked', this.onButtonClicked, this); + } + + private onButtonClicked(data: { buttonName: string }) { + switch (data.buttonName) { + case "开始游戏": + this.transitionToGame(); + break; + case "设置": + this.showSettings(); + break; + case "退出": + this.exitGame(); + break; + } + } + + private transitionToGame() { + // 切换到游戏场景 + const gameScene = new GameScene(); + Core.scene = gameScene; + } +} +``` + +### 2. 游戏主场景 + +```typescript +class GameScene extends Scene { + private player: Entity; + private enemySpawner: Entity; + private ui: Entity; + + onStart() { + this.setupWorld(); + this.setupPlayer(); + this.setupEnemies(); + this.setupSystems(); + this.setupUI(); + } + + private setupWorld() { + // 创建背景 + const background = this.createEntity("Background"); + background.addComponent(new SpriteComponent("background.png")); + background.addComponent(new PositionComponent(0, 0)); + + // 创建边界 + this.createWorldBounds(); + } + + private setupPlayer() { + this.player = this.createEntity("Player"); + this.player.addComponent(new PositionComponent(400, 300)); + this.player.addComponent(new VelocityComponent()); + this.player.addComponent(new HealthComponent(100)); + this.player.addComponent(new SpriteComponent("player.png")); + this.player.addComponent(new PlayerInputComponent()); + this.player.addComponent(new WeaponComponent()); + this.player.tag = EntityTags.PLAYER; + } + + private setupEnemies() { + this.enemySpawner = this.createEntity("EnemySpawner"); + this.enemySpawner.addComponent(new SpawnerComponent()); + this.enemySpawner.addComponent(new PositionComponent(0, 0)); + } + + private setupSystems() { + // 输入系统 + this.addEntityProcessor(new PlayerInputSystem()).updateOrder = 0; + + // 游戏逻辑系统 + this.addEntityProcessor(new MovementSystem()).updateOrder = 10; + this.addEntityProcessor(new AISystem()).updateOrder = 15; + this.addEntityProcessor(new WeaponSystem()).updateOrder = 20; + this.addEntityProcessor(new CollisionSystem()).updateOrder = 30; + this.addEntityProcessor(new HealthSystem()).updateOrder = 40; + + // 生成和清理系统 + this.addEntityProcessor(new EnemySpawnSystem()).updateOrder = 50; + this.addEntityProcessor(new EntityCleanupSystem()).updateOrder = 60; + + // 渲染系统 + this.addEntityProcessor(new RenderSystem()).updateOrder = 100; + this.addEntityProcessor(new UIRenderSystem()).updateOrder = 110; + + // 特效和音频系统 + this.addEntityProcessor(new ParticleSystem()).updateOrder = 120; + this.addEntityProcessor(new AudioSystem()).updateOrder = 130; + } + + private setupUI() { + this.ui = this.createEntity("GameUI"); + this.ui.addComponent(new HealthBarComponent()); + this.ui.addComponent(new ScoreDisplayComponent()); + this.ui.addComponent(new AmmoDisplayComponent()); + } + + private createWorldBounds() { + // 创建世界边界,防止实体跑出屏幕 + const bounds = [ + { x: 0, y: 0, width: 10, height: 600 }, // 左边界 + { x: 790, y: 0, width: 10, height: 600 }, // 右边界 + { x: 0, y: 0, width: 800, height: 10 }, // 上边界 + { x: 0, y: 590, width: 800, height: 10 } // 下边界 + ]; + + bounds.forEach((bound, index) => { + const wall = this.createEntity(`Wall_${index}`); + wall.addComponent(new PositionComponent(bound.x, bound.y)); + wall.addComponent(new ColliderComponent(bound.width, bound.height)); + wall.addComponent(new WallComponent()); + wall.tag = EntityTags.WALL; + }); + } +} +``` + +## 场景切换和管理 + +### 1. 场景管理器 + +> **注意:** 以下的 SceneManager、TransitionManager 等是自定义的场景管理类示例,不是ECS框架提供的内置API。你可以基于这些示例实现自己的场景管理系统。 + +```typescript +enum SceneType { + MENU = "menu", + GAME = "game", + PAUSE = "pause", + GAME_OVER = "game_over", + SETTINGS = "settings" +} + +// 自定义场景管理器(示例实现) +class SceneManager { + private static instance: SceneManager; + private currentScene: Scene | null = null; + private previousScene: Scene | null = null; + private sceneHistory: Scene[] = []; + + static getInstance(): SceneManager { + if (!this.instance) { + this.instance = new SceneManager(); + } + return this.instance; + } + + switchToScene(sceneType: SceneType, data?: any) { + // 保存当前场景到历史 + if (this.currentScene) { + this.previousScene = this.currentScene; + this.sceneHistory.push(this.currentScene); + this.currentScene.onDestroy(); + } + + // 创建新场景 + this.currentScene = this.createScene(sceneType, data); + Core.scene = this.currentScene; + + console.log(`切换到场景: ${sceneType}`); + } + + goBack(): boolean { + if (this.sceneHistory.length > 0) { + const previousScene = this.sceneHistory.pop()!; + + if (this.currentScene) { + this.currentScene.onDestroy(); + } + + this.currentScene = previousScene; + Core.scene = this.currentScene; + return true; + } + return false; + } + + pushScene(sceneType: SceneType, data?: any) { + // 暂停当前场景,不销毁 + if (this.currentScene) { + this.previousScene = this.currentScene; + this.sceneHistory.push(this.currentScene); + this.pauseScene(this.currentScene); + } + + this.currentScene = this.createScene(sceneType, data); + Core.scene = this.currentScene; + } + + popScene() { + if (this.sceneHistory.length > 0) { + if (this.currentScene) { + this.currentScene.onDestroy(); + } + + this.currentScene = this.sceneHistory.pop()!; + this.resumeScene(this.currentScene); + Core.scene = this.currentScene; + } + } + + private createScene(sceneType: SceneType, data?: any): Scene { + switch (sceneType) { + case SceneType.MENU: + return new MenuScene(); + case SceneType.GAME: + return new GameScene(data); + case SceneType.PAUSE: + return new PauseScene(); + case SceneType.GAME_OVER: + return new GameOverScene(data); + case SceneType.SETTINGS: + return new SettingsScene(); + default: + throw new Error(`Unknown scene type: ${sceneType}`); + } + } + + private pauseScene(scene: Scene) { + // 暂停场景的所有系统 + scene.systems.forEach(system => { + system.enabled = false; + }); + } + + private resumeScene(scene: Scene) { + // 恢复场景的所有系统 + scene.systems.forEach(system => { + system.enabled = true; + }); + } +} + +// 使用场景管理器 +const sceneManager = SceneManager.getInstance(); + +// 切换场景 +sceneManager.switchToScene(SceneType.MENU); + +// 推入场景(用于暂停菜单等) +sceneManager.pushScene(SceneType.PAUSE); + +// 弹出场景(返回游戏) +sceneManager.popScene(); +``` + +### 2. 场景转场效果 + +```typescript +class TransitionManager { + private isTransitioning: boolean = false; + + async fadeTransition(fromScene: Scene, toScene: Scene, duration: number = 1.0) { + if (this.isTransitioning) return; + + this.isTransitioning = true; + + // 创建转场覆盖层 + const overlay = this.createFadeOverlay(); + + // 淡出当前场景 + await this.fadeOut(overlay, duration / 2); + + // 切换场景 + fromScene.onDestroy(); + Core.scene = toScene; + + // 淡入新场景 + await this.fadeIn(overlay, duration / 2); + + // 清理覆盖层 + overlay.destroy(); + this.isTransitioning = false; + } + + async slideTransition(fromScene: Scene, toScene: Scene, direction: 'left' | 'right' | 'up' | 'down') { + if (this.isTransitioning) return; + + this.isTransitioning = true; + + // 实现滑动转场效果 + const slideDistance = this.getSlideDistance(direction); + + // 移动当前场景 + await this.slideScene(fromScene, slideDistance); + + // 切换场景 + fromScene.onDestroy(); + Core.scene = toScene; + + // 从相反方向滑入新场景 + await this.slideScene(toScene, -slideDistance); + + this.isTransitioning = false; + } + + private createFadeOverlay(): Entity { + const overlay = Core.scene.createEntity("TransitionOverlay"); + overlay.addComponent(new SpriteComponent("black_pixel.png")); + overlay.addComponent(new PositionComponent(0, 0)); + + const sprite = overlay.getComponent(SpriteComponent); + sprite.width = 800; + sprite.height = 600; + sprite.alpha = 0; + + return overlay; + } +} +``` + +## 场景数据管理 + +### 1. 场景间数据传递 + +```typescript +interface GameData { + score: number; + level: number; + playerName: string; + difficulty: string; +} + +class GameScene extends Scene { + private gameData: GameData; + + constructor(data?: GameData) { + super(); + this.gameData = data || { + score: 0, + level: 1, + playerName: "Player", + difficulty: "normal" + }; + } + + onStart() { + super.onStart(); + + // 根据传入数据配置场景 + this.setupPlayerWithData(); + this.setupLevelWithDifficulty(); + } + + private setupPlayerWithData() { + const player = this.createEntity("Player"); + player.addComponent(new NameComponent(this.gameData.playerName)); + player.addComponent(new ScoreComponent(this.gameData.score)); + // ... 其他组件 + } + + private setupLevelWithDifficulty() { + const difficultySettings = { + easy: { enemySpawnRate: 2.0, enemyHealth: 50 }, + normal: { enemySpawnRate: 1.5, enemyHealth: 75 }, + hard: { enemySpawnRate: 1.0, enemyHealth: 100 } + }; + + const settings = difficultySettings[this.gameData.difficulty]; + + const spawner = this.createEntity("EnemySpawner"); + const spawnerComp = new SpawnerComponent(); + spawnerComp.spawnInterval = settings.enemySpawnRate; + spawnerComp.enemyHealth = settings.enemyHealth; + spawner.addComponent(spawnerComp); + } + + // 游戏结束时传递数据到下一个场景 + gameOver() { + const finalScore = this.getPlayerScore(); + const sceneManager = SceneManager.getInstance(); + + sceneManager.switchToScene(SceneType.GAME_OVER, { + score: finalScore, + level: this.gameData.level, + playerName: this.gameData.playerName + }); + } +} + +class GameOverScene extends Scene { + constructor(private gameData: GameData) { + super(); + } + + onStart() { + this.displayResults(); + this.setupRestartButton(); + } + + private displayResults() { + const scoreText = this.createEntity("ScoreText"); + scoreText.addComponent(new TextComponent(`最终分数: ${this.gameData.score}`)); + scoreText.addComponent(new PositionComponent(400, 200)); + + const levelText = this.createEntity("LevelText"); + levelText.addComponent(new TextComponent(`到达关卡: ${this.gameData.level}`)); + levelText.addComponent(new PositionComponent(400, 250)); + } +} +``` + +### 2. 持久化数据管理 + +```typescript +class SaveManager { + private static SAVE_KEY = "game_save_data"; + + static saveScene(scene: Scene): void { + const saveData = { + playerData: this.extractPlayerData(scene), + sceneState: this.extractSceneState(scene), + timestamp: Date.now() + }; + + localStorage.setItem(this.SAVE_KEY, JSON.stringify(saveData)); + console.log("游戏已保存"); + } + + static loadScene(): Scene | null { + const saveDataStr = localStorage.getItem(this.SAVE_KEY); + if (!saveDataStr) return null; + + try { + const saveData = JSON.parse(saveDataStr); + return this.recreateScene(saveData); + } catch (error) { + console.error("读取存档失败:", error); + return null; + } + } + + private static extractPlayerData(scene: Scene): any { + const player = scene.findEntitiesWithTag(EntityTags.PLAYER)[0]; + if (!player) return null; + + return { + position: player.getComponent(PositionComponent), + health: player.getComponent(HealthComponent), + inventory: player.getComponent(InventoryComponent)?.getItems(), + score: player.getComponent(ScoreComponent)?.score + }; + } + + private static extractSceneState(scene: Scene): any { + return { + enemies: this.extractEnemiesData(scene), + items: this.extractItemsData(scene), + level: this.getCurrentLevel(scene) + }; + } + + private static recreateScene(saveData: any): Scene { + const scene = new GameScene(); + + // 重建玩家 + this.recreatePlayer(scene, saveData.playerData); + + // 重建场景状态 + this.recreateSceneState(scene, saveData.sceneState); + + return scene; + } +} + +// 自动保存系统 +class AutoSaveSystem extends IntervalSystem { + constructor() { + super(30.0); // 每30秒自动保存 + } + + processSystem() { + SaveManager.saveScene(this.scene); + } +} +``` + +## 场景性能优化 + +### 1. 实体管理优化 + +```typescript +class OptimizedScene extends Scene { + private activeEntities: Set = new Set(); + private inactiveEntities: Set = new Set(); + + createEntity(name?: string): Entity { + const entity = super.createEntity(name); + this.activeEntities.add(entity); + return entity; + } + + destroyEntity(entity: Entity) { + this.activeEntities.delete(entity); + super.destroyEntity(entity); + } + + // 暂时禁用实体而不销毁 + deactivateEntity(entity: Entity) { + if (this.activeEntities.has(entity)) { + this.activeEntities.delete(entity); + this.inactiveEntities.add(entity); + entity.enabled = false; + } + } + + // 重新激活实体 + activateEntity(entity: Entity) { + if (this.inactiveEntities.has(entity)) { + this.inactiveEntities.delete(entity); + this.activeEntities.add(entity); + entity.enabled = true; + } + } + + // 只更新活跃实体 + update() { + for (const entity of this.activeEntities) { + if (entity.enabled) { + entity.update(); + } + } + + this.updateEntitySystems(); + } + + // 批量操作 + deactivateAllEnemies() { + const enemies = this.findEntitiesWithTag(EntityTags.ENEMY); + enemies.forEach(enemy => this.deactivateEntity(enemy)); + } + + activateAllEnemies() { + const enemies = Array.from(this.inactiveEntities) + .filter(entity => entity.hasTag(EntityTags.ENEMY)); + enemies.forEach(enemy => this.activateEntity(enemy)); + } +} +``` + +### 2. 系统性能监控 + +```typescript +class PerformanceMonitoredScene extends Scene { + private systemPerformance: Map = new Map(); + + addEntityProcessor(system: T): T { + const wrappedSystem = this.wrapSystemWithMonitoring(system); + return super.addEntityProcessor(wrappedSystem); + } + + private wrapSystemWithMonitoring(system: T): T { + const originalUpdate = system.update.bind(system); + const systemName = system.constructor.name; + + system.update = () => { + const startTime = performance.now(); + originalUpdate(); + const endTime = performance.now(); + + this.recordSystemPerformance(systemName, endTime - startTime); + }; + + return system; + } + + private recordSystemPerformance(systemName: string, duration: number) { + if (!this.systemPerformance.has(systemName)) { + this.systemPerformance.set(systemName, []); + } + + const records = this.systemPerformance.get(systemName)!; + records.push(duration); + + // 只保留最近100次记录 + if (records.length > 100) { + records.shift(); + } + } + + getPerformanceReport(): any { + const report = {}; + + this.systemPerformance.forEach((durations, systemName) => { + const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length; + const maxDuration = Math.max(...durations); + const minDuration = Math.min(...durations); + + report[systemName] = { + average: avgDuration.toFixed(2) + 'ms', + max: maxDuration.toFixed(2) + 'ms', + min: minDuration.toFixed(2) + 'ms', + samples: durations.length + }; + }); + + return report; + } + + // 定期输出性能报告 + private performanceReportTimer() { + Core.schedule(5.0, true, this, () => { + console.table(this.getPerformanceReport()); + }); + } +} +``` + +## 常见问题和最佳实践 + +### Q: 何时创建新场景? + +A: +- 游戏的不同阶段(菜单、游戏、设置) +- 不同的关卡 +- 需要完全不同系统配置的情况 +- 需要清理大量实体时 + +### Q: 场景切换时如何保持数据? + +A: +1. 使用场景构造函数传递数据 +2. 使用全局数据管理器 +3. 使用本地存储进行持久化 + +### Q: 如何优化场景性能? + +A: +1. 合理使用实体的启用/禁用 +2. 监控系统性能 +3. 批量操作实体 +4. 使用对象池减少垃圾回收 + +### Q: 多个场景可以同时存在吗? + +A: 框架同时只支持一个活跃场景,但可以通过场景栈实现多场景管理(如暂停菜单)。 + +通过合理使用场景系统,你可以构建出结构清晰、性能优良的游戏架构! \ No newline at end of file diff --git a/docs/system-guide.md b/docs/system-guide.md new file mode 100644 index 00000000..300d2a00 --- /dev/null +++ b/docs/system-guide.md @@ -0,0 +1,515 @@ +# 系统(System)详解指南 + +系统是ECS架构中的"S",负责处理拥有特定组件的实体。本指南详细介绍框架中的各种系统类型及其使用方法。 + +## 系统基础概念 + +### 什么是系统? + +系统是处理游戏逻辑的地方,它们: +- 🎯 **专注单一职责** - 每个系统只处理一种类型的逻辑 +- 🔄 **自动执行** - 系统会在每帧自动被调用 +- 📊 **基于组件过滤** - 只处理包含特定组件的实体 +- ⚡ **高性能** - 利用ECS的数据局部性优势 + +### 系统的工作原理 + +```typescript +// 系统的基本工作流程: +// 1. 查询符合条件的实体 +// 2. 遍历这些实体 +// 3. 读取/修改实体的组件数据 +// 4. 执行游戏逻辑 + +class MovementSystem extends EntitySystem { + process(entities: Entity[]) { + for (const entity of entities) { + const position = entity.getComponent(PositionComponent); + const velocity = entity.getComponent(VelocityComponent); + + // 更新位置 = 当前位置 + 速度 * 时间 + position.x += velocity.x * Time.deltaTime; + position.y += velocity.y * Time.deltaTime; + } + } +} +``` + +## 系统类型详解 + +### 1. EntitySystem - 基础系统 + +最常用的系统类型,每帧处理所有符合条件的实体。 + +```typescript +import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework'; + +class HealthSystem extends EntitySystem { + constructor() { + // 使用Matcher指定需要的组件 + super(Matcher.empty().all(HealthComponent)); + } + + // 主要处理逻辑 + protected process(entities: Entity[]) { + for (const entity of entities) { + const health = entity.getComponent(HealthComponent); + + // 处理生命值逻辑 + if (health.currentHealth <= 0) { + this.handleDeath(entity); + } else if (health.currentHealth < health.maxHealth) { + this.handleRegeneration(health); + } + } + } + + private handleDeath(entity: Entity) { + // 添加死亡标记 + entity.addComponent(new DeadComponent()); + + // 触发死亡事件 + Core.emitter.emit('entity:died', { + entityId: entity.id, + entityName: entity.name + }); + } + + private handleRegeneration(health: HealthComponent) { + // 缓慢恢复生命值 + health.currentHealth += health.regenRate * Time.deltaTime; + health.currentHealth = Math.min(health.currentHealth, health.maxHealth); + } +} +``` + +**适用场景:** +- 移动系统 +- 渲染系统 +- 碰撞检测系统 +- AI系统 + +### 2. ProcessingSystem - 简化处理系统 + +不需要处理具体实体,主要用于执行全局逻辑或不依赖特定实体的系统处理。 + +```typescript +import { ProcessingSystem, Matcher } from '@esengine/ecs-framework'; + +class GameLogicSystem extends ProcessingSystem { + constructor() { + // ProcessingSystem可以不指定Matcher,或使用空Matcher + super(Matcher.empty()); + } + + // 处理系统逻辑(每帧执行) + public processSystem() { + // 执行全局游戏逻辑 + this.updateGameState(); + this.checkWinConditions(); + this.updateUI(); + } + + private updateGameState() { + // 更新游戏状态逻辑 + console.log("更新游戏状态"); + } + + private checkWinConditions() { + // 检查胜利条件 + const players = this.scene.findEntitiesByTag(EntityTags.PLAYER); + const enemies = this.scene.findEntitiesByTag(EntityTags.ENEMY); + + if (enemies.length === 0) { + this.triggerVictory(); + } else if (players.length === 0) { + this.triggerGameOver(); + } + } + + private updateUI() { + // 更新UI显示 + const gameTime = Time.totalTime; + console.log(`游戏时间: ${gameTime.toFixed(1)}秒`); + } +} + + private processIdle(entity: Entity, ai: AIComponent) { + ai.idleTimer += Time.deltaTime; + + if (ai.idleTimer >= ai.idleTime) { + ai.state = AIState.PATROL; + ai.idleTimer = 0; + } + + // 检查附近是否有玩家 + const nearbyPlayer = this.findNearbyPlayer(entity, ai.detectionRange); + if (nearbyPlayer) { + ai.state = AIState.CHASE; + ai.target = nearbyPlayer; + } + } + + private processPatrol(entity: Entity, ai: AIComponent, position: PositionComponent) { + // 简单的来回巡逻 + if (!ai.patrolTarget) { + ai.patrolTarget = this.getNextPatrolPoint(ai); + } + + const direction = ai.patrolTarget.subtract(position); + const distance = direction.length(); + + if (distance < 10) { + ai.patrolTarget = this.getNextPatrolPoint(ai); + } else { + const normalized = direction.normalize(); + position.x += normalized.x * ai.moveSpeed * Time.deltaTime; + position.y += normalized.y * ai.moveSpeed * Time.deltaTime; + } + } +} +``` + +**适用场景:** +- 全局游戏逻辑系统 +- 胜负判断系统 +- UI更新系统 +- 不依赖特定实体的处理 + +### 3. IntervalSystem - 间隔执行系统 + +不是每帧都执行,而是按指定间隔执行的系统,适合不需要高频更新的逻辑。 + +```typescript +import { IntervalSystem, Matcher } from '@esengine/ecs-framework'; + +class SpawnSystem extends IntervalSystem { + private spawnPoints: { x: number; y: number }[] = [ + { x: 100, y: 100 }, + { x: 700, y: 100 }, + { x: 400, y: 500 } + ]; + + // 每2秒执行一次 + constructor() { + // IntervalSystem需要指定Matcher和间隔时间 + super(Matcher.empty().all(SpawnerComponent), 2.0); + } + + // 间隔执行的逻辑(重写process方法) + protected process(entities: Entity[]) { + // entities就是匹配的生成器实体 + + for (const spawner of entities) { + const spawnerComp = spawner.getComponent(SpawnerComponent); + + if (this.shouldSpawn(spawnerComp)) { + this.spawnEnemy(spawner, spawnerComp); + } + } + } + + private shouldSpawn(spawner: SpawnerComponent): boolean { + // 检查是否应该生成 + const currentEnemyCount = this.getCurrentEnemyCount(); + return currentEnemyCount < spawner.maxEnemies && + Math.random() < spawner.spawnChance; + } + + private spawnEnemy(spawnerEntity: Entity, spawner: SpawnerComponent) { + // 随机选择生成点 + const spawnPoint = this.spawnPoints[ + Math.floor(Math.random() * this.spawnPoints.length) + ]; + + // 创建敌人实体 + const enemy = this.scene.createEntity("Enemy"); + enemy.addComponent(new PositionComponent(spawnPoint.x, spawnPoint.y)); + enemy.addComponent(new HealthComponent(50)); + enemy.addComponent(new AIComponent()); + enemy.addComponent(new VelocityComponent(0, 0)); + enemy.tag = EntityTags.ENEMY; + + // 更新生成器统计 + spawner.spawnedCount++; + spawner.lastSpawnTime = Time.totalTime; + + // 发送生成事件 + Core.emitter.emit('enemy:spawned', { + enemyId: enemy.id, + spawnPoint: spawnPoint, + spawnerEntity: spawnerEntity.id + }); + } +} +``` + +**适用场景:** +- 敌人生成系统 +- 自动保存系统 +- 资源回收系统 +- 定期数据同步 + +### 4. PassiveSystem - 被动系统 + +不主动遍历实体,而是响应事件的系统。 + +```typescript +import { PassiveSystem, Matcher, Core } from '@esengine/ecs-framework'; + +class ScoreSystem extends PassiveSystem { + private score: number = 0; + private multiplier: number = 1; + private combo: number = 0; + + constructor() { + // PassiveSystem也需要Matcher,即使不使用 + super(Matcher.empty()); + } + + initialize() { + super.initialize(); + + // 监听游戏事件(使用Core.emitter) + Core.emitter.addObserver('enemy:killed', this.onEnemyKilled, this); + Core.emitter.addObserver('item:collected', this.onItemCollected, this); + Core.emitter.addObserver('combo:broken', this.onComboBroken, this); + } + + // PassiveSystem被移除时清理 + destroy() { + // 清理事件监听 + Core.emitter.removeObserver('enemy:killed', this.onEnemyKilled, this); + Core.emitter.removeObserver('item:collected', this.onItemCollected, this); + Core.emitter.removeObserver('combo:broken', this.onComboBroken, this); + } + + private onEnemyKilled(data: { enemyType: string; position: { x: number; y: number } }) { + // 根据敌人类型给分 + let baseScore = this.getScoreForEnemyType(data.enemyType); + + // 连击奖励 + this.combo++; + if (this.combo > 3) { + this.multiplier = Math.min(this.combo * 0.1, 3.0); // 最多3倍 + } + + const finalScore = Math.floor(baseScore * this.multiplier); + this.addScore(finalScore); + + // 显示分数奖励 + this.showScorePopup(data.position, finalScore); + } + + private addScore(points: number) { + this.score += points; + + // 发送分数更新事件 + Core.emitter.emit('score:updated', { + score: this.score, + points: points, + multiplier: this.multiplier, + combo: this.combo + }); + } +} +``` + +**适用场景:** +- 分数统计系统 +- 音效播放系统 +- UI更新系统 +- 成就系统 + +## 系统管理和注册 + +### 在场景中添加系统 + +```typescript +import { Scene, Core } from '@esengine/ecs-framework'; + +const scene = new Scene(); + +// 添加各种系统(使用addEntityProcessor方法) +scene.addEntityProcessor(new MovementSystem()); +scene.addEntityProcessor(new GameLogicSystem()); +scene.addEntityProcessor(new SpawnSystem()); +scene.addEntityProcessor(new ScoreSystem()); + +// 设置系统的执行优先级 +const movementSystem = scene.getEntityProcessor(MovementSystem); +if (movementSystem) { + movementSystem.updateOrder = 10; // 数值越小越先执行 +} + +const renderSystem = scene.getEntityProcessor(RenderSystem); +if (renderSystem) { + renderSystem.updateOrder = 100; // 渲染系统最后执行 +} + +// 设置为当前场景 +Core.scene = scene; +``` + +### 系统的启用和禁用 + +```typescript +// 暂时禁用某个系统 +const gameLogicSystem = scene.getEntityProcessor(GameLogicSystem); +if (gameLogicSystem) { + gameLogicSystem.enabled = false; +} + +// 重新启用 +if (gameLogicSystem) { + gameLogicSystem.enabled = true; +} + +// 移除系统 +scene.removeEntityProcessor(gameLogicSystem); +``` + +## 系统设计最佳实践 + +### 1. 单一职责原则 + +```typescript +// ✅ 好的设计:每个系统只负责一件事 +class MovementSystem extends EntitySystem { + // 只负责移动 +} + +class CollisionSystem extends EntitySystem { + // 只负责碰撞检测 +} + +class RenderSystem extends EntitySystem { + // 只负责渲染 +} + +// ❌ 不好的设计:一个系统做太多事情 +class GameplaySystem extends EntitySystem { + // 既处理移动,又处理碰撞,还处理渲染... +} +``` + +### 2. 合理的系统执行顺序 + +```typescript +// 设置合理的执行顺序 +scene.addEntityProcessor(new InputSystem()).updateOrder = 0; // 输入最先 +scene.addEntityProcessor(new GameLogicSystem()).updateOrder = 10; // 游戏逻辑 +scene.addEntityProcessor(new MovementSystem()).updateOrder = 20; // 移动计算 +scene.addEntityProcessor(new CollisionSystem()).updateOrder = 30; // 碰撞检测 +scene.addEntityProcessor(new HealthSystem()).updateOrder = 40; // 生命值处理 +scene.addEntityProcessor(new RenderSystem()).updateOrder = 100; // 渲染最后 +``` + +### 3. 系统间通信 + +```typescript +// 使用事件进行系统间通信 +class CollisionSystem extends EntitySystem { + process(entities: Entity[]) { + // ... 碰撞检测逻辑 + + if (collision) { + // 发送碰撞事件,让其他系统响应 + Core.emitter.emit('collision:detected', { + entity1: collider1, + entity2: collider2, + collisionPoint: point + }); + } + } +} + +class HealthSystem extends PassiveSystem { + onAddedToScene() { + // 监听碰撞事件 + Core.emitter.addObserver('collision:detected', this.onCollision, this); + } + + private onCollision(data: CollisionEventData) { + // 处理碰撞伤害 + if (data.entity1.hasComponent(HealthComponent)) { + const health = data.entity1.getComponent(HealthComponent); + health.takeDamage(10); + } + } +} +``` + +### 4. 性能优化 + +```typescript +class OptimizedMovementSystem extends EntitySystem { + private lastUpdateTime: number = 0; + private readonly UPDATE_INTERVAL = 16; // 60FPS + + process(entities: Entity[]) { + const currentTime = Time.totalTime; + + // 限制更新频率 + if (currentTime - this.lastUpdateTime < this.UPDATE_INTERVAL) { + return; + } + + // 批量处理 + this.processBatch(entities); + + this.lastUpdateTime = currentTime; + } + + private processBatch(entities: Entity[]) { + // 使用for循环而不是forEach,性能更好 + for (let i = 0; i < entities.length; i++) { + const entity = entities[i]; + // 处理逻辑... + } + } +} +``` + +## 常见问题 + +### Q: 系统的执行顺序重要吗? + +A: 非常重要!合理的执行顺序可以避免逻辑错误: + +```typescript +// 正确顺序: +// 1. 输入系统(收集玩家输入) +// 2. AI系统(敌人决策) +// 3. 移动系统(更新位置) +// 4. 碰撞系统(检测碰撞) +// 5. 渲染系统(显示画面) +``` + +### Q: 什么时候使用哪种系统类型? + +A: +- **EntitySystem** - 大部分游戏逻辑(移动、AI、碰撞等) +- **ProcessingSystem** - 复杂的单实体处理(复杂AI、粒子系统) +- **IntervalSystem** - 不需要每帧执行的逻辑(生成器、自动保存) +- **PassiveSystem** - 事件响应系统(分数、音效、UI更新) + +### Q: 系统可以访问其他系统吗? + +A: 不建议直接访问。推荐使用事件系统进行系统间通信,保持松耦合。 + +### Q: 如何调试系统性能? + +A: 使用框架内置的性能监控: + +```typescript +const monitor = PerformanceMonitor.instance; +monitor.startFrame('MovementSystem'); +// 系统逻辑... +monitor.endFrame('MovementSystem'); + +// 查看性能报告 +console.log(monitor.getReport()); +``` + +通过合理使用这些系统类型,你可以构建出高性能、易维护的游戏逻辑! \ No newline at end of file diff --git a/docs/timer-guide.md b/docs/timer-guide.md new file mode 100644 index 00000000..221eebfa --- /dev/null +++ b/docs/timer-guide.md @@ -0,0 +1,651 @@ +# 定时器系统使用指南 + +定时器系统是游戏开发中的重要工具,用于处理延迟执行、重复任务、倒计时等功能。本指南详细介绍如何使用ECS框架的定时器系统。 + +## 定时器基础概念 + +### 什么是定时器? + +定时器允许你: +- ⏰ **延迟执行** - 在指定时间后执行某个操作 +- 🔄 **重复执行** - 定期重复执行某个操作 +- 🛑 **取消执行** - 在执行前取消定时器 +- 🎯 **精确控制** - 精确控制执行时机 + +### 定时器的优势 + +相比直接在游戏循环中计时,定时器系统提供: +- 🧹 **自动管理** - 自动处理定时器的生命周期 +- 🎮 **游戏时间控制** - 支持游戏暂停、时间缩放 +- 💾 **内存优化** - 自动回收完成的定时器 +- 🔧 **易于使用** - 简单的API调用 + +## 基础定时器使用 + +### 1. 简单延迟执行 + +```typescript +import { Core, Timer } from '@esengine/ecs-framework'; + +// 3秒后执行一次 +Core.schedule(3.0, false, this, (timer) => { + console.log("3秒钟到了!"); +}); + +// 实际游戏例子:延迟显示提示 +class GameTutorial { + startTutorial() { + // 2秒后显示第一个提示 + Core.schedule(2.0, false, this, () => { + this.showTip("欢迎来到游戏世界!"); + }); + + // 5秒后显示移动提示 + Core.schedule(5.0, false, this, () => { + this.showTip("使用WASD键移动角色"); + }); + + // 8秒后显示攻击提示 + Core.schedule(8.0, false, this, () => { + this.showTip("按空格键攻击敌人"); + }); + } + + private showTip(message: string) { + // 显示提示的逻辑 + console.log(`提示: ${message}`); + } +} +``` + +### 2. 重复执行 + +```typescript +// 每1秒执行一次,持续执行 +const repeatTimer = Core.schedule(1.0, true, this, (timer) => { + console.log("每秒执行一次"); +}); + +// 实际游戏例子:生命值恢复 +class HealthRegeneration { + private regenTimer: ITimer; + + startRegeneration(entity: Entity) { + const health = entity.getComponent(HealthComponent); + + // 每2秒恢复5点生命值 + this.regenTimer = Core.schedule(2.0, true, this, () => { + if (health.currentHealth < health.maxHealth) { + health.currentHealth += 5; + health.currentHealth = Math.min(health.currentHealth, health.maxHealth); + + console.log(`生命值恢复:${health.currentHealth}/${health.maxHealth}`); + + // 满血时停止恢复 + if (health.currentHealth >= health.maxHealth) { + this.stopRegeneration(); + } + } + }); + } + + stopRegeneration() { + if (this.regenTimer) { + this.regenTimer.stop(); + this.regenTimer = null; + } + } +} +``` + +### 3. 获取定时器引用进行控制 + +```typescript +import { ITimer } from '@esengine/ecs-framework'; + +class BombTimer { + private bombTimer: ITimer; + private explosionTime: number = 5.0; + + startBomb(position: { x: number; y: number }) { + console.log("炸弹已放置!5秒后爆炸..."); + + // 创建定时器并保存引用 + this.bombTimer = Core.schedule(this.explosionTime, false, this, () => { + this.explode(position); + }); + } + + defuseBomb() { + if (this.bombTimer && !this.bombTimer.isDone) { + // 拆除炸弹 + this.bombTimer.stop(); + console.log("炸弹已被拆除!"); + } + } + + getRemainingTime(): number { + if (this.bombTimer && !this.bombTimer.isDone) { + return this.explosionTime - this.bombTimer.elapsedTime; + } + return 0; + } + + private explode(position: { x: number; y: number }) { + console.log("💥 炸弹爆炸了!"); + // 爆炸效果逻辑... + } +} +``` + +## 高级定时器功能 + +### 1. 定时器链 - 顺序执行多个任务 + +```typescript +class CutsceneManager { + playCutscene() { + // 第一个镜头:2秒 + Core.schedule(2.0, false, this, () => { + this.showScene("开场镜头"); + + // 第二个镜头:3秒后 + Core.schedule(3.0, false, this, () => { + this.showScene("角色登场"); + + // 第三个镜头:2秒后 + Core.schedule(2.0, false, this, () => { + this.showScene("背景介绍"); + + // 结束:1秒后 + Core.schedule(1.0, false, this, () => { + this.endCutscene(); + }); + }); + }); + }); + } + + private showScene(sceneName: string) { + console.log(`播放场景: ${sceneName}`); + } + + private endCutscene() { + console.log("过场动画结束,开始游戏!"); + } +} + +// 更优雅的链式写法 +class ImprovedCutsceneManager { + playCutscene() { + this.scheduleSequence([ + { delay: 2.0, action: () => this.showScene("开场镜头") }, + { delay: 3.0, action: () => this.showScene("角色登场") }, + { delay: 2.0, action: () => this.showScene("背景介绍") }, + { delay: 1.0, action: () => this.endCutscene() } + ]); + } + + private scheduleSequence(sequence: Array<{delay: number, action: () => void}>) { + let currentDelay = 0; + + sequence.forEach(step => { + currentDelay += step.delay; + Core.schedule(currentDelay, false, this, step.action); + }); + } +} +``` + +### 2. 条件定时器 - 满足条件时执行 + +```typescript +class ConditionalTimer { + waitForCondition( + condition: () => boolean, + action: () => void, + checkInterval: number = 0.1, + timeout: number = 10.0 + ) { + let timeElapsed = 0; + + const checkTimer = Core.schedule(checkInterval, true, this, () => { + timeElapsed += checkInterval; + + if (condition()) { + // 条件满足,执行动作并停止检查 + checkTimer.stop(); + action(); + } else if (timeElapsed >= timeout) { + // 超时,停止检查 + checkTimer.stop(); + console.log("等待条件超时"); + } + }); + } +} + +// 使用例子 +class WaitForPlayerExample { + waitForPlayerToReachGoal() { + const player = this.getPlayer(); + const goalPosition = { x: 500, y: 300 }; + + this.waitForCondition( + // 条件:玩家到达目标位置 + () => { + const playerPos = player.getComponent(PositionComponent); + return playerPos.distanceTo(goalPosition) < 50; + }, + // 动作:触发下一关 + () => { + console.log("玩家到达目标!开始下一关"); + this.loadNextLevel(); + }, + 0.1, // 每0.1秒检查一次 + 30.0 // 30秒后超时 + ); + } +} +``` + +### 3. 可暂停的定时器 + +```typescript +class PausableTimer { + private timers: ITimer[] = []; + private isPaused: boolean = false; + + schedule(delay: number, repeat: boolean, callback: () => void): ITimer { + const timer = Core.schedule(delay, repeat, this, callback); + this.timers.push(timer); + return timer; + } + + pauseAll() { + this.isPaused = true; + this.timers.forEach(timer => { + if (!timer.isDone) { + timer.stop(); + } + }); + } + + resumeAll() { + if (!this.isPaused) return; + + this.isPaused = false; + // 重新启动所有未完成的定时器 + // 注意:这是简化实现,实际需要保存剩余时间 + this.timers = this.timers.filter(timer => !timer.isDone); + } + + clearAll() { + this.timers.forEach(timer => timer.stop()); + this.timers = []; + } +} + +// 游戏暂停系统 +class GamePauseSystem { + private gameTimers: PausableTimer = new PausableTimer(); + private isGamePaused: boolean = false; + + pauseGame() { + if (this.isGamePaused) return; + + this.isGamePaused = true; + this.gameTimers.pauseAll(); + + // 显示暂停菜单 + this.showPauseMenu(); + } + + resumeGame() { + if (!this.isGamePaused) return; + + this.isGamePaused = false; + this.gameTimers.resumeAll(); + + // 隐藏暂停菜单 + this.hidePauseMenu(); + } + + scheduleGameTimer(delay: number, repeat: boolean, callback: () => void) { + return this.gameTimers.schedule(delay, repeat, callback); + } +} +``` + +## 实际游戏应用示例 + +### 1. Buff/Debuff 系统 + +```typescript +class BuffSystem { + applyBuff(entity: Entity, buffType: string, duration: number) { + const buff = new BuffComponent(buffType, duration); + entity.addComponent(buff); + + // 应用Buff效果 + this.applyBuffEffect(entity, buffType); + + // 设置定时器移除Buff + Core.schedule(duration, false, this, () => { + if (!entity.isDestroyed && entity.hasComponent(BuffComponent)) { + this.removeBuff(entity, buffType); + } + }); + + console.log(`应用了 ${buffType} Buff,持续时间 ${duration} 秒`); + } + + private applyBuffEffect(entity: Entity, buffType: string) { + const stats = entity.getComponent(StatsComponent); + + switch (buffType) { + case 'speed_boost': + stats.moveSpeed *= 1.5; + break; + case 'damage_boost': + stats.damage *= 2.0; + break; + case 'invincible': + entity.addComponent(new InvincibleComponent()); + break; + } + } + + private removeBuff(entity: Entity, buffType: string) { + const buff = entity.getComponent(BuffComponent); + if (buff && buff.buffType === buffType) { + entity.removeComponent(buff); + this.removeBuffEffect(entity, buffType); + console.log(`${buffType} Buff 已过期`); + } + } +} +``` + +### 2. 技能冷却系统 + +```typescript +class SkillSystem { + private cooldowns: Map = new Map(); + + useSkill(player: Entity, skillName: string): boolean { + // 检查冷却 + if (this.isOnCooldown(skillName)) { + const remainingTime = this.getCooldownRemaining(skillName); + console.log(`技能冷却中,还需 ${remainingTime.toFixed(1)} 秒`); + return false; + } + + // 执行技能 + this.executeSkill(player, skillName); + + // 启动冷却 + const cooldownTime = this.getSkillCooldown(skillName); + this.startCooldown(skillName, cooldownTime); + + return true; + } + + private startCooldown(skillName: string, duration: number) { + const endTime = Time.totalTime + duration; + this.cooldowns.set(skillName, endTime); + + // 设置定时器清理冷却 + Core.schedule(duration, false, this, () => { + this.cooldowns.delete(skillName); + console.log(`技能 ${skillName} 冷却完成!`); + }); + } + + private isOnCooldown(skillName: string): boolean { + const endTime = this.cooldowns.get(skillName); + return endTime !== undefined && Time.totalTime < endTime; + } + + private getCooldownRemaining(skillName: string): number { + const endTime = this.cooldowns.get(skillName); + return endTime ? Math.max(0, endTime - Time.totalTime) : 0; + } + + private executeSkill(player: Entity, skillName: string) { + switch (skillName) { + case 'fireball': + this.castFireball(player); + break; + case 'heal': + this.castHeal(player); + break; + case 'dash': + this.performDash(player); + break; + } + } + + private getSkillCooldown(skillName: string): number { + const cooldowns = { + 'fireball': 3.0, + 'heal': 10.0, + 'dash': 5.0 + }; + return cooldowns[skillName] || 1.0; + } +} +``` + +### 3. 关卡时间限制 + +```typescript +class LevelTimer { + private timeLimit: number; + private timeRemaining: number; + private timerActive: boolean = false; + private updateTimer: ITimer; + + startLevel(timeLimitSeconds: number) { + this.timeLimit = timeLimitSeconds; + this.timeRemaining = timeLimitSeconds; + this.timerActive = true; + + // 每秒更新倒计时 + this.updateTimer = Core.schedule(1.0, true, this, () => { + this.updateCountdown(); + }); + + console.log(`关卡开始!时间限制:${timeLimitSeconds} 秒`); + } + + private updateCountdown() { + if (!this.timerActive) return; + + this.timeRemaining--; + + // 更新UI显示 + this.updateTimerUI(this.timeRemaining); + + // 时间警告 + if (this.timeRemaining === 30) { + console.log("⚠️ 警告:还剩30秒!"); + this.playWarningSound(); + } else if (this.timeRemaining === 10) { + console.log("🚨 紧急:还剩10秒!"); + this.playUrgentSound(); + } + + // 时间到 + if (this.timeRemaining <= 0) { + this.timeUp(); + } + } + + private timeUp() { + this.timerActive = false; + this.updateTimer.stop(); + + console.log("⏰ 时间到!游戏结束"); + + // 触发游戏结束 + Core.emitter.emit('level:timeout'); + } + + completeLevel() { + if (this.timerActive) { + this.timerActive = false; + this.updateTimer.stop(); + + const completionTime = this.timeLimit - this.timeRemaining; + console.log(`🎉 关卡完成!用时:${completionTime} 秒`); + + // 根据剩余时间给予奖励 + this.calculateTimeBonus(this.timeRemaining); + } + } + + private calculateTimeBonus(timeLeft: number) { + const bonus = Math.floor(timeLeft * 10); // 每秒剩余10分 + if (bonus > 0) { + console.log(`时间奖励:${bonus} 分`); + Core.emitter.emit('score:time_bonus', { bonus }); + } + } + + getTimeRemaining(): number { + return this.timeRemaining; + } + + getTimeRemainingFormatted(): string { + const minutes = Math.floor(this.timeRemaining / 60); + const seconds = this.timeRemaining % 60; + return `${minutes}:${seconds.toString().padStart(2, '0')}`; + } +} +``` + +## 定时器性能优化 + +### 1. 定时器池化 + +```typescript +class TimerPool { + private static instance: TimerPool; + private timerPool: ITimer[] = []; + + static getInstance(): TimerPool { + if (!this.instance) { + this.instance = new TimerPool(); + } + return this.instance; + } + + getTimer(): ITimer { + return this.timerPool.pop() || this.createTimer(); + } + + releaseTimer(timer: ITimer) { + timer.stop(); + this.timerPool.push(timer); + } + + private createTimer(): ITimer { + // 创建新定时器的逻辑 + return new Timer(); + } +} +``` + +### 2. 批量定时器管理 + +```typescript +class BatchTimerManager { + private timers: Set = new Set(); + + scheduleMany(configs: Array<{delay: number, repeat: boolean, callback: () => void}>) { + return configs.map(config => { + const timer = Core.schedule(config.delay, config.repeat, this, config.callback); + this.timers.add(timer); + return timer; + }); + } + + stopAll() { + this.timers.forEach(timer => timer.stop()); + this.timers.clear(); + } + + cleanup() { + // 清理已完成的定时器 + this.timers.forEach(timer => { + if (timer.isDone) { + this.timers.delete(timer); + } + }); + } +} +``` + +## 常见问题和最佳实践 + +### Q: 定时器会自动清理吗? + +A: 是的,完成的定时器会自动清理。但如果需要提前停止,记得调用 `timer.stop()`。 + +### Q: 定时器会受到游戏暂停影响吗? + +A: 定时器使用游戏时间,如果实现了时间缩放功能,定时器会相应调整。 + +### Q: 如何实现精确的帧同步定时器? + +A: 使用帧计数而不是时间: + +```typescript +class FrameTimer { + private frameCount: number = 0; + private targetFrame: number; + + scheduleFrames(frames: number, callback: () => void) { + this.targetFrame = this.frameCount + frames; + + const checkFrame = () => { + this.frameCount++; + if (this.frameCount >= this.targetFrame) { + callback(); + } else { + requestAnimationFrame(checkFrame); + } + }; + + requestAnimationFrame(checkFrame); + } +} +``` + +### Q: 如何避免定时器内存泄漏? + +A: +1. 及时停止不需要的定时器 +2. 在对象销毁时清理所有定时器 +3. 使用弱引用避免循环引用 + +```typescript +class SafeTimerUser { + private timers: ITimer[] = []; + + scheduleTimer(delay: number, callback: () => void) { + const timer = Core.schedule(delay, false, this, callback); + this.timers.push(timer); + return timer; + } + + destroy() { + // 清理所有定时器 + this.timers.forEach(timer => timer.stop()); + this.timers = []; + } +} +``` + +定时器是游戏开发中非常有用的工具,合理使用可以让你的游戏逻辑更加优雅和高效! \ No newline at end of file diff --git a/docs/use-cases.md b/docs/use-cases.md new file mode 100644 index 00000000..ebbf2b94 --- /dev/null +++ b/docs/use-cases.md @@ -0,0 +1,600 @@ +# ECS框架使用场景示例 + +本文档展示ECS框架在不同类型游戏中的具体应用案例。 + +## 目录 + +1. [小型休闲游戏](#小型休闲游戏) +2. [中型动作游戏](#中型动作游戏) +3. [大型策略游戏](#大型策略游戏) +4. [MMO游戏](#mmo游戏) + +## 小型休闲游戏 + +### 场景:简单的飞机大战游戏 + +```typescript +import { + Scene, + EntityManager, + Entity, + Component, + EntitySystem, + Matcher +} from '@esengine/ecs-framework'; + +// 组件定义 +class PositionComponent extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +class VelocityComponent extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +class PlayerComponent extends Component {} +class EnemyComponent extends Component {} +class BulletComponent extends Component {} + +// 游戏管理器 +class PlaneWarGame { + private scene: Scene; + private entityManager: EntityManager; + + constructor() { + this.scene = new Scene(); + this.entityManager = new EntityManager(); + this.setupGame(); + } + + private setupGame(): void { + // 创建玩家 + const player = this.entityManager.createEntity("Player"); + player.addComponent(new PositionComponent(400, 500)); + player.addComponent(new VelocityComponent(0, 0)); + player.addComponent(new PlayerComponent()); + player.tag = 1; // 玩家标签 + + // 创建敌人 + this.spawnEnemies(5); + + // 添加系统 + this.scene.addEntityProcessor(new MovementSystem()); + this.scene.addEntityProcessor(new CollisionSystem()); + this.scene.addEntityProcessor(new CleanupSystem()); + } + + private spawnEnemies(count: number): void { + const enemies = this.scene.createEntities(count, "Enemy"); + enemies.forEach((enemy, index) => { + enemy.addComponent(new PositionComponent( + Math.random() * 800, + -50 + )); + enemy.addComponent(new VelocityComponent(0, 100)); + enemy.addComponent(new EnemyComponent()); + enemy.tag = 2; // 敌人标签 + }); + } + + public update(): void { + this.scene.update(); + } +} + +// 移动系统 +class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(PositionComponent, VelocityComponent)); + } + + protected process(entities: Entity[]): void { + const movingEntities = this.scene.querySystem.queryAll( + PositionComponent, + VelocityComponent + ); + + movingEntities.entities.forEach(entity => { + const pos = entity.getComponent(PositionComponent); + const vel = entity.getComponent(VelocityComponent); + + pos.x += vel.x * Time.deltaTime; + pos.y += vel.y * Time.deltaTime; + }); + } +} + +## 中型动作游戏 + +### 场景:2D平台跳跃游戏 + +```typescript +// 更复杂的组件 +class HealthComponent extends Component { + constructor( + public maxHealth: number = 100, + public currentHealth: number = 100 + ) { + super(); + } +} + +class AnimationComponent extends Component { + constructor( + public currentAnimation: string = "idle", + public frameIndex: number = 0, + public frameTime: number = 0 + ) { + super(); + } +} + +class PhysicsComponent extends Component { + constructor( + public mass: number = 1, + public friction: number = 0.8, + public isGrounded: boolean = false + ) { + super(); + } +} + +// 平台游戏管理器 +class PlatformGame { + private scene: Scene; + private entityManager: EntityManager; + + constructor() { + this.scene = new Scene(); + this.entityManager = new EntityManager(); + this.setupGame(); + } + + private setupGame(): void { + // 创建玩家 + this.createPlayer(); + + // 创建敌人 + this.createEnemies(10); + + // 创建平台 + this.createPlatforms(); + + // 添加系统(按更新顺序) + this.scene.addEntityProcessor(new InputSystem()).updateOrder = 10; + this.scene.addEntityProcessor(new PhysicsSystem()).updateOrder = 20; + this.scene.addEntityProcessor(new AnimationSystem()).updateOrder = 30; + this.scene.addEntityProcessor(new CombatSystem()).updateOrder = 40; + this.scene.addEntityProcessor(new RenderSystem()).updateOrder = 50; + } + + private createPlayer(): void { + const player = this.entityManager.createEntity("Player"); + player.addComponent(new PositionComponent(100, 300)); + player.addComponent(new VelocityComponent(0, 0)); + player.addComponent(new HealthComponent(100)); + player.addComponent(new AnimationComponent("idle")); + player.addComponent(new PhysicsComponent(1, 0.8)); + player.tag = 1; + } + + private createEnemies(count: number): void { + const enemies = this.scene.createEntities(count, "Enemy"); + enemies.forEach((enemy, index) => { + enemy.addComponent(new PositionComponent( + 200 + index * 100, + 300 + )); + enemy.addComponent(new VelocityComponent(0, 0)); + enemy.addComponent(new HealthComponent(50)); + enemy.addComponent(new AnimationComponent("patrol")); + enemy.addComponent(new PhysicsComponent(0.8, 0.9)); + enemy.tag = 2; + }); + } + + private createPlatforms(): void { + const platforms = this.scene.createEntities(5, "Platform"); + platforms.forEach((platform, index) => { + platform.addComponent(new PositionComponent( + index * 200, + 400 + Math.random() * 100 + )); + platform.tag = 3; // 平台标签 + }); + } +} + +## 大型策略游戏 + +### 场景:即时战略游戏 + +```typescript +// 策略游戏组件 +class UnitComponent extends Component { + constructor( + public unitType: string, + public playerId: number, + public level: number = 1 + ) { + super(); + } +} + +class AIComponent extends Component { + constructor( + public state: string = "idle", + public target: Entity | null = null, + public lastDecisionTime: number = 0 + ) { + super(); + } +} + +class ResourceComponent extends Component { + constructor( + public gold: number = 0, + public wood: number = 0, + public food: number = 0 + ) { + super(); + } +} + +// 策略游戏管理器 +class StrategyGame { + private scene: Scene; + private entityManager: EntityManager; + private players: Map = new Map(); + + constructor() { + this.scene = new Scene(); + this.entityManager = new EntityManager(); + this.setupGame(); + } + + private setupGame(): void { + // 创建玩家 + this.createPlayers(4); + + // 为每个玩家创建初始单位 + this.players.forEach((player, playerId) => { + this.createInitialUnits(playerId, 10); + }); + + // 添加系统 + this.scene.addEntityProcessor(new AISystem()).updateOrder = 10; + this.scene.addEntityProcessor(new CombatSystem()).updateOrder = 20; + this.scene.addEntityProcessor(new ResourceSystem()).updateOrder = 30; + this.scene.addEntityProcessor(new UnitManagementSystem()).updateOrder = 40; + } + + private createPlayers(count: number): void { + for (let i = 0; i < count; i++) { + const player = this.entityManager.createEntity(`Player_${i}`); + player.addComponent(new ResourceComponent(1000, 500, 100)); + player.tag = 10 + i; // 玩家标签从10开始 + this.players.set(i, player); + } + } + + private createInitialUnits(playerId: number, count: number): void { + const units = this.scene.createEntities(count, `Unit_${playerId}`); + + units.forEach((unit, index) => { + unit.addComponent(new PositionComponent( + playerId * 200 + Math.random() * 100, + playerId * 200 + Math.random() * 100 + )); + unit.addComponent(new UnitComponent("warrior", playerId)); + unit.addComponent(new HealthComponent(100)); + unit.addComponent(new AIComponent()); + unit.tag = 20 + playerId; // 单位标签 + }); + } + + // 批量单位操作 + public createArmy(playerId: number, unitType: string, count: number): Entity[] { + const units = this.scene.createEntities(count, `${unitType}_${playerId}`); + + // 批量配置组件 + units.forEach(unit => { + unit.addComponent(new UnitComponent(unitType, playerId)); + unit.addComponent(new HealthComponent(100)); + unit.addComponent(new PositionComponent( + Math.random() * 1000, + Math.random() * 1000 + )); + unit.tag = 20 + playerId; + }); + + return units; + } + + // 查询玩家的所有单位 + public getPlayerUnits(playerId: number): Entity[] { + return this.entityManager + .query() + .withAll(UnitComponent) + .withTag(20 + playerId) + .execute(); + } + + // 查询特定类型的单位 + public getUnitsByType(unitType: string): Entity[] { + return this.entityManager + .query() + .withAll(UnitComponent) + .where(entity => { + const unit = entity.getComponent(UnitComponent); + return unit && unit.unitType === unitType; + }) + .execute(); + } +} + +// AI系统 +class AISystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(AIComponent, UnitComponent)); + } + + protected process(entities: Entity[]): void { + const aiUnits = this.entityManager + .query() + .withAll(AIComponent, UnitComponent) + .execute(); + + aiUnits.forEach(unit => { + this.processAI(unit); + }); + } + + private processAI(unit: Entity): void { + const ai = unit.getComponent(AIComponent); + const unitComp = unit.getComponent(UnitComponent); + + if (!ai || !unitComp) return; + + // 简单AI逻辑 + switch (ai.state) { + case "idle": + this.findTarget(unit); + break; + case "attack": + this.attackTarget(unit); + break; + case "move": + this.moveToTarget(unit); + break; + } + } + + private findTarget(unit: Entity): void { + const unitComp = unit.getComponent(UnitComponent); + if (!unitComp) return; + + // 查找敌方单位 + const enemies = this.entityManager + .query() + .withAll(UnitComponent) + .where(entity => { + const enemyUnit = entity.getComponent(UnitComponent); + return enemyUnit && enemyUnit.playerId !== unitComp.playerId; + }) + .execute(); + + if (enemies.length > 0) { + const ai = unit.getComponent(AIComponent); + if (ai) { + ai.target = enemies[0]; + ai.state = "attack"; + } + } + } + + private attackTarget(unit: Entity): void { + // 攻击逻辑 + } + + private moveToTarget(unit: Entity): void { + // 移动逻辑 + } +} + +## MMO游戏 + +### 场景:大型多人在线游戏 + +```typescript +// MMO特有组件 +class NetworkComponent extends Component { + constructor( + public playerId: string, + public isLocal: boolean = false, + public lastSyncTime: number = 0 + ) { + super(); + } +} + +class InventoryComponent extends Component { + public items: Map = new Map(); + + addItem(itemId: string, count: number): void { + const current = this.items.get(itemId) || 0; + this.items.set(itemId, current + count); + } +} + +class GuildComponent extends Component { + constructor( + public guildId: string, + public rank: string = "member" + ) { + super(); + } +} + +// MMO游戏管理器 +class MMOGame { + private scene: Scene; + private entityManager: EntityManager; + private localPlayerId: string; + + constructor(localPlayerId: string) { + this.scene = new Scene(); + this.entityManager = new EntityManager(); + this.localPlayerId = localPlayerId; + this.setupGame(); + } + + private setupGame(): void { + // 添加MMO特有系统 + this.scene.addEntityProcessor(new NetworkSyncSystem()).updateOrder = 5; + this.scene.addEntityProcessor(new PlayerSystem()).updateOrder = 10; + this.scene.addEntityProcessor(new NPCSystem()).updateOrder = 15; + this.scene.addEntityProcessor(new GuildSystem()).updateOrder = 20; + this.scene.addEntityProcessor(new InventorySystem()).updateOrder = 25; + } + + // 创建玩家角色 + public createPlayer(playerId: string, isLocal: boolean = false): Entity { + const player = this.entityManager.createEntity(`Player_${playerId}`); + player.addComponent(new PositionComponent(0, 0)); + player.addComponent(new HealthComponent(1000)); + player.addComponent(new NetworkComponent(playerId, isLocal)); + player.addComponent(new InventoryComponent()); + player.tag = isLocal ? 1 : 2; // 本地玩家标签1,远程玩家标签2 + + return player; + } + + // 批量创建NPC + public createNPCs(count: number): Entity[] { + const npcs = this.scene.createEntities(count, "NPC"); + + npcs.forEach((npc, index) => { + npc.addComponent(new PositionComponent( + Math.random() * 2000, + Math.random() * 2000 + )); + npc.addComponent(new HealthComponent(500)); + npc.addComponent(new AIComponent("patrol")); + npc.tag = 3; // NPC标签 + }); + + return npcs; + } + + // 查询附近的玩家 + public getNearbyPlayers(centerX: number, centerY: number, radius: number): Entity[] { + return this.entityManager + .query() + .withAll(PositionComponent, NetworkComponent) + .where(entity => { + const pos = entity.getComponent(PositionComponent); + if (!pos) return false; + + const distance = Math.sqrt( + Math.pow(pos.x - centerX, 2) + + Math.pow(pos.y - centerY, 2) + ); + return distance <= radius; + }) + .execute(); + } + + // 查询公会成员 + public getGuildMembers(guildId: string): Entity[] { + return this.entityManager + .query() + .withAll(GuildComponent, NetworkComponent) + .where(entity => { + const guild = entity.getComponent(GuildComponent); + return guild && guild.guildId === guildId; + }) + .execute(); + } + + // 获取在线玩家统计 + public getOnlinePlayerStats(): any { + const allPlayers = this.entityManager.getEntitiesWithComponent(NetworkComponent); + const localPlayers = this.entityManager.getEntitiesByTag(1); + const remotePlayers = this.entityManager.getEntitiesByTag(2); + + return { + total: allPlayers.length, + local: localPlayers.length, + remote: remotePlayers.length + }; + } +} + +// 网络同步系统 +class NetworkSyncSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(NetworkComponent)); + } + + protected process(entities: Entity[]): void { + const networkEntities = this.entityManager.getEntitiesWithComponent(NetworkComponent); + + networkEntities.forEach(entity => { + const network = entity.getComponent(NetworkComponent); + if (!network || network.isLocal) return; + + // 同步远程实体数据 + this.syncRemoteEntity(entity); + }); + } + + private syncRemoteEntity(entity: Entity): void { + // 网络同步逻辑 + const network = entity.getComponent(NetworkComponent); + if (!network) return; + + const currentTime = Date.now(); + if (currentTime - network.lastSyncTime > 100) { // 100ms同步一次 + // 发送同步数据 + network.lastSyncTime = currentTime; + } + } +} + +## 性能优化建议 + +### 小型游戏(< 1000实体) +- 使用简单的查询方法 +- 不需要复杂的优化 +- 重点关注代码可读性 + +### 中型游戏(1000-10000实体) +- 使用标签查询优化性能 +- 实现基础的对象池 +- 缓存频繁查询的结果 + +### 大型游戏(10000-100000实体) +- 使用时间分片处理大量实体 +- 实现空间分区优化邻近查询 +- 使用批量操作减少单次调用开销 + +### MMO游戏(100000+实体) +- 实现分区管理,只处理相关区域的实体 +- 使用异步处理避免阻塞主线程 +- 实现智能缓存和预加载机制 + +## 总结 + +ECS框架的灵活性使其能够适应各种规模的游戏开发需求: + +1. **小型游戏**:简单直接,快速开发 +2. **中型游戏**:平衡性能和复杂度 +3. **大型游戏**:充分利用优化特性 +4. **MMO游戏**:处理海量实体和复杂交互 + +选择合适的架构模式和优化策略,可以让ECS框架在不同场景下都发挥最佳性能。 \ No newline at end of file diff --git a/src/ECS/Entity.ts b/src/ECS/Entity.ts index 457cb2f7..eb8d236b 100644 --- a/src/ECS/Entity.ts +++ b/src/ECS/Entity.ts @@ -181,8 +181,8 @@ class ComponentCache { * // 获取组件 * const health = entity.getComponent(HealthComponent); * - * // 设置位置 - * entity.position = new Vector2(100, 200); + * // 添加位置组件 + * entity.addComponent(new PositionComponent(100, 200)); * * // 添加子实体 * const weapon = new Entity("Weapon", 2); diff --git a/src/ECS/Scene.ts b/src/ECS/Scene.ts index 3c90e14c..67abb43c 100644 --- a/src/ECS/Scene.ts +++ b/src/ECS/Scene.ts @@ -6,7 +6,6 @@ import { EntitySystem } from './Systems/EntitySystem'; import { ComponentStorageManager } from './Core/ComponentStorage'; import { QuerySystem } from './Core/QuerySystem'; import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem'; -import type { IScene } from '../Types'; /** * 游戏场景类 diff --git a/src/Types/index.ts b/src/Types/index.ts index 42ab59da..6faed231 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -2,27 +2,6 @@ * 框架核心类型定义 */ -/** 更新顺序比较器接口 */ -export interface IUpdateOrderComparable { - updateOrder: number; -} - -/** 日志类型枚举 */ -export enum LogType { - Error = 0, - Assert = 1, - Warning = 2, - Log = 3, - Exception = 4 -} - -/** 组件变换类型枚举 */ -export enum ComponentTransform { - Position = 1, - Scale = 2, - Rotation = 4 -} - /** * 组件接口 * @@ -51,136 +30,14 @@ export interface IComponent { } /** - * 实体接口 + * 系统基础接口 * - * 定义实体的基本契约,所有实体都应该实现此接口 - */ -export interface IEntity { - /** 实体唯一标识符 */ - readonly id: string | number; - /** 实体名称 */ - name: string; - /** 实体激活状态 */ - active: boolean; - /** 实体启用状态 */ - enabled: boolean; - /** 实体是否已销毁 */ - readonly isDestroyed: boolean; - /** 更新顺序 */ - updateOrder: number; - /** 实体标签 */ - tag: number; - - /** 添加组件 */ - addComponent(component: T): T; - /** 创建并添加组件 */ - createComponent(componentType: ComponentType, ...args: any[]): T; - /** 获取组件 */ - getComponent(type: ComponentType): T | null; - /** 获取或创建组件 */ - getOrCreateComponent(type: ComponentType, ...args: any[]): T; - /** 移除组件 */ - removeComponent(component: IComponent): void; - /** 通过类型移除组件 */ - removeComponentByType(type: ComponentType): T | null; - /** 检查是否有组件 */ - hasComponent(type: ComponentType): boolean; - /** 获取所有指定类型的组件 */ - getComponents(type: ComponentType): T[]; - - /** 添加子实体 */ - addChild(child: IEntity): IEntity; - /** 移除子实体 */ - removeChild(child: IEntity): boolean; - /** 查找子实体 */ - findChild(name: string, recursive?: boolean): IEntity | null; - - /** 更新实体 */ - update(): void; - /** 销毁实体 */ - destroy(): void; -} - -/** - * 实体基础接口(向后兼容) - * - * 为现有的Entity类提供更灵活的类型定义 - */ -export interface IEntityBase { - /** 实体唯一标识符 */ - readonly id: string | number; - /** 实体名称 */ - name: string; - /** 实体激活状态 */ - active: boolean; - /** 实体启用状态 */ - enabled: boolean; - /** 实体是否已销毁 */ - readonly isDestroyed: boolean; - /** 更新顺序 */ - updateOrder: number; - /** 实体标签 */ - tag: number; - - /** 添加组件(泛型版本) */ - addComponent(component: T): T; - /** 获取组件(泛型版本) */ - getComponent(type: ComponentClass): T | null; - /** 检查是否有组件(泛型版本) */ - hasComponent(type: ComponentClass): boolean; - - /** 添加子实体 */ - addChild(child: any): any; - /** 移除子实体 */ - removeChild(child: any): boolean; - /** 查找子实体 */ - findChild(name: string, recursive?: boolean): any; - - /** 更新实体 */ - update(): void; - /** 销毁实体 */ - destroy(): void; -} - -/** - * 系统接口 - * - * 定义系统的基本契约,所有系统都应该实现此接口 - */ -export interface ISystem { - /** 系统名称 */ - readonly systemName: string; - /** 系统处理的实体列表 */ - readonly entities: readonly IEntity[]; - /** 更新顺序/优先级 */ - updateOrder: number; - /** 系统启用状态 */ - enabled: boolean; - - /** 系统初始化 */ - initialize(): void; - /** 更新系统(主要处理阶段) */ - update(): void; - /** 延迟更新系统 */ - lateUpdate?(): void; - - /** 当实体添加到系统时的回调 */ - onEntityAdded?(entity: IEntity): void; - /** 当实体从系统移除时的回调 */ - onEntityRemoved?(entity: IEntity): void; - /** 当实体组件发生变化时的回调 */ - onEntityChanged?(entity: IEntity): void; -} - -/** - * 系统基础接口(向后兼容) - * - * 为现有的EntitySystem类提供更灵活的类型定义 + * 为现有的EntitySystem类提供类型定义 */ export interface ISystemBase { /** 系统名称 */ readonly systemName: string; - /** 系统处理的实体列表(泛型版本) */ + /** 系统处理的实体列表 */ readonly entities: readonly any[]; /** 更新顺序/优先级 */ updateOrder: number; @@ -195,39 +52,6 @@ export interface ISystemBase { lateUpdate?(): void; } -/** - * 场景接口 - * - * 定义场景的基本契约 - */ -export interface IScene { - /** 场景名称 */ - name: string; - /** 场景中的实体列表 */ - readonly entities: readonly IEntity[]; - /** 场景中的系统列表 */ - readonly systems: readonly ISystem[]; - - /** 创建实体 */ - createEntity(name: string): IEntity; - /** 添加实体到场景 */ - addEntity(entity: IEntity): void; - /** 从场景移除实体 */ - removeEntity(entity: IEntity): void; - /** 查找实体 */ - findEntity(name: string): IEntity | null; - - /** 添加系统到场景 */ - addSystem(system: T): T; - /** 从场景移除系统 */ - removeSystem(system: ISystem): void; - /** 获取系统 */ - getSystem(systemType: new (...args: any[]) => T): T | null; - - /** 更新场景 */ - update(): void; -} - /** * 组件类型定义 * @@ -235,170 +59,6 @@ export interface IScene { */ export type ComponentType = new (...args: any[]) => T; -/** - * 原始组件类型(向后兼容) - * - * 用于与现有Component类的兼容 - */ -export type ComponentClass = new (...args: any[]) => T; - -/** - * 实体查询匹配器接口 - * - * 用于查询符合特定条件的实体 - */ -export interface IMatcher { - /** 必须包含的组件类型 */ - all(...componentTypes: ComponentType[]): IMatcher; - /** 至少包含其中一个组件类型 */ - any(...componentTypes: ComponentType[]): IMatcher; - /** 不能包含的组件类型 */ - exclude(...componentTypes: ComponentType[]): IMatcher; - /** 检查实体是否匹配 */ - isInterestedEntity(entity: IEntity): boolean; -} - -/** - * 性能监控接口 - */ -export interface IPerformanceData { - /** 执行时间(毫秒) */ - executionTime: number; - /** 调用次数 */ - callCount: number; - /** 平均执行时间 */ - averageTime: number; - /** 最大执行时间 */ - maxTime: number; - /** 最小执行时间 */ - minTime: number; -} - -/** - * 生命周期管理接口 - */ -export interface ILifecycle { - /** 初始化 */ - initialize?(): void; - /** 启动 */ - start?(): void; - /** 更新 */ - update?(): void; - /** 延迟更新 */ - lateUpdate?(): void; - /** 停止 */ - stop?(): void; - /** 销毁 */ - destroy?(): void; -} - -/** - * 实体管理器接口 - * - * 提供统一的实体管理和查询机制,支持高效的实体操作 - */ -export interface IEntityManager { - /** 所有实体数量 */ - readonly entityCount: number; - /** 激活的实体数量 */ - readonly activeEntityCount: number; - - /** 创建实体 */ - createEntity(name?: string): IEntity; - /** 销毁实体 */ - destroyEntity(entity: IEntity | string | number): boolean; - /** 批量销毁实体 */ - destroyEntities(entities: (IEntity | string | number)[]): number; - /** 销毁所有实体 */ - destroyAllEntities(): number; - - /** 根据ID获取实体 */ - getEntity(id: string | number): IEntity | null; - /** 根据名称获取实体 */ - getEntityByName(name: string): IEntity | null; - /** 根据名称获取所有实体 */ - getEntitiesByName(name: string): IEntity[]; - /** 根据标签获取实体 */ - getEntitiesByTag(tag: number): IEntity[]; - /** 获取所有实体 */ - getAllEntities(): IEntity[]; - /** 获取所有激活的实体 */ - getActiveEntities(): IEntity[]; - - /** 获取拥有指定组件的实体 */ - getEntitiesWithComponent(componentType: ComponentType): IEntity[]; - /** 获取拥有指定组件的实体及其组件 */ - getEntitiesWithComponentData(componentType: ComponentType): Array<{entity: IEntity, component: T}>; - /** 获取拥有所有指定组件的实体 */ - getEntitiesWithComponents(...componentTypes: ComponentType[]): IEntity[]; - /** 获取拥有任一指定组件的实体 */ - getEntitiesWithAnyComponent(...componentTypes: ComponentType[]): IEntity[]; - /** 获取不包含指定组件的实体 */ - getEntitiesWithoutComponent(componentType: ComponentType): IEntity[]; - - /** 对所有实体执行操作 */ - forEachEntity(action: (entity: IEntity) => void): void; - /** 对符合条件的实体执行操作 */ - forEachEntityWhere(predicate: (entity: IEntity) => boolean, action: (entity: IEntity) => void): void; - /** 对拥有指定组件的实体执行操作 */ - forEachEntityWithComponent( - componentType: ComponentType, - action: (entity: IEntity, component: T) => void - ): void; - - /** 查找第一个符合条件的实体 */ - findEntity(predicate: (entity: IEntity) => boolean): IEntity | null; - /** 查找所有符合条件的实体 */ - findEntities(predicate: (entity: IEntity) => boolean): IEntity[]; - - /** 获取统计信息 */ - getStatistics(): { - totalEntities: number; - activeEntities: number; - destroyedEntities: number; - entitiesByTag: Map; - componentsCount: Map; - }; - - /** 清理已销毁的实体 */ - cleanup(): number; - /** 压缩存储空间 */ - compact(): void; -} - -/** - * 实体查询构建器接口 - * - * 提供流式API构建复杂的实体查询条件 - */ -export interface IEntityQueryBuilder { - /** 必须包含所有指定组件 */ - withAll(...componentTypes: ComponentType[]): IEntityQueryBuilder; - /** 必须包含任一指定组件 */ - withAny(...componentTypes: ComponentType[]): IEntityQueryBuilder; - /** 必须不包含指定组件 */ - without(...componentTypes: ComponentType[]): IEntityQueryBuilder; - /** 必须包含指定标签 */ - withTag(tag: number): IEntityQueryBuilder; - /** 必须不包含指定标签 */ - withoutTag(tag: number): IEntityQueryBuilder; - /** 必须处于激活状态 */ - active(): IEntityQueryBuilder; - /** 必须处于启用状态 */ - enabled(): IEntityQueryBuilder; - /** 自定义过滤条件 */ - where(predicate: (entity: IEntity) => boolean): IEntityQueryBuilder; - - /** 执行查询,返回所有匹配的实体 */ - execute(): IEntity[]; - /** 执行查询,返回第一个匹配的实体 */ - first(): IEntity | null; - /** 执行查询,返回匹配实体的数量 */ - count(): number; - /** 对查询结果执行操作 */ - forEach(action: (entity: IEntity) => void): void; -} - /** * 事件总线接口 * 提供类型安全的事件发布订阅机制 diff --git a/src/Utils/Timers/Timer.ts b/src/Utils/Timers/Timer.ts index b6e555d1..31fb465e 100644 --- a/src/Utils/Timers/Timer.ts +++ b/src/Utils/Timers/Timer.ts @@ -16,6 +16,20 @@ export class Timer implements ITimer{ return this.context as T; } + /** + * 定时器是否已完成 + */ + public get isDone(): boolean { + return this._isDone; + } + + /** + * 定时器已运行的时间 + */ + public get elapsedTime(): number { + return this._elapsedTime; + } + public reset(): void { this._elapsedTime = 0; } diff --git a/src/performance.ts b/src/performance.ts deleted file mode 100644 index 0519ecba..00000000 --- a/src/performance.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file