From 413ce93b315dcd0c1bd23864ed62b3c381fdffcd Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Sun, 28 Sep 2025 12:26:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docs.yml | 61 + .gitignore | 2 + README.md | 448 +----- docs/.vitepress/config.mjs | 167 ++ docs/beginner-tutorials.md | 187 --- docs/component-design-guide.md | 704 --------- docs/concepts-explained.md | 513 ------ docs/core-concepts.md | 926 ----------- docs/entity-guide.md | 370 ----- docs/entity-manager-example.md | 370 ----- docs/event-system-example.md | 496 ------ docs/examples/index.md | 3 + docs/getting-started.md | 689 -------- docs/guide/component.md | 359 +++++ docs/guide/entity.md | 288 ++++ docs/guide/event-system.md | 595 +++++++ docs/guide/getting-started.md | 394 +++++ docs/guide/index.md | 26 + docs/guide/logging.md | 550 +++++++ docs/guide/scene.md | 510 ++++++ docs/guide/system.md | 596 +++++++ docs/guide/time-and-timers.md | 553 +++++++ docs/index.md | 23 + docs/performance-optimization.md | 425 ----- docs/query-system-usage.md | 646 -------- docs/scene-management-guide.md | 1183 -------------- docs/soa-storage-guide.md | 343 ---- docs/system-guide.md | 662 -------- docs/timer-guide.md | 653 -------- docs/use-cases.md | 600 ------- examples/lawn-mower-demo | 2 +- package-lock.json | 2508 +++++++++++++++++++++++++++++- package.json | 16 +- typedoc.json | 63 + 34 files changed, 6750 insertions(+), 9181 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/.vitepress/config.mjs delete mode 100644 docs/beginner-tutorials.md delete mode 100644 docs/component-design-guide.md delete mode 100644 docs/concepts-explained.md delete mode 100644 docs/core-concepts.md delete mode 100644 docs/entity-guide.md delete mode 100644 docs/entity-manager-example.md delete mode 100644 docs/event-system-example.md create mode 100644 docs/examples/index.md delete mode 100644 docs/getting-started.md create mode 100644 docs/guide/component.md create mode 100644 docs/guide/entity.md create mode 100644 docs/guide/event-system.md create mode 100644 docs/guide/getting-started.md create mode 100644 docs/guide/index.md create mode 100644 docs/guide/logging.md create mode 100644 docs/guide/scene.md create mode 100644 docs/guide/system.md create mode 100644 docs/guide/time-and-timers.md create mode 100644 docs/index.md delete mode 100644 docs/performance-optimization.md delete mode 100644 docs/query-system-usage.md delete mode 100644 docs/scene-management-guide.md delete mode 100644 docs/soa-storage-guide.md delete mode 100644 docs/system-guide.md delete mode 100644 docs/timer-guide.md delete mode 100644 docs/use-cases.md create mode 100644 typedoc.json diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..59928c09 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,61 @@ +name: Deploy Documentation + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Install dependencies + run: npm ci + + - name: Build core package + run: npm run build:core + + - name: Generate API documentation + run: npm run docs:api + + - name: Build documentation + run: npm run docs:build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 18327e16..20fffb0e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,8 @@ pnpm-lock.yaml # 文档生成 docs/api/ docs/build/ +docs/.vitepress/cache/ +docs/.vitepress/dist/ # 备份文件 *.bak diff --git a/README.md b/README.md index 6b0ad2e4..a69c5624 100644 --- a/README.md +++ b/README.md @@ -1,180 +1,20 @@ # ECS Framework -[![Typing SVG](https://readme-typing-svg.demolab.com?font=Fira+Code&weight=600&size=22&pause=1000&color=F75C7E¢er=true&vCenter=true&width=435&lines=TypeScript+ECS+Framework;高性能游戏开发框架;支持+Cocos+Creator+%26+Laya)](https://git.io/typing-svg) - [![CI](https://github.com/esengine/ecs-framework/workflows/CI/badge.svg)](https://github.com/esengine/ecs-framework/actions) [![npm version](https://badge.fury.io/js/%40esengine%2Fecs-framework.svg)](https://badge.fury.io/js/%40esengine%2Fecs-framework) -[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitHub stars](https://img.shields.io/github/stars/esengine/ecs-framework?style=social)](https://github.com/esengine/ecs-framework/stargazers) -TypeScript ECS (Entity-Component-System) 框架,专为游戏开发设计。 - -## 项目特色 - -
- -[![Cocos Store](https://img.shields.io/badge/Cocos_Store-专业插件-FF6B35?style=flat&logo=cocos&logoColor=white)](https://store.cocos.com/app/detail/7823) -[![QQ群](https://img.shields.io/badge/QQ群-框架交流-1EAEDB?style=flat&logo=tencentqq&logoColor=white)](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - -
- -## 架构原理 - -ECS Framework 采用多World + 多Scene的现代化架构设计: - -```mermaid -graph TD - subgraph Main["🎮 ECS Framework - 多World・多Scene架构"] - direction TB - - subgraph CoreLayer["⚙️ 核心层 (Core Foundation)"] - direction LR - Core["🔧 Core
📋 生命周期管理
⚙️ 配置系统
🔗 平台兼容"] - Registry["📝 ComponentRegistry
🏷️ 类型注册
✨ 装饰器支持
🔒 类型安全"] - Pool["🔢 IdentifierPool
🆔 实体ID分配
♻️ ID回收
📊 BigInt兼容"] - PoolMgr["♻️ PoolManager
🎯 对象池
⚡ 内存优化
📈 性能提升"] - EventBus["📡 EventBus
🔄 事件系统
⚡ 异步/同步
🎭 类型安全"] - end - - subgraph WorldLayer["🌍 世界管理层 (World Management)"] - direction TB - WorldMgr["🗺️ WorldManager
🚀 多World调度
📊 资源管理
🔍 统计监控
🧹 自动清理"] - - subgraph WorldsContainer["多World容器"] - direction LR - World1["🌐 GameWorld
🎮 游戏逻辑
🌟 全局系统
🔄 跨Scene业务"] - World2["🌐 UIWorld
🎨 界面管理
⚡ 独立更新
🔒 资源隔离"] - end - - GlobalSys["🎭 Global Systems
🌐 NetworkSync
👥 PlayerMgmt
📡 跨Scene通信"] - end - - subgraph SceneLayer["🎬 场景层 (Scene Management)"] - direction LR - Scene1["🎯 BattleScene
⚔️ 实体管理
🎪 系统调度
⚡ 高性能处理"] - Scene2["🎯 MenuScene
🎨 界面逻辑
🔄 生命周期
💾 状态管理"] - Scene3["🎯 UIScene
📦 组件存储
🔍 查询引擎
🎭 交互处理"] - end - - subgraph ECLayer["🤖 实体组件层 (Entity-Component System)"] - direction TB - - subgraph EntityMgmt["📦 实体管理 (Entity Management)"] - direction LR - EntityMgr["👥 EntityManager
📋 集合管理
🌳 层次结构
⚡ 高效操作"] - Entities["🎭 Entities
👤 Player
👹 Enemy
💥 Bullet
🎯 轻量容器"] - end - - subgraph ComponentStore["🧩 组件存储 (Component Storage)"] - direction LR - Storage["💾 ComponentStorage
📊 SoA模式
📚 AoS模式
⚡ 内存优化"] - StorageMgr["🗄️ StorageManager
🏷️ 类型管理
🔄 脏标记
📈 性能监控"] - Components["🎲 Components
📍 Position
🏃 Velocity
❤️ Health
📊 纯数据"] - end - end - - subgraph SystemLayer["⚡ 系统层 (System Processing)"] - direction TB - - subgraph EntitySys["🔄 实体系统 (Entity Systems)"] - direction LR - EntitySystems["🎪 EntitySystems
🏃 MovementSystem
🎨 RenderSystem
🧠 AISystem
⚡ 业务逻辑"] - Processors["📋 EntityProcessors
🎯 调度管理
📊 优先级
⚡ 批量处理"] - end - end - - subgraph QueryLayer["🔍 查询优化层 (Query & Optimization)"] - direction LR - Matcher["🎯 Matcher
✅ withAll
🔄 withAny
❌ withNone
🌊 流式API
💾 智能缓存"] - QuerySys["🔎 QuerySystem
⚡ 实时查询
📦 批量优化
🔄 自动更新"] - Archetype["🏗️ ArchetypeSystem
📊 组件分组
🎯 原型缓存
💻 BitSet优化"] - end - - subgraph DebugLayer["📊 监控调试层 (Debug & Monitoring)"] - direction LR - Debug["🐛 DebugManager
🌐 WebSocket调试
🎮 Cocos Creator插件
📸 内存快照"] - Perf["📈 PerformanceMonitor
📊 性能统计
⚠️ 阈值告警
📱 实时监控"] - Logger["📋 Logger
📊 分级日志
🎨 彩色输出
🔧 自定义处理器"] - end - end - - %% 连接关系 - 使用更丰富的箭头样式 - Core -.->|初始化| WorldMgr - Core -.->|注册| Registry - Core -.->|分配| Pool - Core -.->|管理| PoolMgr - Core -.->|事件| EventBus - - WorldMgr ==>|调度| World1 - WorldMgr ==>|调度| World2 - World1 -.->|管理| GlobalSys - - World1 ==>|包含| Scene1 - World1 ==>|包含| Scene2 - World2 ==>|包含| Scene3 - - Scene1 -->|使用| EntityMgr - Scene2 -->|使用| EntityMgr - Scene3 -->|使用| EntityMgr - - EntityMgr -->|管理| Entities - Entities -->|附加| Components - - Scene1 -->|存储| Storage - Scene2 -->|存储| Storage - Scene3 -->|存储| Storage - Storage -->|管理| StorageMgr - - Scene1 -->|调度| EntitySystems - Scene2 -->|调度| EntitySystems - Scene3 -->|调度| EntitySystems - EntitySystems -->|处理| Processors - - EntitySystems -->|查询| Matcher - Matcher -->|缓存| QuerySys - QuerySys -->|优化| Archetype - - Core -.->|调试| Debug - Core -.->|监控| Perf - Core -.->|日志| Logger - - %% 样式定义 - 使用Mermaid支持的语法 - classDef coreStyle fill:#E3F2FD,stroke:#1976D2,stroke-width:3px,color:#0D47A1 - classDef worldStyle fill:#F3E5F5,stroke:#7B1FA2,stroke-width:3px,color:#4A148C - classDef sceneStyle fill:#FFF3E0,stroke:#F57C00,stroke-width:3px,color:#E65100 - classDef entityStyle fill:#E8F5E8,stroke:#388E3C,stroke-width:3px,color:#1B5E20 - classDef systemStyle fill:#FCE4EC,stroke:#C2185B,stroke-width:3px,color:#880E4F - classDef queryStyle fill:#E0F2F1,stroke:#00695C,stroke-width:3px,color:#004D40 - classDef debugStyle fill:#FFF8E1,stroke:#F9A825,stroke-width:3px,color:#FF8F00 - - class Core,Registry,Pool,PoolMgr,EventBus coreStyle - class WorldMgr,World1,World2,GlobalSys worldStyle - class Scene1,Scene2,Scene3 sceneStyle - class EntityMgr,Entities,Storage,StorageMgr,Components entityStyle - class EntitySystems,Processors systemStyle - class Matcher,QuerySys,Archetype queryStyle - class Debug,Perf,Logger debugStyle -``` - -### 核心概念 - -| 概念 | 职责 | 特点 | -|------|------|------| -| **Entity** | 游戏对象唯一标识 | 轻量级容器,无业务逻辑 | -| **Component** | 纯数据结构 | 描述实体属性,支持SoA优化 | -| **System** | 业务逻辑处理 | 操作组件数据,可热插拔 | -| **Scene** | 实体和系统容器 | 独立的游戏场景 | -| **World** | Scene和全局系统容器 | 支持跨Scene的全局逻辑 | -| **WorldManager** | 多World管理 | 统一调度和资源管理 | +一个高性能的 TypeScript ECS (Entity-Component-System) 框架,专为现代游戏开发而设计。 ## 特性 -- **完整的 TypeScript 支持** - 强类型检查和代码提示 -- **高效查询系统** - 流式 API 和智能缓存 -- **性能优化技术** - SparseSet索引、Archetype 系统、脏标记 -- **事件系统** - 类型安全的事件处理 -- **调试工具** - 内置性能监控和 [Cocos Creator 可视化调试插件](https://store.cocos.com/app/detail/7823) +- **高性能** - 针对大规模实体优化,支持SoA存储和批量处理 +- **类型安全** - 完整的TypeScript支持,编译时类型检查 +- **现代架构** - 支持多World、多Scene的分层架构设计 +- **开发友好** - 内置调试工具和性能监控 +- **跨平台** - 支持Cocos Creator、Laya引擎和Web平台 ## 安装 @@ -184,271 +24,101 @@ npm install @esengine/ecs-framework ## 快速开始 -### 1. 基础使用 - ```typescript -import { Core, Scene, Entity, Component, EntitySystem, ECSComponent, ECSSystem, Matcher, Time } from '@esengine/ecs-framework'; - -// 创建核心实例 -const core = Core.create({ debug: true }); -const scene = new Scene(); -Core.setScene(scene); +import { Core, Scene, Component, EntitySystem, ECSComponent, ECSSystem, Matcher, Time } from '@esengine/ecs-framework'; // 定义组件 -@ECSComponent('PositionComponent') -class PositionComponent extends Component { - public x: number = 0; - public y: number = 0; - - constructor(x: number = 0, y: number = 0) { +@ECSComponent('Position') +class Position extends Component { + constructor(public x = 0, public y = 0) { super(); - this.x = x; - this.y = y; } } -@ECSComponent('VelocityComponent') -class VelocityComponent extends Component { - public x: number = 0; - public y: number = 0; - - constructor(x: number = 0, y: number = 0) { +@ECSComponent('Velocity') +class Velocity extends Component { + constructor(public dx = 0, public dy = 0) { super(); - this.x = x; - this.y = y; } } -// 创建实体 -const entity = scene.createEntity("Player"); -entity.addComponent(new PositionComponent(100, 100)); -entity.addComponent(new VelocityComponent(5, 0)); - // 创建系统 -@ECSSystem('MovementSystem') +@ECSSystem('Movement') class MovementSystem extends EntitySystem { constructor() { - super(Matcher.all(PositionComponent, VelocityComponent)); + super(Matcher.all(Position, Velocity)); } - - protected override process(entities: Entity[]) { + + protected process(entities: readonly Entity[]): void { 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; + const position = entity.getComponent(Position)!; + const velocity = entity.getComponent(Velocity)!; + + position.x += velocity.dx * Time.deltaTime; + position.y += velocity.dy * Time.deltaTime; } } } -scene.addEntityProcessor(new MovementSystem()); +// 创建场景并启动 +class GameScene extends Scene { + protected initialize(): void { + this.addSystem(new MovementSystem()); -// 游戏循环 -Core.update(deltaTime); -``` - -### 2. 类型装饰器 - -在代码压缩混淆后,类名会改变导致框架无法识别组件类型。使用装饰器确保稳定性: - -```typescript -import { ECSComponent, ECSSystem } from '@esengine/ecs-framework'; - -// 组件装饰器 -@ECSComponent('PositionComponent') -class PositionComponent extends Component { - public x: number = 0; - public y: number = 0; -} - -@ECSComponent('VelocityComponent') -class VelocityComponent extends Component { - public x: number = 0; - public y: number = 0; -} - -// 系统装饰器 -@ECSSystem('MovementSystem') -class MovementSystem extends EntitySystem { - constructor() { - super(Matcher.all(PositionComponent, VelocityComponent)); - } - - protected override process(entities: Entity[]) { - // 处理逻辑 - } -} -``` - -## 高级特性 - -### 查询系统 - -```typescript -import { Matcher, ECSSystem } from '@esengine/ecs-framework'; - -// 使用Matcher和EntitySystem进行高效查询 -@ECSSystem('QuerySystem') -class QuerySystem extends EntitySystem { - constructor() { - super(Matcher.all(PositionComponent, VelocityComponent).none(HealthComponent)); - } - - protected override process(entities: Entity[]) { - // 处理匹配的实体 - console.log(`Found ${entities.length} entities`); + const player = this.createEntity("Player"); + player.addComponent(new Position(100, 100)); + player.addComponent(new Velocity(50, 0)); } } -// 更复杂的查询条件 -@ECSSystem('CombatSystem') -class CombatSystem extends EntitySystem { - constructor() { - super( - Matcher - .all(PositionComponent, HealthComponent) // 必须有位置和血量 - .any(WeaponComponent, MagicComponent) // 有武器或魔法 - .none(DeadComponent) // 不能是死亡状态 - ); - } - - protected override process(entities: Entity[]) { - // 处理战斗逻辑 - } -} -``` +// 启动游戏 +Core.create(); +Core.setScene(new GameScene()); -### 事件系统 - -```typescript -import { EventHandler, ECSEventType, IEntityEventData } from '@esengine/ecs-framework'; - -class GameSystem { - @EventHandler(ECSEventType.ENTITY_DESTROYED) - onEntityDestroyed(data: IEntityEventData) { - console.log('实体销毁:', data.entityName, '实体ID:', data.entityId); - } - - @EventHandler(ECSEventType.ENTITY_CREATED) - onEntityCreated(data: IEntityEventData) { - console.log('实体创建:', data.entityName, '标签:', data.entityTag); - } -} -``` - -### SoA 存储优化 - -针对大规模实体处理的内存布局优化: - -| 存储方式 | 内存布局 | 适用场景 | 性能特点 | -|----------|----------|----------|----------| -| **AoS** (Array of Structures) | `[{x,y,z}, {x,y,z}, {x,y,z}]` | 通用场景 | 访问灵活,缓存效率一般 | -| **SoA** (Structure of Arrays) | `{x:[1,2,3], y:[4,5,6], z:[7,8,9]}` | 批量处理 | SIMD优化,缓存友好 | - -**SoA 优势:** -- 🚀 提升 2-4x 批量处理性能 -- 💾 更好的CPU缓存利用率 -- 🔧 支持SIMD向量化操作 -- ⚡ 减少内存访问跳跃 - -用法示例: - -```typescript -import { EnableSoA, Float32, Int32 } from '@esengine/ecs-framework'; - -@EnableSoA -class OptimizedTransformComponent extends Component { - @Float32 public x: number = 0; - @Float32 public y: number = 0; - @Float32 public rotation: number = 0; -} -``` - -**性能优势**: -- **缓存友好** - 连续内存访问,缓存命中率提升85% -- **批量处理** - 同类型数据处理速度提升2-3倍 -- **热切换** - 开发期AoS便于调试,生产期SoA提升性能 -- **自动优化** - `@EnableSoA`装饰器自动转换存储结构 - -## 平台集成 - -### Cocos Creator - -```typescript -update(deltaTime: number) { +// 游戏循环中更新 +function gameLoop(deltaTime: number) { Core.update(deltaTime); } ``` -**专用调试插件**: -- [ECS 可视化调试插件](https://store.cocos.com/app/detail/7823) - 提供完整的可视化调试界面 -- 实体查看器、组件编辑器、系统监控 -- 性能分析和实时数据监控 +## 核心特性 -### Laya 引擎 -```typescript -Laya.timer.frameLoop(1, this, () => { - Core.update(Laya.timer.delta / 1000); -}); -``` +- **实体查询** - 使用 Matcher API 进行高效的实体过滤 +- **事件系统** - 类型安全的事件发布/订阅机制 +- **性能优化** - SoA 存储优化,支持大规模实体处理 +- **多场景** - 支持 World/Scene 分层架构 +- **时间管理** - 内置定时器和时间控制系统 -### 原生浏览器 -```typescript -function gameLoop(currentTime: number) { - const deltaTime = (currentTime - lastTime) / 1000; - Core.update(deltaTime); - requestAnimationFrame(gameLoop); -} -``` +## 平台支持 + +支持主流游戏引擎和 Web 平台: + +- **Cocos Creator** - 内置引擎集成支持,提供[专用调试插件](https://store.cocos.com/app/detail/7823) +- **Laya 引擎** - 完整的生命周期管理 +- **原生 Web** - 浏览器环境直接运行 +- **小游戏平台** - 微信、支付宝等小游戏 -## API 参考 +## 示例项目 -### 核心类 - -| 类 | 描述 | -|---|---| -| `Core` | 框架核心管理 | -| `Scene` | 场景容器 | -| `Entity` | 实体对象 | -| `Component` | 组件基类 | -| `EntitySystem` | 系统基类 | -| `EntityManager` | 实体管理器 | - -### 查询 API - -```typescript -// Matcher API - 推荐方式,高效且类型安全 -Matcher.all(...components) // 包含所有组件 -Matcher.any(...components) // 包含任意组件 -Matcher.none(...components) // 不包含组件 - -// 组合查询示例 -Matcher - .all(PositionComponent, VelocityComponent) // 必须有这些组件 - .any(PlayerComponent, AIComponent) // 其中之一 - .none(DeadComponent, DisabledComponent); // 排除这些 -``` +- [割草机演示](https://github.com/esengine/lawn-mower-demo) - 完整的游戏示例 ## 文档 -- [快速入门](docs/getting-started.md) - 详细教程和平台集成 -- [技术概念](docs/concepts-explained.md) - ECS 架构和框架特性 -- [组件设计](docs/component-design-guide.md) - 组件设计最佳实践 -- [性能优化](docs/performance-optimization.md) - 性能优化技术 -- [API 参考](docs/core-concepts.md) - 完整 API 文档 +- [快速入门](https://esengine.github.io/ecs-framework/guide/getting-started.html) - 详细教程和平台集成 +- [完整指南](https://esengine.github.io/ecs-framework/guide/) - ECS 概念和使用指南 +- [API 参考](https://esengine.github.io/ecs-framework/api/) - 完整 API 文档 -## 扩展库 +## 生态系统 - [路径寻找](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) +- [问题反馈](https://github.com/esengine/ecs-framework/issues) - Bug 报告和功能建议 +- [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - ecs游戏框架交流 ## 许可证 -[MIT](LICENSE) \ No newline at end of file +[MIT](LICENSE) © 2025 ECS Framework \ No newline at end of file diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs new file mode 100644 index 00000000..0a1d816f --- /dev/null +++ b/docs/.vitepress/config.mjs @@ -0,0 +1,167 @@ +import { defineConfig } from 'vitepress' +import Icons from 'unplugin-icons/vite' +import { readFileSync } from 'fs' +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const corePackageJson = JSON.parse( + readFileSync(join(__dirname, '../../packages/core/package.json'), 'utf-8') +) + +export default defineConfig({ + vite: { + plugins: [ + Icons({ + compiler: 'vue3', + autoInstall: true + }) + ] + }, + title: 'ECS Framework', + description: '高性能TypeScript ECS框架 - 为游戏开发而生', + lang: 'zh-CN', + + themeConfig: { + nav: [ + { text: '首页', link: '/' }, + { text: '快速开始', link: '/guide/getting-started' }, + { text: '指南', link: '/guide/' }, + { text: 'API', link: '/api/README' }, + { text: '示例', link: 'https://github.com/esengine/lawn-mower-demo' }, + { + text: `v${corePackageJson.version}`, + link: 'https://github.com/esengine/ecs-framework/releases' + } + ], + + sidebar: { + '/guide/': [ + { + text: '开始使用', + items: [ + { text: '快速开始', link: '/guide/getting-started' }, + { text: '指南概览', link: '/guide/' } + ] + }, + { + text: '核心概念', + collapsed: false, + items: [ + { text: '实体类 (Entity)', link: '/guide/entity' }, + { text: '组件系统 (Component)', link: '/guide/component' }, + { text: '系统架构 (System)', link: '/guide/system' }, + { text: '场景管理 (Scene)', link: '/guide/scene' }, + { text: '事件系统 (Event)', link: '/guide/event-system' }, + { text: '时间和定时器 (Time)', link: '/guide/time-and-timers' }, + { text: '日志系统 (Logger)', link: '/guide/logging' } + ] + } + ], + '/api/': [ + { + text: 'API 参考', + items: [ + { text: '概述', link: '/api/README' }, + { + text: '核心类', + collapsed: false, + items: [ + { text: 'Core', link: '/api/classes/Core' }, + { text: 'Scene', link: '/api/classes/Scene' }, + { text: 'World', link: '/api/classes/World' }, + { text: 'Entity', link: '/api/classes/Entity' }, + { text: 'Component', link: '/api/classes/Component' }, + { text: 'EntitySystem', link: '/api/classes/EntitySystem' } + ] + }, + { + text: '系统类', + collapsed: true, + items: [ + { text: 'PassiveSystem', link: '/api/classes/PassiveSystem' }, + { text: 'ProcessingSystem', link: '/api/classes/ProcessingSystem' }, + { text: 'IntervalSystem', link: '/api/classes/IntervalSystem' } + ] + }, + { + text: '工具类', + collapsed: true, + items: [ + { text: 'Matcher', link: '/api/classes/Matcher' }, + { text: 'Time', link: '/api/classes/Time' }, + { text: 'PerformanceMonitor', link: '/api/classes/PerformanceMonitor' }, + { text: 'DebugManager', link: '/api/classes/DebugManager' } + ] + }, + { + text: '接口', + collapsed: true, + items: [ + { text: 'IScene', link: '/api/interfaces/IScene' }, + { text: 'IComponent', link: '/api/interfaces/IComponent' }, + { text: 'ISystemBase', link: '/api/interfaces/ISystemBase' }, + { text: 'ICoreConfig', link: '/api/interfaces/ICoreConfig' } + ] + }, + { + text: '装饰器', + collapsed: true, + items: [ + { text: '@ECSComponent', link: '/api/functions/ECSComponent' }, + { text: '@ECSSystem', link: '/api/functions/ECSSystem' } + ] + }, + { + text: '枚举', + collapsed: true, + items: [ + { text: 'ECSEventType', link: '/api/enumerations/ECSEventType' }, + { text: 'LogLevel', link: '/api/enumerations/LogLevel' } + ] + } + ] + } + ] + }, + + socialLinks: [ + { icon: 'github', link: 'https://github.com/esengine/ecs-framework' } + ], + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright © 2025 ECS Framework' + }, + + editLink: { + pattern: 'https://github.com/esengine/ecs-framework/edit/master/docs/:path', + text: '在 GitHub 上编辑此页' + }, + + search: { + provider: 'local' + }, + + outline: { + level: [2, 3], + label: '目录' + } + }, + + head: [ + ['meta', { name: 'theme-color', content: '#646cff' }], + ['link', { rel: 'icon', href: '/favicon.ico' }] + ], + + base: '/', + cleanUrls: true, + + markdown: { + lineNumbers: true, + theme: { + light: 'github-light', + dark: 'github-dark' + } + } +}) \ No newline at end of file diff --git a/docs/beginner-tutorials.md b/docs/beginner-tutorials.md deleted file mode 100644 index 96d6343c..00000000 --- a/docs/beginner-tutorials.md +++ /dev/null @@ -1,187 +0,0 @@ -# 新手教程完整指南 - -欢迎使用ECS框架!本指南为新手提供了完整的学习路径,从基础概念到高级应用,帮你快速掌握ECS框架开发游戏。 - -## 学习路径 - -### 第一阶段:基础入门(必读) - -#### 1. [快速开始](getting-started.md) -- **5分钟入门** - 创建你的第一个ECS游戏 -- **环境搭建** - 安装和配置框架 -- **第一个游戏** - 完整的示例游戏 -- **基础API** - 核心功能介绍 - -#### 2. [核心概念](core-concepts.md) -- **ECS架构** - 实体、组件、系统的关系 -- **API参考** - 核心类和方法 -- **最佳实践** - 代码规范和设计模式 -- **查询系统** - 如何高效查找实体 - -#### 3. [概念详解](concepts-explained.md) **新手必读** -- **通俗解释** - 用简单语言解释复杂概念 -- **性能优化技术** - 组件索引、Archetype、脏标记 -- **索引选择指南** - 何时使用哈希索引vs位图索引 -- **应用场景** - 不同游戏类型的选择建议 - -### 第二阶段:核心功能掌握 - -#### 4. [实体管理指南](entity-guide.md) -- **实体基础** - 什么是实体,如何创建和使用 -- **标签系统** - 实体分类和查找 -- **生命周期** - 实体的创建、更新、销毁 -- **简单示例** - 玩家、敌人、道具实体 - -#### 5. [组件设计最佳实践](component-design-guide.md) **设计必读** -- **组件设计原则** - 单一职责、数据为主 -- **组件类型** - 数据组件、标记组件、行为组件 -- **组件通信** - 如何让组件协同工作 -- **性能优化** - 对象池和数据紧凑性 -- **测试和调试** - 如何测试你的组件 - -#### 6. [系统详解指南](system-guide.md) **逻辑必读** -- **四种系统类型** - EntitySystem、ProcessingSystem、IntervalSystem、PassiveSystem -- **使用场景** - 什么时候用哪种系统 -- **执行顺序** - 系统间的依赖关系 -- **系统通信** - 事件驱动的松耦合设计 -- **性能优化** - 批量处理和频率控制 - -### 第三阶段:高级功能应用 - -#### 7. [场景管理指南](scene-management-guide.md) -- **场景概念** - 什么是场景,如何组织游戏世界 -- **场景切换** - 菜单、游戏、暂停场景的切换 -- **数据传递** - 场景间如何传递数据 -- **实际应用** - 完整的游戏场景设计 -- **性能优化** - 场景级别的性能监控 - -#### 8. [定时器系统指南](timer-guide.md) -- **定时器基础** - 延迟执行、重复执行 -- **定时器链** - 顺序执行多个任务 -- **条件定时器** - 等待特定条件满足 -- **可暂停定时器** - 游戏暂停功能 -- **游戏应用** - Buff系统、技能冷却、关卡限时 - -#### 9. [查询系统使用](query-system-usage.md) -- **基础查询** - 按组件查找实体 -- **复杂查询** - 组合条件和排除条件 -- **性能监控** - 查询性能统计 -- **优化技巧** - 提高查询效率 - -#### 10. [事件系统示例](event-system-example.md) -- **事件基础** - 发送和监听事件 -- **游戏事件** - 玩家输入、碰撞、分数等 -- **系统解耦** - 用事件实现系统间通信 -- **事件统计** - 监控事件系统性能 - -### 第四阶段:实战应用 - -#### 11. [实体管理器高级功能](entity-manager-example.md) -- 🏭 **批量操作** - 高效创建和管理大量实体 -- **高级查询** - EntityQueryBuilder的使用 -- **性能监控** - 实体管理性能统计 -- **实际案例** - 弹幕游戏、RTS游戏的实体管理 - -#### 12. [应用案例集合](use-cases.md) -- **不同游戏类型** - 休闲游戏、动作游戏、策略游戏 -- **具体实现** - 完整的代码示例 -- **性能分析** - 各种应用的性能特点 -- **设计思路** - 如何选择合适的架构 - -### 第五阶段:性能优化 - -#### 13. [性能基准测试](performance.md) -- **基准数据** - 框架性能表现 -- **对比分析** - 与其他框架的比较 -- **优化建议** - 针对不同规模的优化策略 -- **性能检查清单** - 确保最佳性能的要点 - -#### 14. [性能优化技术](performance-optimization.md) -- **核心优化** - 组件索引、Archetype、脏标记 -- **内存优化** - 对象池、数据紧凑性 -- **批量处理** - 减少单次操作开销 -- **监控工具** - 性能分析和调试 - -## 推荐学习顺序 - -### 适合完全新手(第一次接触ECS) - -``` -1. 快速开始 → 2. 概念详解 → 3. 核心概念 → 4. 实体管理指南 -→ 5. 组件设计指南 → 6. 系统详解指南 → 7. 应用案例 -``` - -### 适合有游戏开发经验的开发者 - -``` -1. 快速开始 → 2. 核心概念 → 3. 组件设计指南 → 4. 系统详解指南 -→ 5. 场景管理指南 → 6. 性能优化技术 -``` - -### 适合追求高性能的开发者 - -``` -1. 快速开始 → 2. 概念详解(重点看性能优化) → 3. 性能基准测试 -→ 4. 性能优化技术 → 5. 实体管理器高级功能 -``` - -## 常见学习问题 - -### Q: 我应该从哪里开始? - -A: 建议先阅读[快速开始](getting-started.md),然后根据你的背景选择学习路径: -- **新手**:重点看概念详解 -- **有经验**:直接看核心概念和设计指南 -- **追求性能**:重点看性能相关文档 - -### Q: ECS和传统OOP有什么区别? - -A: 详见[概念详解](concepts-explained.md)的"ECS vs 传统架构"部分,用简单例子解释两者差异。 - -### Q: 如何选择组件索引类型? - -A: [概念详解](concepts-explained.md)有详细的索引选择指南,包括决策流程图和具体示例。 - -### Q: 系统的执行顺序重要吗? - -A: 非常重要![系统详解指南](system-guide.md)详细解释了系统顺序的重要性和设置方法。 - -### Q: 如何调试性能问题? - -A: -1. 使用[性能基准测试](performance.md)中的工具 -2. 参考[性能优化技术](performance-optimization.md)的监控方法 -3. 查看[实体管理器示例](entity-manager-example.md)的统计功能 - -## 📖 扩展阅读 - -### 设计模式和架构 -- [组件设计最佳实践](component-design-guide.md) - 如何设计可维护的组件 -- [系统详解指南](system-guide.md) - 系统间的协作模式 - -### 性能和优化 -- [概念详解](concepts-explained.md) - 性能优化技术原理 -- [性能优化技术](performance-optimization.md) - 具体优化实现 - -### 实际应用 -- [应用案例集合](use-cases.md) - 不同类型游戏的实现 -- [场景管理指南](scene-management-guide.md) - 复杂游戏的场景组织 - -## 学习建议 - -### 实践为主 -- **边学边做** - 每学一个概念都尝试写代码实现 -- **从小做起** - 先做简单的游戏,再逐步增加复杂度 -- **多做实验** - 尝试不同的设计方案,体会优劣 - -### 理解原理 -- **思考为什么** - 不只学怎么做,更要理解为什么这样做 -- **关注性能** - 了解各种操作的性能影响 -- **深入源码** - 有疑问时查看框架源码 - -### 循序渐进 -- **按顺序学习** - 先掌握基础,再学高级功能 -- **专注重点** - 每次只专注一个主题,不要贪多 -- **反复练习** - 重要概念要多练习才能熟练 - -开始你的ECS学习之旅吧! \ No newline at end of file diff --git a/docs/component-design-guide.md b/docs/component-design-guide.md deleted file mode 100644 index 9d4d2166..00000000 --- a/docs/component-design-guide.md +++ /dev/null @@ -1,704 +0,0 @@ -# 组件设计最佳实践指南 - -组件是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 PlayerSystem extends EntitySystem { - constructor() { - super(Matcher.all(PlayerComponent)); - } - - protected process(entities: Entity[]): void { - // entities已经是玩家实体 - for (const entity of entities) { - // 处理玩家逻辑 - } - } -} - -class EnemySystem extends EntitySystem { - constructor() { - super(Matcher.all(EnemyComponent)); - } - - protected process(entities: Entity[]): void { - // entities已经是敌人实体 - for (const entity of entities) { - // 处理敌人逻辑 - } - } -} -``` - -### 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; - - // 发送事件,让其他系统响应 - // 注意:需要在实际使用中获取EntityManager实例 - // 示例:entityManager.eventBus.emit('health:damaged', {...}); - - if (this.currentHealth <= 0) { - // 示例:entityManager.eventBus.emit('health:died', {...}); - console.log('实体死亡'); - } - } -} - -// 其他组件响应事件 -class AnimationComponent extends Component { - onAddedToEntity() { - super.onAddedToEntity(); - - // 监听受伤事件(需要在实际使用中获取EntityManager实例) - // 示例:entityManager.eventBus.on('health:damaged', this.onDamaged, { context: this }); - } - - onRemovedFromEntity() { - // 事件监听会在组件移除时自动清理 - // 如需手动清理,保存listenerId并调用eventBus.off() - 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 deleted file mode 100644 index 4d765e19..00000000 --- a/docs/concepts-explained.md +++ /dev/null @@ -1,513 +0,0 @@ -# 技术概念详解 - -本文档用通俗易懂的语言解释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 = scene.createEntity("Player") - .addComponent(new PositionComponent()) // 位置数据 - .addComponent(new HealthComponent()) // 生命值数据 - .addComponent(new PlayerInputComponent()) // 玩家输入标记 - -const enemy = scene.createEntity("Enemy") - .addComponent(new PositionComponent()) // 复用位置数据 - .addComponent(new HealthComponent()) // 复用生命值数据 - .addComponent(new AIComponent()) // AI标记 - -// 系统自动处理具有特定组件的实体 -class MovementSystem extends EntitySystem { - onUpdate() { - // 处理具有Position和Velocity组件的实体 - } -} -``` - -### 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 entitiesWithHealth = entityManager.query() - .withAll(HealthComponent) - .execute(); // 直接获取,SparseSet自动优化 -``` - -**应用场景:** -- 频繁查询特定组件的实体 -- 大规模实体场景(数千到数万个实体) -- 实时游戏中的系统更新 - -### SparseSet 组件索引 - -**什么是 SparseSet?** -SparseSet是一种高效的数据结构,结合了哈希表的快速访问和数组的缓存友好特性。 - -**SparseSet 的优势:** -- **O(1) 添加/删除/查找** - 所有基本操作都是常数时间 -- **缓存友好遍历** - 密集数组存储,提高遍历性能 -- **内存高效** - 自动管理稀疏和密集数据 -- **无需配置** - 框架自动选择最优策略 - -```typescript -// 统一的查询API,无需手动配置 -const entitiesWithHealth = entityManager.query() - .withAll(HealthComponent) - .execute(); // O(1) 访问,SparseSet自动优化 -``` - -**应用场景:** -- 任意规模的实体场景(从几十到数万) -- 频繁的组件添加/删除操作 -- 高性能的批量查询需求 - -### 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"); -player.addComponent(new PositionComponent()); -player.addComponent(new HealthComponent()); - -// 批量创建 - 需要循环处理 -const bullets: Entity[] = []; -for (let i = 0; i < 100; i++) { - const bullet = scene.createEntity(`Bullet_${i}`); - bullet.addComponent(new PositionComponent()); - bullet.addComponent(new VelocityComponent()); - bullets.push(bullet); -} -``` - -### 查询系统 - -**流式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 -// 优化的批量创建方式 -const bullets: Entity[] = []; -for (let i = 0; i < 1000; i++) { - const bullet = scene.createEntity(`Bullet_${i}`); - bullet.addComponent(new PositionComponent()); - bullet.addComponent(new VelocityComponent()); - bullets.push(bullet); -} - -// 批量查询操作 -const allMovableEntities = entityManager.query() - .withAll(PositionComponent, VelocityComponent) - .execute(); -``` - -**应用场景:** -- 生成大量子弹/粒子 -- 加载关卡时创建大量实体 -- 清理场景时删除大量实体 - - -## 总结 - -ECS框架包含以下核心技术概念: - -1. **ECS架构** - 组件化设计模式 -2. **SparseSet索引** - 高效的组件查询 -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 deleted file mode 100644 index 20e8aee5..00000000 --- a/docs/core-concepts.md +++ /dev/null @@ -1,926 +0,0 @@ -# 核心 API 参考 - -本文档详细介绍 ECS Framework 的核心 API 和使用方法。 - -> **不熟悉ECS概念?** 建议先阅读 [技术概念详解](concepts-explained.md) 了解ECS架构基础和性能优化原理 - -## ECS 架构概述 - -ECS 架构将传统的面向对象设计分解为三个核心部分: - -- **Entity(实体)** - 游戏世界中的对象,包含基本属性如位置、旋转、缩放 -- **Component(组件)** - 包含数据和行为的功能模块 -- **System(系统)** - 处理实体集合的逻辑处理单元 - -## Core(核心) - -Core 是框架的核心管理类,负责游戏的生命周期管理。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构。 - -### 创建和配置 - -```typescript -import { Core, ICoreConfig } from '@esengine/ecs-framework'; - -// 创建核心实例(使用配置对象 - 推荐) -const config: ICoreConfig = { - debug: true, // 启用调试模式 - enableEntitySystems: true, // 启用实体系统 - debugConfig: { // 可选:远程调试配置 - enabled: true, - websocketUrl: 'ws://localhost:8080', - autoReconnect: true, - updateInterval: 1000, - channels: { - entities: true, - systems: true, - performance: true, - components: true, - scenes: true - } - } -}; -const core = Core.create(config); - -// 简化创建(向后兼容) -const core1 = Core.create(true); // 调试模式 -const core2 = Core.create(false); // 发布模式 -const core3 = Core.create(); // 默认调试模式 -``` - -### 场景管理API - -```typescript -// 单Scene模式(默认,向后兼容) -const scene = new Scene(); -Core.setScene(scene); // 设置场景 -const currentScene = Core.getScene(); // 获取当前场景 - -// 多World模式(高级功能) -Core.enableWorldManager(); // 启用World管理器 -const worldManager = Core.getWorldManager(); - -// 创建World -const gameWorld = worldManager.createWorld('GameWorld', { - name: 'GameWorld', - maxScenes: 10, - autoCleanup: true -}); - -// 在World中管理Scene -const battleScene = gameWorld.createScene('battle', new Scene()); -const uiScene = gameWorld.createScene('ui', new Scene()); - -gameWorld.setSceneActive('battle', true); -gameWorld.setSceneActive('ui', true); - -// 启动World -gameWorld.start(); - -// 获取World统计 -const worldStats = gameWorld.getStats(); -console.log('World状态:', worldStats); -``` - -### 事件系统 - -```typescript -import { EntityManager, ECSEventType } from '@esengine/ecs-framework'; - -// 获取EntityManager的事件系统 -const entityManager = new EntityManager(); -const eventBus = entityManager.eventBus; - -// 监听实体事件 -eventBus.onEntityCreated((data) => { - console.log(`实体创建: ${data.entityName}`); -}); - -eventBus.onComponentAdded((data) => { - console.log(`组件添加: ${data.componentType}`); -}); - -// 发送自定义事件 -eventBus.emit("customEvent", { data: "value" }); - -// 使用事件装饰器(推荐) -import { EventHandler } from '@esengine/ecs-framework'; - -class GameSystem { - @EventHandler('entity:died') - onEntityDied(data: any) { - console.log('实体死亡:', data); - } -} -``` - -### 定时器系统 - -```typescript -// 延迟执行 -Core.schedule(2.0, false, this, (timer) => { - console.log("2秒后执行"); -}); - -// 重复执行 -Core.schedule(1.0, true, this, (timer) => { - console.log("每秒执行一次"); -}); -``` - -## Scene(场景) - -场景是游戏世界的容器,管理实体和系统的生命周期。 - -### 创建和使用场景 - -```typescript -import { Scene } from '@esengine/ecs-framework'; - -// 创建场景 -const scene = new Scene(); -scene.name = "GameScene"; - -// 设置为当前场景 -Core.setScene(scene; - -// 场景生命周期 -scene.begin(); // 开始场景 -scene.update(); // 更新场景 -scene.end(); // 结束场景 -``` - -### 批量实体管理 - -```typescript -// 批量创建实体 - 高性能 -const entities = scene.createEntities(1000, "Enemy"); - -// 批量添加实体(延迟缓存清理) -entities.forEach(entity => { - scene.addEntity(entity, false); // 延迟清理 -}); -scene.querySystem.clearCache(); // 手动清理缓存 - -// 获取性能统计 -const stats = scene.getStats(); -console.log(`实体数量: ${stats.entityCount}`); -``` - -## Entity(实体) - -实体是游戏世界中的基本对象,包含位置、旋转、缩放等基本属性。 - -### 实体的基本属性 - -```typescript -const entity = scene.createEntity("MyEntity"); - -// 标签(用于分类) -entity.tag = 1; - -// 启用状态 -entity.enabled = true; - -// 活跃状态 -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; -``` - -### 实体层级关系 - -```typescript -// 添加子实体 -const parent = scene.createEntity("Parent"); -const child = scene.createEntity("Child"); -parent.addChild(child); - -// 获取父实体 -const parentEntity = child.parent; - -// 获取所有子实体 -const children = parent.children; - -// 查找子实体 -const foundChild = parent.findChild("Child"); - -// 按标签查找子实体 -const taggedChildren = parent.findChildrenByTag(1); - -// 移除子实体 -parent.removeChild(child); - -// 移除所有子实体 -parent.removeAllChildren(); -``` - -### 实体生命周期 - -```typescript -// 检查实体是否被销毁 -if (!entity.isDestroyed) { - // 实体仍然有效 -} - -// 销毁实体 -entity.destroy(); - -// 获取实体调试信息 -const debugInfo = entity.getDebugInfo(); -console.log(debugInfo); -``` - -## 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 -= damage; - if (this.currentHealth <= 0) { - this.entity.destroy(); - } - } - - public heal(amount: number) { - this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount); - } -} -``` - -### 组件生命周期 - -```typescript -class MyComponent extends Component { - public onAddedToEntity() { - // 组件被添加到实体时调用 - console.log("组件已添加到实体:", this.entity.name); - } - - public onRemovedFromEntity() { - // 组件从实体移除时调用 - console.log("组件已从实体移除"); - } - - public onEnabled() { - // 组件启用时调用 - console.log("组件已启用"); - } - - public onDisabled() { - // 组件禁用时调用 - console.log("组件已禁用"); - } - - public update() { - // 每帧更新(如果组件启用) - console.log("组件更新"); - } -} -``` - -### 组件管理 - -```typescript -// 添加组件 -const health = entity.addComponent(new HealthComponent()); - -// 创建并添加组件 -const movement = entity.createComponent(MovementComponent, 200); // 传递构造参数 - -// 获取组件 -const healthComp = entity.getComponent(HealthComponent); - -// 检查组件是否存在 -if (entity.hasComponent(HealthComponent)) { - // 处理逻辑 -} - -// 获取或创建组件 -const weapon = entity.getOrCreateComponent(WeaponComponent); - -// 获取多个同类型组件 -const allHealthComps = entity.getComponents(HealthComponent); - -// 移除组件 -entity.removeComponent(healthComp); - -// 按类型移除组件 -entity.removeComponentByType(HealthComponent); - -// 移除所有组件 -entity.removeAllComponents(); -``` - -### 组件对象池优化 - -```typescript -import { Component, ComponentPoolManager } from '@esengine/ecs-framework'; - -class BulletComponent extends Component { - public damage: number = 10; - public speed: number = 300; - - // 对象池重置方法 - public reset() { - this.damage = 10; - this.speed = 300; - } -} - -// 注册组件池 -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); - -// 预热所有组件池 -ComponentPoolManager.getInstance().prewarmAll(100); - -// 获取池统计 -const stats = ComponentPoolManager.getInstance().getPoolStats(); -console.log('组件池统计:', stats); -``` - -## Scene(场景) - -场景是实体和系统的容器,管理游戏世界的状态。 - -### 场景生命周期 - -```typescript -import { Scene } from '@esengine/ecs-framework'; - -class GameScene extends Scene { - public initialize() { - // 场景初始化,创建实体和系统 - this.setupEntities(); - this.setupSystems(); - } - - public onStart() { - // 场景开始运行时调用 - console.log("场景开始"); - } - - public unload() { - // 场景卸载时调用 - console.log("场景卸载"); - } - - private setupEntities() { - const player = this.createEntity("Player"); - player.addComponent(new PlayerComponent()); - } - - private setupSystems() { - this.addEntityProcessor(new MovementSystem()); - } -} -``` - -### 实体管理 - -```typescript -// 创建实体 -const entity = scene.createEntity("MyEntity"); - -// 添加现有实体 -scene.addEntity(entity); - -// 查找实体 -const player = scene.findEntity("Player"); -const entityById = scene.findEntityById(123); -const entitiesByTag = scene.findEntitiesByTag(1); - -// 销毁所有实体 -scene.destroyAllEntities(); - -// 获取场景统计信息 -const stats = scene.getStats(); -console.log("实体数量:", stats.entityCount); -console.log("系统数量:", stats.processorCount); -``` - -## World(世界) - -World是Scene的容器,提供了更高级的场景管理功能。每个World可以包含多个Scene,适用于复杂的游戏架构。 - -### World基本使用 - -```typescript -import { World, Scene, IWorldConfig } from '@esengine/ecs-framework'; - -// 创建World配置 -const worldConfig: IWorldConfig = { - name: 'GameWorld', - debug: true, - maxScenes: 10, - autoCleanup: true -}; - -// 创建World -const gameWorld = new World(worldConfig); - -// 在World中创建Scene -const battleScene = gameWorld.createScene('battle', new Scene()); -const uiScene = gameWorld.createScene('ui', new Scene()); -const menuScene = gameWorld.createScene('menu'); - -// 激活Scene -gameWorld.setSceneActive('battle', true); -gameWorld.setSceneActive('ui', true); - -// 启动World -gameWorld.start(); -``` - -### World生命周期管理 - -```typescript -// 启动World(启动所有全局System) -gameWorld.start(); - -// 检查World状态 -if (gameWorld.isActive) { - console.log('World正在运行'); -} - -// 停止World(停止所有Scene和全局System) -gameWorld.stop(); - -// 销毁World(清理所有资源) -gameWorld.destroy(); -``` - -### Scene管理 - -```typescript -// 获取Scene -const battleScene = gameWorld.getScene('battle'); - -// 检查Scene是否激活 -if (gameWorld.isSceneActive('battle')) { - console.log('战斗场景正在运行'); -} - -// 移除Scene -gameWorld.removeScene('menu'); - -// 获取所有Scene ID -const sceneIds = gameWorld.getSceneIds(); -console.log('所有Scene:', sceneIds); - -// 获取活跃Scene数量 -const activeCount = gameWorld.getActiveSceneCount(); -console.log('活跃Scene数量:', activeCount); -``` - -### 全局System管理 - -World支持全局System,这些System会在所有Scene之前执行,适用于跨Scene的业务逻辑: - -```typescript -import { IGlobalSystem } from '@esengine/ecs-framework'; - -// 全局网络同步系统 -class GlobalNetworkSystem implements IGlobalSystem { - public readonly name = 'GlobalNetworkSystem'; - - public initialize(): void { - // 初始化网络连接 - console.log('网络系统初始化'); - } - - public update(): void { - // 处理全局网络同步逻辑 - // 注意:全局系统处理的是World级别的逻辑,不直接处理实体 - // 如需处理特定实体,请在Scene中使用EntitySystem - this.syncGlobalNetworkState(); - } - - public reset(): void { - // 重置系统状态 - } - - public destroy(): void { - // 清理网络连接 - console.log('网络系统销毁'); - } - - private syncGlobalNetworkState(): void { - // 全局网络状态同步 - } -} - -// 添加全局System -const networkSystem = gameWorld.addGlobalSystem(new GlobalNetworkSystem()); - -// 获取全局System -const existingSystem = gameWorld.getGlobalSystem(GlobalNetworkSystem); - -// 移除全局System -gameWorld.removeGlobalSystem(networkSystem); -``` - -> **注意**:全局System适用于World级别的业务逻辑(如网络管理、资源管理、全局状态管理等)。如果需要处理具体的实体和组件,请在Scene中使用EntitySystem。 - -### World状态监控 - -```typescript -// 获取World状态 -const status = gameWorld.getStatus(); -console.log('World状态:', { - name: status.name, - isActive: status.isActive, - sceneCount: status.sceneCount, - activeSceneCount: status.activeSceneCount, - globalSystemCount: status.globalSystemCount, - scenes: status.scenes -}); - -// 获取World统计信息 -const stats = gameWorld.getStats(); -console.log('World统计:', { - totalEntities: stats.totalEntities, - totalSystems: stats.totalSystems, - memoryUsage: stats.memoryUsage -}); -``` - -## WorldManager(世界管理器) - -WorldManager是单例模式的World管理器,负责管理多个World实例。 - -### WorldManager基本使用 - -```typescript -import { WorldManager, IWorldManagerConfig } from '@esengine/ecs-framework'; - -// 获取WorldManager实例 -const worldManager = WorldManager.getInstance({ - maxWorlds: 50, - autoCleanup: true, - debug: true -}); - -// 或者通过Core获取 -Core.enableWorldManager(); -const worldManager2 = Core.getWorldManager(); -``` - -### World管理 - -```typescript -// 创建World -const gameWorld = worldManager.createWorld('GameRoom_001', { - name: 'GameRoom_001', - maxScenes: 5, - autoCleanup: true -}); - -// 获取World -const existingWorld = worldManager.getWorld('GameRoom_001'); - -// 检查World是否存在 -if (worldManager.getWorld('GameRoom_001')) { - console.log('World存在'); -} - -// 销毁World -worldManager.removeWorld('GameRoom_001'); - -// 获取所有World ID -const worldIds = worldManager.getWorldIds(); -console.log('所有World ID:', worldIds); - -// 获取活跃World -const activeWorlds = worldManager.getActiveWorlds(); -console.log('活跃World数量:', activeWorlds.length); -``` - -### WorldManager统计和监控 - -```typescript -// 获取WorldManager状态 -const managerStatus = worldManager.getStatus(); -console.log('WorldManager状态:', { - totalWorlds: managerStatus.totalWorlds, - activeWorlds: managerStatus.activeWorlds, - maxWorlds: managerStatus.maxWorlds, - memoryUsage: managerStatus.memoryUsage -}); - -// 获取所有World的统计 -const allStats = worldManager.getAllWorldStats(); -allStats.forEach(stat => { - console.log(`World ${stat.worldName}:`, stat); -}); - -// 清理空闲World -const cleanedCount = worldManager.cleanup(); -console.log(`清理了 ${cleanedCount} 个空闲World`); -``` - -### 使用场景 - -World和WorldManager适用于: -- **游戏服务器**:每个房间一个独立World -- **复杂客户端**:按功能分层管理Scene(游戏层、UI层、特效层) -- **并发世界**:需要同时运行多个独立游戏世界的场景 - -> **完整示例和最佳实践**:查看 [场景管理完整指南](scene-management-guide.md#world多场景管理) 了解详细的实现方案和架构设计 - -## System(系统) - -系统处理实体集合,实现游戏的核心逻辑。 - -### EntitySystem - -最常用的系统类型,处理实体集合: - -```typescript -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) { - movement.update(); - } - } - } -} -``` - -### ProcessingSystem - -定期处理的系统: - -```typescript -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 * Time.deltaTime; - } - } - } -} -``` - -### IntervalSystem - -按时间间隔执行的系统: - -```typescript -import { IntervalSystem, Matcher } from '@esengine/ecs-framework'; - -class SpawnSystem extends IntervalSystem { - constructor() { - // IntervalSystem需要Matcher和间隔时间 - super(Matcher.empty(), 3.0); // 每3秒执行一次 - } - - protected process(entities: Entity[]) { - // 生成敌人 - const enemy = this.scene.createEntity("Enemy"); - enemy.addComponent(new EnemyComponent()); - } -} -``` - -### PassiveSystem - -被动系统,不自动处理实体: - -```typescript -import { PassiveSystem, Matcher } from '@esengine/ecs-framework'; - -class CollisionSystem extends PassiveSystem { - constructor() { - super(Matcher.empty()); - } - - public checkCollisions() { - // 手动调用的碰撞检测逻辑 - } -} -``` - -## Time(时间) - -时间管理工具类,提供游戏时间相关功能: - -```typescript -import { Time } from '@esengine/ecs-framework'; - -// 获取时间信息 -console.log("帧时间:", Time.deltaTime); -console.log("总时间:", Time.totalTime); -console.log("帧数:", Time.frameCount); -console.log("时间缩放:", Time.timeScale); - -// 设置时间缩放(慢动作效果) -Time.timeScale = 0.5; - -// 检查时间间隔 -if (Time.checkEvery(1.0, lastCheckTime)) { - // 每秒执行一次 -} -``` - -## 性能监控 - -框架内置性能监控工具: - -```typescript -import { PerformanceMonitor } from '@esengine/ecs-framework'; - -// 获取性能监控实例 -const monitor = PerformanceMonitor.instance; - -// 查看性能数据 -console.log("平均FPS:", monitor.averageFPS); -console.log("最小FPS:", monitor.minFPS); -console.log("最大FPS:", monitor.maxFPS); -console.log("内存使用:", monitor.memoryUsage); - -// 重置性能数据 -monitor.reset(); -``` - -## 对象池 - -内存管理优化工具: - -```typescript -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 Pool(() => new Bullet(), 100); - -// 预热对象池 -bulletPool.warmUp(20); - -// 使用对象池 -const bullet = bulletPool.obtain(); -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); -``` - -## 最佳实践 - -### 1. 实体设计 - -- 实体只包含基本属性,功能通过组件实现 -- 合理使用实体层级关系 -- 及时销毁不需要的实体 - -### 2. 组件设计 - -- 组件保持单一职责 -- 使用生命周期方法进行初始化和清理 -- 避免组件间直接依赖 - -### 3. 系统设计 - -- 系统专注于特定逻辑处理 -- 合理设置系统更新顺序 -- 使用被动系统处理特殊逻辑 - -### 4. 性能优化 - -- 使用对象池减少内存分配 -- 监控性能数据 -- 合理使用时间缩放 - -## 高级性能优化功能 - -### 查询系统优化 - -框架内部已集成查询优化,无需手动配置。查询系统会自动使用最优的算法: - -```typescript -// 查询系统会自动优化这些操作 -const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent); -const renderableEntities = scene.querySystem.queryAll(PositionComponent, RenderComponent); - -// 获取查询统计信息 -const queryStats = scene.querySystem.getStats(); -console.log('查询统计:', queryStats); -``` - -### 批量操作API - -```typescript -// 批量创建实体 - 最高性能 -const entities = scene.createEntities(10000, "Bullets"); - -// 批量查询优化 -const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent).entities; -``` - -## 总结 - -ECS Framework 提供了完整的实体组件系统架构: - -- **Core** 管理游戏生命周期和全局功能,支持单Scene和多World模式 -- **Entity** 作为游戏对象的基础容器 -- **Component** 实现具体的功能模块,支持对象池优化 -- **System** 处理游戏逻辑 -- **Scene** 管理游戏世界状态,支持批量操作 -- **World** 高级场景容器,支持多Scene管理和全局System -- **WorldManager** 管理多个World实例,适用于复杂架构 -- **高级优化** 位掩码优化器、组件对象池、批量操作等 - -### 架构选择指南 - -- **单Scene模式**:适合简单游戏、单机游戏、原型开发 -- **多World模式**:适合多人游戏服务器、复杂应用、需要场景隔离的项目 - -框架采用融合设计,确保向后兼容性的同时提供强大的扩展能力。通过合理使用这些核心概念和优化功能,可以构建出高性能、结构清晰、易于维护的游戏代码。 \ No newline at end of file diff --git a/docs/entity-guide.md b/docs/entity-guide.md deleted file mode 100644 index add89bf0..00000000 --- a/docs/entity-guide.md +++ /dev/null @@ -1,370 +0,0 @@ -# 实体基础指南 - -本指南介绍实体(Entity)的基本概念和基础使用方法。 - -> 📖 **需要高级实体管理?** 请参考 [EntityManager 指南](entity-manager-example.md) 了解高性能查询和批量操作 - -## 实体概述 - -实体(Entity)是 ECS 架构中的核心概念之一,它作为组件的容器存在。实体本身不包含游戏逻辑,所有功能都通过添加不同的组件来实现。 - -### 实体的特点 - -- **轻量级容器**:实体只是组件的载体,不包含具体的游戏逻辑 -- **唯一标识**:每个实体都有唯一的ID和名称 -- **层次结构**:支持父子关系,可以构建复杂的实体层次 -- **高性能查询**:基于位掩码的组件查询系统 -- **生命周期管理**:完整的创建、更新、销毁流程 - -## 创建实体 - -### 基本创建方式 - -```typescript -import { Scene } from '@esengine/ecs-framework'; - -// 通过场景创建实体 -const scene = new Scene(); -const entity = scene.createEntity("Player"); - -console.log(entity.name); // "Player" -console.log(entity.id); // 唯一的数字ID -``` - -### 批量创建实体(推荐) - -```typescript -import { Scene } from '@esengine/ecs-framework'; - -const scene = new Scene(); - -// 批量创建1000个实体 - 高性能 -const entities = scene.createEntities(1000, "Enemy"); - -// 批量配置 -entities.forEach((entity, index) => { - entity.tag = 2; // 敌人标签 - // 添加组件... -}); -``` - -### 使用流式API创建 - -```typescript -import { Core } from '@esengine/ecs-framework'; - -// 使用ECS流式API -const entity = Core.ecsAPI - ?.entity("Enemy") - .withComponent(new PositionComponent(100, 200)) - .withComponent(new HealthComponent(50)) - .withTag(2) - .build(); -``` - -## 实体属性 - -### 基本属性 - -```typescript -// 实体名称 - 用于调试和标识 -entity.name = "Player"; - -// 实体ID - 只读,场景内唯一 -console.log(entity.id); // 例如: 1 - -// 标签 - 用于分类和快速查询 -entity.tag = 1; // 玩家标签 -entity.tag = 2; // 敌人标签 - -// 更新顺序 - 控制实体在系统中的处理优先级 -entity.updateOrder = 0; // 数值越小优先级越高 -``` - -### 状态控制 - -```typescript -// 启用状态 - 控制实体是否参与更新和处理 -entity.enabled = true; // 启用实体 -entity.enabled = false; // 禁用实体 - -// 激活状态 - 控制实体及其子实体的活跃状态 -entity.active = true; // 激活实体 -entity.active = false; // 停用实体 - -// 检查层次结构中的激活状态 -if (entity.activeInHierarchy) { - // 实体在整个层次结构中都是激活的 -} - -// 检查销毁状态 -if (entity.isDestroyed) { - // 实体已被销毁 -} -``` - -### 更新间隔 - -```typescript -// 控制实体更新频率 -entity.updateInterval = 1; // 每帧更新 -entity.updateInterval = 2; // 每2帧更新一次 -entity.updateInterval = 5; // 每5帧更新一次 -``` - -## 组件管理 - -### 添加组件 - -```typescript -// 创建并添加组件 -const healthComponent = entity.addComponent(new HealthComponent(100)); - -// 使用工厂方法创建组件 -const positionComponent = entity.createComponent(PositionComponent, 100, 200); - -// 批量添加组件 -const components = entity.addComponents([ - new PositionComponent(0, 0), - new VelocityComponent(50, 0), - new HealthComponent(100) -]); -``` - -### 获取组件 - -```typescript -// 获取单个组件 -const health = entity.getComponent(HealthComponent); -if (health) { - console.log(`当前生命值: ${health.currentHealth}`); -} - -// 获取或创建组件(如果不存在则创建) -const position = entity.getOrCreateComponent(PositionComponent, 0, 0); - -// 获取多个同类型组件(如果组件可以重复添加) -const allHealthComponents = entity.getComponents(HealthComponent); -``` - -### 检查组件 - -```typescript -// 检查是否拥有指定组件 -if (entity.hasComponent(HealthComponent)) { - // 实体拥有生命值组件 -} - -// 检查组件掩码(高性能) -const mask = entity.componentMask; -console.log(`组件掩码: ${mask.toString(2)}`); // 二进制表示 -``` - -### 移除组件 - -```typescript -// 移除指定组件实例 -const healthComponent = entity.getComponent(HealthComponent); -if (healthComponent) { - entity.removeComponent(healthComponent); -} - -// 按类型移除组件 -const removedHealth = entity.removeComponentByType(HealthComponent); - -// 批量移除组件 -const removedComponents = entity.removeComponentsByTypes([ - HealthComponent, - VelocityComponent -]); - -// 移除所有组件 -entity.removeAllComponents(); -``` - -## 层次结构管理 - -### 父子关系 - -```typescript -// 创建父子实体 -const player = scene.createEntity("Player"); -const weapon = scene.createEntity("Weapon"); -const shield = scene.createEntity("Shield"); - -// 添加子实体 -player.addChild(weapon); -player.addChild(shield); - -// 获取父实体 -console.log(weapon.parent === player); // true - -// 获取所有子实体 -const children = player.children; -console.log(children.length); // 2 - -// 获取子实体数量 -console.log(player.childCount); // 2 -``` - -### 查找子实体 - -```typescript -// 按名称查找子实体 -const weapon = player.findChild("Weapon"); - -// 递归查找子实体 -const deepChild = player.findChild("DeepChild", true); - -// 按标签查找子实体 -const enemies = player.findChildrenByTag(2); // 查找所有敌人标签的子实体 - -// 递归按标签查找 -const allEnemies = player.findChildrenByTag(2, true); -``` - -### 层次结构操作 - -```typescript -// 移除子实体 -const removed = player.removeChild(weapon); - -// 移除所有子实体 -player.removeAllChildren(); - -// 获取根实体 -const root = weapon.getRoot(); - -// 检查祖先关系 -if (player.isAncestorOf(weapon)) { - // player 是 weapon 的祖先 -} - -// 检查后代关系 -if (weapon.isDescendantOf(player)) { - // weapon 是 player 的后代 -} - -// 获取实体在层次结构中的深度 -const depth = weapon.getDepth(); // 从根实体开始计算的深度 -``` - -### 遍历子实体 - -```typescript -// 遍历直接子实体 -player.forEachChild((child, index) => { - console.log(`子实体 ${index}: ${child.name}`); -}); - -// 递归遍历所有子实体 -player.forEachChild((child, index) => { - console.log(`子实体 ${index}: ${child.name} (深度: ${child.getDepth()})`); -}, true); -``` - -## 实体生命周期 - -### 更新循环 - -```typescript -// 手动更新实体(通常由场景自动调用) -entity.update(); - -// 实体会自动调用所有组件的update方法 -class MyComponent extends Component { - public update(): void { - // 组件更新逻辑 - } -} -``` - -### 销毁实体 - -```typescript -// 销毁实体 -entity.destroy(); - -// 检查是否已销毁 -if (entity.isDestroyed) { - console.log("实体已被销毁"); -} - -// 销毁实体时会自动: -// 1. 移除所有组件 -// 2. 从父实体中移除 -// 3. 销毁所有子实体 -// 4. 从场景中移除 -``` - -# 高级特性请参考其他指南 - -> **更多功能:** -> - **高性能查询和批量操作** → [EntityManager 指南](entity-manager-example.md) -> - **性能优化技术** → [性能优化指南](performance-optimization.md) -> - **组件索引和缓存** → [技术概念详解](concepts-explained.md) - -## 基础最佳实践 - -### 1. 合理使用标签 - -```typescript -// 定义标签常量 -const EntityTags = { - PLAYER: 1, - ENEMY: 2, - PROJECTILE: 3, - PICKUP: 4 -} as const; - -// 使用标签进行分类 -player.tag = EntityTags.PLAYER; -enemy.tag = EntityTags.ENEMY; -``` - -### 2. 正确的销毁处理 - -```typescript -// 确保正确销毁实体 -if (!entity.isDestroyed) { - entity.destroy(); // 自动移除组件和层次关系 -} - -// 检查实体状态 -if (entity.isDestroyed) { - return; // 避免操作已销毁的实体 -} -``` - -### 3. 组件生命周期 - -```typescript -// 正确添加组件 -const health = entity.addComponent(new HealthComponent(100)); - -// 安全获取组件 -const healthComp = entity.getComponent(HealthComponent); -if (healthComp && healthComp.currentHealth <= 0) { - entity.destroy(); -} -``` - -## 常见问题 - -### Q: 实体如何实现位置、旋转等变换? - -A: 通过添加相应的组件: - -```typescript -class TransformComponent extends Component { - public position = { x: 0, y: 0 }; - public rotation = 0; - public scale = { x: 1, y: 1 }; -} - -entity.addComponent(new TransformComponent()); -``` - -### Q: 实体可以在场景间移动吗? - -A: 不可以。实体与场景绑定,需要在新场景中重新创建。 \ No newline at end of file diff --git a/docs/entity-manager-example.md b/docs/entity-manager-example.md deleted file mode 100644 index b2b638f2..00000000 --- a/docs/entity-manager-example.md +++ /dev/null @@ -1,370 +0,0 @@ -# EntityManager 使用指南 - -本文档详细介绍 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 enemies = scene.createEntities(50, "Enemy"); - -// 为实体添加组件 -enemies.forEach((enemy, index) => { - enemy.addComponent(new PositionComponent( - Math.random() * 800, - Math.random() * 600 - )); - enemy.addComponent(new HealthComponent(100)); - enemy.addComponent(new VelocityComponent( - (Math.random() - 0.5) * 100, - (Math.random() - 0.5) * 100 - )); - enemy.tag = 2; // 敌人标签 -}); -``` - -## 查询系统 - -### 基础查询 - -```typescript -// 按组件类型查询 -const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent); - -// 按标签查询 -const enemies = entityManager.getEntitiesByTag(2); -const players = entityManager.getEntitiesByTag(1); - -// 按名称查询 -const boss = entityManager.getEntityByName("BossEnemy"); - -// 获取所有实体 -const allEntities = entityManager.getAllEntities(); -``` - -### 流式查询 API - -```typescript -// 复杂查询条件 -const movingEnemies = entityManager - .query() - .withAll(PositionComponent, VelocityComponent, HealthComponent) - .withTag(2) // 敌人标签 - .execute(); - -// 查询活跃的玩家 -const activePlayers = entityManager - .query() - .withAll(PositionComponent) - .withTag(1) // 玩家标签 - .active() // 只查询活跃实体 - .execute(); - -// 排除特定组件的实体 -const nonCombatEntities = entityManager - .query() - .withAll(PositionComponent) - .without(WeaponComponent, HealthComponent) - .execute(); - -// 自定义条件查询 -const nearbyEnemies = entityManager - .query() - .withAll(PositionComponent) - .withTag(2) - .where(entity => { - const pos = entity.getComponent(PositionComponent); - return pos && Math.abs(pos.x - playerX) < 100; - }) - .execute(); -``` - -## 实体管理 - -### 创建和销毁实体 - -```typescript -// 创建单个实体 -const player = entityManager.createEntity("Player"); -player.addComponent(new PositionComponent(400, 300)); -player.addComponent(new HealthComponent(100)); -player.tag = 1; - -// 销毁实体 -entityManager.destroyEntity(player); - -// 按名称销毁 -entityManager.destroyEntity("Enemy_1"); - -// 按ID销毁 -entityManager.destroyEntity(123); -``` - -### 实体查找 - -```typescript -// 按ID查找 -const entity = entityManager.getEntity(123); - -// 按名称查找 -const player = entityManager.getEntityByName("Player"); - -// 检查实体是否存在 -if (entity && !entity.isDestroyed) { - // 实体有效 -} -``` - -## 性能监控 - -### 基础统计 - -```typescript -// 获取实体数量 -console.log('总实体数:', entityManager.entityCount); -console.log('活跃实体数:', entityManager.activeEntityCount); - -// 获取场景统计 -const sceneStats = scene.getStats(); -console.log('场景统计:', { - 实体数量: sceneStats.entityCount, - 系统数量: sceneStats.processorCount -}); - -// 获取查询系统统计 -const queryStats = scene.querySystem.getStats(); -console.log('查询统计:', queryStats); -``` - -## 最佳实践 - -### 1. 高效查询 - -```typescript -// 好的做法:缓存查询结果 -class CombatSystem extends EntitySystem { - private cachedEnemies: Entity[] = []; - private lastUpdateFrame = 0; - - protected process(entities: Entity[]): void { - // 每5帧更新一次缓存 - if (Time.frameCount - this.lastUpdateFrame > 5) { - this.cachedEnemies = this.entityManager - .query() - .withAll(PositionComponent, HealthComponent) - .withTag(2) - .execute(); - this.lastUpdateFrame = Time.frameCount; - } - - // 使用缓存的结果 - this.cachedEnemies.forEach(enemy => { - // 处理敌人逻辑 - }); - } -} -``` - -### 2. 批量操作 - -```typescript -// 好的做法:批量创建和配置 -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 - .getEntitiesByTag(2) // 敌人标签 - .filter(enemy => { - const enemyPos = enemy.getComponent(PositionComponent); - if (!enemyPos) return false; - - const distance = Math.sqrt( - Math.pow(attackerPos.x - enemyPos.x, 2) + - Math.pow(attackerPos.y - enemyPos.y, 2) - ); - return distance <= range; - }); - } -} -``` - -## 完整示例 - -```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(); - } - - private setupGame(): void { - // 创建玩家 - const player = this.entityManager.createEntity("Player"); - player.addComponent(new PositionComponent(400, 300)); - player.addComponent(new HealthComponent(100)); - player.tag = 1; - - // 创建敌人 - 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); - } -} - -// 生命值系统 -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(); - } - }); - } -} - -// 启动游戏 -const game = new GameManager(); -setInterval(() => game.update(), 16); // 60 FPS -``` - -## 总结 - -EntityManager 提供了强大的实体管理功能: - -- **创建管理**:`createEntity()`, `destroyEntity()` -- **查询功能**:`getEntitiesWithComponent()`, `getEntitiesByTag()`, `query()` -- **实体查找**:`getEntity()`, `getEntityByName()` -- **统计信息**:`entityCount`, `activeEntityCount` - -通过合理使用这些API,可以构建高性能的游戏系统。记住要及时清理无用实体,缓存频繁查询的结果,并使用合适的查询方法来优化性能。 \ No newline at end of file diff --git a/docs/event-system-example.md b/docs/event-system-example.md deleted file mode 100644 index 5bdb0694..00000000 --- a/docs/event-system-example.md +++ /dev/null @@ -1,496 +0,0 @@ -# ECS事件系统使用指南 - -本文档介绍如何使用ECS框架的增强事件系统,包括类型安全的事件发布订阅、预定义的ECS事件类型和高级功能。 - -## 目录 - -1. [基础用法](#基础用法) -2. [预定义ECS事件](#预定义ecs事件) -3. [事件装饰器](#事件装饰器) -4. [高级功能](#高级功能) -5. [性能优化](#性能优化) -6. [最佳实践](#最佳实践) - -## 基础用法 - -### 创建事件总线 - -```typescript -import { EventBus, GlobalEventBus } from './ECS'; - -// 方式1:创建独立的事件总线 -const eventBus = new EventBus(true); // true启用调试模式 - -// 方式2:使用全局事件总线 -const globalEventBus = GlobalEventBus.getInstance(true); -``` - -### 基本事件发布订阅 - -```typescript -// 定义事件数据类型 -interface PlayerDiedEvent { - playerId: number; - cause: string; - position: { x: number; y: number }; -} - -// 监听事件 -const listenerId = eventBus.on('player:died', (data) => { - console.log(`Player ${data.playerId} died at (${data.position.x}, ${data.position.y})`); - console.log(`Cause: ${data.cause}`); -}); - -// 发射事件 -eventBus.emit('player:died', { - playerId: 123, - cause: 'enemy_attack', - position: { x: 100, y: 200 } -}); - -// 移除监听器 -eventBus.off('player:died', listenerId); -``` - -### 一次性事件监听 - -```typescript -// 只监听一次 -eventBus.once('player:died', (data) => { - console.log('This will only be called once'); -}); -``` - -### 异步事件处理 - -```typescript -// 异步事件监听 -eventBus.onAsync('player:died', async (data) => { - await savePlayerDeathToDatabase(data); - await updateLeaderboard(data.playerId); -}); - -// 异步事件发射 -await eventBus.emitAsync('player:died', playerData); -``` - -## 预定义ECS事件 - -框架提供了完整的ECS事件类型定义,支持实体、组件、系统等核心概念的事件。 - -### 实体事件 - -```typescript -import { ECSEventType, IEntityEventData } from './ECS'; - -// 监听实体创建事件 -eventBus.onEntityCreated((data: IEntityEventData) => { - console.log(`Entity created: ${data.entityName} (ID: ${data.entityId})`); -}); - -// 监听实体销毁事件 -eventBus.on(ECSEventType.ENTITY_DESTROYED, (data) => { - console.log(`Entity destroyed: ${data.entityName}`); -}); - -// 手动发射实体事件 -eventBus.emitEntityCreated({ - timestamp: Date.now(), - source: 'GameManager', - entityId: 123, - entityName: 'Player', - entityTag: 'player' -}); -``` - -### 组件事件 - -```typescript -import { IComponentEventData } from './ECS'; - -// 监听组件添加事件 -eventBus.onComponentAdded((data: IComponentEventData) => { - console.log(`Component ${data.componentType} added to entity ${data.entityId}`); -}); - -// 监听组件移除事件 -eventBus.on(ECSEventType.COMPONENT_REMOVED, (data) => { - console.log(`Component ${data.componentType} removed from entity ${data.entityId}`); -}); -``` - -### 系统事件 - -```typescript -import { ISystemEventData } from './ECS'; - -// 监听系统错误 -eventBus.onSystemError((data: ISystemEventData) => { - console.error(`System error in ${data.systemName}: ${data.systemType}`); -}); - -// 监听系统处理开始/结束 -eventBus.on(ECSEventType.SYSTEM_PROCESSING_START, (data) => { - console.log(`System ${data.systemName} started processing`); -}); -``` - -### 性能事件 - -```typescript -import { IPerformanceEventData } from './ECS'; - -// 监听性能警告 -eventBus.onPerformanceWarning((data: IPerformanceEventData) => { - console.warn(`Performance warning: ${data.operation} took ${data.executionTime}ms`); -}); - -// 监听内存使用过高 -eventBus.on(ECSEventType.MEMORY_USAGE_HIGH, (data) => { - console.warn(`High memory usage: ${data.memoryUsage}MB`); -}); -``` - -## 事件装饰器 - -使用装饰器可以自动注册事件监听器,简化代码编写。 - -### 基础装饰器 - -```typescript -import { EventHandler, AsyncEventHandler, EventPriority } from './ECS'; - -class GameManager { - @EventHandler(ECSEventType.ENTITY_CREATED, { priority: EventPriority.HIGH }) - onEntityCreated(data: IEntityEventData) { - console.log(`New entity: ${data.entityName}`); - } - - @AsyncEventHandler(ECSEventType.ENTITY_DESTROYED) - async onEntityDestroyed(data: IEntityEventData) { - await this.cleanupEntityResources(data.entityId); - } - - @EventHandler('custom:game:event', { once: true }) - onGameStart(data: any) { - console.log('Game started!'); - } - - // 需要手动调用初始化方法 - constructor() { - // 如果类有initEventListeners方法,会自动注册装饰器定义的监听器 - if (this.initEventListeners) { - this.initEventListeners(); - } - } - - private async cleanupEntityResources(entityId: number) { - // 清理实体相关资源 - } -} -``` - -### 优先级和配置 - -```typescript -class SystemManager { - @EventHandler(ECSEventType.SYSTEM_ERROR, { - priority: EventPriority.CRITICAL, - context: this - }) - handleSystemError(data: ISystemEventData) { - this.logError(data); - this.restartSystem(data.systemName); - } - - @AsyncEventHandler(ECSEventType.PERFORMANCE_WARNING, { - priority: EventPriority.LOW, - async: true - }) - async handlePerformanceWarning(data: IPerformanceEventData) { - await this.optimizePerformance(data); - } - - private logError(data: ISystemEventData) { - // 错误日志记录 - } - - private restartSystem(systemName: string) { - // 重启系统 - } - - private async optimizePerformance(data: IPerformanceEventData) { - // 性能优化逻辑 - } -} -``` - -## 高级功能 - -### 事件批处理 - -```typescript -// 设置批处理配置 -eventBus.setBatchConfig('entity:update', 100, 16); // 批量100个,延迟16ms - -// 发射事件(会被批处理) -for (let i = 0; i < 1000; i++) { - eventBus.emit('entity:update', { entityId: i, data: 'update' }); -} - -// 手动刷新批处理队列 -eventBus.flushBatch('entity:update'); -``` - -### 事件统计和监控 - -```typescript -// 获取单个事件统计 -const stats = eventBus.getStats('entity:created'); -console.log(`Event triggered ${stats.triggerCount} times`); -console.log(`Average execution time: ${stats.averageExecutionTime}ms`); - -// 获取所有事件统计 -const allStats = eventBus.getStats(); -if (allStats instanceof Map) { - allStats.forEach((stat, eventType) => { - console.log(`${eventType}: ${stat.triggerCount} triggers`); - }); -} - -// 重置统计 -eventBus.resetStats('entity:created'); -``` - -### 事件类型验证 - -```typescript -import { EventTypeValidator } from './ECS'; - -// 检查事件类型是否有效 -if (EventTypeValidator.isValid('entity:created')) { - eventBus.emit('entity:created', data); -} - -// 添加自定义事件类型 -EventTypeValidator.addCustomType('game:custom:event'); - -// 获取所有有效事件类型 -const validTypes = EventTypeValidator.getAllValidTypes(); -console.log('Valid event types:', validTypes); -``` - -### 调试和日志 - -```typescript -// 启用调试模式 -eventBus.setDebugMode(true); - -// 设置最大监听器数量 -eventBus.setMaxListeners(50); - -// 检查是否有监听器 -if (eventBus.hasListeners('entity:created')) { - console.log('Has listeners for entity:created'); -} - -// 获取监听器数量 -const count = eventBus.getListenerCount('entity:created'); -console.log(`${count} listeners for entity:created`); -``` - -## 性能优化 - -### 事件过滤和条件监听 - -```typescript -// 使用条件过滤减少不必要的事件处理 -eventBus.on(ECSEventType.ENTITY_CREATED, (data) => { - // 只处理玩家实体 - if (data.entityTag === 'player') { - handlePlayerCreated(data); - } -}); - -// 更好的方式:使用具体的事件类型 -eventBus.on('entity:player:created', handlePlayerCreated); -``` - -### 内存管理 - -```typescript -class EventManager { - private listeners: string[] = []; - - public setupListeners() { - // 保存监听器ID以便清理 - this.listeners.push( - eventBus.on('event1', this.handler1.bind(this)), - eventBus.on('event2', this.handler2.bind(this)) - ); - } - - public cleanup() { - // 清理所有监听器 - this.listeners.forEach(id => { - eventBus.off('event1', id); - eventBus.off('event2', id); - }); - this.listeners.length = 0; - } - - private handler1(data: any) { /* ... */ } - private handler2(data: any) { /* ... */ } -} -``` - -### 异步事件优化 - -```typescript -// 使用Promise.all并行处理多个异步事件 -const promises = [ - eventBus.emitAsync('save:player', playerData), - eventBus.emitAsync('update:leaderboard', scoreData), - eventBus.emitAsync('notify:friends', notificationData) -]; - -await Promise.all(promises); -``` - -## 最佳实践 - -### 1. 事件命名规范 - -```typescript -// 推荐的事件命名格式:模块:对象:动作 -const EVENT_NAMES = { - // 实体相关 - ENTITY_PLAYER_CREATED: 'entity:player:created', - ENTITY_ENEMY_DESTROYED: 'entity:enemy:destroyed', - - // 游戏逻辑相关 - GAME_LEVEL_COMPLETED: 'game:level:completed', - GAME_SCORE_UPDATED: 'game:score:updated', - - // UI相关 - UI_MENU_OPENED: 'ui:menu:opened', - UI_BUTTON_CLICKED: 'ui:button:clicked' -}; -``` - -### 2. 类型安全 - -```typescript -// 定义强类型的事件数据接口 -interface GameEvents { - 'player:levelup': { playerId: number; newLevel: number; experience: number }; - 'inventory:item:added': { itemId: string; quantity: number; playerId: number }; - 'combat:damage:dealt': { attackerId: number; targetId: number; damage: number }; -} - -// 创建类型安全的事件发射器 -class TypedEventBus { - private eventBus = new EventBus(); - - emit(eventType: K, data: GameEvents[K]) { - this.eventBus.emit(eventType, data); - } - - on( - eventType: K, - handler: (data: GameEvents[K]) => void - ) { - return this.eventBus.on(eventType, handler); - } -} -``` - -### 3. 错误处理 - -```typescript -// 在事件处理器中添加错误处理 -eventBus.on(ECSEventType.ENTITY_CREATED, (data) => { - try { - processEntityCreation(data); - } catch (error) { - console.error('Error processing entity creation:', error); - // 发射错误事件 - eventBus.emit(ECSEventType.ERROR_OCCURRED, { - timestamp: Date.now(), - source: 'EntityCreationHandler', - error: error.message, - context: data - }); - } -}); -``` - -### 4. 模块化事件管理 - -```typescript -// 为不同模块创建专门的事件管理器 -class PlayerEventManager { - constructor(private eventBus: EventBus) { - this.setupListeners(); - } - - private setupListeners() { - this.eventBus.onEntityCreated(this.onPlayerCreated.bind(this)); - this.eventBus.on('player:levelup', this.onPlayerLevelUp.bind(this)); - this.eventBus.on('player:died', this.onPlayerDied.bind(this)); - } - - private onPlayerCreated(data: IEntityEventData) { - if (data.entityTag === 'player') { - // 处理玩家创建逻辑 - } - } - - private onPlayerLevelUp(data: any) { - // 处理玩家升级逻辑 - } - - private onPlayerDied(data: any) { - // 处理玩家死亡逻辑 - } -} -``` - -### 5. 与EntityManager集成 - -```typescript -import { EntityManager } from './ECS'; - -// EntityManager会自动设置事件总线 -const entityManager = new EntityManager(); - -// 获取事件总线实例 -const eventBus = entityManager.eventBus; - -// 监听自动发射的ECS事件 -eventBus.onEntityCreated((data) => { - console.log('Entity created automatically:', data); -}); - -eventBus.onComponentAdded((data) => { - console.log('Component added automatically:', data); -}); - -// 创建实体时会自动发射事件 -const entity = entityManager.createEntity('Player'); - -// 添加组件时会自动发射事件 -entity.addComponent(new HealthComponent(100)); -``` - -## 总结 - -ECS框架的事件系统提供了: - -- **类型安全**:完整的TypeScript类型支持 -- **高性能**:批处理、缓存和优化机制 -- **易用性**:装饰器、预定义事件类型 -- **可扩展**:自定义事件类型和验证 -- **调试友好**:详细的统计信息和调试模式 - -通过合理使用事件系统,可以实现松耦合的模块化架构,提高代码的可维护性和扩展性。 \ No newline at end of file diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 00000000..270acc77 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,3 @@ +# 示例 + +代码示例。 \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 08a1f2cc..00000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,689 +0,0 @@ -# 快速入门 - -本指南将帮助您快速上手 ECS Framework,这是一个专业级的实体组件系统框架,采用现代化架构设计,专为高性能游戏开发打造。 - -## 安装 - -```bash -npm install @esengine/ecs-framework -``` - -## 更新机制说明 - -ECS框架需要在游戏引擎的更新循环中调用,并传入deltaTime: - -```typescript -// 统一的更新方式:让外部引擎传入deltaTime -Core.update(deltaTime); -``` - -**不同平台的集成方式:** -- **Laya引擎**:使用 `Laya.timer.delta / 1000` -- **Cocos Creator**:使用组件的 `update(deltaTime)` 参数 -- **原生浏览器**:自己计算deltaTime -- **Node.js服务器**:自己计算deltaTime - -**优势:** -- 与引擎时间系统完全同步 -- 支持引擎的时间缩放和暂停功能 -- 更精确的时间控制 -- 统一的API,简化集成 - -## Core配置 - -### 基础配置 - -ECS框架提供了灵活的配置选项来满足不同项目需求。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构: - -```typescript -import { Core, ICoreConfig } from '@esengine/ecs-framework'; - -// 方式1:简化配置(向后兼容) -Core.create(true); // 启用调试模式 -Core.create(false); // 发布模式 -Core.create(); // 默认调试模式 - -// 方式2:详细配置(推荐) -const config: ICoreConfig = { - debug: true, // 启用调试模式 - enableEntitySystems: true, // 启用实体系统(默认true) - debugConfig: { // 可选:远程调试配置 - enabled: true, - websocketUrl: 'ws://localhost:8080', - autoReconnect: true, - updateInterval: 1000, // 调试数据更新间隔(毫秒) - channels: { // 调试数据通道 - entities: true, // 实体信息 - systems: true, // 系统信息 - performance: true, // 性能数据 - components: true, // 组件信息 - scenes: true // 场景信息 - } - } -}; - -const core = Core.create(config); -``` - -### 架构说明 - -ECS框架支持两种使用模式: - -1. **单Scene模式(默认,向后兼容)**: - ```typescript - // 传统用法,无需任何修改 - const scene = new Scene(); - Core.setScene(scene); - ``` - -2. **多World模式(高级功能)**: - ```typescript - // 启用World管理器,支持多World/多Scene架构 - Core.enableWorldManager(); - const roomWorld = Core.getWorldManager().createWorld('Room_001'); - const battleScene = roomWorld.createScene('battle'); - ``` - -**使用场景:** -- **单Scene模式**:适合简单游戏、单机游戏、原型开发 -- **多World模式**:适合多人游戏服务器、复杂应用、需要场景隔离的项目 - -> **详细了解World系统**:查看 [场景管理完整指南](scene-management-guide.md) 获取完整的多World架构示例和最佳实践 - -### 调试功能 - -ECS框架内置了强大的调试功能,支持运行时监控和远程调试: - -#### Cocos Creator专用调试插件 - -** 对于Cocos Creator用户,我们提供了专门的可视化调试插件:** - -- **插件地址**:[cocos-ecs-framework 调试插件](https://store.cocos.com/app/detail/7823) -- **插件版本**:v1.0.0 -- **支持版本**:Cocos Creator v3.0.0+ -- **支持平台**:Android | iOS | HTML5 - -这个插件提供了完整的ECS可视化调试界面,包括实体查看器、组件编辑器、系统监控、性能分析等功能。 - -#### 通用调试配置 - -```typescript -// 运行时启用调试 -Core.enableDebug({ - enabled: true, - websocketUrl: 'ws://localhost:8080', - autoReconnect: true, - updateInterval: 500, - channels: { - entities: true, - systems: true, - performance: true, - components: false, // 可以选择性禁用某些通道 - scenes: true - } -}); - -// 获取调试数据 -const debugData = Core.getDebugData(); -console.log('当前实体数量:', debugData?.entities?.totalEntities); - -// 禁用调试 -Core.disableDebug(); - -// 检查调试状态 -if (Core.isDebugEnabled) { - console.log('调试模式已启用'); -} -``` - -### 生产环境配置建议 - -```typescript -// 开发环境 - Cocos Creator -const devConfigForCocos: ICoreConfig = { - debug: true, - enableEntitySystems: true, - debugConfig: { - enabled: true, - websocketUrl: 'ws://localhost:8080', // 连接Cocos插件 - autoReconnect: true, - updateInterval: 1000, - channels: { - entities: true, - systems: true, - performance: true, - components: true, - scenes: true - } - } -}; - -// 开发环境 - 其他平台 -const devConfig: ICoreConfig = { - debug: true, - enableEntitySystems: true, - debugConfig: { - enabled: true, - websocketUrl: 'ws://localhost:8080', - autoReconnect: true, - updateInterval: 1000, - channels: { - entities: true, - systems: true, - performance: true, - components: true, - scenes: true - } - } -}; - -// 生产环境 -const prodConfig: ICoreConfig = { - debug: false, // 关闭调试以提升性能 - enableEntitySystems: true, - // debugConfig 可以省略或设为 undefined -}; - -const isDevelopment = process.env.NODE_ENV === 'development'; -Core.create(isDevelopment ? devConfig : prodConfig); -``` - -** 调试功能说明:** -- **Cocos Creator**:推荐使用[官方调试插件](https://store.cocos.com/app/detail/7823)获得最佳调试体验 -- **其他平台**:可以通过WebSocket连接自定义调试工具 -- **生产环境**:建议关闭调试功能以获得最佳性能 - -## 平台集成 - -### Laya引擎 - -```typescript -import { Scene as LayaScene } from "laya/display/Scene"; -import { Core, Scene as ECSScene, EntityManager, EntitySystem } from '@esengine/ecs-framework'; - -class LayaECSGame extends LayaScene { - private ecsScene: ECSScene; - private entityManager: EntityManager; - - constructor() { - super(); - - // 初始化ECS框架(简化方式) - Core.create(true); // 启用调试模式 - // 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} }) - - this.ecsScene = new ECSScene(); - this.ecsScene.name = "LayaGameScene"; - Core.setScene(this.ecsScene); - - this.entityManager = new EntityManager(); - this.setupSystems(); - } - - onAwake(): void { - super.onAwake(); - // 使用Laya的帧循环更新ECS - Laya.timer.frameLoop(1, this, this.updateECS); - } - - onDestroy(): void { - Laya.timer.clear(this, this.updateECS); - super.onDestroy(); - } - - private updateECS(): void { - // 使用Laya的deltaTime更新ECS - const deltaTime = Laya.timer.delta / 1000; // 转换为秒 - Core.update(deltaTime); - } - - private setupSystems(): void { - this.ecsScene.addEntityProcessor(new LayaRenderSystem(this)); - this.ecsScene.addEntityProcessor(new MovementSystem()); - } -} - -// Laya渲染系统 -class LayaRenderSystem extends EntitySystem { - private layaScene: LayaScene; - - constructor(layaScene: LayaScene) { - super(Matcher.all(PositionComponent, SpriteComponent)); - this.layaScene = layaScene; - } - - protected override process(entities: Entity[]): void { - entities.forEach(entity => { - const pos = entity.getComponent(PositionComponent)!; - const sprite = entity.getComponent(SpriteComponent)!; - - if (sprite.layaSprite) { - sprite.layaSprite.x = pos.x; - sprite.layaSprite.y = pos.y; - } - }); - } -} - -// 使用方法 -Laya.Scene.open("GameScene.scene", false, null, null, LayaECSGame); -``` - -### Cocos Creator - -```typescript -import { Component as CocosComponent, _decorator, Node } from 'cc'; -import { Core, Scene as ECSScene, EntityManager, EntitySystem } from '@esengine/ecs-framework'; - -const { ccclass, property } = _decorator; - -@ccclass('ECSGameManager') -export class ECSGameManager extends CocosComponent { - private ecsScene: ECSScene; - private entityManager: EntityManager; - - start() { - // 初始化ECS框架(简化方式) - Core.create(true); // 启用调试模式 - // 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} }) - - this.ecsScene = new ECSScene(); - this.ecsScene.name = "CocosGameScene"; - Core.setScene(this.ecsScene); - - this.entityManager = new EntityManager(); - this.setupSystems(); - } - - update(deltaTime: number) { - // 使用Cocos Creator的deltaTime更新ECS - Core.update(deltaTime); - } - - onDestroy() { - if (this.ecsScene) { - this.ecsScene.onDestroy(); - } - } - - private setupSystems(): void { - this.ecsScene.addEntityProcessor(new CocosRenderSystem(this.node)); - this.ecsScene.addEntityProcessor(new MovementSystem()); - } - - public getEntityManager(): EntityManager { - return this.entityManager; - } -} - -// Cocos渲染系统 -class CocosRenderSystem extends EntitySystem { - private rootNode: Node; - - constructor(rootNode: Node) { - super(Matcher.all(PositionComponent, SpriteComponent)); - this.rootNode = rootNode; - } - - protected override process(entities: Entity[]): void { - entities.forEach(entity => { - const pos = entity.getComponent(PositionComponent)!; - const sprite = entity.getComponent(SpriteComponent)!; - - if (sprite.cocosNode) { - sprite.cocosNode.setPosition(pos.x, pos.y); - } - }); - } -} - -// 将ECSGameManager脚本挂载到场景根节点 -``` - -** Cocos Creator调试提示:** -为了获得最佳的ECS调试体验,建议安装我们的专用调试插件: -- 插件地址:[https://store.cocos.com/app/detail/7823](https://store.cocos.com/app/detail/7823) -- 支持Cocos Creator v3.0.0+ -- 提供实体查看器、组件编辑器、系统监控等功能 - -### Node.js后端 - -```typescript -import { Core, Scene, EntityManager, EntitySystem, Time, World } from '@esengine/ecs-framework'; - -class ServerGameManager { - private scene: Scene; - private entityManager: EntityManager; - private isRunning: boolean = false; - private tickRate: number = 60; // 60 TPS - private lastUpdate: number = Date.now(); - - constructor() { - // 初始化ECS框架(简化方式) - Core.create(true); // 启用调试模式 - // 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} }) - - // 单Scene模式(简单场景) - this.scene = new Scene(); - this.scene.name = "ServerScene"; - Core.setScene(this.scene); - - this.entityManager = new EntityManager(); - this.setupSystems(); - } - - public start(): void { - this.isRunning = true; - console.log(`游戏服务器启动,TPS: ${this.tickRate}`); - this.gameLoop(); - } - - public stop(): void { - this.isRunning = false; - } - - private gameLoop(): void { - if (!this.isRunning) return; - - const now = Date.now(); - const deltaTime = (now - this.lastUpdate) / 1000; - this.lastUpdate = now; - - // 使用计算出的deltaTime更新ECS - Core.update(deltaTime); - - const frameTime = 1000 / this.tickRate; - const processingTime = Date.now() - now; - const delay = Math.max(0, frameTime - processingTime); - - setTimeout(() => this.gameLoop(), delay); - } - - private setupSystems(): void { - this.scene.addEntityProcessor(new ServerMovementSystem()); - this.scene.addEntityProcessor(new NetworkSyncSystem()); - this.scene.addEntityProcessor(new AISystem()); - } - - public handlePlayerInput(playerId: string, input: any): void { - const playerEntity = this.findPlayerEntity(playerId); - if (playerEntity) { - const inputComp = playerEntity.getComponent(InputComponent); - if (inputComp) { - inputComp.updateInput(input); - } - } - } - - public getWorldState(): any { - const entities = this.entityManager - .query() - .withAll(PositionComponent, NetworkComponent) - .execute(); - - return entities.map(entity => ({ - id: entity.id, - position: entity.getComponent(PositionComponent), - })); - } - - private findPlayerEntity(playerId: string): Entity | null { - const players = this.entityManager - .query() - .withAll(PlayerComponent) - .execute(); - - return players.find(player => - player.getComponent(PlayerComponent).playerId === playerId - ) || null; - } -} - -// 启动服务器 -const server = new ServerGameManager(); -server.start(); -``` - -> **多房间游戏服务器示例**:查看 [场景管理完整指南](scene-management-guide.md#world多场景管理) 了解如何使用多World架构实现复杂的多房间游戏服务器 - -### 原生浏览器 - -```typescript -import { Core, Scene, EntityManager, EntitySystem } from '@esengine/ecs-framework'; - -class BrowserGame { - private scene: Scene; - private entityManager: EntityManager; - - constructor() { - // 初始化ECS框架(简化方式) - Core.create(true); // 启用调试模式 - // 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} }) - - this.scene = new Scene(); - this.scene.name = "BrowserScene"; - Core.setScene(this.scene); - - this.entityManager = new EntityManager(); - this.setupSystems(); - } - - public start(): void { - this.createEntities(); - this.gameLoop(); - } - - private gameLoop(): void { - let lastTime = 0; - const update = (currentTime: number) => { - // 计算deltaTime并更新ECS(原生浏览器环境) - const deltaTime = lastTime > 0 ? (currentTime - lastTime) / 1000 : 0.016; - lastTime = currentTime; - Core.update(deltaTime); - requestAnimationFrame(update); - }; - requestAnimationFrame(update); - } - - private setupSystems(): void { - this.scene.addEntityProcessor(new MovementSystem()); - this.scene.addEntityProcessor(new RenderSystem()); - } - - private createEntities(): void { - const player = this.entityManager.createEntity("Player"); - player.addComponent(new PositionComponent(400, 300)); - player.addComponent(new VelocityComponent(0, 0)); - } -} - -const game = new BrowserGame(); -game.start(); -``` - -## 基础组件定义 - -```typescript -import { Component } from '@esengine/ecs-framework'; - -// 位置组件 -class PositionComponent extends Component { - public x: number = 0; - public y: number = 0; - - constructor(...args: unknown[]) { - super(); - const [x = 0, y = 0] = args as [number?, number?]; - this.x = x; - this.y = y; - } - - public reset() { - this.x = 0; - this.y = 0; - } -} - -// 速度组件 -class VelocityComponent extends Component { - public x: number = 0; - public y: number = 0; - - constructor(...args: unknown[]) { - super(); - const [x = 0, y = 0] = args as [number?, number?]; - this.x = x; - this.y = y; - } - - public reset() { - this.x = 0; - this.y = 0; - } -} - -// 生命值组件 -class HealthComponent extends Component { - public maxHealth: number = 100; - public currentHealth: number = 100; - - constructor(...args: unknown[]) { - super(); - const [maxHealth = 100] = args as [number?]; - this.maxHealth = maxHealth; - this.currentHealth = maxHealth; - } - - public reset() { - this.maxHealth = 100; - this.currentHealth = 100; - } - - public takeDamage(damage: number): void { - this.currentHealth = Math.max(0, this.currentHealth - damage); - } - - public heal(amount: number): void { - this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount); - } - - public isDead(): boolean { - return this.currentHealth <= 0; - } -} -``` - -## 基础系统创建 - -```typescript -import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework'; - -class MovementSystem extends EntitySystem { - constructor() { - super(Matcher.all(PositionComponent, VelocityComponent)); - } - - protected override process(entities: Entity[]): void { - entities.forEach(entity => { - const position = entity.getComponent(PositionComponent)!; - const velocity = entity.getComponent(VelocityComponent)!; - - position.x += velocity.x * Time.deltaTime; - position.y += velocity.y * Time.deltaTime; - }); - } -} - -class HealthSystem extends EntitySystem { - constructor() { - super(Matcher.all(HealthComponent)); - } - - protected override process(entities: Entity[]): void { - entities.forEach(entity => { - const health = entity.getComponent(HealthComponent)!; - if (health.currentHealth <= 0) { - entity.destroy(); - } - }); - } -} -``` - -## 实体管理 - -```typescript -// 创建实体 -const player = entityManager.createEntity("Player"); -player.addComponent(new PositionComponent(100, 100)); -player.addComponent(new VelocityComponent(5, 0)); -player.addComponent(new HealthComponent(100)); - -// 批量创建实体 -const enemies = scene.createEntities(50, "Enemy"); -enemies.forEach(enemy => { - enemy.addComponent(new PositionComponent(Math.random() * 800, Math.random() * 600)); - enemy.addComponent(new HealthComponent(50)); -}); - -// 查询实体 - 推荐方式 -const movingEntities = entityManager - .query() - .withAll(PositionComponent, VelocityComponent) - .execute(); - -// 简单查询方式 -const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent); -const taggedEntities = entityManager.getEntitiesByTag(2); -``` - -## 事件系统 - -推荐使用Scene的事件系统或EntityManager的事件系统: - -```typescript -// 使用EntityManager的事件系统(推荐) -const eventBus = entityManager.eventBus; - -// 监听ECS事件 -eventBus.onEntityCreated((data) => { - console.log(`实体创建: ${data.entityName}`); -}); - -eventBus.onComponentAdded((data) => { - console.log(`组件添加: ${data.componentType}`); -}); - -// 发射自定义事件 -eventBus.emit('player:died', { player: entity, score: 1000 }); - -// 使用装饰器自动注册事件监听器 -import { EventHandler } from '@esengine/ecs-framework'; - -class GameSystem { - @EventHandler('player:died') - onPlayerDied(data: { player: Entity; score: number }) { - console.log(`玩家死亡,得分: ${data.score}`); - } -} -``` - -## 性能监控 - -```typescript -// 获取场景统计 -const sceneStats = scene.getStats(); -console.log('实体数量:', sceneStats.entityCount); -console.log('系统数量:', sceneStats.processorCount); - -// 获取查询统计 -const queryStats = scene.querySystem.getStats(); -console.log('查询统计:', queryStats); -``` - -## 下一步 - -- [EntityManager 使用指南](entity-manager-example.md) - 详细了解实体管理器的高级功能 -- [性能优化指南](performance-optimization.md) - 深入了解三大性能优化系统 -- [核心概念](core-concepts.md) - 深入了解 ECS 架构和设计原理 -- [查询系统使用指南](query-system-usage.md) - 学习高性能查询系统的详细用法 \ No newline at end of file diff --git a/docs/guide/component.md b/docs/guide/component.md new file mode 100644 index 00000000..3a74edd5 --- /dev/null +++ b/docs/guide/component.md @@ -0,0 +1,359 @@ +# 组件系统 + +在 ECS 架构中,组件(Component)是数据和行为的载体。组件定义了实体具有的属性和功能,是 ECS 架构的核心构建块。 + +## 基本概念 + +组件是继承自 `Component` 抽象基类的具体类,用于: +- 存储实体的数据(如位置、速度、健康值等) +- 定义与数据相关的行为方法 +- 提供生命周期回调钩子 +- 支持序列化和调试 + +## 创建组件 + +### 基础组件定义 + +```typescript +import { Component, ECSComponent } from '@esengine/ecs-framework'; + +@ECSComponent('Position') +class Position extends Component { + x: number = 0; + y: number = 0; + + constructor(x: number = 0, y: number = 0) { + super(); + this.x = x; + this.y = y; + } +} + +@ECSComponent('Health') +class Health extends Component { + current: number; + max: number; + + constructor(max: number = 100) { + super(); + this.max = max; + this.current = max; + } + + // 组件可以包含行为方法 + takeDamage(damage: number): void { + this.current = Math.max(0, this.current - damage); + } + + heal(amount: number): void { + this.current = Math.min(this.max, this.current + amount); + } + + isDead(): boolean { + return this.current <= 0; + } +} +``` + +### 组件装饰器 + +**必须使用 `@ECSComponent` 装饰器**,这确保了: +- 组件在代码混淆后仍能正确识别 +- 提供稳定的类型名称用于序列化和调试 +- 框架能正确管理组件注册 + +```typescript +// ✅ 正确的用法 +@ECSComponent('Velocity') +class Velocity extends Component { + dx: number = 0; + dy: number = 0; +} + +// ❌ 错误的用法 - 没有装饰器 +class BadComponent extends Component { + // 这样定义的组件可能在生产环境出现问题 +} +``` + +## 组件生命周期 + +组件提供了生命周期钩子,可以重写来执行特定的逻辑: + +```typescript +@ECSComponent('ExampleComponent') +class ExampleComponent extends Component { + private resource: SomeResource | null = null; + + /** + * 组件被添加到实体时调用 + * 用于初始化资源、建立引用等 + */ + onAddedToEntity(): void { + console.log(`组件 ${this.constructor.name} 被添加到实体 ${this.entity.name}`); + this.resource = new SomeResource(); + } + + /** + * 组件从实体移除时调用 + * 用于清理资源、断开引用等 + */ + onRemovedFromEntity(): void { + console.log(`组件 ${this.constructor.name} 从实体 ${this.entity.name} 移除`); + if (this.resource) { + this.resource.cleanup(); + this.resource = null; + } + } +} +``` + +## 访问实体 + +组件可以通过 `this.entity` 访问其所属的实体: + +```typescript +@ECSComponent('Damage') +class Damage extends Component { + damage: number; + + constructor(damage: number) { + super(); + this.damage = damage; + } + + // 在组件方法中访问实体和其他组件 + applyDamage(): void { + const health = this.entity.getComponent(Health); + if (health) { + health.takeDamage(this.damage); + + // 如果生命值为0,销毁实体 + if (health.isDead()) { + this.entity.destroy(); + } + } + } +} +``` + +## 组件属性 + +每个组件都有一些内置属性: + +```typescript +@ECSComponent('ExampleComponent') +class ExampleComponent extends Component { + someData: string = "example"; + + showComponentInfo(): void { + console.log(`组件ID: ${this.id}`); // 唯一的组件ID + console.log(`所属实体: ${this.entity.name}`); // 所属实体引用 + } +} +``` + + +## 复杂组件示例 + +### 状态机组件 + +```typescript +enum EntityState { + Idle, + Moving, + Attacking, + Dead +} + +@ECSComponent('StateMachine') +class StateMachine extends Component { + private _currentState: EntityState = EntityState.Idle; + private _previousState: EntityState = EntityState.Idle; + private _stateTimer: number = 0; + + get currentState(): EntityState { + return this._currentState; + } + + get previousState(): EntityState { + return this._previousState; + } + + get stateTimer(): number { + return this._stateTimer; + } + + changeState(newState: EntityState): void { + if (this._currentState !== newState) { + this._previousState = this._currentState; + this._currentState = newState; + this._stateTimer = 0; + } + } + + updateTimer(deltaTime: number): void { + this._stateTimer += deltaTime; + } + + isInState(state: EntityState): boolean { + return this._currentState === state; + } +} +``` + +### 配置数据组件 + +```typescript +interface WeaponData { + damage: number; + range: number; + fireRate: number; + ammo: number; +} + +@ECSComponent('WeaponConfig') +class WeaponConfig extends Component { + data: WeaponData; + + constructor(weaponData: WeaponData) { + super(); + this.data = { ...weaponData }; // 深拷贝避免共享引用 + } + + // 提供便捷的访问方法 + getDamage(): number { + return this.data.damage; + } + + canFire(): boolean { + return this.data.ammo > 0; + } + + consumeAmmo(): boolean { + if (this.data.ammo > 0) { + this.data.ammo--; + return true; + } + return false; + } +} +``` + +## 最佳实践 + +### 1. 保持组件简单 + +```typescript +// ✅ 好的组件设计 - 单一职责 +@ECSComponent('Position') +class Position extends Component { + x: number = 0; + y: number = 0; +} + +@ECSComponent('Velocity') +class Velocity extends Component { + dx: number = 0; + dy: number = 0; +} + +// ❌ 避免的组件设计 - 职责过多 +@ECSComponent('GameObject') +class GameObject extends Component { + x: number; + y: number; + dx: number; + dy: number; + health: number; + damage: number; + sprite: string; + // 太多不相关的属性 +} +``` + +### 2. 使用构造函数初始化 + +```typescript +@ECSComponent('Transform') +class Transform extends Component { + x: number; + y: number; + rotation: number; + scale: number; + + constructor(x = 0, y = 0, rotation = 0, scale = 1) { + super(); + this.x = x; + this.y = y; + this.rotation = rotation; + this.scale = scale; + } +} +``` + +### 3. 明确的类型定义 + +```typescript +interface InventoryItem { + id: string; + name: string; + quantity: number; + type: 'weapon' | 'consumable' | 'misc'; +} + +@ECSComponent('Inventory') +class Inventory extends Component { + items: InventoryItem[] = []; + maxSlots: number; + + constructor(maxSlots: number = 20) { + super(); + this.maxSlots = maxSlots; + } + + addItem(item: InventoryItem): boolean { + if (this.items.length < this.maxSlots) { + this.items.push(item); + return true; + } + return false; + } + + removeItem(itemId: string): InventoryItem | null { + const index = this.items.findIndex(item => item.id === itemId); + if (index !== -1) { + return this.items.splice(index, 1)[0]; + } + return null; + } +} +``` + +### 4. 避免在组件中存储实体引用 + +```typescript +// ❌ 避免:在组件中存储其他实体的引用 +@ECSComponent('BadFollower') +class BadFollower extends Component { + target: Entity; // 直接引用可能导致内存泄漏 +} + +// ✅ 推荐:存储实体ID,通过场景查找 +@ECSComponent('Follower') +class Follower extends Component { + targetId: number; + followDistance: number = 50; + + constructor(targetId: number) { + super(); + this.targetId = targetId; + } + + getTarget(): Entity | null { + return this.entity.scene?.findEntityById(this.targetId) || null; + } +} +``` + +组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。 \ No newline at end of file diff --git a/docs/guide/entity.md b/docs/guide/entity.md new file mode 100644 index 00000000..d0188c3a --- /dev/null +++ b/docs/guide/entity.md @@ -0,0 +1,288 @@ +# 实体类 + +在 ECS 架构中,实体(Entity)是游戏世界中的基本对象。实体本身不包含游戏逻辑或数据,它只是一个容器,用来组合不同的组件来实现各种功能。 + +## 基本概念 + +实体是一个轻量级的对象,主要用于: +- 作为组件的容器 +- 提供唯一标识(ID) +- 管理组件的生命周期 + +## 创建实体 + +**重要提示:实体必须通过场景创建,不支持手动创建!** + +实体必须通过场景的 `createEntity()` 方法来创建,这样才能确保: +- 实体被正确添加到场景的实体管理系统中 +- 实体被添加到查询系统中,供系统使用 +- 实体获得正确的场景引用 +- 触发相关的生命周期事件 + +```typescript +// 正确的方式:通过场景创建实体 +const player = scene.createEntity("Player"); + +// ❌ 错误的方式:手动创建实体 +// const entity = new Entity("MyEntity", 1); // 这样创建的实体系统无法管理 +``` + +## 添加组件 + +实体通过添加组件来获得功能: + +```typescript +import { Component, ECSComponent } from '@esengine/ecs-framework'; + +// 定义位置组件 +@ECSComponent('Position') +class Position extends Component { + x: number = 0; + y: number = 0; + + constructor(x: number = 0, y: number = 0) { + super(); + this.x = x; + this.y = y; + } +} + +// 定义健康组件 +@ECSComponent('Health') +class Health extends Component { + current: number = 100; + max: number = 100; + + constructor(max: number = 100) { + super(); + this.max = max; + this.current = max; + } +} + +// 给实体添加组件 +const player = scene.createEntity("Player"); +player.addComponent(new Position(100, 200)); +player.addComponent(new Health(150)); +``` + +## 获取组件 + +```typescript +// 获取组件(传入组件类,不是实例) +const position = player.getComponent(Position); // 返回 Position | null +const health = player.getComponent(Health); // 返回 Health | null + +// 检查组件是否存在 +if (position) { + console.log(`玩家位置: x=${position.x}, y=${position.y}`); +} + +// 检查是否有某个组件 +if (player.hasComponent(Position)) { + console.log("玩家有位置组件"); +} + +// 获取所有组件实例(直接访问 components 属性) +const allComponents = player.components; // Component[] + +// 获取指定类型的所有组件(支持同类型多组件) +const allHealthComponents = player.getComponents(Health); // Health[] + +// 获取或创建组件(如果不存在则自动创建) +const position = player.getOrCreateComponent(Position, 0, 0); // 传入构造参数 +const health = player.getOrCreateComponent(Health, 100); // 如果存在则返回现有的,不存在则创建新的 +``` + +## 移除组件 + +```typescript +// 方式1:通过组件类型移除 +const removedHealth = player.removeComponentByType(Health); +if (removedHealth) { + console.log("健康组件已被移除"); +} + +// 方式2:通过组件实例移除 +const healthComponent = player.getComponent(Health); +if (healthComponent) { + player.removeComponent(healthComponent); +} + +// 批量移除多种组件类型 +const removedComponents = player.removeComponentsByTypes([Position, Health]); + +// 检查组件是否被移除 +if (!player.hasComponent(Health)) { + console.log("健康组件已被移除"); +} +``` + +## 实体查找 + +场景提供了多种方式来查找实体: + +### 通过名称查找 + +```typescript +// 查找单个实体 +const player = scene.findEntity("Player"); +// 或使用别名方法 +const player2 = scene.getEntityByName("Player"); + +if (player) { + console.log("找到玩家实体"); +} +``` + +### 通过 ID 查找 + +```typescript +// 通过实体 ID 查找 +const entity = scene.findEntityById(123); +``` + +### 通过标签查找 + +实体支持标签系统,用于快速分类和查找: + +```typescript +// 设置标签 +player.tag = 1; // 玩家标签 +enemy.tag = 2; // 敌人标签 + +// 通过标签查找所有相关实体 +const players = scene.findEntitiesByTag(1); +const enemies = scene.findEntitiesByTag(2); +// 或使用别名方法 +const allPlayers = scene.getEntitiesByTag(1); +``` + + +## 实体生命周期 + +```typescript +// 销毁实体 +player.destroy(); + +// 检查实体是否已销毁 +if (player.isDestroyed) { + console.log("实体已被销毁"); +} +``` + +## 实体事件 + +实体的组件变化会触发事件: + +```typescript +// 监听组件添加事件 +scene.eventSystem.on('component:added', (data) => { + console.log('组件已添加:', data); +}); + +// 监听实体创建事件 +scene.eventSystem.on('entity:created', (data) => { + console.log('实体已创建:', data.entityName); +}); +``` + +## 性能优化 + + +### 批量创建实体 + +框架提供了高性能的批量创建方法: + +```typescript +// 批量创建 100 个子弹实体(高性能版本) +const bullets = scene.createEntities(100, "Bullet"); + +// 为每个子弹添加组件 +bullets.forEach((bullet, index) => { + bullet.addComponent(new Position(Math.random() * 800, Math.random() * 600)); + bullet.addComponent(new Velocity(Math.random() * 100 - 50, Math.random() * 100 - 50)); +}); +``` + +`createEntities()` 方法会: +- 批量分配实体 ID +- 批量添加到实体列表 +- 优化查询系统更新 +- 减少系统缓存清理次数 + +## 最佳实践 + +### 1. 合理的组件粒度 + +```typescript +// 好的做法:功能单一的组件 +@ECSComponent('Position') +class Position extends Component { + x: number = 0; + y: number = 0; +} + +@ECSComponent('Velocity') +class Velocity extends Component { + dx: number = 0; + dy: number = 0; +} + +// 避免:功能过于复杂的组件 +@ECSComponent('Player') +class Player extends Component { + // 避免在一个组件中包含太多不相关的属性 + x: number; + y: number; + health: number; + inventory: Item[]; + skills: Skill[]; +} +``` + +### 2. 使用装饰器 + +始终使用 `@ECSComponent` 装饰器: + +```typescript +@ECSComponent('Transform') +class Transform extends Component { + // 组件实现 +} +``` + +### 3. 合理命名 + +```typescript +// 清晰的实体命名 +const mainCharacter = scene.createEntity("MainCharacter"); +const enemy1 = scene.createEntity("Goblin_001"); +const collectible = scene.createEntity("HealthPotion"); +``` + +### 4. 及时清理 + +```typescript +// 不再需要的实体应该及时销毁 +if (enemy.getComponent(Health).current <= 0) { + enemy.destroy(); +} +``` + +## 调试实体 + +框架提供了调试功能来帮助开发: + +```typescript +// 获取实体调试信息 +const debugInfo = entity.getDebugInfo(); +console.log('实体信息:', debugInfo); + +// 列出实体的所有组件 +entity.components.forEach(component => { + console.log('组件:', component.constructor.name); +}); +``` + +实体是 ECS 架构的核心概念之一,理解如何正确使用实体将帮助你构建高效、可维护的游戏代码。 \ No newline at end of file diff --git a/docs/guide/event-system.md b/docs/guide/event-system.md new file mode 100644 index 00000000..bbed1688 --- /dev/null +++ b/docs/guide/event-system.md @@ -0,0 +1,595 @@ +# 事件系统 + +ECS 框架内置了强大的类型安全事件系统,支持同步/异步事件、优先级、批处理等高级功能。事件系统是实现组件间通信、系统间协作的核心机制。 + +## 基本概念 + +事件系统提供了发布-订阅模式的实现,包含以下核心概念: +- **事件发布者**:发射事件的对象 +- **事件监听者**:监听并处理特定事件的对象 +- **事件类型**:字符串标识,用于区分不同类型的事件 +- **事件数据**:事件携带的相关信息 + +## 事件系统架构 + +框架提供了两层事件系统: + +1. **TypeSafeEventSystem** - 底层高性能事件系统 +2. **EventBus** - 上层增强事件总线,提供更多便利功能 + +## 基本使用 + +### 在场景中使用事件系统 + +每个场景都有内置的事件系统: + +```typescript +class GameScene extends Scene { + protected initialize(): void { + // 监听事件 + this.eventSystem.on('player_died', this.onPlayerDied.bind(this)); + this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this)); + this.eventSystem.on('score_changed', this.onScoreChanged.bind(this)); + } + + private onPlayerDied(data: { player: Entity, cause: string }): void { + console.log(`玩家死亡,原因: ${data.cause}`); + // 处理玩家死亡逻辑 + } + + private onEnemySpawned(data: { enemy: Entity, position: { x: number, y: number } }): void { + console.log('敌人生成于:', data.position); + // 处理敌人生成逻辑 + } + + private onScoreChanged(data: { newScore: number, oldScore: number }): void { + console.log(`分数变化: ${data.oldScore} -> ${data.newScore}`); + // 更新UI显示 + } + + // 在系统中发射事件 + someGameLogic(): void { + // 发射同步事件 + this.eventSystem.emitSync('score_changed', { + newScore: 1000, + oldScore: 800 + }); + } +} +``` + +### 在系统中使用事件 + +系统可以方便地监听和发送事件: + +```typescript +@ECSSystem('Combat') +class CombatSystem extends EntitySystem { + constructor() { + super(Matcher.all(Health, Combat)); + } + + protected onInitialize(): void { + // 使用系统提供的事件监听方法(自动清理) + this.addEventListener('player_attack', this.onPlayerAttack.bind(this)); + this.addEventListener('enemy_death', this.onEnemyDeath.bind(this)); + } + + private onPlayerAttack(data: { damage: number, target: Entity }): void { + // 处理玩家攻击事件 + const health = data.target.getComponent(Health); + if (health) { + health.current -= data.damage; + + if (health.current <= 0) { + // 发送敌人死亡事件 + this.scene?.eventSystem.emitSync('enemy_death', { + enemy: data.target, + killer: 'player' + }); + } + } + } + + private onEnemyDeath(data: { enemy: Entity, killer: string }): void { + // 处理敌人死亡 + data.enemy.destroy(); + + // 发送经验奖励事件 + this.scene?.eventSystem.emitSync('experience_gained', { + amount: 100, + source: 'enemy_kill' + }); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const combat = entity.getComponent(Combat); + if (combat && combat.shouldAttack()) { + // 发射攻击事件 + this.scene?.eventSystem.emitSync('player_attack', { + damage: combat.damage, + target: combat.target + }); + } + } + } +} +``` + +## 高级功能 + +### 一次性监听器 + +```typescript +class GameScene extends Scene { + protected initialize(): void { + // 只监听一次的事件 + this.eventSystem.once('game_start', this.onGameStart.bind(this)); + + // 或者使用配置对象 + this.eventSystem.on('level_complete', this.onLevelComplete.bind(this), { + once: true // 只执行一次 + }); + } + + private onGameStart(): void { + console.log('游戏开始!'); + // 这个方法只会被调用一次 + } + + private onLevelComplete(): void { + console.log('关卡完成!'); + // 这个方法也只会被调用一次 + } +} +``` + +### 优先级控制 + +```typescript +class GameScene extends Scene { + protected initialize(): void { + // 高优先级监听器先执行 + this.eventSystem.on('damage_dealt', this.onDamageDealt.bind(this), { + priority: 100 // 高优先级 + }); + + // 普通优先级 + this.eventSystem.on('damage_dealt', this.updateUI.bind(this), { + priority: 0 // 默认优先级 + }); + + // 低优先级最后执行 + this.eventSystem.on('damage_dealt', this.logDamage.bind(this), { + priority: -100 // 低优先级 + }); + } + + private onDamageDealt(data: any): void { + // 最先执行 - 处理核心游戏逻辑 + } + + private updateUI(data: any): void { + // 中等优先级 - 更新界面 + } + + private logDamage(data: any): void { + // 最后执行 - 记录日志 + } +} +``` + +### 异步事件处理 + +```typescript +class GameScene extends Scene { + protected initialize(): void { + // 监听异步事件 + this.eventSystem.onAsync('save_game', this.onSaveGame.bind(this)); + this.eventSystem.onAsync('load_data', this.onLoadData.bind(this)); + } + + private async onSaveGame(data: { saveSlot: number }): Promise { + console.log(`开始保存游戏到槽位 ${data.saveSlot}`); + + // 模拟异步保存操作 + await this.saveGameData(data.saveSlot); + + console.log('游戏保存完成'); + } + + private async onLoadData(data: { url: string }): Promise { + try { + const response = await fetch(data.url); + const gameData = await response.json(); + // 处理加载的数据 + } catch (error) { + console.error('数据加载失败:', error); + } + } + + private async saveGameData(slot: number): Promise { + // 模拟保存操作 + return new Promise(resolve => setTimeout(resolve, 1000)); + } + + // 发射异步事件 + public async triggerSave(): Promise { + // 使用 emit 而不是 emitSync 来触发异步监听器 + await this.eventSystem.emit('save_game', { saveSlot: 1 }); + console.log('所有异步保存操作完成'); + } +} +``` + +### 事件统计和调试 + +```typescript +class GameScene extends Scene { + protected initialize(): void { + this.eventSystem.on('debug_event', this.onDebugEvent.bind(this)); + } + + private onDebugEvent(): void { + // 处理调试事件 + } + + public showEventStats(): void { + // 获取特定事件的统计信息 + const stats = this.eventSystem.getStats('debug_event') as any; + if (stats) { + console.log('事件统计:'); + console.log(`- 事件类型: ${stats.eventType}`); + console.log(`- 监听器数量: ${stats.listenerCount}`); + console.log(`- 触发次数: ${stats.triggerCount}`); + console.log(`- 平均执行时间: ${stats.averageExecutionTime.toFixed(2)}ms`); + } + + // 获取所有事件统计 + const allStats = this.eventSystem.getStats() as Map; + console.log(`总共有 ${allStats.size} 种事件类型`); + } + + public resetEventStats(): void { + // 重置特定事件的统计 + this.eventSystem.resetStats('debug_event'); + + // 或重置所有统计 + this.eventSystem.resetStats(); + } +} +``` + +## 全局事件总线 + +对于跨场景的事件通信,可以使用全局事件总线: + +```typescript +import { GlobalEventBus } from '@esengine/ecs-framework'; + +class GameManager { + private eventBus = GlobalEventBus.getInstance(); + + constructor() { + this.setupGlobalEvents(); + } + + private setupGlobalEvents(): void { + // 监听全局事件 + this.eventBus.on('player_level_up', this.onPlayerLevelUp.bind(this)); + this.eventBus.on('achievement_unlocked', this.onAchievementUnlocked.bind(this)); + this.eventBus.onAsync('upload_score', this.onUploadScore.bind(this)); + } + + private onPlayerLevelUp(data: { level: number, experience: number }): void { + console.log(`玩家升级到 ${data.level} 级!`); + // 处理全局升级逻辑 + } + + private onAchievementUnlocked(data: { achievementId: string, name: string }): void { + console.log(`解锁成就: ${data.name}`); + // 显示成就通知 + } + + private async onUploadScore(data: { score: number, playerName: string }): Promise { + // 异步上传分数到服务器 + try { + await this.uploadToServer(data); + console.log('分数上传成功'); + } catch (error) { + console.error('分数上传失败:', error); + } + } + + public triggerGlobalEvent(): void { + // 发射全局事件 + this.eventBus.emit('player_level_up', { + level: 10, + experience: 2500 + }); + } + + private async uploadToServer(data: any): Promise { + // 模拟服务器上传 + return new Promise(resolve => setTimeout(resolve, 2000)); + } +} +``` + +## 事件装饰器 + +使用装饰器自动注册事件监听器: + +```typescript +import { EventHandler, AsyncEventHandler } from '@esengine/ecs-framework'; + +class PlayerController { + constructor() { + // 自动调用事件监听器注册 + this.initEventListeners(); + } + + @EventHandler('player_input') + private onPlayerInput(data: { action: string, value: number }): void { + console.log(`玩家输入: ${data.action} = ${data.value}`); + // 处理玩家输入 + } + + @EventHandler('player_attack', { priority: 100 }) + private onPlayerAttack(data: { damage: number, target: string }): void { + console.log(`玩家攻击 ${data.target},造成 ${data.damage} 伤害`); + // 处理攻击逻辑 + } + + @AsyncEventHandler('save_progress') + private async onSaveProgress(data: { checkpointId: string }): Promise { + console.log(`保存进度到检查点: ${data.checkpointId}`); + // 异步保存进度 + await this.saveToCloud(data.checkpointId); + } + + @EventHandler('game_over', { once: true }) + private onGameOver(): void { + console.log('游戏结束!'); + // 这个方法只会被调用一次 + } + + private async saveToCloud(checkpointId: string): Promise { + // 模拟云端保存 + return new Promise(resolve => setTimeout(resolve, 1500)); + } +} +``` + +## 批处理事件 + +对于高频事件,可以使用批处理来提升性能: + +```typescript +class MovementSystem extends EntitySystem { + protected onInitialize(): void { + // 设置位置更新事件的批处理 + this.scene?.eventSystem.setBatchConfig('position_updated', { + batchSize: 50, // 批处理大小 + delay: 16, // 延迟时间(毫秒) + enabled: true + }); + + // 监听批处理事件 + this.addEventListener('position_updated:batch', this.onPositionBatch.bind(this)); + } + + private onPositionBatch(batchData: any): void { + console.log(`批处理位置更新,共 ${batchData.count} 个事件`); + + // 批量处理所有位置更新 + for (const event of batchData.events) { + this.updateMinimap(event.entityId, event.position); + } + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const position = entity.getComponent(Position); + if (position && position.hasChanged) { + // 发射高频位置更新事件(会被批处理) + this.scene?.eventSystem.emitSync('position_updated', { + entityId: entity.id, + position: { x: position.x, y: position.y } + }); + } + } + } + + private updateMinimap(entityId: number, position: { x: number, y: number }): void { + // 更新小地图显示 + } + + public flushPositionUpdates(): void { + // 立即处理所有待处理的位置更新 + this.scene?.eventSystem.flushBatch('position_updated'); + } +} +``` + +## 预定义的 ECS 事件 + +框架提供了一些预定义的 ECS 生命周期事件: + +```typescript +import { ECSEventType } from '@esengine/ecs-framework'; + +class ECSMonitor { + private eventBus = GlobalEventBus.getInstance(); + + constructor() { + this.setupECSEvents(); + } + + private setupECSEvents(): void { + // 监听实体生命周期事件 + this.eventBus.onEntityCreated(this.onEntityCreated.bind(this)); + this.eventBus.on(ECSEventType.ENTITY_DESTROYED, this.onEntityDestroyed.bind(this)); + + // 监听组件生命周期事件 + this.eventBus.onComponentAdded(this.onComponentAdded.bind(this)); + this.eventBus.on(ECSEventType.COMPONENT_REMOVED, this.onComponentRemoved.bind(this)); + + // 监听系统事件 + this.eventBus.on(ECSEventType.SYSTEM_ADDED, this.onSystemAdded.bind(this)); + this.eventBus.onSystemError(this.onSystemError.bind(this)); + + // 监听性能警告 + this.eventBus.onPerformanceWarning(this.onPerformanceWarning.bind(this)); + } + + private onEntityCreated(data: any): void { + console.log(`实体创建: ${data.entityName} (ID: ${data.entity.id})`); + } + + private onEntityDestroyed(data: any): void { + console.log(`实体销毁: ${data.entity.name} (ID: ${data.entity.id})`); + } + + private onComponentAdded(data: any): void { + console.log(`组件添加: ${data.componentType} 到实体 ${data.entity.name}`); + } + + private onComponentRemoved(data: any): void { + console.log(`组件移除: ${data.componentType} 从实体 ${data.entity.name}`); + } + + private onSystemAdded(data: any): void { + console.log(`系统添加: ${data.systemName}`); + } + + private onSystemError(data: any): void { + console.error(`系统错误: ${data.systemName}`, data.error); + } + + private onPerformanceWarning(data: any): void { + console.warn(`性能警告: ${data.systemName} 执行时间过长 (${data.executionTime}ms)`); + } +} +``` + +## 最佳实践 + +### 1. 事件命名规范 + +```typescript +// ✅ 好的事件命名 +this.eventSystem.emitSync('player:health_changed', data); +this.eventSystem.emitSync('enemy:spawned', data); +this.eventSystem.emitSync('ui:score_updated', data); +this.eventSystem.emitSync('game:paused', data); + +// ❌ 避免的事件命名 +this.eventSystem.emitSync('event1', data); +this.eventSystem.emitSync('update', data); +this.eventSystem.emitSync('change', data); +``` + +### 2. 类型安全的事件数据 + +```typescript +// 定义事件数据接口 +interface PlayerHealthChangedEvent { + entityId: number; + oldHealth: number; + newHealth: number; + cause: 'damage' | 'healing'; +} + +interface EnemySpawnedEvent { + enemyType: string; + position: { x: number, y: number }; + level: number; +} + +// 使用类型安全的事件 +class HealthSystem extends EntitySystem { + private onHealthChanged(data: PlayerHealthChangedEvent): void { + // TypeScript 会提供完整的类型检查 + console.log(`生命值变化: ${data.oldHealth} -> ${data.newHealth}`); + } +} +``` + +### 3. 避免事件循环 + +```typescript +// ❌ 避免:可能导致无限循环 +class BadEventHandler { + private onScoreChanged(data: any): void { + // 在处理分数变化时又触发分数变化事件 + this.scene?.eventSystem.emitSync('score_changed', newData); // 危险! + } +} + +// ✅ 正确:使用不同的事件类型或条件判断 +class GoodEventHandler { + private isProcessingScore = false; + + private onScoreChanged(data: any): void { + if (this.isProcessingScore) return; // 防止循环 + + this.isProcessingScore = true; + // 处理分数变化 + this.updateUI(data); + this.isProcessingScore = false; + } +} +``` + +### 4. 及时清理事件监听器 + +```typescript +class TemporaryUI { + private listenerId: string; + + constructor(scene: Scene) { + // 保存监听器ID用于后续清理 + this.listenerId = scene.eventSystem.on('ui_update', this.onUpdate.bind(this)); + } + + private onUpdate(data: any): void { + // 处理UI更新 + } + + public destroy(): void { + // 清理事件监听器 + if (this.listenerId) { + scene.eventSystem.off('ui_update', this.listenerId); + } + } +} +``` + +### 5. 性能考虑 + +```typescript +class OptimizedEventHandler { + protected onInitialize(): void { + // 对于高频事件,使用批处理 + this.scene?.eventSystem.setBatchConfig('movement_update', { + batchSize: 100, + delay: 16, + enabled: true + }); + + // 对于低频但重要的事件,使用高优先级 + this.addEventListener('game_over', this.onGameOver.bind(this), { + priority: 1000 + }); + + // 对于一次性事件,使用 once + this.addEventListener('level_start', this.onLevelStart.bind(this), { + once: true + }); + } +} +``` + +事件系统是 ECS 框架中实现松耦合架构的重要工具,正确使用事件系统能让你的游戏代码更加模块化、可维护和可扩展。 \ No newline at end of file diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md new file mode 100644 index 00000000..ac5a8320 --- /dev/null +++ b/docs/guide/getting-started.md @@ -0,0 +1,394 @@ +# 快速开始 + +本指南将帮助你快速上手 ECS Framework,从安装到创建第一个 ECS 应用。 + +## 安装 + +### NPM 安装 + +```bash +# 使用 npm +npm install @esengine/ecs-framework +``` + +## 初始化 Core + +### 基础初始化 + +ECS Framework 的核心是 `Core` 类,它是一个单例模式,负责管理整个框架的生命周期。 + +```typescript +import { Core } from '@esengine/ecs-framework' + +// 方式1:使用配置对象(推荐) +const core = Core.create({ + debug: true, // 启用调试模式,提供详细的日志和性能监控 + enableEntitySystems: true, // 启用实体系统,这是ECS的核心功能 + debugConfig: { // 可选:高级调试配置 + enabled: false, // 是否启用WebSocket调试服务器 + websocketUrl: 'ws://localhost:8080', + debugFrameRate: 30, // 调试数据发送帧率 + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + } +}); + +// 方式2:简化创建(向后兼容) +const core = Core.create(true); // 等同于 { debug: true, enableEntitySystems: true } + +// 方式3:生产环境配置 +const core = Core.create({ + debug: false, // 生产环境关闭调试 + enableEntitySystems: true +}); +``` + +### Core 配置详解 + +```typescript +interface ICoreConfig { + /** 是否启用调试模式 - 影响日志级别和性能监控 */ + debug?: boolean; + + /** 是否启用实体系统 - 核心ECS功能开关 */ + enableEntitySystems?: boolean; + + /** 高级调试配置 - 用于开发工具集成 */ + debugConfig?: { + enabled: boolean; // 是否启用调试服务器 + websocketUrl: string; // WebSocket服务器地址 + autoReconnect?: boolean; // 是否自动重连 + debugFrameRate?: 60 | 30 | 15; // 调试数据发送帧率 + channels: { // 数据通道配置 + entities: boolean; // 实体数据 + systems: boolean; // 系统数据 + performance: boolean; // 性能数据 + components: boolean; // 组件数据 + scenes: boolean; // 场景数据 + }; + }; +} +``` + +### Core 实例管理 + +Core 采用单例模式,创建后可以通过静态属性获取: + +```typescript +// 创建实例 +const core = Core.create(true); + +// 获取已创建的实例 +const instance = Core.Instance; // 获取当前实例,如果未创建则为 null +``` + +### 游戏循环集成 + +**重要**: 在创建实体和系统之前,你需要先了解如何将 ECS Framework 集成到你的游戏引擎中。 + +`Core.update(deltaTime)` 是整个框架的心跳,必须在游戏引擎的每一帧中调用。它负责: +- 更新框架内置的 Time 类 +- 更新所有全局管理器(定时器、对象池等) +- 更新所有场景中的实体系统 +- 处理实体的创建和销毁 +- 收集性能数据(调试模式下) + +各引擎集成示例请参考:[与游戏引擎集成](#与游戏引擎集成) + +## 创建第一个 ECS 应用 + +### 1. 定义组件 + +组件是纯数据容器,用于存储实体的状态: + +```typescript +import { Component, ECSComponent } from '@esengine/ecs-framework' + +// 位置组件 +@ECSComponent('Position') +class Position extends Component { + x: number = 0 + y: number = 0 + + constructor(x: number = 0, y: number = 0) { + super() + this.x = x + this.y = y + } +} + +// 速度组件 +@ECSComponent('Velocity') +class Velocity extends Component { + dx: number = 0 + dy: number = 0 + + constructor(dx: number = 0, dy: number = 0) { + super() + this.dx = dx + this.dy = dy + } +} + +// 渲染组件 +@ECSComponent('Sprite') +class Sprite extends Component { + texture: string = '' + width: number = 32 + height: number = 32 + + constructor(texture: string, width: number = 32, height: number = 32) { + super() + this.texture = texture + this.width = width + this.height = height + } +} +``` + +### 2. 创建实体系统 + +系统包含游戏逻辑,处理具有特定组件的实体。ECS Framework 提供了基于 Matcher 的实体过滤机制: + +```typescript +import { EntitySystem, Matcher, Time, ECSSystem } from '@esengine/ecs-framework' + +// 移动系统 - 处理位置和速度 +@ECSSystem('MovementSystem') +class MovementSystem extends EntitySystem { + + constructor() { + // 使用 Matcher 定义要处理的实体:必须同时拥有 Position 和 Velocity 组件 + super(Matcher.empty().all(Position, Velocity)) + } + + protected process(entities: readonly Entity[]): void { + // process 方法接收所有匹配的实体 + for (const entity of entities) { + const position = entity.getComponent(Position)! + const velocity = entity.getComponent(Velocity)! + + // 更新位置(使用框架的Time类) + position.x += velocity.dx * Time.deltaTime + position.y += velocity.dy * Time.deltaTime + + // 边界检查示例 + if (position.x < 0) position.x = 0 + if (position.y < 0) position.y = 0 + } + } +} + +// 渲染系统 - 处理可见对象 +@ECSSystem('RenderSystem') +class RenderSystem extends EntitySystem { + + constructor() { + // 必须有 Position 和 Sprite,可选 Velocity(用于方向判断) + super(Matcher.empty().all(Position, Sprite).any(Velocity)) + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const position = entity.getComponent(Position)! + const sprite = entity.getComponent(Sprite)! + const velocity = entity.getComponent(Velocity) // 可能为 null + + // 根据速度方向翻转精灵(可选逻辑) + let flipX = false + if (velocity && velocity.dx < 0) { + flipX = true + } + + // 渲染逻辑(这里是伪代码) + this.drawSprite(sprite.texture, position.x, position.y, sprite.width, sprite.height, flipX) + } + } + + private drawSprite(texture: string, x: number, y: number, width: number, height: number, flipX: boolean = false) { + // 实际的渲染实现将取决于你使用的游戏引擎 + const direction = flipX ? '←' : '→' + console.log(`渲染 ${texture} 在位置 (${x.toFixed(1)}, ${y.toFixed(1)}) 方向: ${direction}`) + } +} +``` + + +### 3. 创建场景 + +推荐继承 Scene 类来创建自定义场景: + +```typescript +import { Scene } from '@esengine/ecs-framework' + +// 推荐:继承Scene创建自定义场景 +class GameScene extends Scene { + + initialize(): void { + // 场景初始化逻辑 + this.name = "MainScene"; + + // 添加系统到场景 + this.addSystem(new MovementSystem()); + this.addSystem(new RenderSystem()); + } + + onStart(): void { + // 场景开始运行时的逻辑 + console.log("游戏场景已启动"); + } + + unload(): void { + // 场景卸载时的清理逻辑 + console.log("游戏场景已卸载"); + } +} + +// 创建并设置场景 +const gameScene = new GameScene(); +Core.setScene(gameScene); +``` + +### 4. 创建实体 + +```typescript +// 创建玩家实体 +const player = gameScene.createEntity("Player"); +player.addComponent(new Position(100, 100)); +player.addComponent(new Velocity(50, 30)); // 每秒移动 50 像素(x方向),30 像素(y方向) +player.addComponent(new Sprite("player.png", 64, 64)); +``` + +## World 概念 + +World 是 Scene 的容器,用于管理多个独立的游戏世界。这种设计特别适用于: +- 多人游戏房间(每个房间一个 World) +- 不同的游戏模式 +- 独立的模拟环境 + +### 基本用法 + +```typescript +import { World, Scene } from '@esengine/ecs-framework' + +// 创建游戏房间的World +const roomWorld = new World({ name: 'Room_001' }); + +// 在World中创建多个Scene +class GameScene extends Scene { + initialize(): void { + this.name = "GamePlay"; + this.addSystem(new MovementSystem()); + this.addSystem(new RenderSystem()); + } +} + +class UIScene extends Scene { + initialize(): void { + this.name = "UI"; + // UI相关系统 + } +} + +// 添加Scene到World +const gameScene = roomWorld.createScene('game', new GameScene()); +const uiScene = roomWorld.createScene('ui', new UIScene()); + +// 激活Scene +roomWorld.setSceneActive('game', true); +roomWorld.setSceneActive('ui', true); + +// 启动World +roomWorld.start(); +``` + +### World 生命周期 + +World 提供了完整的生命周期管理: +- `start()`: 启动 World 和所有全局系统 +- `updateGlobalSystems()`: 更新全局系统(由 Core.update() 调用) +- `updateScenes()`: 更新所有激活的 Scene(由 Core.update() 调用) +- `stop()`: 停止 World +- `destroy()`: 销毁 World 和所有资源 + +## 与游戏引擎集成 + +### Laya 引擎集成 + +```typescript +import { Stage } from "laya/display/Stage" +import { Stat } from "laya/utils/Stat" +import { Laya } from "Laya" + +// 初始化 Laya +Laya.init(800, 600).then(() => { + // 初始化 ECS + const core = Core.create(true) + + // 设置场景... + + // 启动游戏循环 + Laya.timer.frameLoop(1, this, () => { + const deltaTime = Laya.timer.delta / 1000 // 转换为秒 + Core.update(deltaTime) + }) +}) +``` + +### Cocos Creator 集成 + +```typescript +import { Component, _decorator } from 'cc' + +const { ccclass } = _decorator + +@ccclass('ECSGameManager') +export class ECSGameManager extends Component { + + onLoad() { + // 初始化 ECS + const core = Core.create(true) + + // 设置场景... + } + + update(deltaTime: number) { + // 更新 ECS + Core.update(deltaTime) + } +} +``` + + +## 下一步 + +现在你已经成功创建了第一个 ECS 应用!接下来可以: + +- 查看完整的 [API 文档](/api/README) +- 探索更多[实际应用示例](/examples/) + +## 常见问题 + +### 为什么我的系统没有执行? + +确保: +1. 系统已添加到场景:`this.addSystem(system)` (在 Scene 的 initialize 方法中) +2. 场景已设置为当前场景:`Core.setScene(scene)` +3. 游戏循环在调用:`Core.update(deltaTime)` + +### 如何调试 ECS 应用? + +启用调试模式: + +```typescript +Core.create({ debug: true }) + +// 获取调试数据 +const debugData = Core.getDebugData() +console.log(debugData) +``` \ No newline at end of file diff --git a/docs/guide/index.md b/docs/guide/index.md new file mode 100644 index 00000000..78796a66 --- /dev/null +++ b/docs/guide/index.md @@ -0,0 +1,26 @@ +# 指南 + +欢迎使用 ECS Framework 指南。这里将详细介绍框架的各个核心概念和使用方法。 + +## 核心概念 + +### [实体类 (Entity)](./entity.md) +了解 ECS 架构的基础 - 实体类的使用方法、生命周期管理和最佳实践。 + +### [组件系统 (Component)](./component.md) +学习如何创建和使用组件,实现游戏功能的模块化设计。 + +### [系统架构 (System)](./system.md) +掌握系统的编写方法,实现游戏逻辑的处理。 + +### [场景管理 (Scene)](./scene.md) +了解场景的生命周期、系统管理和实体容器功能。 + +### [事件系统 (Event)](./event-system.md) +掌握类型安全的事件系统,实现组件间通信和系统协作。 + +### [时间和定时器 (Time)](./time-and-timers.md) +学习时间管理和定时器系统,实现游戏逻辑的精确时间控制。 + +### [日志系统 (Logger)](./logging.md) +掌握分级日志系统,用于调试、监控和错误追踪。 \ No newline at end of file diff --git a/docs/guide/logging.md b/docs/guide/logging.md new file mode 100644 index 00000000..495bab91 --- /dev/null +++ b/docs/guide/logging.md @@ -0,0 +1,550 @@ +# 日志系统 + +ECS 框架提供了功能强大的分级日志系统,支持多种日志级别、颜色输出、自定义前缀和灵活的配置选项。日志系统可以帮助开发者调试代码和监控应用运行状态。 + +## 基本概念 + +日志系统包含以下核心概念: +- **日志级别**:Debug < Info < Warn < Error < Fatal < None +- **日志器**:具名的日志输出器,每个模块可以有自己的日志器 +- **日志管理器**:全局管理所有日志器的单例 +- **颜色配置**:支持控制台颜色输出 + +## 日志级别 + +```typescript +import { LogLevel } from '@esengine/ecs-framework'; + +// 日志级别从低到高 +LogLevel.Debug // 0 - 调试信息 +LogLevel.Info // 1 - 一般信息 +LogLevel.Warn // 2 - 警告信息 +LogLevel.Error // 3 - 错误信息 +LogLevel.Fatal // 4 - 致命错误 +LogLevel.None // 5 - 不输出任何日志 +``` + +## 基本使用 + +### 使用默认日志器 + +```typescript +import { Logger } from '@esengine/ecs-framework'; + +class GameSystem extends EntitySystem { + protected process(entities: readonly Entity[]): void { + // 输出不同级别的日志 + Logger.debug('处理实体数量:', entities.length); + Logger.info('系统正常运行'); + Logger.warn('检测到性能问题'); + Logger.error('处理过程中发生错误', new Error('示例错误')); + Logger.fatal('致命错误,系统即将停止'); + } +} +``` + +### 创建命名日志器 + +```typescript +import { createLogger } from '@esengine/ecs-framework'; + +class MovementSystem extends EntitySystem { + private logger = createLogger('MovementSystem'); + + protected process(entities: readonly Entity[]): void { + this.logger.info(`处理 ${entities.length} 个移动实体`); + + for (const entity of entities) { + const position = entity.getComponent(Position); + const velocity = entity.getComponent(Velocity); + + if (position && velocity) { + position.x += velocity.dx * Time.deltaTime; + position.y += velocity.dy * Time.deltaTime; + + this.logger.debug(`实体 ${entity.id} 移动到位置 (${position.x}, ${position.y})`); + } + } + } + + protected onAdded(entity: Entity): void { + this.logger.info(`实体 ${entity.name} 加入移动系统`); + } + + protected onRemoved(entity: Entity): void { + this.logger.warn(`实体 ${entity.name} 离开移动系统`); + } +} +``` + +### 系统内置日志器 + +框架的各个系统都有自己的日志器: + +```typescript +// 框架内部使用示例 +class Scene { + private static readonly _logger = createLogger('Scene'); + + public addSystem(system: EntitySystem): void { + Scene._logger.info(`添加系统: ${system.systemName}`); + // 系统添加逻辑 + } + + public removeSystem(system: EntitySystem): void { + Scene._logger.warn(`移除系统: ${system.systemName}`); + // 系统移除逻辑 + } +} +``` + +## 日志配置 + +### 设置全局日志级别 + +```typescript +import { setGlobalLogLevel, LogLevel } from '@esengine/ecs-framework'; + +// 在开发环境显示所有日志 +setGlobalLogLevel(LogLevel.Debug); + +// 在生产环境只显示警告及以上级别 +setGlobalLogLevel(LogLevel.Warn); + +// 完全禁用日志输出 +setGlobalLogLevel(LogLevel.None); +``` + +### 创建自定义配置的日志器 + +```typescript +import { ConsoleLogger, LogLevel } from '@esengine/ecs-framework'; + +class CustomLoggerExample { + private debugLogger: ConsoleLogger; + private productionLogger: ConsoleLogger; + + constructor() { + // 开发环境日志器 + this.debugLogger = new ConsoleLogger({ + level: LogLevel.Debug, + enableTimestamp: true, + enableColors: true, + prefix: 'DEV' + }); + + // 生产环境日志器 + this.productionLogger = new ConsoleLogger({ + level: LogLevel.Error, + enableTimestamp: true, + enableColors: false, + prefix: 'PROD' + }); + } + + public logDevelopmentInfo(): void { + this.debugLogger.debug('这是调试信息'); + this.debugLogger.info('开发环境信息'); + } + + public logProductionError(): void { + this.productionLogger.error('生产环境错误'); + this.productionLogger.fatal('致命错误'); + } +} +``` + +## 颜色配置 + +### 使用预定义颜色 + +```typescript +import { Colors, setLoggerColors } from '@esengine/ecs-framework'; + +// 自定义颜色方案 +setLoggerColors({ + debug: Colors.BRIGHT_BLACK, + info: Colors.BLUE, + warn: Colors.YELLOW, + error: Colors.RED, + fatal: Colors.BRIGHT_RED +}); +``` + +### 完整颜色示例 + +```typescript +import { LoggerManager, Colors, LogLevel } from '@esengine/ecs-framework'; + +class ColorLoggerDemo { + private logger = createLogger('ColorDemo'); + + constructor() { + // 设置自定义颜色 + const manager = LoggerManager.getInstance(); + manager.setGlobalColors({ + debug: Colors.CYAN, + info: Colors.GREEN, + warn: Colors.YELLOW, + error: Colors.RED, + fatal: `${Colors.BOLD}${Colors.BRIGHT_RED}` + }); + } + + public demonstrateColors(): void { + this.logger.debug('这是蓝绿色的调试信息'); + this.logger.info('这是绿色的信息'); + this.logger.warn('这是黄色的警告'); + this.logger.error('这是红色的错误'); + this.logger.fatal('这是加粗的亮红色致命错误'); + } + + public resetToDefaults(): void { + // 重置为默认颜色 + LoggerManager.getInstance().resetColors(); + } +} +``` + +## 高级功能 + +### 分层日志器 + +```typescript +import { LoggerManager } from '@esengine/ecs-framework'; + +class HierarchicalLoggingExample { + private systemLogger = createLogger('GameSystems'); + private movementLogger: ILogger; + private renderLogger: ILogger; + + constructor() { + const manager = LoggerManager.getInstance(); + + // 创建子日志器 + this.movementLogger = manager.createChildLogger('GameSystems', 'Movement'); + this.renderLogger = manager.createChildLogger('GameSystems', 'Render'); + } + + public demonstrateHierarchy(): void { + this.systemLogger.info('游戏系统启动'); + + // 子日志器会显示完整路径:[GameSystems.Movement] + this.movementLogger.debug('移动系统初始化'); + + // 子日志器会显示完整路径:[GameSystems.Render] + this.renderLogger.info('渲染系统启动'); + } +} +``` + +### 自定义输出 + +```typescript +import { ConsoleLogger, LogLevel } from '@esengine/ecs-framework'; + +class CustomOutputLogger { + private fileLogger: ConsoleLogger; + private networkLogger: ConsoleLogger; + + constructor() { + // 输出到文件的日志器(模拟) + this.fileLogger = new ConsoleLogger({ + level: LogLevel.Info, + output: (level: LogLevel, message: string) => { + this.writeToFile(LogLevel[level], message); + } + }); + + // 发送到网络的日志器(模拟) + this.networkLogger = new ConsoleLogger({ + level: LogLevel.Error, + output: (level: LogLevel, message: string) => { + this.sendToServer(LogLevel[level], message); + } + }); + } + + private writeToFile(level: string, message: string): void { + // 模拟文件写入 + console.log(`[FILE] ${level}: ${message}`); + } + + private sendToServer(level: string, message: string): void { + // 模拟网络发送 + console.log(`[NETWORK] ${level}: ${message}`); + } + + public logToFile(message: string): void { + this.fileLogger.info(message); + } + + public logCriticalError(error: Error): void { + this.networkLogger.error('Critical error occurred', error); + } +} +``` + +## 实际应用示例 + +### 游戏系统日志 + +```typescript +class GameWithLogging { + private gameLogger = createLogger('Game'); + private performanceLogger = createLogger('Performance'); + private networkLogger = createLogger('Network'); + + constructor() { + // 在开发环境启用详细日志 + if (process.env.NODE_ENV === 'development') { + setGlobalLogLevel(LogLevel.Debug); + } else { + setGlobalLogLevel(LogLevel.Warn); + } + } + + public startGame(): void { + this.gameLogger.info('游戏开始启动'); + + try { + this.initializeSystems(); + this.loadResources(); + this.startGameLoop(); + + this.gameLogger.info('游戏启动成功'); + } catch (error) { + this.gameLogger.fatal('游戏启动失败', error); + throw error; + } + } + + private initializeSystems(): void { + this.gameLogger.debug('初始化游戏系统'); + + const systems = [ + new MovementSystem(), + new RenderSystem(), + new PhysicsSystem() + ]; + + for (const system of systems) { + const startTime = performance.now(); + + // 初始化系统 + system.initialize(); + + const endTime = performance.now(); + this.performanceLogger.debug( + `系统 ${system.systemName} 初始化耗时: ${(endTime - startTime).toFixed(2)}ms` + ); + } + } + + private loadResources(): void { + this.gameLogger.info('开始加载资源'); + + const resources = ['textures', 'sounds', 'data']; + for (const resource of resources) { + try { + this.loadResource(resource); + this.gameLogger.debug(`资源 ${resource} 加载成功`); + } catch (error) { + this.gameLogger.error(`资源 ${resource} 加载失败`, error); + } + } + } + + private startGameLoop(): void { + this.gameLogger.info('启动游戏循环'); + this.performanceLogger.debug('开始性能监控'); + } + + private loadResource(name: string): void { + // 模拟资源加载 + if (Math.random() < 0.1) { + throw new Error(`Failed to load ${name}`); + } + } + + public handleNetworkEvent(event: string, data: any): void { + this.networkLogger.info(`网络事件: ${event}`, data); + + if (event === 'connection_lost') { + this.networkLogger.warn('网络连接丢失,尝试重连'); + } else if (event === 'sync_error') { + this.networkLogger.error('数据同步错误', data); + } + } +} +``` + +### 错误追踪和调试 + +```typescript +class ErrorTrackingSystem extends EntitySystem { + private logger = createLogger('ErrorTracker'); + private errorCounts = new Map(); + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + try { + this.processEntity(entity); + } catch (error) { + this.handleError(entity, error as Error); + } + } + } + + private processEntity(entity: Entity): void { + // 模拟可能出错的实体处理 + if (Math.random() < 0.01) { // 1% 概率出错 + throw new Error(`Processing error for entity ${entity.id}`); + } + + this.logger.debug(`成功处理实体 ${entity.id}`); + } + + private handleError(entity: Entity, error: Error): void { + const errorKey = error.message; + const count = this.errorCounts.get(errorKey) || 0; + this.errorCounts.set(errorKey, count + 1); + + this.logger.error( + `实体 ${entity.id} 处理失败 (第${count + 1}次): ${error.message}`, + { + entityId: entity.id, + entityName: entity.name, + componentCount: entity.components.length, + errorStack: error.stack + } + ); + + // 如果同一类型错误发生太多次,升级为警告 + if (count >= 5) { + this.logger.warn(`错误 "${errorKey}" 已发生 ${count + 1} 次,可能需要关注`); + } + + // 如果错误次数过多,升级为致命错误 + if (count >= 20) { + this.logger.fatal(`错误 "${errorKey}" 发生次数过多,系统可能存在严重问题`); + } + } + + public getErrorSummary(): void { + this.logger.info('=== 错误统计 ==='); + for (const [error, count] of this.errorCounts) { + this.logger.info(`${error}: ${count} 次`); + } + } +} +``` + +## 最佳实践 + +### 1. 合理的日志级别选择 + +```typescript +class LoggingBestPractices { + private logger = createLogger('BestPractices'); + + public demonstrateLogLevels(): void { + // ✅ Debug - 详细的调试信息 + this.logger.debug('变量值', { x: 10, y: 20 }); + + // ✅ Info - 重要的状态变化 + this.logger.info('系统启动完成'); + + // ✅ Warn - 异常但不致命的情况 + this.logger.warn('资源未找到,使用默认值'); + + // ✅ Error - 错误但程序可以继续 + this.logger.error('保存失败,将重试', new Error('Network timeout')); + + // ✅ Fatal - 致命错误,程序无法继续 + this.logger.fatal('内存不足,程序即将退出'); + } +} +``` + +### 2. 结构化日志数据 + +```typescript +class StructuredLogging { + private logger = createLogger('Structured'); + + public logWithStructuredData(): void { + // ✅ 提供结构化的上下文信息 + this.logger.info('用户操作', { + userId: 12345, + action: 'move', + position: { x: 100, y: 200 }, + timestamp: Date.now() + }); + + // ✅ 包含相关的错误上下文 + this.logger.error('数据库查询失败', { + query: 'SELECT * FROM users', + parameters: { id: 123 }, + connectionId: 'conn_456', + retryCount: 3 + }); + } +} +``` + +### 3. 避免日志性能问题 + +```typescript +class PerformanceConsciousLogging { + private logger = createLogger('Performance'); + + public efficientLogging(): void { + // ✅ 检查日志级别避免不必要的计算 + if (this.logger.debug) { + const expensiveData = this.calculateExpensiveDebugInfo(); + this.logger.debug('详细调试信息', expensiveData); + } + + // ❌ 避免:总是计算昂贵的日志数据 + // this.logger.debug('调试信息', this.calculateExpensiveDebugInfo()); + } + + private calculateExpensiveDebugInfo(): any { + // 模拟昂贵的计算 + return { /* 复杂的调试数据 */ }; + } +} +``` + +### 4. 日志配置管理 + +```typescript +class LoggingConfiguration { + public static setupLogging(): void { + // 根据环境配置日志级别 + const isDevelopment = process.env.NODE_ENV === 'development'; + const isProduction = process.env.NODE_ENV === 'production'; + + if (isDevelopment) { + setGlobalLogLevel(LogLevel.Debug); + setLoggerColors({ + debug: Colors.CYAN, + info: Colors.GREEN, + warn: Colors.YELLOW, + error: Colors.RED, + fatal: Colors.BRIGHT_RED + }); + } else if (isProduction) { + setGlobalLogLevel(LogLevel.Warn); + // 生产环境禁用颜色 + LoggerManager.getInstance().resetColors(); + } + } +} + +// 在应用启动时配置日志 +LoggingConfiguration.setupLogging(); +``` + +日志系统是调试和监控应用的重要工具,正确使用日志系统能大大提高开发效率和问题排查能力。 \ No newline at end of file diff --git a/docs/guide/scene.md b/docs/guide/scene.md new file mode 100644 index 00000000..6e98f182 --- /dev/null +++ b/docs/guide/scene.md @@ -0,0 +1,510 @@ +# 场景管理 + +在 ECS 架构中,场景(Scene)是游戏世界的容器,负责管理实体、系统和组件的生命周期。场景提供了完整的 ECS 运行环境。 + +## 基本概念 + +场景是 ECS 框架的核心容器,提供: +- 实体的创建、管理和销毁 +- 系统的注册和执行调度 +- 组件的存储和查询 +- 事件系统支持 +- 性能监控和调试信息 + +## 创建场景 + +### 继承 Scene 类 + +**推荐做法:继承 Scene 类来创建自定义场景** + +```typescript +import { Scene, EntitySystem } from '@esengine/ecs-framework'; + +class GameScene extends Scene { + protected initialize(): void { + // 设置场景名称 + this.name = "GameScene"; + + // 添加系统 + this.addSystem(new MovementSystem()); + this.addSystem(new RenderSystem()); + this.addSystem(new PhysicsSystem()); + + // 创建初始实体 + this.createInitialEntities(); + } + + private createInitialEntities(): void { + // 创建玩家 + const player = this.createEntity("Player"); + player.addComponent(new Position(400, 300)); + player.addComponent(new Health(100)); + player.addComponent(new PlayerController()); + + // 创建敌人 + for (let i = 0; i < 5; i++) { + const enemy = this.createEntity(`Enemy_${i}`); + enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600)); + enemy.addComponent(new Health(50)); + enemy.addComponent(new EnemyAI()); + } + } + + public onStart(): void { + console.log("游戏场景已启动"); + // 场景启动时的逻辑 + } + + public unload(): void { + console.log("游戏场景已卸载"); + // 场景卸载时的清理逻辑 + } +} +``` + +### 使用场景配置 + +```typescript +import { ISceneConfig } from '@esengine/ecs-framework'; + +const config: ISceneConfig = { + name: "MainGame", + enableEntityDirectUpdate: false +}; + +class ConfiguredScene extends Scene { + constructor() { + super(config); + } +} +``` + +## 场景生命周期 + +场景提供了完整的生命周期管理: + +```typescript +class ExampleScene extends Scene { + protected initialize(): void { + // 场景初始化:设置系统和初始实体 + console.log("场景初始化"); + } + + public onStart(): void { + // 场景开始运行:游戏逻辑开始执行 + console.log("场景开始运行"); + } + + public unload(): void { + // 场景卸载:清理资源 + console.log("场景卸载"); + } +} + +// 使用场景(由框架自动管理生命周期) +const scene = new ExampleScene(); +// 场景的 initialize(), begin(), update(), end() 由框架自动调用 +``` + +## 实体管理 + +### 创建实体 + +```typescript +class EntityScene extends Scene { + createGameEntities(): void { + // 创建单个实体 + const player = this.createEntity("Player"); + + // 批量创建实体(高性能) + const bullets = this.createEntities(100, "Bullet"); + + // 为批量创建的实体添加组件 + bullets.forEach((bullet, index) => { + bullet.addComponent(new Position(index * 10, 100)); + bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300)); + }); + } +} +``` + +### 查找实体 + +```typescript +class SearchScene extends Scene { + findEntities(): void { + // 按名称查找 + const player = this.findEntity("Player"); + const player2 = this.getEntityByName("Player"); // 别名方法 + + // 按 ID 查找 + const entity = this.findEntityById(123); + + // 按标签查找 + const enemies = this.findEntitiesByTag(2); + const enemies2 = this.getEntitiesByTag(2); // 别名方法 + + if (player) { + console.log(`找到玩家: ${player.name}`); + } + + console.log(`找到 ${enemies.length} 个敌人`); + } +} +``` + +### 销毁实体 + +```typescript +class DestroyScene extends Scene { + cleanupEntities(): void { + // 销毁所有实体 + this.destroyAllEntities(); + + // 单个实体的销毁通过实体本身 + const enemy = this.findEntity("Enemy_1"); + if (enemy) { + enemy.destroy(); // 实体会自动从场景中移除 + } + } +} +``` + +## 系统管理 + +### 添加和移除系统 + +```typescript +class SystemScene extends Scene { + protected initialize(): void { + // 添加系统 + const movementSystem = new MovementSystem(); + this.addSystem(movementSystem); + + // 设置系统更新顺序 + movementSystem.updateOrder = 1; + + // 添加更多系统 + this.addSystem(new PhysicsSystem()); + this.addSystem(new RenderSystem()); + } + + public removeUnnecessarySystems(): void { + // 获取系统 + const physicsSystem = this.getEntityProcessor(PhysicsSystem); + + // 移除系统 + if (physicsSystem) { + this.removeSystem(physicsSystem); + } + } +} +``` + +### 系统访问 + +```typescript +class SystemAccessScene extends Scene { + public pausePhysics(): void { + const physicsSystem = this.getEntityProcessor(PhysicsSystem); + if (physicsSystem) { + physicsSystem.enabled = false; + } + } + + public getAllSystems(): EntitySystem[] { + return this.systems; // 获取所有系统 + } +} +``` + +## 事件系统 + +场景内置了类型安全的事件系统: + +```typescript +class EventScene extends Scene { + protected initialize(): void { + // 监听事件 + this.eventSystem.on('player_died', this.onPlayerDied.bind(this)); + this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this)); + this.eventSystem.on('level_complete', this.onLevelComplete.bind(this)); + } + + private onPlayerDied(data: any): void { + console.log('玩家死亡事件'); + // 处理玩家死亡 + } + + private onEnemySpawned(data: any): void { + console.log('敌人生成事件'); + // 处理敌人生成 + } + + private onLevelComplete(data: any): void { + console.log('关卡完成事件'); + // 处理关卡完成 + } + + public triggerGameEvent(): void { + // 发送事件 + this.eventSystem.emitSync('custom_event', { + message: "这是自定义事件", + timestamp: Date.now() + }); + } +} +``` + +## 场景统计和调试 + +### 获取场景统计 + +```typescript +class StatsScene extends Scene { + public showStats(): void { + const stats = this.getStats(); + console.log(`实体数量: ${stats.entityCount}`); + console.log(`系统数量: ${stats.processorCount}`); + console.log('组件存储统计:', stats.componentStorageStats); + } + + public showDebugInfo(): void { + const debugInfo = this.getDebugInfo(); + console.log('场景调试信息:', debugInfo); + + // 显示所有实体信息 + debugInfo.entities.forEach(entity => { + console.log(`实体 ${entity.name}(${entity.id}): ${entity.componentCount} 个组件`); + console.log('组件类型:', entity.componentTypes); + }); + + // 显示所有系统信息 + debugInfo.processors.forEach(processor => { + console.log(`系统 ${processor.name}: 处理 ${processor.entityCount} 个实体`); + }); + } +} +``` + +## 场景集成到框架 + +场景可以通过两种方式运行: + +### 1. 简单的单场景应用 + +```typescript +import { Core } from '@esengine/ecs-framework'; + +// 创建游戏场景 +class GameScene extends Scene { + protected initialize(): void { + this.name = "GameScene"; + this.addSystem(new MovementSystem()); + this.addSystem(new RenderSystem()); + } +} + +// 启动游戏 +Core.create(); +const gameScene = new GameScene(); +Core.setScene(gameScene); +``` + +### 2. 复杂的多场景应用 + +```typescript +import { WorldManager } from '@esengine/ecs-framework'; + +// 获取WorldManager实例 +const worldManager = WorldManager.getInstance(); + +// 创建World +const gameWorld = worldManager.createWorld('game', { + name: 'MainGame', + maxScenes: 5 +}); + +// 在World中创建场景 +const menuScene = gameWorld.createScene('menu', new MenuScene()); +const gameScene = gameWorld.createScene('game', new GameScene()); + +// 激活场景 +gameWorld.setSceneActive('menu', true); +``` + +## 多场景管理 + +在World中可以管理多个场景,通过激活/停用来切换: + +```typescript +class GameWorld extends World { + private menuScene: Scene; + private gameScene: Scene; + private gameOverScene: Scene; + + public initialize(): void { + // 创建多个场景 + this.menuScene = this.createScene('menu', new MenuScene()); + this.gameScene = this.createScene('game', new GameScene()); + this.gameOverScene = this.createScene('gameover', new GameOverScene()); + + // 设置初始场景 + this.showMenu(); + } + + public showMenu(): void { + this.deactivateAllScenes(); + this.setSceneActive('menu', true); + } + + public startGame(): void { + this.deactivateAllScenes(); + this.setSceneActive('game', true); + } + + public showGameOver(): void { + this.deactivateAllScenes(); + this.setSceneActive('gameover', true); + } + + private deactivateAllScenes(): void { + this.setSceneActive('menu', false); + this.setSceneActive('game', false); + this.setSceneActive('gameover', false); + } +} +``` + +## 与 World 的关系 + +Scene 的运行架构层次: + +```typescript +// Core -> WorldManager -> World -> Scene -> EntitySystem -> Entity -> Component + +// 1. 简单应用:Core直接管理单个Scene +Core.setScene(new GameScene()); + +// 2. 复杂应用:WorldManager管理多个World,每个World管理多个Scene +const worldManager = WorldManager.getInstance(); +const world = worldManager.createWorld('gameWorld'); +const scene = world.createScene('mainScene', new GameScene()); +world.setSceneActive('mainScene', true); +``` + +## 最佳实践 + +### 1. 场景职责分离 + +```typescript +// ✅ 好的场景设计 - 职责清晰 +class MenuScene extends Scene { + // 只处理菜单相关逻辑 +} + +class GameScene extends Scene { + // 只处理游戏玩法逻辑 +} + +class InventoryScene extends Scene { + // 只处理物品栏逻辑 +} + +// ❌ 避免的场景设计 - 职责混乱 +class MegaScene extends Scene { + // 包含菜单、游戏、物品栏等所有逻辑 +} +``` + +### 2. 合理的系统组织 + +```typescript +class OrganizedScene extends Scene { + protected initialize(): void { + // 按功能和依赖关系添加系统 + this.addInputSystems(); + this.addLogicSystems(); + this.addRenderSystems(); + } + + private addInputSystems(): void { + this.addSystem(new InputSystem()); + } + + private addLogicSystems(): void { + this.addSystem(new MovementSystem()); + this.addSystem(new PhysicsSystem()); + this.addSystem(new CollisionSystem()); + } + + private addRenderSystems(): void { + this.addSystem(new RenderSystem()); + this.addSystem(new UISystem()); + } +} +``` + +### 3. 资源管理 + +```typescript +class ResourceScene extends Scene { + private textures: Map = new Map(); + private sounds: Map = new Map(); + + protected initialize(): void { + this.loadResources(); + } + + private loadResources(): void { + // 加载场景所需资源 + } + + public unload(): void { + // 清理资源 + this.textures.clear(); + this.sounds.clear(); + } +} +``` + +### 4. 事件处理规范 + +```typescript +class EventHandlingScene extends Scene { + protected initialize(): void { + // 集中管理事件监听 + this.setupEventListeners(); + } + + private setupEventListeners(): void { + this.eventSystem.on('game_pause', this.onGamePause.bind(this)); + this.eventSystem.on('game_resume', this.onGameResume.bind(this)); + this.eventSystem.on('player_input', this.onPlayerInput.bind(this)); + } + + private onGamePause(): void { + // 暂停游戏逻辑 + this.systems.forEach(system => { + if (system instanceof GameLogicSystem) { + system.enabled = false; + } + }); + } + + private onGameResume(): void { + // 恢复游戏逻辑 + this.systems.forEach(system => { + if (system instanceof GameLogicSystem) { + system.enabled = true; + } + }); + } + + private onPlayerInput(data: any): void { + // 处理玩家输入 + } +} +``` + +场景是 ECS 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。 \ No newline at end of file diff --git a/docs/guide/system.md b/docs/guide/system.md new file mode 100644 index 00000000..2d51e96b --- /dev/null +++ b/docs/guide/system.md @@ -0,0 +1,596 @@ +# 系统架构 + +在 ECS 架构中,系统(System)是处理业务逻辑的地方。系统负责对拥有特定组件组合的实体执行操作,是 ECS 架构的逻辑处理单元。 + +## 基本概念 + +系统是继承自 `EntitySystem` 抽象基类的具体类,用于: +- 定义实体的处理逻辑(如移动、碰撞检测、渲染等) +- 根据组件组合筛选需要处理的实体 +- 提供生命周期管理和性能监控 +- 管理实体的添加、移除事件 + +## 系统类型 + +框架提供了几种不同类型的系统基类: + +### EntitySystem - 基础系统 + +最基础的系统类,所有其他系统都继承自它: + +```typescript +import { EntitySystem, ECSSystem, Matcher } from '@esengine/ecs-framework'; + +@ECSSystem('Movement') +class MovementSystem extends EntitySystem { + constructor() { + // 使用 Matcher 定义需要处理的实体条件 + super(Matcher.all(Position, Velocity)); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const position = entity.getComponent(Position); + const velocity = entity.getComponent(Velocity); + + if (position && velocity) { + position.x += velocity.dx * Time.deltaTime; + position.y += velocity.dy * Time.deltaTime; + } + } + } +} +``` + +### ProcessingSystem - 处理系统 + +适用于不需要逐个处理实体的系统: + +```typescript +@ECSSystem('Physics') +class PhysicsSystem extends ProcessingSystem { + constructor() { + super(); // 不需要指定 Matcher + } + + public processSystem(): void { + // 执行物理世界步进 + this.physicsWorld.step(Time.deltaTime); + } +} +``` + +### PassiveSystem - 被动系统 + +被动系统不进行主动处理,主要用于监听实体的添加和移除事件: + +```typescript +@ECSSystem('EntityTracker') +class EntityTrackerSystem extends PassiveSystem { + constructor() { + super(Matcher.all(Health)); + } + + protected onAdded(entity: Entity): void { + console.log(`生命值实体被添加: ${entity.name}`); + } + + protected onRemoved(entity: Entity): void { + console.log(`生命值实体被移除: ${entity.name}`); + } +} +``` + +### IntervalSystem - 间隔系统 + +按固定时间间隔执行的系统: + +```typescript +@ECSSystem('AutoSave') +class AutoSaveSystem extends IntervalSystem { + constructor() { + // 每 5 秒执行一次 + super(5.0, Matcher.all(SaveData)); + } + + protected process(entities: readonly Entity[]): void { + console.log('执行自动保存...'); + // 保存游戏数据 + this.saveGameData(entities); + } + + private saveGameData(entities: readonly Entity[]): void { + // 保存逻辑 + } +} +``` + +## 实体匹配器 (Matcher) + +Matcher 用于定义系统需要处理哪些实体。它提供了灵活的条件组合: + +### 基本匹配条件 + +```typescript +// 必须同时拥有 Position 和 Velocity 组件 +const matcher1 = Matcher.all(Position, Velocity); + +// 至少拥有 Health 或 Shield 组件之一 +const matcher2 = Matcher.any(Health, Shield); + +// 不能拥有 Dead 组件 +const matcher3 = Matcher.none(Dead); +``` + +### 复合匹配条件 + +```typescript +// 复杂的组合条件 +const complexMatcher = Matcher.all(Position, Velocity) + .any(Player, Enemy) + .none(Dead, Disabled); + +@ECSSystem('Combat') +class CombatSystem extends EntitySystem { + constructor() { + super(complexMatcher); + } +} +``` + +### 特殊匹配条件 + +```typescript +// 按标签匹配 +const tagMatcher = Matcher.byTag(1); // 匹配标签为 1 的实体 + +// 按名称匹配 +const nameMatcher = Matcher.byName("Player"); // 匹配名称为 "Player" 的实体 + +// 单组件匹配 +const componentMatcher = Matcher.byComponent(Health); // 匹配拥有 Health 组件的实体 +``` + +## 系统生命周期 + +系统提供了完整的生命周期回调: + +```typescript +@ECSSystem('Example') +class ExampleSystem extends EntitySystem { + protected onInitialize(): void { + console.log('系统初始化'); + // 系统被添加到场景时调用,用于初始化资源 + } + + protected onBegin(): void { + // 每帧处理开始前调用 + } + + protected process(entities: readonly Entity[]): void { + // 主要的处理逻辑 + for (const entity of entities) { + // 处理每个实体 + } + } + + protected lateProcess(entities: readonly Entity[]): void { + // 主处理之后的后期处理 + } + + protected onEnd(): void { + // 每帧处理结束后调用 + } + + protected onDestroy(): void { + console.log('系统销毁'); + // 系统从场景移除时调用,用于清理资源 + } +} +``` + +## 实体事件监听 + +系统可以监听实体的添加和移除事件: + +```typescript +@ECSSystem('EnemyManager') +class EnemyManagerSystem extends EntitySystem { + private enemyCount = 0; + + constructor() { + super(Matcher.all(Enemy, Health)); + } + + protected onAdded(entity: Entity): void { + this.enemyCount++; + console.log(`敌人加入战斗,当前敌人数量: ${this.enemyCount}`); + + // 可以在这里为新敌人设置初始状态 + const health = entity.getComponent(Health); + if (health) { + health.current = health.max; + } + } + + protected onRemoved(entity: Entity): void { + this.enemyCount--; + console.log(`敌人被移除,剩余敌人数量: ${this.enemyCount}`); + + // 检查是否所有敌人都被消灭 + if (this.enemyCount === 0) { + this.scene?.eventSystem.emitSync('all_enemies_defeated'); + } + } +} +``` + +## 系统属性和方法 + +### 重要属性 + +```typescript +@ECSSystem('Example') +class ExampleSystem extends EntitySystem { + showSystemInfo(): void { + console.log(`系统名称: ${this.systemName}`); // 系统名称 + console.log(`更新顺序: ${this.updateOrder}`); // 更新时序 + console.log(`是否启用: ${this.enabled}`); // 启用状态 + console.log(`实体数量: ${this.entities.length}`); // 匹配的实体数量 + console.log(`所属场景: ${this.scene?.name}`); // 所属场景 + } +} +``` + +### 实体访问 + +```typescript +protected process(entities: readonly Entity[]): void { + // 方式1:使用参数中的实体列表 + for (const entity of entities) { + // 处理实体 + } + + // 方式2:使用 this.entities 属性(与参数相同) + for (const entity of this.entities) { + // 处理实体 + } +} +``` + +### 控制系统执行 + +```typescript +@ECSSystem('Conditional') +class ConditionalSystem extends EntitySystem { + private shouldProcess = true; + + protected onCheckProcessing(): boolean { + // 返回 false 时跳过本次处理 + return this.shouldProcess && this.entities.length > 0; + } + + public pause(): void { + this.shouldProcess = false; + } + + public resume(): void { + this.shouldProcess = true; + } +} +``` + +## 事件系统集成 + +系统可以方便地监听和发送事件: + +```typescript +@ECSSystem('GameLogic') +class GameLogicSystem extends EntitySystem { + protected onInitialize(): void { + // 添加事件监听器(系统销毁时自动清理) + this.addEventListener('player_died', this.onPlayerDied.bind(this)); + this.addEventListener('level_complete', this.onLevelComplete.bind(this)); + } + + private onPlayerDied(data: any): void { + console.log('玩家死亡,重新开始游戏'); + // 处理玩家死亡逻辑 + } + + private onLevelComplete(data: any): void { + console.log('关卡完成,加载下一关'); + // 处理关卡完成逻辑 + } + + protected process(entities: readonly Entity[]): void { + // 在处理过程中发送事件 + for (const entity of entities) { + const health = entity.getComponent(Health); + if (health && health.current <= 0) { + this.scene?.eventSystem.emitSync('entity_died', { entity }); + } + } + } +} +``` + +## 性能监控 + +系统内置了性能监控功能: + +```typescript +@ECSSystem('Performance') +class PerformanceSystem extends EntitySystem { + protected onEnd(): void { + // 获取性能数据 + const perfData = this.getPerformanceData(); + if (perfData) { + console.log(`执行时间: ${perfData.executionTime.toFixed(2)}ms`); + } + + // 获取性能统计 + const stats = this.getPerformanceStats(); + if (stats) { + console.log(`平均执行时间: ${stats.averageTime.toFixed(2)}ms`); + } + } + + public resetPerformance(): void { + this.resetPerformanceData(); + } +} +``` + +## 系统管理 + +### 添加系统到场景 + +```typescript +// 在场景子类中添加系统 +class GameScene extends Scene { + protected initialize(): void { + // 添加系统 + this.addSystem(new MovementSystem()); + this.addSystem(new RenderSystem()); + this.addSystem(new PhysicsSystem()); + + // 设置系统更新顺序 + const movementSystem = this.getSystem(MovementSystem); + if (movementSystem) { + movementSystem.updateOrder = 1; + } + } +} +``` + +### 系统更新顺序 + +```typescript +@ECSSystem('Input') +class InputSystem extends EntitySystem { + constructor() { + super(Matcher.all(InputComponent)); + this.updateOrder = -100; // 输入系统优先执行 + } +} + +@ECSSystem('Physics') +class PhysicsSystem extends EntitySystem { + constructor() { + super(Matcher.all(RigidBody)); + this.updateOrder = 0; // 默认顺序 + } +} + +@ECSSystem('Render') +class RenderSystem extends EntitySystem { + constructor() { + super(Matcher.all(Sprite, Transform)); + this.updateOrder = 100; // 渲染系统最后执行 + } +} +``` + +## 复杂系统示例 + +### 碰撞检测系统 + +```typescript +@ECSSystem('Collision') +class CollisionSystem extends EntitySystem { + constructor() { + super(Matcher.all(Transform, Collider)); + } + + protected process(entities: readonly Entity[]): void { + // 简单的 n² 碰撞检测 + for (let i = 0; i < entities.length; i++) { + for (let j = i + 1; j < entities.length; j++) { + this.checkCollision(entities[i], entities[j]); + } + } + } + + private checkCollision(entityA: Entity, entityB: Entity): void { + const transformA = entityA.getComponent(Transform); + const transformB = entityB.getComponent(Transform); + const colliderA = entityA.getComponent(Collider); + const colliderB = entityB.getComponent(Collider); + + if (this.isColliding(transformA, colliderA, transformB, colliderB)) { + // 发送碰撞事件 + this.scene?.eventSystem.emitSync('collision', { + entityA, + entityB + }); + } + } + + private isColliding(transformA: Transform, colliderA: Collider, + transformB: Transform, colliderB: Collider): boolean { + // 碰撞检测逻辑 + return false; // 简化示例 + } +} +``` + +### 状态机系统 + +```typescript +@ECSSystem('StateMachine') +class StateMachineSystem extends EntitySystem { + constructor() { + super(Matcher.all(StateMachine)); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const stateMachine = entity.getComponent(StateMachine); + if (stateMachine) { + stateMachine.updateTimer(Time.deltaTime); + this.updateState(entity, stateMachine); + } + } + } + + private updateState(entity: Entity, stateMachine: StateMachine): void { + switch (stateMachine.currentState) { + case EntityState.Idle: + this.handleIdleState(entity, stateMachine); + break; + case EntityState.Moving: + this.handleMovingState(entity, stateMachine); + break; + case EntityState.Attacking: + this.handleAttackingState(entity, stateMachine); + break; + } + } + + private handleIdleState(entity: Entity, stateMachine: StateMachine): void { + // 空闲状态逻辑 + } + + private handleMovingState(entity: Entity, stateMachine: StateMachine): void { + // 移动状态逻辑 + } + + private handleAttackingState(entity: Entity, stateMachine: StateMachine): void { + // 攻击状态逻辑 + } +} +``` + +## 最佳实践 + +### 1. 系统单一职责 + +```typescript +// ✅ 好的系统设计 - 职责单一 +@ECSSystem('Movement') +class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.all(Position, Velocity)); + } +} + +@ECSSystem('Rendering') +class RenderingSystem extends EntitySystem { + constructor() { + super(Matcher.all(Sprite, Transform)); + } +} + +// ❌ 避免的系统设计 - 职责过多 +@ECSSystem('GameSystem') +class GameSystem extends EntitySystem { + // 一个系统处理移动、渲染、音效等多种逻辑 +} +``` + +### 2. 使用装饰器 + +**必须使用 `@ECSSystem` 装饰器**: + +```typescript +// ✅ 正确的用法 +@ECSSystem('Physics') +class PhysicsSystem extends EntitySystem { + // 系统实现 +} + +// ❌ 错误的用法 - 没有装饰器 +class BadSystem extends EntitySystem { + // 这样定义的系统可能在生产环境出现问题 +} +``` + +### 3. 合理的更新顺序 + +```typescript +// 按逻辑顺序设置系统的更新时序 +@ECSSystem('Input') +class InputSystem extends EntitySystem { + constructor() { + super(); + this.updateOrder = -100; // 最先处理输入 + } +} + +@ECSSystem('Logic') +class GameLogicSystem extends EntitySystem { + constructor() { + super(); + this.updateOrder = 0; // 处理游戏逻辑 + } +} + +@ECSSystem('Render') +class RenderSystem extends EntitySystem { + constructor() { + super(); + this.updateOrder = 100; // 最后进行渲染 + } +} +``` + +### 4. 避免在系统间直接引用 + +```typescript +// ❌ 避免:系统间直接引用 +@ECSSystem('Bad') +class BadSystem extends EntitySystem { + private otherSystem: SomeOtherSystem; // 避免直接引用其他系统 +} + +// ✅ 推荐:通过事件系统通信 +@ECSSystem('Good') +class GoodSystem extends EntitySystem { + protected process(entities: readonly Entity[]): void { + // 通过事件系统与其他系统通信 + this.scene?.eventSystem.emitSync('data_updated', { entities }); + } +} +``` + +### 5. 及时清理资源 + +```typescript +@ECSSystem('Resource') +class ResourceSystem extends EntitySystem { + private resources: Map = new Map(); + + protected onDestroy(): void { + // 清理资源 + for (const [key, resource] of this.resources) { + if (resource.dispose) { + resource.dispose(); + } + } + this.resources.clear(); + } +} +``` + +系统是 ECS 架构的逻辑处理核心,正确设计和使用系统能让你的游戏代码更加模块化、高效和易于维护。 \ No newline at end of file diff --git a/docs/guide/time-and-timers.md b/docs/guide/time-and-timers.md new file mode 100644 index 00000000..70b6c331 --- /dev/null +++ b/docs/guide/time-and-timers.md @@ -0,0 +1,553 @@ +# 时间和定时器系统 + +ECS 框架提供了完整的时间管理和定时器系统,包括时间缩放、帧时间计算和灵活的定时器调度功能。 + +## Time 类 + +Time 类是框架的时间管理核心,提供了游戏时间相关的所有功能。 + +### 基本时间属性 + +```typescript +import { Time } from '@esengine/ecs-framework'; + +class GameSystem extends EntitySystem { + protected process(entities: readonly Entity[]): void { + // 获取帧时间(秒) + const deltaTime = Time.deltaTime; + + // 获取未缩放的帧时间 + const unscaledDelta = Time.unscaledDeltaTime; + + // 获取游戏总时间 + const totalTime = Time.totalTime; + + // 获取当前帧数 + const frameCount = Time.frameCount; + + console.log(`第 ${frameCount} 帧,帧时间: ${deltaTime}s,总时间: ${totalTime}s`); + } +} +``` + +### 时间缩放 + +Time 类支持时间缩放功能,可以实现慢动作、快进等效果: + +```typescript +class TimeControlSystem extends EntitySystem { + public enableSlowMotion(): void { + // 设置为慢动作(50%速度) + Time.timeScale = 0.5; + console.log('慢动作模式启用'); + } + + public enableFastForward(): void { + // 设置为快进(200%速度) + Time.timeScale = 2.0; + console.log('快进模式启用'); + } + + public pauseGame(): void { + // 暂停游戏(时间静止) + Time.timeScale = 0; + console.log('游戏暂停'); + } + + public resumeNormalSpeed(): void { + // 恢复正常速度 + Time.timeScale = 1.0; + console.log('恢复正常速度'); + } + + protected process(entities: readonly Entity[]): void { + // deltaTime 会受到 timeScale 影响 + const scaledDelta = Time.deltaTime; // 受时间缩放影响 + const realDelta = Time.unscaledDeltaTime; // 不受时间缩放影响 + + for (const entity of entities) { + const movement = entity.getComponent(Movement); + if (movement) { + // 使用缩放时间进行游戏逻辑更新 + movement.update(scaledDelta); + } + + const ui = entity.getComponent(UIComponent); + if (ui) { + // UI 动画使用真实时间,不受游戏时间缩放影响 + ui.update(realDelta); + } + } + } +} +``` + +### 时间检查工具 + +```typescript +class CooldownSystem extends EntitySystem { + private lastAttackTime = 0; + private lastSpawnTime = 0; + + constructor() { + super(Matcher.all(Weapon)); + } + + protected process(entities: readonly Entity[]): void { + // 检查攻击冷却 + if (Time.checkEvery(1.5, this.lastAttackTime)) { + this.performAttack(); + this.lastAttackTime = Time.totalTime; + } + + // 检查生成间隔 + if (Time.checkEvery(3.0, this.lastSpawnTime)) { + this.spawnEnemy(); + this.lastSpawnTime = Time.totalTime; + } + } + + private performAttack(): void { + console.log('执行攻击!'); + } + + private spawnEnemy(): void { + console.log('生成敌人!'); + } +} +``` + +## Core.schedule 定时器系统 + +Core 提供了强大的定时器调度功能,可以创建一次性或重复执行的定时器。 + +### 基本定时器使用 + +```typescript +import { Core } from '@esengine/ecs-framework'; + +class GameScene extends Scene { + protected initialize(): void { + // 创建一次性定时器 + this.createOneTimeTimers(); + + // 创建重复定时器 + this.createRepeatingTimers(); + + // 创建带上下文的定时器 + this.createContextTimers(); + } + + private createOneTimeTimers(): void { + // 2秒后执行一次 + Core.schedule(2.0, false, null, (timer) => { + console.log('2秒延迟执行'); + }); + + // 5秒后显示提示 + Core.schedule(5.0, false, this, (timer) => { + const scene = timer.getContext(); + scene.showTip('游戏提示:5秒已过!'); + }); + } + + private createRepeatingTimers(): void { + // 每秒重复执行 + const heartbeatTimer = Core.schedule(1.0, true, null, (timer) => { + console.log(`游戏心跳 - 总时间: ${Time.totalTime.toFixed(1)}s`); + }); + + // 可以保存定时器引用用于后续控制 + this.saveTimerReference(heartbeatTimer); + } + + private createContextTimers(): void { + const gameData = { score: 0, level: 1 }; + + // 每2秒增加分数 + Core.schedule(2.0, true, gameData, (timer) => { + const data = timer.getContext(); + data.score += 10; + console.log(`分数增加!当前分数: ${data.score}`); + }); + } + + private saveTimerReference(timer: any): void { + // 可以稍后停止定时器 + setTimeout(() => { + timer.stop(); + console.log('定时器已停止'); + }, 10000); // 10秒后停止 + } + + private showTip(message: string): void { + console.log('提示:', message); + } +} +``` + +### 定时器控制 + +```typescript +class TimerControlExample { + private attackTimer: any; + private spawnerTimer: any; + + public startCombat(): void { + // 启动攻击定时器 + this.attackTimer = Core.schedule(0.5, true, this, (timer) => { + const self = timer.getContext(); + self.performAttack(); + }); + + // 启动敌人生成定时器 + this.spawnerTimer = Core.schedule(3.0, true, null, (timer) => { + this.spawnEnemy(); + }); + } + + public stopCombat(): void { + // 停止所有战斗相关定时器 + if (this.attackTimer) { + this.attackTimer.stop(); + console.log('攻击定时器已停止'); + } + + if (this.spawnerTimer) { + this.spawnerTimer.stop(); + console.log('生成定时器已停止'); + } + } + + public resetAttackTimer(): void { + // 重置攻击定时器 + if (this.attackTimer) { + this.attackTimer.reset(); + console.log('攻击定时器已重置'); + } + } + + private performAttack(): void { + console.log('执行攻击'); + } + + private spawnEnemy(): void { + console.log('生成敌人'); + } +} +``` + +### 复杂定时器场景 + +```typescript +class AdvancedTimerUsage { + private powerUpDuration = 0; + private powerUpActive = false; + + public activatePowerUp(): void { + if (this.powerUpActive) { + console.log('能力提升已激活'); + return; + } + + this.powerUpActive = true; + this.powerUpDuration = 10; // 10秒持续时间 + + console.log('能力提升激活!'); + + // 每秒更新剩余时间 + const countdownTimer = Core.schedule(1.0, true, this, (timer) => { + const self = timer.getContext(); + self.powerUpDuration--; + + console.log(`能力提升剩余时间: ${self.powerUpDuration}秒`); + + if (self.powerUpDuration <= 0) { + self.deactivatePowerUp(); + timer.stop(); // 停止倒计时 + } + }); + + // 能力提升结束定时器(备用) + Core.schedule(10.0, false, this, (timer) => { + const self = timer.getContext(); + if (self.powerUpActive) { + self.deactivatePowerUp(); + } + }); + } + + private deactivatePowerUp(): void { + this.powerUpActive = false; + this.powerUpDuration = 0; + console.log('能力提升结束'); + } + + // 创建波次攻击定时器 + public startWaveAttack(): void { + let waveCount = 0; + const maxWaves = 5; + + const waveTimer = Core.schedule(2.0, true, { waveCount, maxWaves }, (timer) => { + const context = timer.getContext<{ waveCount: number, maxWaves: number }>(); + context.waveCount++; + + console.log(`第 ${context.waveCount} 波攻击!`); + + if (context.waveCount >= context.maxWaves) { + console.log('所有波次攻击完成'); + timer.stop(); + } + }); + } + + // 创建条件定时器 + public startConditionalTimer(): void { + Core.schedule(0.1, true, this, (timer) => { + const self = timer.getContext(); + + // 检查某个条件 + if (self.shouldStopTimer()) { + console.log('条件满足,停止定时器'); + timer.stop(); + return; + } + + // 继续执行定时器逻辑 + self.performTimerAction(); + }); + } + + private shouldStopTimer(): boolean { + // 检查停止条件 + return Time.totalTime > 30; // 30秒后停止 + } + + private performTimerAction(): void { + console.log('执行定时器动作'); + } +} +``` + +## 实际应用示例 + +### 技能冷却系统 + +```typescript +class SkillCooldownSystem extends EntitySystem { + constructor() { + super(Matcher.all(SkillComponent)); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const skill = entity.getComponent(SkillComponent); + + // 更新技能冷却 + if (skill.isOnCooldown) { + skill.cooldownRemaining -= Time.deltaTime; + + if (skill.cooldownRemaining <= 0) { + skill.cooldownRemaining = 0; + skill.isOnCooldown = false; + console.log(`技能 ${skill.name} 冷却完成`); + } + } + } + } + + public useSkill(entity: Entity, skillName: string): boolean { + const skill = entity.getComponent(SkillComponent); + + if (skill.isOnCooldown) { + console.log(`技能 ${skillName} 还在冷却中,剩余 ${skill.cooldownRemaining.toFixed(1)}秒`); + return false; + } + + // 执行技能 + this.executeSkill(entity, skill); + + // 开始冷却 + skill.isOnCooldown = true; + skill.cooldownRemaining = skill.cooldownDuration; + + return true; + } + + private executeSkill(entity: Entity, skill: SkillComponent): void { + console.log(`执行技能: ${skill.name}`); + // 技能效果逻辑 + } +} +``` + +### 游戏状态定时器 + +```typescript +class GameStateManager { + private gamePhase = 'preparation'; + private phaseTimer: any; + + public startGame(): void { + this.startPreparationPhase(); + } + + private startPreparationPhase(): void { + this.gamePhase = 'preparation'; + console.log('准备阶段开始 - 10秒准备时间'); + + this.phaseTimer = Core.schedule(10.0, false, this, (timer) => { + const self = timer.getContext(); + self.startCombatPhase(); + }); + } + + private startCombatPhase(): void { + this.gamePhase = 'combat'; + console.log('战斗阶段开始 - 60秒战斗时间'); + + this.phaseTimer = Core.schedule(60.0, false, this, (timer) => { + const self = timer.getContext(); + self.startResultPhase(); + }); + + // 每10秒刷新一波敌人 + Core.schedule(10.0, true, null, (timer) => { + if (this.gamePhase === 'combat') { + this.spawnEnemyWave(); + } else { + timer.stop(); // 战斗阶段结束时停止刷新 + } + }); + } + + private startResultPhase(): void { + this.gamePhase = 'result'; + console.log('结算阶段开始 - 5秒结算时间'); + + this.phaseTimer = Core.schedule(5.0, false, this, (timer) => { + const self = timer.getContext(); + self.endGame(); + }); + } + + private endGame(): void { + console.log('游戏结束'); + this.gamePhase = 'ended'; + } + + private spawnEnemyWave(): void { + console.log('刷新敌人波次'); + } + + public getCurrentPhase(): string { + return this.gamePhase; + } +} +``` + +## 最佳实践 + +### 1. 合理使用时间类型 + +```typescript +class MovementSystem extends EntitySystem { + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const movement = entity.getComponent(Movement); + + // ✅ 游戏逻辑使用缩放时间 + movement.position.x += movement.velocity.x * Time.deltaTime; + + // ✅ UI动画使用真实时间(不受游戏暂停影响) + const ui = entity.getComponent(UIAnimation); + if (ui) { + ui.update(Time.unscaledDeltaTime); + } + } + } +} +``` + +### 2. 定时器管理 + +```typescript +class TimerManager { + private timers: any[] = []; + + public createManagedTimer(duration: number, repeats: boolean, callback: () => void): any { + const timer = Core.schedule(duration, repeats, null, callback); + this.timers.push(timer); + return timer; + } + + public stopAllTimers(): void { + for (const timer of this.timers) { + timer.stop(); + } + this.timers = []; + } + + public cleanupCompletedTimers(): void { + this.timers = this.timers.filter(timer => !timer.isDone); + } +} +``` + +### 3. 避免过多的定时器 + +```typescript +// ❌ 避免:为每个实体创建定时器 +class BadExample extends EntitySystem { + protected onAdded(entity: Entity): void { + Core.schedule(1.0, true, entity, (timer) => { + // 每个实体一个定时器,性能差 + }); + } +} + +// ✅ 推荐:在系统中统一管理时间 +class GoodExample extends EntitySystem { + private lastUpdateTime = 0; + + protected process(entities: readonly Entity[]): void { + // 每秒执行一次逻辑 + if (Time.checkEvery(1.0, this.lastUpdateTime)) { + this.processAllEntities(entities); + this.lastUpdateTime = Time.totalTime; + } + } + + private processAllEntities(entities: readonly Entity[]): void { + // 批量处理所有实体 + } +} +``` + +### 4. 定时器上下文使用 + +```typescript +interface TimerContext { + entityId: number; + duration: number; + onComplete: () => void; +} + +class ContextualTimerExample { + public createEntityTimer(entityId: number, duration: number, onComplete: () => void): void { + const context: TimerContext = { + entityId, + duration, + onComplete + }; + + Core.schedule(duration, false, context, (timer) => { + const ctx = timer.getContext(); + console.log(`实体 ${ctx.entityId} 的定时器完成`); + ctx.onComplete(); + }); + } +} +``` + +时间和定时器系统是游戏开发中的重要工具,正确使用这些功能能让你的游戏逻辑更加精确和可控。 \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..01302665 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,23 @@ +--- +layout: home + +hero: + name: "ECS Framework" + text: "高性能ECS框架" + tagline: "为Javascript游戏开发而设计" + actions: + - theme: brand + text: 快速开始 + link: /guide/getting-started + - theme: alt + text: 查看示例 + link: https://github.com/esengine/lawn-mower-demo + +features: + - title: 高性能 + details: 支持大规模实体处理 + - title: 类型安全 + details: 完整的TypeScript支持,编译时类型检查 + - title: 模块化设计 + details: 核心功能独立打包,支持多平台 +--- \ No newline at end of file diff --git a/docs/performance-optimization.md b/docs/performance-optimization.md deleted file mode 100644 index 232a9a86..00000000 --- a/docs/performance-optimization.md +++ /dev/null @@ -1,425 +0,0 @@ -# 性能优化指南 - -本文档介绍ECS框架的性能优化技术和最佳实践。 - -## 目录 - -1. [查询系统优化](#查询系统优化) -2. [实体管理优化](#实体管理优化) -3. [组件设计优化](#组件设计优化) -4. [系统设计优化](#系统设计优化) -5. [内存管理](#内存管理) -6. [性能监控](#性能监控) - -## 查询系统优化 - -### 使用高效的查询方法 - -```typescript -// 推荐:使用标签查询(快速) -const enemies = entityManager.getEntitiesByTag(2); - -// 推荐:使用组件查询 -const healthEntities = entityManager.getEntitiesWithComponent(HealthComponent); - -// 推荐:使用Scene的查询系统 -const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent); - -// 谨慎:自定义条件查询(较慢) -const nearbyEnemies = entityManager - .query() - .withAll(PositionComponent) - .where(entity => { - const pos = entity.getComponent(PositionComponent); - return pos && Math.abs(pos.x - playerX) < 100; - }) - .execute(); -``` - -### 查询结果缓存 - -```typescript -class OptimizedCombatSystem extends EntitySystem { - private cachedEnemies: Entity[] = []; - private lastCacheUpdate = 0; - private cacheInterval = 5; // 每5帧更新一次 - - protected process(entities: Entity[]): void { - // 缓存查询结果 - if (Time.frameCount - this.lastCacheUpdate >= this.cacheInterval) { - this.cachedEnemies = this.entityManager.getEntitiesByTag(2); - this.lastCacheUpdate = Time.frameCount; - } - - // 使用缓存的结果 - this.cachedEnemies.forEach(enemy => { - this.processEnemy(enemy); - }); - } - - private processEnemy(enemy: Entity): void { - // 处理敌人逻辑 - } -} -``` - -## 实体管理优化 - -### 批量创建实体 - -```typescript -// 推荐:使用Scene的批量创建 -function createEnemyWave(count: number): Entity[] { - const enemies = scene.createEntities(count, "Enemy"); - - // 批量配置组件 - 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 EntityReusableManager { - private inactiveEntities: Entity[] = []; - private scene: Scene; - - constructor(scene: Scene) { - this.scene = scene; - } - - // 预创建实体 - 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 - ); - - 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()); - } - return bullet; - } - - destroyBullet(bullet: Entity): void { - this.bulletManager.recycleEntity(bullet); - } -} -``` - -## 性能监控 - -### 基础性能统计 - -```typescript -class PerformanceMonitor { - private scene: Scene; - private entityManager: EntityManager; - - constructor(scene: Scene, entityManager: EntityManager) { - this.scene = scene; - this.entityManager = entityManager; - } - - public getPerformanceReport(): any { - return { - // 实体统计 - entities: { - total: this.entityManager.entityCount, - active: this.entityManager.activeEntityCount - }, - - // 场景统计 - scene: this.scene.getStats(), - - // 查询系统统计 - querySystem: this.scene.querySystem.getStats(), - - // 内存使用 - memory: { - used: (performance as any).memory?.usedJSHeapSize || 0, - total: (performance as any).memory?.totalJSHeapSize || 0 - } - }; - } - - 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; - } -} -``` - -## 最佳实践总结 - -### 查询优化 -1. 优先使用标签查询和组件查询 -2. 缓存频繁使用的查询结果 -3. 避免过度使用自定义条件查询 -4. 合理设置查询缓存更新频率 - -### 实体管理 -1. 使用批量创建方法 -2. 实现实体池化减少GC压力 -3. 及时清理无用实体 -4. 合理设置实体标签 - -### 组件设计 -1. 保持组件数据紧凑 -2. 避免在组件中分配大量对象 -3. 使用组件池化 -4. 分离数据和行为 - -### 系统设计 -1. 合理安排系统更新顺序 -2. 对重计算任务使用时间分片 -3. 避免在系统中进行复杂查询 -4. 缓存系统间的共享数据 - -### 内存管理 -1. 定期清理无用实体和组件 -2. 使用对象池减少GC -3. 监控内存使用情况 -4. 避免内存泄漏 - -通过遵循这些最佳实践,可以显著提升ECS框架的性能表现。 \ No newline at end of file diff --git a/docs/query-system-usage.md b/docs/query-system-usage.md deleted file mode 100644 index 860edbe4..00000000 --- a/docs/query-system-usage.md +++ /dev/null @@ -1,646 +0,0 @@ - # QuerySystem 使用指南 - -QuerySystem 是 ECS Framework 中的高性能实体查询系统,支持多级索引、智能缓存和类型安全的查询操作。 - -## 基本用法 - -### 1. 获取查询系统 - -```typescript -import { Scene, Entity } from '@esengine/ecs-framework'; - -// 创建场景,查询系统会自动创建 -const scene = new Scene(); -const querySystem = scene.querySystem; - -// 或者从Core获取当前场景的查询系统 -import { Core } from '@esengine/ecs-framework'; -const currentQuerySystem = Core.scene?.querySystem; -``` - -### 2. 基本查询操作 - -```typescript -// 查询包含所有指定组件的实体 -const result = querySystem.queryAll(PositionComponent, VelocityComponent); -console.log(`找到 ${result.count} 个实体`); - -// 查询包含任意指定组件的实体 -const anyResult = querySystem.queryAny(HealthComponent, ManaComponent); - -// 查询不包含指定组件的实体 -const noneResult = querySystem.queryNone(DeadComponent); -``` - -### 3. 类型安全查询 - -```typescript -// 类型安全的查询,返回实体和对应的组件 -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]; - // position 和 velocity 都是类型安全的 -} - -// 查询单个组件类型 -const healthResult = querySystem.queryAll(HealthComponent); -for (const entity of healthResult.entities) { - const health = entity.getComponent(HealthComponent); - if (health) { - console.log(`实体 ${entity.name} 的生命值: ${health.value}`); - } -} - -// 查询两个组件类型 -const movableResult = querySystem.queryAll(PositionComponent, VelocityComponent); -for (const entity of movableResult.entities) { - const position = entity.getComponent(PositionComponent); - const velocity = entity.getComponent(VelocityComponent); - if (position && velocity) { - // 更新位置 - position.x += velocity.x; - position.y += velocity.y; - } -} -``` - -### 4. 使用查询构建器 - -```typescript -// QuerySystem不提供查询构建器,请使用Matcher进行复杂查询 -// 推荐使用Matcher配合EntitySystem - -import { Matcher } from '@esengine/ecs-framework'; - -// 创建复杂查询条件 -const visibleMatcher = Matcher.all(PositionComponent, RenderComponent) - .none(HiddenComponent); - -// 通过QuerySystem执行查询 -const visibleEntities = querySystem.query(visibleMatcher.getCondition()); - -// 过滤和排序需要手动处理 -const sortedEntities = visibleEntities.entities - .filter(entity => entity.name.startsWith('Boss')) - .sort((a, b) => a.id - b.id) - .slice(0, 10); // 限制数量 - -// 迭代结果 -sortedEntities.forEach((entity, index) => { - console.log(`敌人 ${index}: ${entity.name}`); -}); -``` - -### 5. 高级查询功能 - -```typescript -// QuerySystem主要提供基础查询方法 -// 复杂查询推荐使用Matcher和EntitySystem - -// 基本查询 -const positionResult = querySystem.queryAll(PositionComponent, VelocityComponent); -const healthResult = querySystem.queryAll(HealthComponent); -const manaResult = querySystem.queryAll(ManaComponent); - -// 排除死亡实体的移动实体 -const aliveMovingEntities = positionResult.entities.filter(entity => - !entity.hasComponent(DeadComponent) -); - -// 如果需要复杂查询,推荐在EntitySystem中使用Matcher -class ComplexQuerySystem extends EntitySystem { - constructor() { - super(Matcher.all(PositionComponent, VelocityComponent).none(DeadComponent)); - } - - protected process(entities: Entity[]): void { - // entities已经是过滤后的结果 - for (const entity of entities) { - // 处理逻辑 - } - } -} -``` - -## 场景级别的实体查询 - -除了使用QuerySystem,您还可以直接使用Scene提供的便捷查询方法: - -### 基本场景查询 - -```typescript -// 按名称查找实体 -const player = scene.findEntity("Player"); -const playerAlt = scene.getEntityByName("Player"); // 别名方法 - -// 按ID查找实体 -const entity = scene.findEntityById(123); - -// 按标签查找实体 -const enemies = scene.findEntitiesByTag(2); -const enemiesAlt = scene.getEntitiesByTag(2); // 别名方法 - -// 获取所有实体 -const allEntities = scene.entities.buffer; -``` - -## 性能优化 - -### 1. 缓存管理 - -```typescript -// 设置缓存配置 -querySystem.setCacheConfig(200, 2000); // 最大200个缓存项,2秒超时 - -// 清空缓存 -querySystem.clearCache(); - -// 预热常用查询(使用基础查询方法) -querySystem.queryAll(PositionComponent); // 预热Position查询 -querySystem.queryAll(VelocityComponent); // 预热Velocity查询 -``` - -### 2. 索引优化 - -```typescript -// 获取性能统计 -const stats = querySystem.getStats(); -console.log(`缓存命中率: ${(stats.hitRate * 100).toFixed(1)}%`); -console.log(`实体数量: ${stats.entityCount}`); - -// 获取详细性能报告 -const report = querySystem.getPerformanceReport(); -console.log(report); -``` - -### 3. 查询监听和快照 - -```typescript -// QuerySystem主要用于基础查询,高级功能请使用EntitySystem和事件系统 - -// 如需监听实体变化,使用事件系统 -scene.entityManager.eventBus.on('entity:added', (entity) => { - if (entity.hasComponent(EnemyComponent)) { - console.log('新增敌人实体'); - } -}); - -scene.entityManager.eventBus.on('entity:removed', (entity) => { - if (entity.hasComponent(EnemyComponent)) { - console.log('移除敌人实体'); - } -}); - -// 手动创建快照进行比较 -const snapshot1 = querySystem.queryAll(PlayerComponent).entities.map(e => e.id); -// 稍后 -const snapshot2 = querySystem.queryAll(PlayerComponent).entities.map(e => e.id); - -// 比较快照 -const added = snapshot2.filter(id => !snapshot1.includes(id)); -const removed = snapshot1.filter(id => !snapshot2.includes(id)); -console.log(`新增: ${added.length}, 移除: ${removed.length}`); -``` - -## 使用Matcher进行高级查询 - -Matcher是一个优雅的查询封装器,提供流畅的API和强大的缓存机制。 - -### 基本Matcher用法 - -```typescript -import { Matcher } from '@esengine/ecs-framework'; - -// 创建Matcher查询条件 -const movingMatcher = Matcher.all(PositionComponent, VelocityComponent); -// 在QuerySystem中需要使用基础查询方法 -const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent).entities; - -// 复合查询需要使用EntitySystem或手动过滤 -const aliveEnemiesMatcher = Matcher.all(EnemyComponent, HealthComponent) - .any(WeaponComponent, MagicComponent) - .none(DeadComponent, StunnedComponent); - -// 在EntitySystem中使用 -class AliveEnemySystem extends EntitySystem { - constructor() { - super(aliveEnemiesMatcher); - } - - protected process(entities: Entity[]): void { - // entities已经是匹配的实体 - for (const entity of entities) { - // 处理存活的敌人 - } - } -} - -// 或者手动过滤 -const enemyResult = scene.querySystem.queryAll(EnemyComponent, HealthComponent); -const aliveEnemies = enemyResult.entities.filter(entity => { - const hasWeaponOrMagic = entity.hasComponent(WeaponComponent) || entity.hasComponent(MagicComponent); - const isAlive = !entity.hasComponent(DeadComponent) && !entity.hasComponent(StunnedComponent); - return hasWeaponOrMagic && isAlive; -}); - -// 单个实体检查 -const playerResult = scene.querySystem.queryAll(PlayerComponent); -if (playerResult.entities.includes(someEntity)) { - console.log('这是玩家实体'); -} - -// 统计信息 -console.log(`玩家数量: ${playerResult.count}`); -console.log(`是否有玩家: ${playerResult.count > 0}`); -``` - -### 系统中使用Matcher - -```typescript -import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework'; - -class MovementSystem extends EntitySystem { - constructor() { - // 在构造函数中直接传入Matcher - super(Matcher.all(PositionComponent, VelocityComponent)); - } - - protected process(entities: Entity[]): void { - // entities参数已经是系统自动过滤后的实体 - 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; - - // 边界检查 - if (position.x < 0 || position.x > 800) { - velocity.x = -velocity.x; - } - if (position.y < 0 || position.y > 600) { - velocity.y = -velocity.y; - } - } - } -} -``` - -### 碰撞检测示例 - -```typescript -class CollisionSystem extends EntitySystem { - constructor() { - // 在构造函数中传入Matcher - super(Matcher.all(PositionComponent, ColliderComponent)); - } - - protected process(entities: Entity[]): void { - // entities已经是匹配的实体 - const collidableEntities = entities; - - // 检测碰撞 - for (let i = 0; i < collidableEntities.length; i++) { - for (let j = i + 1; j < collidableEntities.length; j++) { - const entityA = collidableEntities[i]; - const entityB = collidableEntities[j]; - - if (this.checkCollision(entityA, entityB)) { - this.handleCollision(entityA, entityB); - } - } - } - } - - private checkCollision(entityA: Entity, entityB: Entity): boolean { - const posA = entityA.getComponent(PositionComponent)!; - const posB = entityB.getComponent(PositionComponent)!; - const distance = Math.sqrt( - Math.pow(posA.x - posB.x, 2) + Math.pow(posA.y - posB.y, 2) - ); - return distance < 50; - } - - private handleCollision(entityA: Entity, entityB: Entity): void { - console.log(`碰撞检测: ${entityA.name} 与 ${entityB.name}`); - } -} -``` - -### 生命值管理示例 - -```typescript -class HealthSystem extends EntitySystem { - constructor() { - // 在构造函数中传入Matcher - super(Matcher.all(HealthComponent)); - } - - protected process(entities: Entity[]): void { - // entities已经是匹配的实体 - const healthEntities = entities; - const deadEntities: Entity[] = []; - - for (const entity of healthEntities) { - const health = entity.getComponent(HealthComponent)!; - - // 检查死亡 - if (health.currentHealth <= 0) { - deadEntities.push(entity); - } - } - - // 移除死亡实体 - deadEntities.forEach(entity => { - console.log(`实体 ${entity.name} 已死亡`); - entity.destroy(); - }); - } -} -``` - -### Matcher完整API参考 - -#### 静态创建方法 - -```typescript -// 基础静态方法 -const allMatcher = Matcher.all(PositionComponent, VelocityComponent); // 必须包含所有组件 -const anyMatcher = Matcher.any(WeaponComponent, MagicComponent); // 必须包含任意一个组件 -const noneMatcher = Matcher.none(DeadComponent, DisabledComponent); // 不能包含任何指定组件 - -// 特殊查询静态方法 -const tagMatcher = Matcher.byTag(1); // 按标签查询 -const nameMatcher = Matcher.byName("Player"); // 按名称查询 -const componentMatcher = Matcher.byComponent(HealthComponent); // 单组件查询 - -// 构建器方法 -const complexMatcher = Matcher.complex(); // 创建复杂查询构建器 -const emptyMatcher = Matcher.empty(); // 创建空匹配器 -``` - -#### 实例方法 - 条件构建 - -```typescript -// 基础条件方法 -const matcher = Matcher.empty() - .all(PositionComponent, VelocityComponent) // 必须包含所有组件 - .any(WeaponComponent, MagicComponent) // 必须包含任意一个组件 - .none(DeadComponent, StunnedComponent); // 不能包含任何指定组件 - -// 别名方法(提供更语义化的API) -const semanticMatcher = Matcher.empty() - .all(PositionComponent) - .exclude(DeadComponent) // exclude() 等同于 none() - .one(WeaponComponent, MagicComponent); // one() 等同于 any() - -// 特殊条件方法 -const advancedMatcher = Matcher.empty() - .all(EnemyComponent) - .withTag(2) // 指定标签 - .withName("Boss") // 指定名称 - .withComponent(HealthComponent); // 单组件条件 -``` - -#### 条件移除方法 - -```typescript -// 移除特殊条件 -const matcher = Matcher.byTag(1) - .withName("Player") - .withComponent(HealthComponent); - -// 移除各种条件 -matcher.withoutTag(); // 移除标签条件 -matcher.withoutName(); // 移除名称条件 -matcher.withoutComponent(); // 移除单组件条件 -``` - -#### 实用工具方法 - -```typescript -// 检查和调试 -const matcher = Matcher.all(PositionComponent, VelocityComponent) - .none(DeadComponent); - -// 检查是否为空条件 -if (matcher.isEmpty()) { - console.log('匹配器没有设置任何条件'); -} - -// 获取条件信息(只读) -const condition = matcher.getCondition(); -console.log('必须组件:', condition.all.map(c => c.name)); -console.log('任选组件:', condition.any.map(c => c.name)); -console.log('排除组件:', condition.none.map(c => c.name)); -console.log('标签:', condition.tag); -console.log('名称:', condition.name); -console.log('单组件:', condition.component?.name); - -// 调试输出 -console.log('匹配器描述:', matcher.toString()); -// 输出: "Matcher[all(PositionComponent, VelocityComponent) & none(DeadComponent)]" -``` - -#### 克隆和重置 - -```typescript -// 克隆匹配器 -const baseMatcher = Matcher.all(PositionComponent); -const livingMatcher = baseMatcher.clone().all(HealthComponent).none(DeadComponent); -const deadMatcher = baseMatcher.clone().all(DeadComponent); - -// 重置匹配器 -const reusableMatcher = Matcher.all(PositionComponent); -console.log(reusableMatcher.toString()); // "Matcher[all(PositionComponent)]" - -reusableMatcher.reset(); // 清空所有条件 -console.log(reusableMatcher.toString()); // "Matcher[]" - -reusableMatcher.all(PlayerComponent); // 重新设置条件 -console.log(reusableMatcher.toString()); // "Matcher[all(PlayerComponent)]" -``` - -#### 链式调用示例 - -```typescript -// 复杂的链式调用 -const complexMatcher = Matcher.empty() - .all(PositionComponent, RenderComponent) // 必须有位置和渲染组件 - .any(PlayerComponent, NPCComponent) // 必须是玩家或NPC - .none(DeadComponent, HiddenComponent) // 不能死亡或隐藏 - .withTag(1) // 标签为1 - .exclude(DisabledComponent); // 不能被禁用 - -// 在EntitySystem中使用 -class VisibleCharacterSystem extends EntitySystem { - constructor() { - super(complexMatcher); - } - - protected process(entities: Entity[]): void { - // entities已经是符合所有条件的实体 - for (const entity of entities) { - // 处理可见角色的逻辑 - } - } -} -``` - -## 最佳实践 - -### 1. Matcher使用建议 - -```typescript -// 推荐的用法: -const matcher = Matcher.all(Position, Velocity); - -// 在系统中使用 -class MySystem extends EntitySystem { - constructor() { - super(Matcher.all(RequiredComponent)); - } - - protected process(entities: Entity[]): void { - // entities已经是系统自动过滤的结果 - for (const entity of entities) { - // 处理逻辑... - } - } -} - -// 避免在process方法中重复查询 -class InefficientSystem extends EntitySystem { - protected process(entities: Entity[]): void { - // 不必要的额外查询,性能差 - const condition = Matcher.all(RequiredComponent).getCondition(); - const result = this.scene.querySystem.query(condition); - } -} -``` - -### 2. Matcher API最佳实践 - -#### 选择合适的创建方式 - -```typescript -// 推荐:单一条件使用静态方法 -const movingEntities = Matcher.all(PositionComponent, VelocityComponent); -const playerEntities = Matcher.byTag(PLAYER_TAG); -const specificEntity = Matcher.byName("Boss"); - -// 推荐:复杂条件使用链式调用 -const complexMatcher = Matcher.empty() - .all(PositionComponent, HealthComponent) - .any(WeaponComponent, MagicComponent) - .none(DeadComponent); - -// ❌ 不推荐:简单条件使用复杂语法 -const simpleButBad = Matcher.empty().all(PositionComponent); -// 应该用: Matcher.all(PositionComponent) -``` - -#### 合理使用别名方法 - -```typescript -// 使用语义化的别名提高可读性 -const combatUnits = Matcher.all(PositionComponent, HealthComponent) - .one(WeaponComponent, MagicComponent) // one() 比 any() 更语义化 - .exclude(DeadComponent, PacifistComponent); // exclude() 比 none() 更直观 -``` - -#### 合理的克隆和重用 - -```typescript -// 推荐:基础匹配器重用 -const livingEntityMatcher = Matcher.all(HealthComponent).none(DeadComponent); -const livingPlayerMatcher = livingEntityMatcher.clone().all(PlayerComponent); -const livingEnemyMatcher = livingEntityMatcher.clone().all(EnemyComponent); - -// 推荐:重置匹配器重用 -const reusableMatcher = Matcher.empty(); - -// 用于玩家系统 -reusableMatcher.reset().all(PlayerComponent); -const playerSystem = new PlayerSystem(reusableMatcher.clone()); - -// 用于敌人系统 -reusableMatcher.reset().all(EnemyComponent); -const enemySystem = new EnemySystem(reusableMatcher.clone()); -``` - -#### 调试和维护 - -```typescript -// 在开发阶段添加调试信息 -const debugMatcher = Matcher.all(ComplexComponent) - .any(VariantA, VariantB) - .none(DisabledComponent); - -if (DEBUG_MODE) { - console.log('系统匹配条件:', debugMatcher.toString()); - const condition = debugMatcher.getCondition(); - console.log('预期匹配实体数:', - scene.querySystem.queryAll(...condition.all).count); -} -``` - -### 3. 查询优化 - -- **使用Matcher封装复杂查询**:提供更好的可读性和缓存 -- **避免频繁创建查询**:在系统初始化时创建,重复使用 -- **合理使用any()和none()条件**:减少不必要的实体遍历 -- **利用Matcher的缓存机制**:自动优化重复查询性能 -- **使用克隆方法复用基础条件**:避免重复定义相似的匹配条件 -- **选择合适的静态方法**:单一条件优先使用对应的静态方法 - -### 3. 性能监控 - -- 定期检查查询性能报告 -- 监控缓存命中率 -- 优化频繁使用的查询 -- 使用性能测试验证优化效果 - -### 4. 内存管理 - -- 及时清理不需要的查询监听器 -- 合理设置缓存大小 -- 避免创建过多的查询快照 -- 适当使用Matcher的clone()和reset()方法 - -### 5. 代码组织 - -- **系统级别的Matcher**:在系统中创建和管理Matcher -- **查询逻辑封装**:将复杂查询封装到专门的方法中 -- **条件复用**:使用clone()方法复用基础查询条件 -- **清晰的命名**:给Matcher变量使用描述性的名称 - -### 6. 迁移指南 - -系统中Matcher的推荐用法: - -```typescript -// 在EntitySystem中使用Matcher -class MySystem extends EntitySystem { - constructor() { - super(Matcher.all(ComponentA, ComponentB).none(ComponentC)); - } - - protected process(entities: Entity[]): void { - // entities已经是系统自动过滤的结果 - for (const entity of entities) { - // 处理逻辑 - } - } -} -``` - -## 使用最佳实践 - -- 在EntitySystem构造函数中传入Matcher -- 使用`none()`来排除组件 -- 使用`any()`来匹配任意组件 -- 直接使用EntitySystem的entities参数,避免额外查询 -- 定期检查查询性能和缓存命中率 \ No newline at end of file diff --git a/docs/scene-management-guide.md b/docs/scene-management-guide.md deleted file mode 100644 index 5b52091a..00000000 --- a/docs/scene-management-guide.md +++ /dev/null @@ -1,1183 +0,0 @@ -# 场景管理完整指南 - -场景(Scene)是ECS框架中管理游戏对象和系统的核心容器。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构。本指南将详细介绍如何有效地使用场景来构建和管理你的游戏。 - -## 场景基础概念 - -### 什么是场景? - -场景是一个完整的游戏世界容器,它包含: -- **实体集合** - 所有游戏对象 -- **系统集合** - 处理游戏逻辑的系统 -- **事件系统** - 场景内的事件通信 -- **查询系统** - 高效的实体查询 -- **性能监控** - 场景级别的性能统计 - -```typescript -import { Scene, Core } from '@esengine/ecs-framework'; - -// 创建场景 -const gameScene = new Scene(); - -// 设置为当前活动场景(推荐使用setScene方法) -Core.setScene(gameScene); -``` - -> **注意**: `Core.scene = ` 设置方式已被标记为废弃,推荐使用 `Core.setScene()` 方法。新方法提供更好的类型安全性和可预测的激活时序。 - -### 架构选择指南 - -ECS框架提供两种使用模式: - -#### 1. 单Scene模式(默认,向后兼容) - -```typescript -// 传统用法,无需任何修改 -const scene = new Scene(); -Core.setScene(scene); -``` - -**适用场景:** -- 简单游戏、单机游戏 -- 原型开发、快速验证 -- 学习ECS架构 -- 不需要复杂场景管理的项目 - -#### 2. 多World模式(高级功能) - -```typescript -// 启用World管理器 -Core.enableWorldManager(); -const worldManager = Core.getWorldManager(); - -// 创建多个World,每个World可包含多个Scene -const roomWorld = worldManager.createWorld('Room_001'); -const battleScene = roomWorld.createScene('battle'); -const uiScene = roomWorld.createScene('ui'); - -roomWorld.start(); -roomWorld.setSceneActive('battle', true); -roomWorld.setSceneActive('ui', true); -``` - -**适用场景:** -- 多人游戏服务器(每个房间一个World) -- 复杂应用架构(需要场景隔离) -- 需要并发处理多个游戏世界 -- 高级场景管理需求 - -### 场景的生命周期 - -```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.setScene(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; - }); - } -} -``` - -## World多场景管理 - -### 1. World基础使用 - -对于需要复杂场景管理的项目,可以使用World系统: - -```typescript -import { Core, World, Scene, WorldManager, IGlobalSystem } from '@esengine/ecs-framework'; - -// 定义全局系统(跨Scene的业务逻辑) -class NetworkSyncSystem implements IGlobalSystem { - public readonly name = 'NetworkSyncSystem'; - - public initialize(): void { - console.log('网络同步系统初始化'); - } - - public update(): void { - // 同步所有Scene的网络状态 - this.syncNetworkData(); - } - - public reset(): void { - // 重置网络连接 - } - - public destroy(): void { - console.log('网络同步系统销毁'); - } - - private syncNetworkData(): void { - // 网络数据同步逻辑 - } -} - -class PlayerManagementSystem implements IGlobalSystem { - public readonly name = 'PlayerManagementSystem'; - - public initialize(): void { - console.log('玩家管理系统初始化'); - } - - public update(): void { - // 管理跨Scene的玩家数据 - this.managePlayerStates(); - } - - public reset(): void { - // 重置玩家状态 - } - - public destroy(): void { - console.log('玩家管理系统销毁'); - } - - private managePlayerStates(): void { - // 玩家状态管理逻辑 - } -} - -// 启用World管理功能 -Core.enableWorldManager(); -const worldManager = Core.getWorldManager(); - -// 创建游戏房间World -const roomWorld = worldManager.createWorld('GameRoom_001', { - name: 'GameRoom_001', - maxScenes: 5, - autoCleanup: true, - debug: true -}); - -// 在World中创建多个Scene -const gameScene = roomWorld.createScene('game', new GameScene()); -const uiScene = roomWorld.createScene('ui', new UIScene()); -const backgroundScene = roomWorld.createScene('background', new BackgroundScene()); - -// 添加全局系统(跨Scene的系统) -roomWorld.addGlobalSystem(new NetworkSyncSystem()); -roomWorld.addGlobalSystem(new PlayerManagementSystem()); - -// 启动World并激活Scene -roomWorld.start(); -roomWorld.setSceneActive('game', true); -roomWorld.setSceneActive('ui', true); -roomWorld.setSceneActive('background', true); -``` - -### 2. 多房间游戏服务器示例 - -```typescript -// 房间管理系统 -class RoomManagementSystem implements IGlobalSystem { - public readonly name = 'RoomManagementSystem'; - private roomId: string; - - constructor(roomId: string) { - this.roomId = roomId; - } - - public initialize(): void { - console.log(`房间管理系统初始化: ${this.roomId}`); - } - - public update(): void { - // 管理房间状态、玩家进出等 - this.manageRoomState(); - } - - public reset(): void { - // 重置房间状态 - } - - public destroy(): void { - console.log(`房间管理系统销毁: ${this.roomId}`); - } - - private manageRoomState(): void { - // 房间状态管理逻辑 - } -} - -// 玩家同步系统 -class PlayerSyncSystem implements IGlobalSystem { - public readonly name = 'PlayerSyncSystem'; - - public initialize(): void { - console.log('玩家同步系统初始化'); - } - - public update(): void { - // 同步房间内所有玩家的状态 - this.syncPlayerData(); - } - - public reset(): void { - // 重置同步状态 - } - - public destroy(): void { - console.log('玩家同步系统销毁'); - } - - private syncPlayerData(): void { - // 玩家数据同步逻辑 - } -} - -class MultiRoomGameServer { - private worldManager: WorldManager; - private rooms: Map = new Map(); - - constructor() { - Core.create({ debug: false }); - Core.enableWorldManager(); - this.worldManager = Core.getWorldManager(); - } - - // 创建游戏房间 - createRoom(roomId: string): World { - const roomWorld = this.worldManager.createWorld(`Room_${roomId}`, { - name: `GameRoom_${roomId}`, - maxScenes: 3, - autoCleanup: true - }); - - // 房间内的Scene设置 - const gameScene = roomWorld.createScene('game', new ServerGameScene()); - const lobbyScene = roomWorld.createScene('lobby', new LobbyScene()); - - // 设置房间级的全局系统 - roomWorld.addGlobalSystem(new RoomManagementSystem(roomId)); - roomWorld.addGlobalSystem(new PlayerSyncSystem()); - - // 启动房间 - roomWorld.start(); - roomWorld.setSceneActive('lobby', true); // 默认激活大厅 - - this.rooms.set(roomId, roomWorld); - console.log(`创建房间: ${roomId}`); - - return roomWorld; - } - - // 开始游戏 - startGame(roomId: string): boolean { - const roomWorld = this.rooms.get(roomId); - if (!roomWorld) return false; - - // 停用大厅,激活游戏Scene - roomWorld.setSceneActive('lobby', false); - roomWorld.setSceneActive('game', true); - - console.log(`房间 ${roomId} 开始游戏`); - return true; - } - - // 销毁房间 - destroyRoom(roomId: string): boolean { - const roomWorld = this.rooms.get(roomId); - if (!roomWorld) return false; - - roomWorld.destroy(); - this.rooms.delete(roomId); - console.log(`销毁房间: ${roomId}`); - return true; - } - - // 获取服务器状态 - getServerStats() { - return { - totalRooms: this.rooms.size, - activeWorlds: this.worldManager.getActiveWorlds().length, - rooms: Array.from(this.rooms.keys()).map(roomId => ({ - roomId, - world: this.rooms.get(roomId)?.getStatus() - })) - }; - } - - // 游戏循环 - start(): void { - const gameLoop = () => { - const deltaTime = 1000 / 60; // 60 TPS - Core.update(deltaTime / 1000); - setTimeout(gameLoop, deltaTime); - }; - gameLoop(); - } -} - -// 使用示例 -const gameServer = new MultiRoomGameServer(); -gameServer.start(); - -// 创建房间 -const room1 = gameServer.createRoom('room_001'); -const room2 = gameServer.createRoom('room_002'); - -// 开始游戏 -setTimeout(() => { - gameServer.startGame('room_001'); -}, 5000); - -console.log('服务器状态:', gameServer.getServerStats()); -``` - -### 3. 客户端多Scene管理示例 - -```typescript -class GameClient { - private worldManager: WorldManager; - private mainWorld: World; - - constructor() { - Core.create({ debug: true }); - Core.enableWorldManager(); - this.worldManager = Core.getWorldManager(); - - this.setupGameWorld(); - } - - private setupGameWorld(): void { - // 创建主游戏世界 - this.mainWorld = this.worldManager.createWorld('MainWorld', { - name: 'ClientWorld', - maxScenes: 10, - autoCleanup: false // 客户端通常不需要自动清理 - }); - - // 创建不同层级的Scene - this.createGameplayScenes(); - this.createUIScenes(); - this.createEffectScenes(); - - // 启动世界 - this.mainWorld.start(); - this.activateDefaultScenes(); - } - - private createGameplayScenes(): void { - // 游戏主场景 - const gameScene = this.mainWorld.createScene('gameplay', new GameplayScene()); - - // 背景场景 - const backgroundScene = this.mainWorld.createScene('background', new BackgroundScene()); - - // 特效场景 - const effectsScene = this.mainWorld.createScene('effects', new EffectsScene()); - } - - private createUIScenes(): void { - // 主UI场景 - const mainUIScene = this.mainWorld.createScene('mainUI', new MainUIScene()); - - // 菜单场景 - const menuScene = this.mainWorld.createScene('menu', new MenuScene()); - - // 设置场景 - const settingsScene = this.mainWorld.createScene('settings', new SettingsScene()); - } - - private createEffectScenes(): void { - // 粒子效果场景 - const particleScene = this.mainWorld.createScene('particles', new ParticleScene()); - - // 音效场景 - const audioScene = this.mainWorld.createScene('audio', new AudioScene()); - } - - private activateDefaultScenes(): void { - // 激活基础Scene - this.mainWorld.setSceneActive('background', true); - this.mainWorld.setSceneActive('gameplay', true); - this.mainWorld.setSceneActive('mainUI', true); - this.mainWorld.setSceneActive('particles', true); - this.mainWorld.setSceneActive('audio', true); - - // 菜单和设置默认不激活 - this.mainWorld.setSceneActive('menu', false); - this.mainWorld.setSceneActive('settings', false); - } - - // 切换到菜单 - showMenu(): void { - this.mainWorld.setSceneActive('gameplay', false); - this.mainWorld.setSceneActive('menu', true); - } - - // 切换回游戏 - hideMenu(): void { - this.mainWorld.setSceneActive('menu', false); - this.mainWorld.setSceneActive('gameplay', true); - } - - // 显示设置 - showSettings(): void { - this.mainWorld.setSceneActive('settings', true); - } - - // 隐藏设置 - hideSettings(): void { - this.mainWorld.setSceneActive('settings', false); - } - - // 获取World状态 - getWorldStatus() { - return this.mainWorld.getStatus(); - } -} - -// 使用示例 -const gameClient = new GameClient(); - -// 显示菜单 -gameClient.showMenu(); - -// 5秒后返回游戏 -setTimeout(() => { - gameClient.hideMenu(); -}, 5000); - -console.log('客户端World状态:', gameClient.getWorldStatus()); -``` - -## 传统场景切换和管理 - -### 1. 单Scene模式场景管理器 - -> **注意:** 以下的 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.setScene(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.setScene(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.setScene(this.currentScene); - } - - popScene() { - if (this.sceneHistory.length > 0) { - if (this.currentScene) { - this.currentScene.onDestroy(); - } - - this.currentScene = this.sceneHistory.pop()!; - this.resumeScene(this.currentScene); - Core.setScene(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.setScene(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.setScene(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: -- **单Scene模式**:框架同时只支持一个活跃场景,但可以通过场景栈实现多场景管理(如暂停菜单) -- **多World模式**:每个World可以包含多个同时激活的Scene,支持复杂的多场景架构 - -### Q: 什么时候使用World系统? - -A: -- 多人游戏服务器(每个房间独立管理) -- 需要并发运行多个独立游戏世界 -- 复杂的客户端架构(游戏层、UI层、特效层分离) -- 需要跨Scene的全局系统支持 - -### Q: World和Scene的性能影响? - -A: -- **单Scene模式**:最佳性能,适合简单项目 -- **多World模式**:每个World独立更新,合理使用不会显著影响性能 -- **建议**:根据项目复杂度选择合适的架构 - -### Q: 如何从单Scene迁移到多World? - -A: -```typescript -// 原始单Scene代码 -const scene = new Scene(); -Core.setScene(scene); - -// 迁移到World模式(可选) -Core.enableWorldManager(); -const world = Core.getWorldManager().createWorld('MainWorld'); -const scene = world.createScene('main', new Scene()); -world.start(); -world.setSceneActive('main', true); -``` - -### Q: World系统的最佳实践? - -A: -1. **服务器端**:每个游戏房间使用独立World -2. **客户端**:按功能层级划分Scene(游戏、UI、特效) -3. **全局系统**:将跨Scene的逻辑放在World的全局System中 -4. **资源管理**:使用World的autoCleanup功能自动清理空闲资源 - -通过合理选择单Scene或多World架构,你可以构建出结构清晰、性能优良的游戏架构! \ No newline at end of file diff --git a/docs/soa-storage-guide.md b/docs/soa-storage-guide.md deleted file mode 100644 index 7cc0fa11..00000000 --- a/docs/soa-storage-guide.md +++ /dev/null @@ -1,343 +0,0 @@ -# SoA存储优化指南 - -SoA (Structure of Arrays) 存储模式是ECS框架中的高级性能优化特性,适用于大规模实体系统和批量操作场景。 - -## 目录 - -1. [什么是SoA存储](#什么是soa存储) -2. [适用场景](#适用场景) -3. [不适用场景](#不适用场景) -4. [装饰器使用指南](#装饰器使用指南) -5. [性能对比](#性能对比) -6. [最佳实践](#最佳实践) -7. [故障排除](#故障排除) - -## 什么是SoA存储 - -### AoS vs SoA 对比 - -**传统AoS (Array of Structures):** -```typescript -// 数据在内存中的布局 -[{x:1, y:2, z:3}, {x:4, y:5, z:6}, {x:7, y:8, z:9}] -// 内存布局: x1,y1,z1,x2,y2,z2,x3,y3,z3 -``` - -**SoA (Structure of Arrays):** -```typescript -// 数据在内存中的布局 -{ - x: [1, 4, 7], // Float32Array - y: [2, 5, 8], // Float32Array - z: [3, 6, 9] // Float32Array -} -// 内存布局: x1,x2,x3,y1,y2,y3,z1,z2,z3 -``` - -### SoA的优势 - -- **缓存友好**: 相同类型数据连续存储,提高缓存命中率 -- **向量化优化**: 支持SIMD指令并行处理 -- **内存局部性**: 批量操作时减少缓存miss -- **类型优化**: 针对不同数据类型使用最优存储格式 - -## 适用场景 - -### 推荐使用SoA的场景 - -1. **大规模实体系统** - ```typescript - // 大量相似实体的物理系统 - @EnableSoA - class PhysicsComponent extends Component { - @Float64 public x: number = 0; - @Float64 public y: number = 0; - @Float32 public velocityX: number = 0; - @Float32 public velocityY: number = 0; - } - ``` - -2. **频繁批量更新操作** - ```typescript - // 每帧更新大量实体位置 - system.performVectorizedOperation((fields, indices) => { - const x = fields.get('x') as Float32Array; - const y = fields.get('y') as Float32Array; - const vx = fields.get('velocityX') as Float32Array; - const vy = fields.get('velocityY') as Float32Array; - - // 向量化更新所有实体 - for (let i = 0; i < indices.length; i++) { - const idx = indices[i]; - x[idx] += vx[idx] * deltaTime; - y[idx] += vy[idx] * deltaTime; - } - }); - ``` - -3. **数值密集计算** - ```typescript - @EnableSoA - class AIBrainComponent extends Component { - @Float32 public neuron1: number = 0; - @Float32 public neuron2: number = 0; - @Float32 public output: number = 0; - } - ``` - -## 不适用场景 - -### ❌ 不推荐使用SoA的场景 - -1. **小规模系统** - - SoA的开销大于收益 - - 原始存储更简单高效 - -2. **随机访问为主** - ```typescript - // 经常需要随机获取单个组件 - const component = entityManager.getComponent(randomId, SomeComponent); - ``` - -3. **复杂对象为主的组件** - ```typescript - // 大量复杂对象,序列化开销大 - class UIComponent extends Component { - public domElement: HTMLElement; - public eventHandlers: Map; - public children: UIComponent[]; - } - ``` - -4. **频繁增删实体** - - SoA在频繁增删时性能不如AoS - - 适合稳定的实体集合 - -## 装饰器使用指南 - -### 基础装饰器 - -```typescript -import { Component, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '@esengine/ecs-framework'; - -@EnableSoA // 启用SoA优化 -class GameComponent extends Component { - // 数值类型装饰器 - @HighPrecision // 高精度数值,保持完整精度 - public entityId: number = 0; - - @Float64 // 64位浮点数 (8字节,高精度) - public precisePosition: number = 0; - - @Int32 // 32位整数 (4字节,整数优化) - public health: number = 100; - - // 普通数值 (默认Float32Array,4字节) - public x: number = 0; - public y: number = 0; - - // 集合类型装饰器 - @SerializeMap - public playerStats: Map = new Map(); - - @SerializeSet - public achievements: Set = new Set(); - - @SerializeArray - public inventory: any[] = []; - - @DeepCopy - public config: any = { settings: {} }; - - // 未装饰的字段自动选择最优存储 - public name: string = ''; // string[] 数组 - public active: boolean = true; // Float32Array (0/1) - public metadata: any = null; // 复杂对象存储 -} -``` - -### 装饰器选择指南 - -| 装饰器 | 用途 | 存储方式 | 开销 | 适用场景 | -|--------|------|----------|------|----------| -| `@HighPrecision` | 高精度数值 | 复杂对象 | 高 | ID、时间戳、大整数 | -| `@Float64` | 双精度浮点 | Float64Array | 中 | 精密计算 | -| `@Int32` | 32位整数 | Int32Array | 低 | 整数计数、枚举值 | -| `@SerializeMap` | Map序列化 | JSON字符串 | 高 | 配置映射、属性集合 | -| `@SerializeSet` | Set序列化 | JSON字符串 | 高 | 标签集合、ID集合 | -| `@SerializeArray` | Array序列化 | JSON字符串 | 中 | 列表数据、队列 | -| `@DeepCopy` | 深拷贝对象 | 复杂对象副本 | 高 | 嵌套配置、独立状态 | - -## 性能对比 - -### 基准测试结果 - -``` -测试场景: 2000个实体,包含位置、速度、生命值组件 - -创建性能: -- 原始存储: 12.45ms -- SoA存储: 15.72ms (慢26%) - -随机访问性能: -- 原始存储: 8.33ms -- SoA存储: 14.20ms (慢70%) - -批量更新性能: -- 原始存储: 25.67ms -- SoA存储: 8.91ms (快188%) - -内存使用: -- 原始存储: ~45KB (对象开销) -- SoA存储: ~28KB (TypedArray优化) -``` - -### 性能权衡总结 - -- **SoA优势**: 批量操作、内存效率、向量化计算 -- **SoA劣势**: 随机访问、创建开销、复杂度增加 -- **建议**: 大规模批量操作场景使用,小规模随机访问避免使用 - -## 最佳实践 - -### 1. 合理的组件设计 - -```typescript -// 好的设计:纯数值组件 -@EnableSoA -class TransformComponent extends Component { - @Float64 public x: number = 0; - @Float64 public y: number = 0; - @Float32 public rotation: number = 0; - @Float32 public scaleX: number = 1; - @Float32 public scaleY: number = 1; -} - -// ❌ 不好的设计:混合复杂对象 -@EnableSoA -class MixedComponent extends Component { - public x: number = 0; - public domElement: HTMLElement = null; // 复杂对象开销大 - public callback: Function = null; // 无法序列化 -} -``` - -### 2. 批量操作优化 - -```typescript -// 使用向量化操作 -const storage = entityManager.getStorage(TransformComponent) as SoAStorage; -storage.performVectorizedOperation((fields, indices) => { - const x = fields.get('x') as Float64Array; - const y = fields.get('y') as Float64Array; - - // 批量处理,利用缓存局部性 - for (let i = 0; i < indices.length; i++) { - const idx = indices[i]; - x[idx] += deltaX; - y[idx] += deltaY; - } -}); - -// ❌ 避免逐个访问 -for (const entity of entities) { - const transform = entity.getComponent(TransformComponent); - transform.x += deltaX; - transform.y += deltaY; -} -``` - -### 3. 组件分离策略 - -```typescript -// 将频繁批量操作的数据分离 -@EnableSoA -class PositionComponent extends Component { - @Float32 public x: number = 0; - @Float32 public y: number = 0; -} - -// 复杂数据使用普通组件 -class MetadataComponent extends Component { - public name: string = ''; - public config: any = {}; - public references: any[] = []; -} -``` - -### 4. 性能监控 - -```typescript -// 监控SoA存储使用情况 -const storage = entityManager.getStorage(MyComponent) as SoAStorage; -const stats = storage.getStats(); - -console.log('SoA存储统计:', { - size: stats.size, - capacity: stats.capacity, - memoryUsage: stats.memoryUsage, - fragmentation: stats.fragmentation, - fieldStats: stats.fieldStats -}); -``` - -## 故障排除 - -### 常见问题 - -1. **精度丢失** - ```typescript - // 问题:大整数精度丢失 - public bigId: number = Number.MAX_SAFE_INTEGER; - - // 解决:使用高精度装饰器 - @HighPrecision - public bigId: number = Number.MAX_SAFE_INTEGER; - ``` - -2. **序列化失败** - ```typescript - // 问题:循环引用导致序列化失败 - @SerializeMap - public cyclicMap: Map = new Map(); - - // 解决:避免循环引用或使用DeepCopy - @DeepCopy - public cyclicData: any = {}; - ``` - -3. **性能反向优化** - ```typescript - // 问题:小规模数据使用SoA - @EnableSoA // 只有10个实体,不需要SoA - class SmallComponent extends Component {} - - // 解决:移除@EnableSoA装饰器 - class SmallComponent extends Component {} - ``` - -### 调试技巧 - -```typescript -// 检查存储类型 -const storage = entityManager.getStorage(MyComponent); -console.log('存储类型:', storage.constructor.name); -// 输出: 'SoAStorage' 或 'ComponentStorage' - -// 检查字段存储方式 -if (storage instanceof SoAStorage) { - const fieldArray = storage.getFieldArray('myField'); - console.log('字段类型:', fieldArray?.constructor.name); - // 输出: 'Float32Array', 'Float64Array', 'Int32Array', 或 null -} -``` - -## 总结 - -SoA存储是一个强大的性能优化工具,但需要在合适的场景下使用: - -- **适合**: 大规模、批量操作、数值密集的场景 -- **不适合**: 小规模、随机访问、复杂对象为主的场景 -- **关键**: 通过性能测试验证优化效果,避免过度优化 - -正确使用SoA存储可以显著提升ECS系统性能,但滥用会带来相反的效果。建议在实际项目中先进行基准测试,确认优化效果后再应用到生产环境。 \ No newline at end of file diff --git a/docs/system-guide.md b/docs/system-guide.md deleted file mode 100644 index afb775c7..00000000 --- a/docs/system-guide.md +++ /dev/null @@ -1,662 +0,0 @@ -# 系统(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.all(HealthComponent)); - // 或者使用链式语法 - // super(Matcher.empty().all(HealthComponent)); - } - - // 主要处理逻辑 - protected process(entities: Entity[]) { - // 直接使用传入的entities参数,已经是匹配的实体 - 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()); - - // 触发死亡事件 - const eventBus = this.scene.entityManager.eventBus; - eventBus.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 triggerVictory() { - console.log("游戏胜利!"); - // 处理胜利逻辑 - } - - private triggerGameOver() { - console.log("游戏结束!"); - // 处理游戏结束逻辑 - } -} -``` - -**适用场景:** -- 全局游戏逻辑系统 -- 胜负判断系统 -- UI更新系统 -- 不依赖特定实体的处理 - -## AI系统示例 - -下面是一个完整的AI系统示例,展示EntitySystem的典型用法: - -```typescript -import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework'; - -enum AIState { - IDLE, - PATROL, - CHASE, - ATTACK -} - -class AISystem extends EntitySystem { - constructor() { - // 复杂匹配条件可以使用链式语法 - super(Matcher.empty().all(AIComponent, PositionComponent)); - // 或者使用简洁语法 - // super(Matcher.all(AIComponent, PositionComponent)); - } - - // 处理所有匹配的实体 - protected process(entities: Entity[]) { - for (const entity of entities) { - this.processEntity(entity); - } - } - - // 处理单个实体的逻辑(自定义方法) - private processEntity(entity: Entity) { - const ai = entity.getComponent(AIComponent); - const position = entity.getComponent(PositionComponent); - - switch (ai.state) { - case AIState.IDLE: - this.processIdle(entity, ai); - break; - case AIState.PATROL: - this.processPatrol(entity, ai, position); - break; - case AIState.CHASE: - this.processChase(entity, ai, position); - break; - case AIState.ATTACK: - this.processAttack(entity, ai); - break; - } - } - - 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(2.0, Matcher.all(SpawnerComponent)); - } - - // 间隔执行的逻辑(重写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; - - // 发送生成事件 - const eventBus = this.scene.entityManager.eventBus; - eventBus.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(); - - // 监听游戏事件(使用EntityManager的事件系统) - const eventBus = this.scene.entityManager.eventBus; - eventBus.on('enemy:killed', this.onEnemyKilled, { context: this }); - eventBus.on('item:collected', this.onItemCollected, { context: this }); - eventBus.on('combo:broken', this.onComboBroken, { context: this }); - } - - // PassiveSystem被移除时清理 - destroy() { - // 事件监听会在系统销毁时自动清理 - // 如需手动清理,可以保存listenerId并调用eventBus.off() - } - - 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; - - // 发送分数更新事件 - const eventBus = this.scene.entityManager.eventBus; - eventBus.emit('score:updated', { - score: this.score, - points: points, - multiplier: this.multiplier, - combo: this.combo - }); - } -} -``` - -**适用场景:** -- 分数统计系统 -- 音效播放系统 -- UI更新系统 -- 成就系统 - -## 系统生命周期方法 - -系统提供了多个生命周期方法,可以根据需要重写: - -### 重要的生命周期方法 - -```typescript -class ExampleSystem extends EntitySystem { - /** - * 系统初始化回调 - 系统被添加到场景时调用 - * 用于设置事件监听器、初始化资源等 - * 注意:不要重写initialize()方法,而是重写onInitialize() - */ - protected onInitialize() { - // 设置事件监听 - const eventBus = this.scene.entityManager.eventBus; - eventBus.on('someEvent', this.handleEvent, { context: this }); - - console.log('系统已初始化'); - } - - /** - * 每帧处理开始前调用 - */ - protected onBegin() { - // 预处理逻辑,如重置计数器 - this.frameCounter++; - } - - /** - * 主要处理逻辑 - 每帧调用 - * @param entities 符合条件的实体列表 - */ - protected process(entities: Entity[]) { - for (const entity of entities) { - // 处理每个实体 - this.processEntity(entity); - } - } - - /** - * 后期处理 - 在process之后调用 - * @param entities 符合条件的实体列表 - */ - protected lateProcess(entities: Entity[]) { - // 后期处理逻辑,如碰撞检测后的响应 - this.handlePostProcessing(); - } - - /** - * 每帧处理结束后调用 - */ - protected onEnd() { - // 后处理逻辑,如统计数据更新 - this.updateStatistics(); - } -} -``` - -### 生命周期执行顺序 - -系统的生命周期方法按以下顺序执行: - -1. **initialize()** - 系统被添加到场景时执行一次(框架调用) - - **onInitialize()** - 用户可重写的初始化回调 -2. 每帧循环: - - **onBegin()** - 帧开始前(用户可重写) - - **process(entities)** - 主要处理逻辑(用户必须实现) - - **lateProcess(entities)** - 后期处理(用户可重写) - - **onEnd()** - 帧结束后(用户可重写) - -## 系统管理和注册 - -### 在场景中添加系统 - -```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.setScene(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) { - // 发送碰撞事件,让其他系统响应 - const eventBus = this.scene.entityManager.eventBus; - eventBus.emit('collision:detected', { - entity1: collider1, - entity2: collider2, - collisionPoint: point - }); - } - } -} - -class HealthSystem extends PassiveSystem { - initialize() { - super.initialize(); - - // 监听碰撞事件 - const eventBus = this.scene.entityManager.eventBus; - eventBus.on('collision:detected', this.onCollision, { context: 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** - 不依赖特定实体的全局处理(游戏状态管理、全局逻辑) -- **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 deleted file mode 100644 index a0ac8f10..00000000 --- a/docs/timer-guide.md +++ /dev/null @@ -1,653 +0,0 @@ -# 定时器系统使用指南 - -定时器系统是游戏开发中的重要工具,用于处理延迟执行、重复任务、倒计时等功能。本指南详细介绍如何使用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("⏰ 时间到!游戏结束"); - - // 触发游戏结束(需要在实际使用中获取EntityManager实例) - // 示例:entityManager.eventBus.emit('level:timeout'); - console.log('触发关卡超时事件'); - } - - 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} 分`); - // 触发时间奖励事件(需要在实际使用中获取EntityManager实例) - // 示例:entityManager.eventBus.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 deleted file mode 100644 index ebbf2b94..00000000 --- a/docs/use-cases.md +++ /dev/null @@ -1,600 +0,0 @@ -# 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/examples/lawn-mower-demo b/examples/lawn-mower-demo index 8f217c95..5a4976b1 160000 --- a/examples/lawn-mower-demo +++ b/examples/lawn-mower-demo @@ -1 +1 @@ -Subproject commit 8f217c95dbfeeeeb852b25da6294ed40c5e9e9ba +Subproject commit 5a4976b192d3901b32bd463ae2898f7456e49a18 diff --git a/package-lock.json b/package-lock.json index 425f9f89..254b7135 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "ws": "^8.18.2" }, "devDependencies": { + "@iconify/json": "^2.2.388", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", @@ -32,7 +33,269 @@ "rollup-plugin-dts": "^6.2.1", "semver": "^7.6.3", "ts-jest": "^29.4.0", - "typescript": "^5.8.3" + "typedoc": "^0.28.13", + "typedoc-plugin-markdown": "^4.9.0", + "typescript": "^5.8.3", + "unplugin-icons": "^22.3.0", + "vitepress": "^1.6.4" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.5.0.tgz", + "integrity": "sha512-W/ohRkbKQsqDWALJg28X15KF7Tcyg53L1MfdOkLgvkcCcofdzGHSimHHeNG05ojjFw9HK8+VPhe/Vwq4MozIJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.39.0.tgz", + "integrity": "sha512-Vf0ZVe+qo3sHDrCinouJqlg8VoxM4Qo/KxNIqMYybkuctutfnp3kIY9OmESplOQ/9NGBthU9EG+4d5fBibWK/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.39.0.tgz", + "integrity": "sha512-V16ITZxYIwcv1arNce65JZmn94Ft6vKlBZ//gXw8AvIH32glJz1KcbaVAUr9p7PYlGZ/XVHP6LxDgrpNdtwgcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.39.0.tgz", + "integrity": "sha512-UCJTuwySEQeiKPWV3wruhuI/wHbDYenHzgL9pYsvh6r/u5Z+g61ip1iwdAlFp02CnywzI9O7+AQPh2ManYyHmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.39.0.tgz", + "integrity": "sha512-s0ia8M/ZZR+iO2uLNTBrlQdEb6ZMAMcKMHckp5mcoglxrf8gHifL4LmdhGKdAxAn3UIagtqIP0RCnIymHUbm7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.39.0.tgz", + "integrity": "sha512-vZPIt7Lw+toNsHZUiPhNIc1Z3vUjDp7nzn6AMOaPC73gEuTq2iLPNvM06CSB6aHePo5eMeJIP5YEKBUQUA/PJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.39.0.tgz", + "integrity": "sha512-jcPQr3iKTWNVli2NYHPv02aNLwixDjPCpOgMp9CZTvEiPI6Ec4jHX+oFr3LDZagOFY9e1xJhc/JrgMGGW1sHnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.39.0.tgz", + "integrity": "sha512-/IYpF10BpthGZEJQZMhMqV4AqWr5avcWfZm/SIKK1RvUDmzGqLoW/+xeJVX9C8ZnNkIC8hivbIQFaNaRw0BFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.39.0.tgz", + "integrity": "sha512-IgSHKUiuecqLfBlXiuCSdRTdsO3/yvpmXrMFz8fAJ8M4QmDtHkOuD769dmybRYqsbYMHivw+lir4BgbRGMtOIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.39.0.tgz", + "integrity": "sha512-8Xnd4+609SKC/hqVsuFc4evFBmvA2765/4NcH+Dpr756SKPbL1BY0X8kVxlmM3YBLNqnduSQxHxpDJUK58imCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.39.0.tgz", + "integrity": "sha512-D7Ye2Ss/5xqUkQUxKm/VqEJLt5kARd9IMmjdzlxaKhGgNlOemTay0lwBmOVFuJRp7UODjp5c9+K+B8g0ORObIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.39.0.tgz", + "integrity": "sha512-mgPte1ZJqpk9dkVs44J3wKAbHATvHZNlSpzhMdjMLIg/3qTycSZyDiomLiSlxE8CLsxyBAOJWnyKRHfom+Z1rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.39.0.tgz", + "integrity": "sha512-LIrCkrxu1WnO3ev1+w6NnZ12JZL/o+2H9w6oWnZAjQZIlA/Ym6M9QHkt+OQ/SwkuoiNkW3DAo+Pi4A2V9FPtqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.39.0.tgz", + "integrity": "sha512-6beG+egPwXmvhAg+m0STCj+ZssDcjrLzf4L05aKm2nGglMXSSPz0cH/rM+kVD9krNfldiMctURd4wjojW1fV0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@ampproject/remapping": { @@ -48,6 +311,30 @@ "node": ">=6.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-TMilPqXyii1AsiEii6l6ubRzbo76p6oshUSYPaKsmXDavyMLqjzVDkcp3pHp5ELMUNJHATcEOGxKTTsX9yYhGg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -430,13 +717,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -1797,9 +2084,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1840,6 +2127,57 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, "node_modules/@emnapi/core": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", @@ -1871,6 +2209,397 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esengine/ecs-framework": { "resolved": "packages/core", "link": true @@ -1891,6 +2620,62 @@ "resolved": "packages/network-shared", "link": true }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", + "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.13.0", + "@shikijs/langs": "^3.13.0", + "@shikijs/themes": "^3.13.0", + "@shikijs/types": "^3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", + "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/langs": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", + "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.13.0" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/themes": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", + "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.13.0" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/types": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", + "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", @@ -1901,6 +2686,51 @@ "node": ">=6.9.0" } }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.53", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.53.tgz", + "integrity": "sha512-8GEW5mshsPAZpVAJmkBG/niR2qn8t4U03Wmz6aSD9R4VMZKTECqbOxH3z4inA0JfZOoTvP4qoK9T2VXAx2Xg5g==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/json": { + "version": "2.2.388", + "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.388.tgz", + "integrity": "sha512-sNwZo4oDovAA7IT1XHfJiEP4fLialEFYALrGVQAblbflkICEJFnK6rUK0sHhvkkX2HUTNTMywOX+LG9AGVN3Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*", + "pathe": "^1.1.2" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.1", + "globals": "^15.15.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "mlly": "^1.7.4" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2367,6 +3197,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2387,10 +3228,11 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", @@ -3906,6 +4748,93 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@sigstore/bundle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", @@ -4175,6 +5104,16 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -4225,6 +5164,41 @@ "parse5": "^7.0.0" } }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -4315,6 +5289,13 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -4322,6 +5303,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -4346,6 +5334,291 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", + "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/shared": "3.5.22", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", + "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", + "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/compiler-core": "3.5.22", + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.19", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", + "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz", + "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz", + "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", + "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/runtime-core": "3.5.22", + "@vue/shared": "3.5.22", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz", + "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "vue": "3.5.22" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz", + "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -4471,6 +5744,32 @@ "node": ">=8" } }, + "node_modules/algoliasearch": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.39.0.tgz", + "integrity": "sha512-DzTfhUxzg9QBNGzU/0kZkxEV72TeA4MmPJ7RVfLnQwHNhhliPo7ynglEWJS791rNlLFoTyrKvkapwr/P3EXV9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.5.0", + "@algolia/client-abtesting": "5.39.0", + "@algolia/client-analytics": "5.39.0", + "@algolia/client-common": "5.39.0", + "@algolia/client-insights": "5.39.0", + "@algolia/client-personalization": "5.39.0", + "@algolia/client-query-suggestions": "5.39.0", + "@algolia/client-search": "5.39.0", + "@algolia/ingestion": "1.39.0", + "@algolia/monitoring": "1.39.0", + "@algolia/recommend": "5.39.0", + "@algolia/requester-browser-xhr": "5.39.0", + "@algolia/requester-fetch": "5.39.0", + "@algolia/requester-node-http": "5.39.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -4867,6 +6166,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/birpc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz", + "integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -5096,6 +6405,17 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5121,6 +6441,28 @@ "node": ">=10" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -5326,6 +6668,17 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -5378,6 +6731,13 @@ "typedarray": "^0.0.6" } }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -5563,6 +6923,22 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/core-js-compat": { "version": "3.45.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", @@ -5727,6 +7103,13 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, "node_modules/dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -5883,6 +7266,16 @@ "dev": true, "license": "ISC" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-indent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", @@ -5902,6 +7295,20 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -6038,6 +7445,13 @@ "dev": true, "license": "MIT" }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -6168,6 +7582,45 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -6312,6 +7765,13 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true, + "license": "MIT" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -6464,6 +7924,16 @@ "flat": "cli.js" } }, + "node_modules/focus-trap": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", + "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.2.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -6907,6 +8377,19 @@ "node": ">=10.13.0" } }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7012,6 +8495,51 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -7050,6 +8578,17 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -7540,6 +9079,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -8604,6 +10156,13 @@ "node": ">=6" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lerna": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/lerna/-/lerna-8.2.3.tgz", @@ -9015,6 +10574,16 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-json-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", @@ -9041,6 +10610,24 @@ "node": ">=8" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -9112,13 +10699,21 @@ "yallist": "^3.0.2" } }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-dir": { @@ -9188,6 +10783,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -9197,6 +10837,35 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -9385,6 +11054,100 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -9630,6 +11393,13 @@ "dev": true, "license": "ISC" }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -9664,6 +11434,13 @@ "dev": true, "license": "ISC" }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -9677,6 +11454,45 @@ "node": ">=10" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -9754,6 +11570,25 @@ "dev": true, "license": "ISC" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10171,6 +12006,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -10386,6 +12233,13 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pacote": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", @@ -10587,6 +12441,20 @@ "node": ">=4" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10594,10 +12462,11 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -10639,6 +12508,54 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", @@ -10653,6 +12570,17 @@ "node": ">=4" } }, + "node_modules/preact": { + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -10773,6 +12701,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/protobufjs": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", @@ -10832,6 +12771,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -10848,6 +12797,23 @@ } ] }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -11172,6 +13138,33 @@ "node": ">=4" } }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, "node_modules/regexpu-core": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", @@ -11319,6 +13312,13 @@ "node": ">= 4" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", @@ -11460,6 +13460,14 @@ "node": ">=v12.22.7" } }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -11525,6 +13533,23 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -11650,6 +13675,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -11660,6 +13695,17 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -11696,6 +13742,16 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -11804,6 +13860,21 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11874,6 +13945,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11904,6 +13988,13 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true, + "license": "MIT" + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -12128,6 +14219,13 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", @@ -12210,6 +14308,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -12403,6 +14512,43 @@ "dev": true, "license": "MIT" }, + "node_modules/typedoc": { + "version": "0.28.13", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", + "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.12.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.9.0.tgz", + "integrity": "sha512-9Uu4WR9L7ZBgAl60N/h+jqmPxxvnC9nQAlnnO/OujtG2ubjnKTVUFY1XDhcMY+pCqlX3N2HsQM2QTYZIU9tJuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.28.x" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -12417,6 +14563,20 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -12507,6 +14667,79 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-user-agent": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", @@ -12523,6 +14756,67 @@ "node": ">= 4.0.0" } }, + "node_modules/unplugin": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-icons": { + "version": "22.3.0", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-22.3.0.tgz", + "integrity": "sha512-Q7c2RoVUn4LzFADT0H/oT5ApJgiWW+xTK7D5/hi6gYtObmAuEE6ebyvejvfbinJL8tH4wanoNjkWcmlqEsTcXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/utils": "^3.0.1", + "debug": "^4.4.1", + "local-pkg": "^1.1.2", + "unplugin": "^2.3.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@svgx/core": "^1.0.1", + "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0", + "vue-template-compiler": "^2.6.12", + "vue-template-es2015-compiler": "^1.9.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@svgx/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + }, + "vue-template-es2015-compiler": { + "optional": true + } + } + }, "node_modules/upath": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", @@ -12636,6 +14930,160 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.20", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", + "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", + "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-sfc": "3.5.22", + "@vue/runtime-dom": "3.5.22", + "@vue/server-renderer": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -12683,6 +15131,13 @@ "node": ">=12" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -13040,9 +15495,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "packages/core": { "name": "@esengine/ecs-framework", - "version": "2.1.48", + "version": "2.1.49", "license": "MIT", "devDependencies": { "@babel/core": "^7.28.3", diff --git a/package.json b/package.json index 8654b7d6..bfc6cbd5 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "publish:all:dist": "npm run publish:core && npm run publish:math && npm run publish:network-shared && npm run publish:network-client && npm run publish:network-server", "publish:core": "cd packages/core && npm run publish:npm", "publish:core:patch": "cd packages/core && npm run publish:patch", - "publish:math": "cd packages/math && npm run publish:npm", + "publish:math": "cd packages/math && npm run publish:npm", "publish:math:patch": "cd packages/math && npm run publish:patch", "publish:network-shared": "cd packages/network-shared && npm run publish:npm", "publish:network-shared:patch": "cd packages/network-shared && npm run publish:patch", @@ -48,11 +48,17 @@ "publish:network-server": "cd packages/network-server && npm run publish:npm", "publish:network-server:patch": "cd packages/network-server && npm run publish:patch", "publish": "lerna publish", - "version": "lerna version" + "version": "lerna version", + "docs:dev": "vitepress dev docs", + "docs:build": "npm run docs:api && vitepress build docs", + "docs:preview": "vitepress preview docs", + "docs:api": "typedoc", + "docs:api:watch": "typedoc --watch" }, "author": "yhh", "license": "MIT", "devDependencies": { + "@iconify/json": "^2.2.388", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", @@ -66,7 +72,11 @@ "rollup-plugin-dts": "^6.2.1", "semver": "^7.6.3", "ts-jest": "^29.4.0", - "typescript": "^5.8.3" + "typedoc": "^0.28.13", + "typedoc-plugin-markdown": "^4.9.0", + "typescript": "^5.8.3", + "unplugin-icons": "^22.3.0", + "vitepress": "^1.6.4" }, "publishConfig": { "access": "public" diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 00000000..ac729065 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,63 @@ +{ + "entryPoints": ["./packages/core/src/index.ts"], + "out": "./docs/api", + "plugin": ["typedoc-plugin-markdown"], + "readme": "none", + "excludePrivate": true, + "excludeProtected": false, + "excludeInternal": false, + "includeVersion": true, + "sort": ["source-order"], + "kindSortOrder": [ + "Document", + "Project", + "Module", + "Namespace", + "Enum", + "EnumMember", + "Class", + "Interface", + "TypeAlias", + "Constructor", + "Property", + "Variable", + "Function", + "Accessor", + "Method", + "Parameter", + "TypeParameter", + "TypeLiteral", + "CallSignature", + "ConstructorSignature", + "IndexSignature", + "GetSignature", + "SetSignature" + ], + "categorizeByGroup": false, + "defaultCategory": "其他", + "categoryOrder": [ + "核心", + "ECS", + "组件", + "系统", + "工具", + "*" + ], + "searchInComments": true, + "cleanOutputDir": true, + "titleLink": "/", + "navigationLinks": { + "首页": "/", + "指南": "/guide/", + "示例": "/examples/" + }, + "sidebarLinks": { + "GitHub": "https://github.com/esengine/ecs-framework", + "NPM": "https://www.npmjs.com/package/@esengine/ecs-framework" + }, + "hideGenerator": true, + "githubPages": false, + "disableSources": false, + "name": "ECS Framework API", + "skipErrorChecking": true +} \ No newline at end of file