Compare commits

...

124 Commits

Author SHA1 Message Date
YHH
d798995876 Merge pull request #111 from esengine/issue-110-_Inject_装饰器在_Cocos_Creator_环境中不执行
扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器
2025-10-12 23:44:22 +08:00
YHH
43e6b7bf88 扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器 2025-10-12 23:39:32 +08:00
YHH
9253686de1 v2.2.3 2025-10-12 21:41:09 +08:00
YHH
7e7eae2d1a Merge pull request #109 from esengine/issue-108-world和scene进行了多次更新
Revert "Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰"
2025-10-12 21:39:35 +08:00
YHH
1924d979d6 Revert "Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰"
This reverts commit f2b9c5cc5a, reversing
changes made to 5f507532ed.
2025-10-12 21:38:53 +08:00
YHH
ed84394301 更新序列化文档 2025-10-12 19:02:17 +08:00
YHH
bb99cf5389 v2.2.2 2025-10-12 18:56:39 +08:00
YHH
2d0700f441 Merge pull request #107 from esengine/issue-106-类型定义声明返回_Buffer导致浏览器兼容问题
修复buffer再浏览器环境不兼容的问题
2025-10-12 18:53:53 +08:00
YHH
e3ead8a695 修复buffer再浏览器环境不兼容的问题 2025-10-12 18:49:20 +08:00
YHH
701f538e57 Merge pull request #104 from esengine/issue-103-WorldManager统一管理所有World实例_含默认World
Issue 103 world manager统一管理所有world实例 含默认world
2025-10-11 15:26:27 +08:00
YHH
bb3017ffc2 Merge remote-tracking branch 'origin/master' into issue-103-WorldManager统一管理所有World实例_含默认World
# Conflicts:
#	packages/core/src/Core.ts
#	packages/core/src/ECS/SceneManager.ts
2025-10-11 15:21:43 +08:00
YHH
f2b9c5cc5a Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰
统一World与Scene架构,SceneManager内部使用DefaultWorld
2025-10-11 15:16:52 +08:00
YHH
532a52acfc 统一的World管理路径 2025-10-11 15:14:37 +08:00
YHH
c19b5ae9a7 统一World与Scene架构,SceneManager内部使用DefaultWorld 2025-10-11 14:44:21 +08:00
YHH
5f507532ed 统一World与Scene架构,SceneManager内部使用DefaultWorld 2025-10-11 14:27:09 +08:00
YHH
6e48f22540 更新v2.2.1文档 2025-10-11 11:33:07 +08:00
YHH
66aa9f4f20 更新文档 2025-10-11 10:48:24 +08:00
YHH
62f895efe0 v2.2.1 2025-10-11 10:46:46 +08:00
YHH
4a060e1ce3 WorldManager 现在由 ServiceContainer 统一管理 2025-10-11 10:40:10 +08:00
YHH
a0177c9163 从 tslib 导入辅助函数 2025-10-11 10:36:59 +08:00
YHH
f45af34614 更新v2.2.0文档 2025-10-11 10:16:52 +08:00
YHH
14a8d755f0 PoolManager 现在由 ServiceContainer 统一管理 2025-10-11 09:38:16 +08:00
YHH
b67ab80c75 Merge pull request #93 from esengine/issue-80-插件系统
插件系统
2025-10-11 09:32:12 +08:00
YHH
ae71af856b 插件系统 2025-10-11 09:26:36 +08:00
YHH
279c1d9bc9 Merge pull request #92 from esengine/issue-82-组件引用完整性
组件引用完整性,升级到es2021使用weakref
2025-10-11 00:30:36 +08:00
YHH
9068a109b0 降级es2020,实现 WeakRef Polyfill 2025-10-11 00:25:10 +08:00
YHH
7850fc610c 组件引用完整性,升级到es2021使用weakref 2025-10-10 23:38:48 +08:00
YHH
536871d09b Merge pull request #88 from esengine/issue-76-依赖注入
依赖注入引入DI容器
2025-10-10 22:15:24 +08:00
YHH
1af2cf5f99 Scene 构造函数注入 PerformanceMonitor 2025-10-10 22:08:10 +08:00
YHH
b13132b259 依赖注入引入DI容器 2025-10-10 21:52:43 +08:00
YHH
a1a6970ea4 Merge pull request #87 from esengine/issue-73-Core类职责过重需要进行拆分
新增ServiceContainer服务容器, 所有服务统一实现 IService 接口
2025-10-10 18:17:55 +08:00
YHH
41bbe23404 新增ServiceContainer服务容器, 所有服务统一实现 IService 接口 2025-10-10 18:13:28 +08:00
YHH
1d2a3e283e Merge pull request #86 from esengine/issue-75-组件存储策略不统一
组件存储策略不统一
2025-10-10 16:38:06 +08:00
YHH
62d7521384 移除 Entity._localComponents/强制Entity必须属于Scene/简化组件操作逻辑 2025-10-10 16:31:43 +08:00
YHH
bf14b59a28 空查询应该返回所有实体 2025-10-10 11:49:06 +08:00
YHH
0a0f64510f 更新测试用例 2025-10-10 10:58:52 +08:00
YHH
9445c735c3 对象池内存管理优化 2025-10-10 10:16:44 +08:00
YHH
7339e7ecec 新增scenemanager,重构core类减少多世界造成的性能压力 2025-10-09 23:33:11 +08:00
YHH
79f7c89e23 修复再不同环境下buffer兼容性问题 2025-10-09 17:44:15 +08:00
YHH
e724e5a1ba 更新demo 2025-10-09 17:43:46 +08:00
YHH
fdaa94a61d v2.1.52 2025-10-09 17:26:07 +08:00
YHH
6af0074c36 导航增加序列化章节 2025-10-09 17:18:14 +08:00
YHH
97a69fed09 增量序列化支持二进制 2025-10-09 17:14:18 +08:00
YHH
959879440d 更新序列化文档 2025-10-09 14:18:43 +08:00
YHH
fd1bbb0e00 新增增量序列化 2025-10-09 12:30:04 +08:00
YHH
072e68cf43 修复序列化ci测试 2025-10-08 20:58:07 +08:00
YHH
610232e6b0 core库demo更新 2025-10-08 20:52:31 +08:00
YHH
69c46f32eb 支持二进制序列化 2025-10-08 20:42:55 +08:00
YHH
06b3f92007 场景自定义序列化支持 2025-10-08 18:34:15 +08:00
YHH
c631290049 对query/entity进行安全类型扩展 2025-10-08 13:13:23 +08:00
YHH
f41c1a3ca3 冗余测试合并 2025-10-08 12:04:13 +08:00
YHH
bd6ba84087 Merge pull request #72 from 0MirageTank0/master
优化掩码数据结构,新增BitMaskHashMap类型,支持无限数量原型
2025-10-05 09:13:30 +08:00
MirageTank
1512409eb3 优化位掩码工具的输出格式
- 十六进制不再输出无意义的前导0符号
- 修正部分测试单元检测逻辑
2025-10-04 13:16:51 +08:00
MirageTank
bcb5feeb1c 实现高性能 BitMaskHashMap 并优化ArchetypeSystem
- 引入 BitMaskHashMap 类,使用双层 MurmurHash3 哈希算法提升查找性能
- 替换 ArchetypeSystem 中原有的嵌套 Map 结构为 BitMaskHashMap,支持任意数量的原型
- 验证在十万级连续键值下无哈希冲突,确保生产环境可用性
2025-10-04 10:26:19 +08:00
MirageTank
da8b7cf601 重构位掩码数据结构,修复部分方法未考虑扩展位的问题
- 所有操作均考虑扩展位、扩展长度不一致的使用场景,无感扩容掩码位
- 使用定长数组存储高低位,遍历友好,为高效哈希计算提供结构支持
- 补充相应单元测试,覆盖所有方法及分支
2025-10-03 16:55:07 +08:00
YHH
316527c459 更新实体文档(components为只读属性) 2025-10-01 00:15:19 +08:00
YHH
da70818b22 v2.1.51 2025-10-01 00:11:14 +08:00
YHH
5ea3b72b2b 使用BitMask64Data.segments扩展ComponentRegistry 2025-09-30 23:58:52 +08:00
YHH
632864b361 单一数据源 + 懒加载缓存 2025-09-30 23:37:47 +08:00
YHH
952247def0 重构 Component基类违反ECS纯粹性问题 2025-09-30 22:26:44 +08:00
YHH
51debede52 移除废弃的文件 2025-09-30 20:44:08 +08:00
YHH
ce7b731bcf Merge pull request #71 from esengine/fix/issue-70-component-mask-string
Fix/issue 70 component mask string
2025-09-30 18:19:11 +08:00
YHH
86e2dc8fdb 完全删除 ComponentIndexManager 2025-09-30 18:12:49 +08:00
YHH
78047134c2 完全移除 byComponentType 2025-09-30 18:03:03 +08:00
YHH
125a1686ab 完全删除 byComponentType 2025-09-30 17:54:22 +08:00
YHH
d542ac48b8 重构byComponentType为lazy cache模式 2025-09-30 17:07:13 +08:00
YHH
1ac0227c90 移除bymask 2025-09-30 17:01:49 +08:00
YHH
a5e70bcd99 修复QuerySystem组件掩码索引使用toString()返回[object Object]的问题 #70 2025-09-30 16:38:32 +08:00
YHH
38763de7f6 BitMask64Segment 独立类型 2025-09-30 15:56:28 +08:00
YHH
db73b077c5 支持分层 BitMask 自动扩容,避免用户超过组件后报错问题 2025-09-30 15:38:50 +08:00
YHH
0969d09da1 优化ArchetypeSystem的AND指令 2025-09-30 15:08:33 +08:00
YHH
a07108a431 修复soa测试用例 2025-09-30 13:43:12 +08:00
YHH
6693b56ab8 Merge pull request #69 from 0MirageTank0/master
优化ArchetypeSystem性能
2025-09-30 13:31:11 +08:00
MirageTank
a7349bd360 补充TypeDecorators文件对ComponentType参数类型的修改 2025-09-30 12:53:23 +08:00
MirageTank
e92c0040b5 重构原型系统以提升性能
- 将原型ID类型从字符串改为BitMask64Data,避免拼接字符串的性能损耗,降低内存占用.
- 原型ID生成不再依赖组件名称.
2025-09-30 11:57:31 +08:00
MirageTank
f448fa48c4 重构组件类型管理器相关函数参数以使用ComponentType,而不是Component. 2025-09-30 11:42:01 +08:00
YHH
aa33cad4fa 扩展typedarray存储系统,允许自动类型推断@AutoTyped 2025-09-30 11:00:05 +08:00
YHH
d0cb7d5359 v2.1.50 2025-09-30 09:59:46 +08:00
YHH
90153b98fe 更新 2025-09-30 09:51:02 +08:00
YHH
8c4e8d523e 更新微信小游戏worker文档 2025-09-30 09:37:30 +08:00
YHH
90ad4b3ec4 抽象worker接口,避免污染项目 2025-09-29 18:15:47 +08:00
YHH
62bc6b547e 支持wx/browser的worker(由于wx限制默认不开启worker) 2025-09-29 13:21:08 +08:00
YHH
be11060674 archetypesystem只负责原型管理,querysytem负责查询和缓存 2025-09-29 11:01:39 +08:00
YHH
d62bf9f7f9 entitysytem的logger返回类型更改,需要重写实现getLoggerName 2025-09-29 09:53:13 +08:00
YHH
61fcd52c65 移除eventhandler装饰器 2025-09-29 09:35:02 +08:00
YHH
2947ddeb64 不限制ci的超时时间 2025-09-29 09:04:53 +08:00
YHH
d9b752c180 修复workerentitysysten和entitysytem的logger重复问题 2025-09-29 00:01:59 +08:00
YHH
b82891caee 装饰器事件自动清理 2025-09-28 23:58:43 +08:00
YHH
05f04ef37e 允许用户自定义核心数量 2025-09-28 23:35:25 +08:00
YHH
66dc9780b9 使用coi-serviceworker用于无法控制标头的情况 2025-09-28 21:11:17 +08:00
YHH
d48b22c656 更新demo界面 2025-09-28 21:00:28 +08:00
YHH
727b1864eb 更新demo 2025-09-28 20:49:00 +08:00
YHH
de3bfd7551 新增禁用sab功能 2025-09-28 20:41:23 +08:00
YHH
dedb91379f worker系统不支持sab回退到普通worker 2025-09-28 20:22:06 +08:00
YHH
1dfcd008aa 新增worker-system文档及源码示例 2025-09-28 20:03:29 +08:00
YHH
cf2dc91af6 更新worker-demo 2025-09-28 18:29:32 +08:00
YHH
a66f80a766 新增WorkerEntitySystem系统 2025-09-28 17:36:36 +08:00
YHH
f4e49c316e getWorldManager允许传入可选配置用于覆盖默认配置 2025-09-28 15:52:46 +08:00
YHH
d1cd72bbb2 修复QuerySystem中addEntities没有更新componentIndexManager/archetypeSystem索引 2025-09-28 15:32:54 +08:00
YHH
6178851def 修复QuerySystem/ArchetypeSystem未响应实体增删Component的问题 2025-09-28 15:23:59 +08:00
YHH
945f772c30 只在有package改动的时候触发ci流程 2025-09-28 12:32:35 +08:00
YHH
b546c9c712 vitepress修改为githubpage路径 2025-09-28 12:29:43 +08:00
YHH
413ce93b31 更新文档 2025-09-28 12:26:51 +08:00
YHH
cffe32911d v2.1.49 2025-09-28 10:36:01 +08:00
YHH
4f651eb42e 优化querysystem系统(减少数组拷贝)
移除dirtytracksystem
2025-09-28 09:40:36 +08:00
YHH
6da1585b6b entitysystem实用帧缓存和长期缓存策略 2025-09-26 18:30:49 +08:00
YHH
b988e81a1b 数学库新增Vector3 2025-09-26 17:45:52 +08:00
YHH
1a1c1087d2 标记组件不符合规范的废弃属性 2025-09-26 17:45:26 +08:00
YHH
1a1549230f 更新references库 2025-09-26 13:06:39 +08:00
YHH
64ea53eba1 系统添加缓存实体机制避免频繁开销 2025-09-26 10:50:31 +08:00
YHH
5e052a7e7d 默认不增强事件,避免事件性能开销 2025-09-26 10:28:00 +08:00
YHH
cf9ea495d0 移除过时类并标记组件和实体的update为过时方法 2025-09-26 10:09:23 +08:00
YHH
9603c6423b 系统避免在同一帧内进行多次query操作 2025-09-26 09:45:22 +08:00
YHH
457eef585e Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-09-26 09:38:57 +08:00
YHH
1ade449c4d 控制实体update默认不更新 2025-09-26 09:38:51 +08:00
YHH
aa9d73a810 Merge pull request #68 from snakenjq/master
场景移除时, 清理系统
2025-09-24 18:25:55 +08:00
SNDA\niujiaqun.nathan
cc266a7ba9 修改错误方法调用 2025-09-24 18:18:48 +08:00
SNDA\niujiaqun.nathan
d8ea324018 场景移除时, 清理系统 2025-09-24 18:14:22 +08:00
YHH
60566e8d78 v2.1.48 2025-09-24 16:05:22 +08:00
YHH
306d2994dc Merge pull request #67 from foxling/fix/global-log-level
修复 setGlobalLogLevel 不影响新创建 logger 的问题
2025-09-24 15:54:28 +08:00
YHH
e6a8791fc3 系统中提供更安全的事件监听器方法(避免内存泄露) 2025-09-24 11:03:37 +08:00
YHH
6cbbc06998 规范jsdoc注释 2025-09-24 10:45:33 +08:00
YHH
0b4244fd8e 修复循环依赖问题 2025-09-24 10:20:36 +08:00
LING YE
367ddfbf8a 修复 setGlobalLogLevel 方法,使其可以在后续新建的 Logger 实例中生效 2025-09-22 15:28:29 +08:00
196 changed files with 47685 additions and 17620 deletions

View File

@@ -3,8 +3,22 @@ name: CI
on:
push:
branches: [ master, main, develop ]
paths:
- 'packages/**'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
- 'jest.config.*'
- '.github/workflows/ci.yml'
pull_request:
branches: [ master, main, develop ]
paths:
- 'packages/**'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
- 'jest.config.*'
- '.github/workflows/ci.yml'
jobs:
test:

67
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Deploy Documentation
on:
push:
branches: [master]
paths:
- 'docs/**'
- 'packages/**'
- 'typedoc.json'
- 'package.json'
- '.github/workflows/docs.yml'
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

2
.gitignore vendored
View File

@@ -55,6 +55,8 @@ pnpm-lock.yaml
# 文档生成
docs/api/
docs/build/
docs/.vitepress/cache/
docs/.vitepress/dist/
# 备份文件
*.bak

451
README.md
View File

@@ -1,180 +1,21 @@
# ECS Framework
[![Typing SVG](https://readme-typing-svg.demolab.com?font=Fira+Code&weight=600&size=22&pause=1000&color=F75C7E&center=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) 框架,专为游戏开发设计。
## 项目特色
<div align="center">
[![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)
</div>
## 架构原理
ECS Framework 采用多World + 多Scene的现代化架构设计
```mermaid
graph TD
subgraph Main["🎮 ECS Framework - 多World・多Scene架构"]
direction TB
subgraph CoreLayer["⚙️ 核心层 (Core Foundation)"]
direction LR
Core["🔧 <b>Core</b><br/>📋 生命周期管理<br/>⚙️ 配置系统<br/>🔗 平台兼容"]
Registry["📝 <b>ComponentRegistry</b><br/>🏷️ 类型注册<br/>✨ 装饰器支持<br/>🔒 类型安全"]
Pool["🔢 <b>IdentifierPool</b><br/>🆔 实体ID分配<br/>♻️ ID回收<br/>📊 BigInt兼容"]
PoolMgr["♻️ <b>PoolManager</b><br/>🎯 对象池<br/>⚡ 内存优化<br/>📈 性能提升"]
EventBus["📡 <b>EventBus</b><br/>🔄 事件系统<br/>⚡ 异步/同步<br/>🎭 类型安全"]
end
subgraph WorldLayer["🌍 世界管理层 (World Management)"]
direction TB
WorldMgr["🗺️ <b>WorldManager</b><br/>🚀 多World调度<br/>📊 资源管理<br/>🔍 统计监控<br/>🧹 自动清理"]
subgraph WorldsContainer["多World容器"]
direction LR
World1["🌐 <b>GameWorld</b><br/>🎮 游戏逻辑<br/>🌟 全局系统<br/>🔄 跨Scene业务"]
World2["🌐 <b>UIWorld</b><br/>🎨 界面管理<br/>⚡ 独立更新<br/>🔒 资源隔离"]
end
GlobalSys["🎭 <b>Global Systems</b><br/>🌐 NetworkSync<br/>👥 PlayerMgmt<br/>📡 跨Scene通信"]
end
subgraph SceneLayer["🎬 场景层 (Scene Management)"]
direction LR
Scene1["🎯 <b>BattleScene</b><br/>⚔️ 实体管理<br/>🎪 系统调度<br/>⚡ 高性能处理"]
Scene2["🎯 <b>MenuScene</b><br/>🎨 界面逻辑<br/>🔄 生命周期<br/>💾 状态管理"]
Scene3["🎯 <b>UIScene</b><br/>📦 组件存储<br/>🔍 查询引擎<br/>🎭 交互处理"]
end
subgraph ECLayer["🤖 实体组件层 (Entity-Component System)"]
direction TB
subgraph EntityMgmt["📦 实体管理 (Entity Management)"]
direction LR
EntityMgr["👥 <b>EntityManager</b><br/>📋 集合管理<br/>🌳 层次结构<br/>⚡ 高效操作"]
Entities["🎭 <b>Entities</b><br/>👤 Player<br/>👹 Enemy<br/>💥 Bullet<br/>🎯 轻量容器"]
end
subgraph ComponentStore["🧩 组件存储 (Component Storage)"]
direction LR
Storage["💾 <b>ComponentStorage</b><br/>📊 SoA模式<br/>📚 AoS模式<br/>⚡ 内存优化"]
StorageMgr["🗄️ <b>StorageManager</b><br/>🏷️ 类型管理<br/>🔄 脏标记<br/>📈 性能监控"]
Components["🎲 <b>Components</b><br/>📍 Position<br/>🏃 Velocity<br/>❤️ Health<br/>📊 纯数据"]
end
end
subgraph SystemLayer["⚡ 系统层 (System Processing)"]
direction TB
subgraph EntitySys["🔄 实体系统 (Entity Systems)"]
direction LR
EntitySystems["🎪 <b>EntitySystems</b><br/>🏃 MovementSystem<br/>🎨 RenderSystem<br/>🧠 AISystem<br/>⚡ 业务逻辑"]
Processors["📋 <b>EntityProcessors</b><br/>🎯 调度管理<br/>📊 优先级<br/>⚡ 批量处理"]
end
end
subgraph QueryLayer["🔍 查询优化层 (Query & Optimization)"]
direction LR
Matcher["🎯 <b>Matcher</b><br/>✅ withAll<br/>🔄 withAny<br/>❌ withNone<br/>🌊 流式API<br/>💾 智能缓存"]
QuerySys["🔎 <b>QuerySystem</b><br/>⚡ 实时查询<br/>📦 批量优化<br/>🔄 自动更新"]
Archetype["🏗️ <b>ArchetypeSystem</b><br/>📊 组件分组<br/>🎯 原型缓存<br/>💻 BitSet优化"]
end
subgraph DebugLayer["📊 监控调试层 (Debug & Monitoring)"]
direction LR
Debug["🐛 <b>DebugManager</b><br/>🌐 WebSocket调试<br/>🎮 Cocos Creator插件<br/>📸 内存快照"]
Perf["📈 <b>PerformanceMonitor</b><br/>📊 性能统计<br/>⚠️ 阈值告警<br/>📱 实时监控"]
Logger["📋 <b>Logger</b><br/>📊 分级日志<br/>🎨 彩色输出<br/>🔧 自定义处理器"]
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存储和批量处理
- **多线程计算** - Worker系统支持真正的并行处理充分利用多核CPU性能
- **类型安全** - 完整的TypeScript支持编译时类型检查
- **现代架构** - 支持多World、多Scene的分层架构设计
- **开发友好** - 内置调试工具和性能监控
- **跨平台** - 支持Cocos Creator、Laya引擎和Web平台
## 安装
@@ -184,271 +25,103 @@ 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 存储优化,支持大规模实体处理
- **多线程支持** - Worker系统实现真正的并行计算充分利用多核CPU
- **多场景** - 支持 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); // 排除这些
```
- [Worker系统演示](https://esengine.github.io/ecs-framework/demos/worker-system/) - 多线程物理系统演示,展示高性能并行计算
- [割草机演示](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)
[MIT](LICENSE) © 2025 ECS Framework

224
docs/.vitepress/config.mjs Normal file
View File

@@ -0,0 +1,224 @@
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
})
],
server: {
fs: {
allow: ['..']
},
middlewareMode: false,
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin'
}
}
},
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: '示例',
items: [
{ text: 'Worker系统演示', link: '/examples/worker-system-demo' },
{ 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',
items: [
{ text: 'Worker系统 (多线程)', link: '/guide/worker-system' }
]
},
{
text: '场景管理 (Scene)',
link: '/guide/scene',
items: [
{ text: 'SceneManager', link: '/guide/scene-manager' },
{ text: 'WorldManager', link: '/guide/world-manager' }
]
},
{ text: '序列化系统 (Serialization)', link: '/guide/serialization' },
{ text: '事件系统 (Event)', link: '/guide/event-system' },
{ text: '时间和定时器 (Time)', link: '/guide/time-and-timers' },
{ text: '日志系统 (Logger)', link: '/guide/logging' }
]
},
{
text: '高级特性',
collapsed: false,
items: [
{ text: '服务容器 (Service Container)', link: '/guide/service-container' },
{ text: '插件系统 (Plugin System)', link: '/guide/plugin-system' }
]
},
{
text: '平台适配器',
link: '/guide/platform-adapter',
collapsed: false,
items: [
{ text: '浏览器适配器', link: '/guide/platform-adapter/browser' },
{ text: '微信小游戏适配器', link: '/guide/platform-adapter/wechat-minigame' },
{ text: 'Node.js适配器', link: '/guide/platform-adapter/nodejs' }
]
}
],
'/examples/': [
{
text: '示例',
items: [
{ text: '示例概览', link: '/examples/' },
{ text: 'Worker系统演示', link: '/examples/worker-system-demo' }
]
}
],
'/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: '/ecs-framework/',
cleanUrls: true,
markdown: {
lineNumbers: true,
theme: {
light: 'github-light',
dark: 'github-dark'
}
}
})

View File

@@ -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学习之旅吧

View File

@@ -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<string, number> = 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<string, number> {
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 {
// 方形碰撞检测
}
}
```
遵循这些原则,你就能设计出高质量、易维护的组件系统!

View File

@@ -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<T>(eventType: string, data: T): void;
emitAsync<T>(eventType: string, data: T): Promise<void>;
on<T>(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<T>**
```typescript
type ComponentType<T extends IComponent = IComponent> = new (...args: any[]) => T;
```
- 用于类型安全的组件操作
- 支持泛型约束
- 广泛用于实体和查询系统
### 设计原则
#### 1. 接口简化原则
- 只保留实际使用的接口
- 移除了未使用的复杂接口如IEntityManager、IEntityQueryBuilder等
- 减少认知负担,提高开发效率
#### 2. 实现灵活性原则
- 接口作为类型约束而非强制实现
- 允许具体类有更丰富的实现
- 保持向后兼容性
#### 3. 类型安全原则
- 编译时类型检查
- 泛型支持提供精确的类型推断
- 事件系统的完整类型安全
### 使用指南
#### 在项目中使用接口
```typescript
// 作为类型约束
function processComponent<T extends IComponent>(component: T) {
if (component.enabled) {
component.update();
}
}
// 作为参数类型
function registerSystem(system: ISystemBase) {
scene.addEntityProcessor(system);
}
// 作为泛型约束
function getComponent<T extends IComponent>(type: ComponentType<T>): 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个冗余接口** - 移除了所有未使用的接口定义
- **完整的类型覆盖** - 为所有主要功能提供类型支持
这种设计确保了框架的类型安全性,同时保持了代码的简洁性和可维护性。

View File

@@ -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<Scene>('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<Bullet>(() => 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模式**:适合多人游戏服务器、复杂应用、需要场景隔离的项目
框架采用融合设计,确保向后兼容性的同时提供强大的扩展能力。通过合理使用这些核心概念和优化功能,可以构建出高性能、结构清晰、易于维护的游戏代码。

View File

@@ -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: 不可以。实体与场景绑定,需要在新场景中重新创建。

View File

@@ -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可以构建高性能的游戏系统。记住要及时清理无用实体缓存频繁查询的结果并使用合适的查询方法来优化性能。

View File

@@ -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<PlayerDiedEvent>('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<PlayerDiedEvent>('player:died', (data) => {
console.log('This will only be called once');
});
```
### 异步事件处理
```typescript
// 异步事件监听
eventBus.onAsync<PlayerDiedEvent>('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<IEntityEventData>(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<IComponentEventData>(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<ISystemEventData>(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<IPerformanceEventData>(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<IEntityEventData>(ECSEventType.ENTITY_CREATED, (data) => {
// 只处理玩家实体
if (data.entityTag === 'player') {
handlePlayerCreated(data);
}
});
// 更好的方式:使用具体的事件类型
eventBus.on<IEntityEventData>('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<K extends keyof GameEvents>(eventType: K, data: GameEvents[K]) {
this.eventBus.emit(eventType, data);
}
on<K extends keyof GameEvents>(
eventType: K,
handler: (data: GameEvents[K]) => void
) {
return this.eventBus.on(eventType, handler);
}
}
```
### 3. 错误处理
```typescript
// 在事件处理器中添加错误处理
eventBus.on<IEntityEventData>(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类型支持
- **高性能**:批处理、缓存和优化机制
- **易用性**:装饰器、预定义事件类型
- **可扩展**:自定义事件类型和验证
- **调试友好**:详细的统计信息和调试模式
通过合理使用事件系统,可以实现松耦合的模块化架构,提高代码的可维护性和扩展性。

23
docs/examples/index.md Normal file
View File

@@ -0,0 +1,23 @@
# 示例
这里展示了ECS Framework的各种使用示例通过实际的演示帮助您理解框架的功能和最佳实践。
## 🎮 互动演示
### [Worker系统演示](./worker-system-demo)
- **功能**: 展示Worker多线程物理计算和渲染优化
- **特性**: 1000+粒子实时物理模拟、碰撞检测、性能对比
- **技术点**: SharedArrayBuffer、Canvas 2D优化、实体生命周期管理
## 🔗 外部示例
### [割草机演示](https://github.com/esengine/lawn-mower-demo)
- **平台**: Cocos Creator 3.x
- **功能**: 完整的游戏演示项目
- **特性**: 展示ECS架构在实际游戏项目中的应用
## 📚 更多资源
- [快速开始指南](/guide/getting-started)
- [核心概念](/guide/)
- [API文档](/api/README)

View File

@@ -0,0 +1,13 @@
# Worker系统演示
这是一个展示ECS框架Worker系统功能的交互式演示。
## 在线演示
<div style="text-align: center; margin: 30px 0;">
<a href="/ecs-framework/demos/worker-system/index.html" target="_blank" style="display: inline-block; padding: 15px 30px; background: #4a9eff; color: white; text-decoration: none; border-radius: 8px; font-weight: bold; font-size: 16px; box-shadow: 0 4px 8px rgba(74, 158, 255, 0.3); transition: all 0.3s ease;">
🚀 打开Worker系统演示
</a>
</div>
> **注意**: 演示将在新窗口中打开展示完整的Worker系统功能包括实体管理、物理模拟和性能监控。

View File

@@ -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) - 学习高性能查询系统的详细用法

658
docs/guide/component.md Normal file
View File

@@ -0,0 +1,658 @@
# 组件系统
在 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} 已添加实体ID: ${this.entityId}`);
this.resource = new SomeResource();
}
/**
* 组件从实体移除时调用
* 用于清理资源、断开引用等
*/
onRemovedFromEntity(): void {
console.log(`组件 ${this.constructor.name} 已移除`);
if (this.resource) {
this.resource.cleanup();
this.resource = null;
}
}
}
```
## 组件与实体的关系
组件存储了所属实体的ID (`entityId`)而不是直接引用实体对象。这是ECS数据导向设计的体现避免了循环引用。
在实际使用中,**应该在 System 中处理实体和组件的交互**,而不是在组件内部:
```typescript
@ECSComponent('Health')
class Health extends Component {
current: number;
max: number;
constructor(max: number = 100) {
super();
this.max = max;
this.current = max;
}
isDead(): boolean {
return this.current <= 0;
}
}
@ECSComponent('Damage')
class Damage extends Component {
value: number;
constructor(value: number) {
super();
this.value = value;
}
}
// 推荐:在 System 中处理逻辑
class DamageSystem extends EntitySystem {
constructor() {
super(new Matcher().all(Health, Damage));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health)!;
const damage = entity.getComponent(Damage)!;
health.current -= damage.value;
if (health.isDead()) {
entity.destroy();
}
// 应用伤害后移除 Damage 组件
entity.removeComponent(damage);
}
}
}
```
## 组件属性
每个组件都有一些内置属性:
```typescript
@ECSComponent('ExampleComponent')
class ExampleComponent extends Component {
someData: string = "example";
onAddedToEntity(): void {
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
console.log(`所属实体ID: ${this.entityId}`); // 所属实体的ID
}
}
```
如果需要访问实体对象,应该在 System 中进行:
```typescript
class ExampleSystem extends EntitySystem {
constructor() {
super(new Matcher().all(ExampleComponent));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const comp = entity.getComponent(ExampleComponent)!;
console.log(`实体名称: ${entity.name}`);
console.log(`组件数据: ${comp.someData}`);
}
}
}
```
## 复杂组件示例
### 状态机组件
```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. 引用其他实体
当组件需要关联其他实体时(如父子关系、跟随目标等),**推荐方式是存储实体ID**,然后在 System 中查找:
```typescript
@ECSComponent('Follower')
class Follower extends Component {
targetId: number;
followDistance: number = 50;
constructor(targetId: number) {
super();
this.targetId = targetId;
}
}
// 在 System 中查找目标实体并处理逻辑
class FollowerSystem extends EntitySystem {
constructor() {
super(new Matcher().all(Follower, Position));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const follower = entity.getComponent(Follower)!;
const position = entity.getComponent(Position)!;
// 通过场景查找目标实体
const target = entity.scene?.findEntityById(follower.targetId);
if (target) {
const targetPos = target.getComponent(Position);
if (targetPos) {
// 跟随逻辑
const dx = targetPos.x - position.x;
const dy = targetPos.y - position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > follower.followDistance) {
// 移动靠近目标
}
}
}
}
}
}
```
这种方式的优势:
- 组件保持简单,只存储基本数据类型
- 符合数据导向设计
- 在 System 中统一处理查找和逻辑
- 易于理解和维护
**避免在组件中直接存储实体引用**
```typescript
// 错误示范:直接存储实体引用
@ECSComponent('BadFollower')
class BadFollower extends Component {
target: Entity; // 实体销毁后仍持有引用,可能导致内存泄漏
}
```
## 高级特性
### EntityRef 装饰器 - 自动引用追踪
框架提供了 `@EntityRef` 装饰器用于**特殊场景**下安全地存储实体引用。这是一个高级特性,一般情况下推荐使用存储ID的方式。
#### 什么时候需要 EntityRef
在以下场景中,`@EntityRef` 可以简化代码:
1. **父子关系**: 需要在组件中直接访问父实体或子实体
2. **复杂关联**: 实体之间有多个引用关系
3. **频繁访问**: 需要在多处访问引用的实体,使用ID查找会有性能开销
#### 核心特性
`@EntityRef` 装饰器通过 **ReferenceTracker** 自动追踪引用关系:
- 当被引用的实体销毁时,所有指向它的 `@EntityRef` 属性自动设为 `null`
- 防止跨场景引用(会输出警告并拒绝设置)
- 防止引用已销毁的实体(会输出警告并设为 `null`)
- 使用 WeakRef 避免内存泄漏(自动GC支持)
- 组件移除时自动清理引用注册
#### 基本用法
```typescript
import { Component, ECSComponent, EntityRef, Entity } from '@esengine/ecs-framework';
@ECSComponent('Parent')
class ParentComponent extends Component {
@EntityRef()
parent: Entity | null = null;
}
// 使用示例
const scene = new Scene();
const parent = scene.createEntity('Parent');
const child = scene.createEntity('Child');
const comp = child.addComponent(new ParentComponent());
comp.parent = parent;
console.log(comp.parent); // Entity { name: 'Parent' }
// 当 parent 被销毁时comp.parent 自动变为 null
parent.destroy();
console.log(comp.parent); // null
```
#### 多个引用属性
一个组件可以有多个 `@EntityRef` 属性:
```typescript
@ECSComponent('Combat')
class CombatComponent extends Component {
@EntityRef()
target: Entity | null = null;
@EntityRef()
ally: Entity | null = null;
@EntityRef()
lastAttacker: Entity | null = null;
}
// 使用示例
const player = scene.createEntity('Player');
const enemy = scene.createEntity('Enemy');
const npc = scene.createEntity('NPC');
const combat = player.addComponent(new CombatComponent());
combat.target = enemy;
combat.ally = npc;
// enemy 销毁后,只有 target 变为 nullally 仍然有效
enemy.destroy();
console.log(combat.target); // null
console.log(combat.ally); // Entity { name: 'NPC' }
```
#### 安全检查
`@EntityRef` 提供了多重安全检查:
```typescript
const scene1 = new Scene();
const scene2 = new Scene();
const entity1 = scene1.createEntity('Entity1');
const entity2 = scene2.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
// 跨场景引用会失败
comp.parent = entity2; // 输出错误日志comp.parent 为 null
console.log(comp.parent); // null
// 引用已销毁的实体会失败
const entity3 = scene1.createEntity('Entity3');
entity3.destroy();
comp.parent = entity3; // 输出警告日志comp.parent 为 null
console.log(comp.parent); // null
```
#### 实现原理
`@EntityRef` 使用以下机制实现自动引用追踪:
1. **ReferenceTracker**: Scene 持有一个引用追踪器,记录所有实体引用关系
2. **WeakRef**: 使用弱引用存储组件,避免循环引用导致内存泄漏
3. **属性拦截**: 通过 `Object.defineProperty` 拦截 getter/setter
4. **自动清理**: 实体销毁时,ReferenceTracker 遍历所有引用并设为 null
```typescript
// 简化的实现原理
class ReferenceTracker {
// entityId -> 引用该实体的所有组件记录
private _references: Map<number, Set<{ component: WeakRef<Component>, propertyKey: string }>>;
// 实体销毁时调用
clearReferencesTo(entityId: number): void {
const records = this._references.get(entityId);
if (records) {
for (const record of records) {
const component = record.component.deref();
if (component) {
// 将组件的引用属性设为 null
(component as any)[record.propertyKey] = null;
}
}
this._references.delete(entityId);
}
}
}
```
#### 性能考虑
`@EntityRef` 会带来一些性能开销:
- **写入开销**: 每次设置引用时需要更新 ReferenceTracker
- **内存开销**: ReferenceTracker 需要维护引用映射表
- **销毁开销**: 实体销毁时需要遍历所有引用并清理
对于大多数场景,这些开销是可以接受的。但如果有**大量实体和频繁的引用变更**,存储ID可能更高效。
#### 最佳实践
```typescript
// 推荐:适合使用 @EntityRef 的场景 - 父子关系
@ECSComponent('Transform')
class Transform extends Component {
@EntityRef()
parent: Entity | null = null;
position: { x: number, y: number } = { x: 0, y: 0 };
// 可以直接访问父实体的组件
getWorldPosition(): { x: number, y: number } {
if (!this.parent) {
return { ...this.position };
}
const parentTransform = this.parent.getComponent(Transform);
if (parentTransform) {
const parentPos = parentTransform.getWorldPosition();
return {
x: parentPos.x + this.position.x,
y: parentPos.y + this.position.y
};
}
return { ...this.position };
}
}
// 不推荐:不适合使用 @EntityRef 的场景 - 大量动态目标
@ECSComponent('AITarget')
class AITarget extends Component {
@EntityRef()
target: Entity | null = null; // 如果目标频繁变化用ID更好
updateCooldown: number = 0;
}
// 推荐这种场景用ID更好
@ECSComponent('AITarget')
class AITargetBetter extends Component {
targetId: number | null = null; // 存储ID
updateCooldown: number = 0;
}
```
#### 调试支持
ReferenceTracker 提供了调试接口:
```typescript
// 查看某个实体被哪些组件引用
const references = scene.referenceTracker.getReferencesTo(entity.id);
console.log(`实体 ${entity.name}${references.length} 个组件引用`);
// 获取完整的调试信息
const debugInfo = scene.referenceTracker.getDebugInfo();
console.log(debugInfo);
```
#### 总结
- **推荐做法**: 大部分情况使用存储ID + System查找的方式
- **EntityRef 适用场景**: 父子关系、复杂关联、组件内需要直接访问引用实体的场景
- **核心优势**: 自动清理、防止悬空引用、代码更简洁
- **注意事项**: 有性能开销,不适合大量动态引用的场景
组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。

288
docs/guide/entity.md Normal file
View File

@@ -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("玩家有位置组件");
}
// 获取所有组件实例(只读属性)
const allComponents = player.components; // readonly 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 架构的核心概念之一,理解如何正确使用实体将帮助你构建高效、可维护的游戏代码。

551
docs/guide/event-system.md Normal file
View File

@@ -0,0 +1,551 @@
# 事件系统
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<void> {
console.log(`开始保存游戏到槽位 ${data.saveSlot}`);
// 模拟异步保存操作
await this.saveGameData(data.saveSlot);
console.log('游戏保存完成');
}
private async onLoadData(data: { url: string }): Promise<void> {
try {
const response = await fetch(data.url);
const gameData = await response.json();
// 处理加载的数据
} catch (error) {
console.error('数据加载失败:', error);
}
}
private async saveGameData(slot: number): Promise<void> {
// 模拟保存操作
return new Promise(resolve => setTimeout(resolve, 1000));
}
// 发射异步事件
public async triggerSave(): Promise<void> {
// 使用 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<string, any>;
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<void> {
// 异步上传分数到服务器
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<void> {
// 模拟服务器上传
return new Promise(resolve => setTimeout(resolve, 2000));
}
}
```
## 批处理事件
对于高频事件,可以使用批处理来提升性能:
```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 框架中实现松耦合架构的重要工具,正确使用事件系统能让你的游戏代码更加模块化、可维护和可扩展。

View File

@@ -0,0 +1,417 @@
# 快速开始
本指南将帮助你快速上手 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));
```
## 场景管理
Core 内置了场景管理功能,使用非常简单:
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 创建并设置场景
class GameScene extends Scene {
initialize(): void {
this.name = "GamePlay";
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
}
}
const gameScene = new GameScene();
Core.setScene(gameScene);
// 游戏循环(自动更新场景)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新全局服务和场景
}
// 切换场景
Core.loadScene(new MenuScene()); // 延迟切换(下一帧)
Core.setScene(new GameScene()); // 立即切换
// 访问当前场景
const currentScene = Core.scene;
// 使用流式API
const player = Core.ecsAPI?.createEntity('Player')
.addComponent(Position, 100, 100)
.addComponent(Velocity, 50, 0);
```
### 高级:使用 WorldManager 管理多世界
仅适用于复杂的服务器端应用MMO游戏服务器、游戏房间系统等
```typescript
import { Core, WorldManager } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 从服务容器获取 WorldManagerCore 已自动创建并注册)
const worldManager = Core.services.resolve(WorldManager);
// 创建多个独立的游戏世界
const room1 = worldManager.createWorld('room_001');
const room2 = worldManager.createWorld('room_002');
// 在每个世界中创建场景
const gameScene1 = room1.createScene('game', new GameScene());
const gameScene2 = room2.createScene('game', new GameScene());
// 激活场景
room1.setSceneActive('game', true);
room2.setSceneActive('game', true);
// 游戏循环(需要手动更新世界)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 更新全局服务
worldManager.updateAll(); // 手动更新所有世界
}
```
## 与游戏引擎集成
### Laya 引擎集成
```typescript
import { Stage } from "laya/display/Stage";
import { Laya } from "Laya";
import { Core } from '@esengine/ecs-framework';
// 初始化 Laya
Laya.init(800, 600).then(() => {
// 初始化 ECS
Core.create(true);
Core.setScene(new GameScene());
// 启动游戏循环
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime); // 自动更新全局服务和场景
});
});
```
### Cocos Creator 集成
```typescript
import { Component, _decorator } from 'cc';
import { Core } from '@esengine/ecs-framework';
const { ccclass } = _decorator;
@ccclass('ECSGameManager')
export class ECSGameManager extends Component {
onLoad() {
// 初始化 ECS
Core.create(true);
Core.setScene(new GameScene());
}
update(deltaTime: number) {
// 自动更新全局服务和场景
Core.update(deltaTime);
}
onDestroy() {
// 清理资源
Core.destroy();
}
}
```
## 下一步
现在你已经成功创建了第一个 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)
```

40
docs/guide/index.md Normal file
View File

@@ -0,0 +1,40 @@
# 指南
欢迎使用 ECS Framework 指南。这里将详细介绍框架的各个核心概念和使用方法。
## 核心概念
### [实体类 (Entity)](./entity.md)
了解 ECS 架构的基础 - 实体类的使用方法、生命周期管理和最佳实践。
### [组件系统 (Component)](./component.md)
学习如何创建和使用组件,实现游戏功能的模块化设计。
### [系统架构 (System)](./system.md)
掌握系统的编写方法,实现游戏逻辑的处理。
### [场景管理 (Scene)](./scene.md)
了解场景的生命周期、系统管理和实体容器功能。
### [事件系统 (Event)](./event-system.md)
掌握类型安全的事件系统,实现组件间通信和系统协作。
### [序列化系统 (Serialization)](./serialization.md)
掌握场景、实体和组件的序列化方案,支持全量序列化和增量序列化,实现游戏存档、网络同步等功能。
### [时间和定时器 (Time)](./time-and-timers.md)
学习时间管理和定时器系统,实现游戏逻辑的精确时间控制。
### [日志系统 (Logger)](./logging.md)
掌握分级日志系统,用于调试、监控和错误追踪。
### [平台适配器 (Platform Adapter)](./platform-adapter.md)
了解如何为不同平台实现和注册平台适配器支持浏览器、小游戏、Node.js等环境。
## 高级特性
### [服务容器 (Service Container)](./service-container.md)
掌握依赖注入和服务管理,实现松耦合的架构设计。
### [插件系统 (Plugin System)](./plugin-system.md)
学习如何开发和使用插件,扩展框架功能,实现功能模块化。

550
docs/guide/logging.md Normal file
View File

@@ -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<string, number>();
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();
```
日志系统是调试和监控应用的重要工具,正确使用日志系统能大大提高开发效率和问题排查能力。

View File

@@ -0,0 +1,289 @@
# 平台适配器
## 概述
ECS框架提供了平台适配器接口允许用户为不同的运行环境实现自定义的平台适配器。
**核心库只提供接口定义,平台适配器实现代码请从文档中复制使用。**
## 为什么不提供单独的适配器包?
1. **灵活性**: 不同项目对平台适配的需求可能不同,复制代码可以让用户根据需要自由修改
2. **减少依赖**: 避免引入不必要的依赖包,保持核心框架的精简
3. **定制化**: 用户可以根据具体的运行环境和需求进行定制
## 支持的平台
### 🌐 [浏览器适配器](./platform-adapter/browser.md)
支持所有现代浏览器环境,包括 Chrome、Firefox、Safari、Edge 等。
**特性支持**:
- ✅ Worker (Web Worker)
- ✅ SharedArrayBuffer (需要COOP/COEP)
- ✅ Transferable Objects
- ✅ Module Worker (现代浏览器)
**适用场景**: Web游戏、Web应用、PWA
---
### 📱 [微信小游戏适配器](./platform-adapter/wechat-minigame.md)
专为微信小游戏环境设计处理微信小游戏的特殊限制和API。
**特性支持**:
- ✅ Worker (最多1个需配置game.json)
- ❌ SharedArrayBuffer
- ❌ Transferable Objects
- ✅ 微信设备信息API
**适用场景**: 微信小游戏开发
---
### 🖥️ [Node.js适配器](./platform-adapter/nodejs.md)
为 Node.js 服务器环境提供支持,适用于游戏服务器和计算服务器。
**特性支持**:
- ✅ Worker Threads
- ✅ SharedArrayBuffer
- ✅ Transferable Objects
- ✅ 完整系统信息
**适用场景**: 游戏服务器、计算服务器、CLI工具
---
## 核心接口
### IPlatformAdapter
```typescript
export interface IPlatformAdapter {
readonly name: string;
readonly version?: string;
isWorkerSupported(): boolean;
isSharedArrayBufferSupported(): boolean;
getHardwareConcurrency(): number;
createWorker(script: string, options?: WorkerCreationOptions): PlatformWorker;
createSharedArrayBuffer(length: number): SharedArrayBuffer | null;
getHighResTimestamp(): number;
getPlatformConfig(): PlatformConfig;
getPlatformConfigAsync?(): Promise<PlatformConfig>;
}
```
### PlatformWorker 接口
```typescript
export interface PlatformWorker {
postMessage(message: any, transfer?: Transferable[]): void;
onMessage(handler: (event: { data: any }) => void): void;
onError(handler: (error: ErrorEvent) => void): void;
terminate(): void;
readonly state: 'running' | 'terminated';
}
```
## 使用方法
### 1. 选择合适的平台适配器
根据你的运行环境选择对应的适配器:
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
// 浏览器环境
if (typeof window !== 'undefined') {
const { BrowserAdapter } = await import('./platform/BrowserAdapter');
PlatformManager.getInstance().registerAdapter(new BrowserAdapter());
}
// 微信小游戏环境
else if (typeof wx !== 'undefined') {
const { WeChatMiniGameAdapter } = await import('./platform/WeChatMiniGameAdapter');
PlatformManager.getInstance().registerAdapter(new WeChatMiniGameAdapter());
}
// Node.js环境
else if (typeof process !== 'undefined' && process.versions?.node) {
const { NodeAdapter } = await import('./platform/NodeAdapter');
PlatformManager.getInstance().registerAdapter(new NodeAdapter());
}
```
### 2. 检查适配器状态
```typescript
const manager = PlatformManager.getInstance();
// 检查是否已注册适配器
if (manager.hasAdapter()) {
const adapter = manager.getAdapter();
console.log('当前平台:', adapter.name);
console.log('平台版本:', adapter.version);
// 检查功能支持
console.log('Worker支持:', manager.supportsFeature('worker'));
console.log('SharedArrayBuffer支持:', manager.supportsFeature('shared-array-buffer'));
}
```
## 创建自定义适配器
如果现有的平台适配器不能满足你的需求,你可以创建自定义适配器:
### 1. 实现接口
```typescript
import type { IPlatformAdapter, PlatformWorker, WorkerCreationOptions, PlatformConfig } from '@esengine/ecs-framework';
export class CustomAdapter implements IPlatformAdapter {
public readonly name = 'custom';
public readonly version = '1.0.0';
public isWorkerSupported(): boolean {
// 实现你的 Worker 支持检查逻辑
return false;
}
public isSharedArrayBufferSupported(): boolean {
// 实现你的 SharedArrayBuffer 支持检查逻辑
return false;
}
public getHardwareConcurrency(): number {
// 返回你的平台的并发数
return 1;
}
public createWorker(script: string, options?: WorkerCreationOptions): PlatformWorker {
throw new Error('Worker not supported on this platform');
}
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
return null;
}
public getHighResTimestamp(): number {
return Date.now();
}
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: 1,
supportsModuleWorker: false,
supportsTransferableObjects: false,
limitations: {
workerNotSupported: true
}
};
}
}
```
### 2. 注册自定义适配器
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { CustomAdapter } from './CustomAdapter';
const customAdapter = new CustomAdapter();
PlatformManager.getInstance().registerAdapter(customAdapter);
```
## 最佳实践
### 1. 平台检测顺序
建议按照以下顺序检测和注册平台适配器:
```typescript
async function initializePlatform() {
const manager = PlatformManager.getInstance();
try {
// 1. 微信小游戏 (优先级最高,环境特征最明显)
if (typeof wx !== 'undefined' && wx.getSystemInfoSync) {
const { WeChatMiniGameAdapter } = await import('./platform/WeChatMiniGameAdapter');
manager.registerAdapter(new WeChatMiniGameAdapter());
return;
}
// 2. Node.js 环境
if (typeof process !== 'undefined' && process.versions?.node) {
const { NodeAdapter } = await import('./platform/NodeAdapter');
manager.registerAdapter(new NodeAdapter());
return;
}
// 3. 浏览器环境 (最后检测,覆盖面最广)
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
const { BrowserAdapter } = await import('./platform/BrowserAdapter');
manager.registerAdapter(new BrowserAdapter());
return;
}
// 4. 未知环境,使用默认适配器
console.warn('未识别的平台环境,使用默认适配器');
manager.registerAdapter(new CustomAdapter());
} catch (error) {
console.error('平台适配器初始化失败:', error);
throw error;
}
}
```
### 2. 功能降级处理
```typescript
function createWorkerSystem() {
const manager = PlatformManager.getInstance();
if (!manager.hasAdapter()) {
throw new Error('未注册平台适配器');
}
const config: WorkerSystemConfig = {
enableWorker: manager.supportsFeature('worker'),
workerCount: manager.supportsFeature('worker') ?
manager.getAdapter().getHardwareConcurrency() : 1,
useSharedArrayBuffer: manager.supportsFeature('shared-array-buffer')
};
// 如果不支持Worker自动降级到同步处理
if (!config.enableWorker) {
console.info('当前平台不支持Worker使用同步处理模式');
}
return new PhysicsSystem(config);
}
```
### 3. 错误处理
```typescript
try {
await initializePlatform();
// 验证适配器功能
const manager = PlatformManager.getInstance();
const adapter = manager.getAdapter();
console.log(`平台适配器初始化成功: ${adapter.name} v${adapter.version}`);
} catch (error) {
console.error('平台初始化失败:', error);
// 提供降级方案
const fallbackAdapter = new CustomAdapter();
PlatformManager.getInstance().registerAdapter(fallbackAdapter);
console.warn('使用降级适配器继续运行');
}
```

View File

@@ -0,0 +1,370 @@
# 浏览器适配器
## 概述
浏览器平台适配器为标准Web浏览器环境提供支持包括 Chrome、Firefox、Safari、Edge 等现代浏览器。
## 特性支持
-**Worker**: 支持 Web Worker 和 Module Worker
-**SharedArrayBuffer**: 支持需要COOP/COEP头部
-**Transferable Objects**: 完全支持
-**高精度时间**: 使用 `performance.now()`
-**基础信息**: 浏览器版本和基本配置
## 完整实现
```typescript
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig
} from '@esengine/ecs-framework';
/**
* 浏览器平台适配器
* 支持标准Web浏览器环境
*/
export class BrowserAdapter implements IPlatformAdapter {
public readonly name = 'browser';
public readonly version: string;
constructor() {
this.version = this.getBrowserInfo();
}
/**
* 检查是否支持Worker
*/
public isWorkerSupported(): boolean {
return typeof Worker !== 'undefined';
}
/**
* 检查是否支持SharedArrayBuffer
*/
public isSharedArrayBufferSupported(): boolean {
return typeof SharedArrayBuffer !== 'undefined' && this.checkSharedArrayBufferEnabled();
}
/**
* 获取硬件并发数CPU核心数
*/
public getHardwareConcurrency(): number {
return navigator.hardwareConcurrency || 4;
}
/**
* 创建Worker
*/
public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
if (!this.isWorkerSupported()) {
throw new Error('浏览器不支持Worker');
}
try {
return new BrowserWorker(script, options);
} catch (error) {
throw new Error(`创建浏览器Worker失败: ${(error as Error).message}`);
}
}
/**
* 创建SharedArrayBuffer
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
if (!this.isSharedArrayBufferSupported()) {
return null;
}
try {
return new SharedArrayBuffer(length);
} catch (error) {
console.warn('SharedArrayBuffer创建失败:', error);
return null;
}
}
/**
* 获取高精度时间戳
*/
public getHighResTimestamp(): number {
return performance.now();
}
/**
* 获取平台配置
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: this.getHardwareConcurrency(),
supportsModuleWorker: false,
supportsTransferableObjects: true,
maxSharedArrayBufferSize: 1024 * 1024 * 1024, // 1GB
workerScriptPrefix: '',
limitations: {
noEval: false,
requiresWorkerInit: false
}
};
}
/**
* 获取浏览器信息
*/
private getBrowserInfo(): string {
const userAgent = navigator.userAgent;
if (userAgent.includes('Chrome')) {
const match = userAgent.match(/Chrome\/([0-9.]+)/);
return match ? `Chrome ${match[1]}` : 'Chrome';
} else if (userAgent.includes('Firefox')) {
const match = userAgent.match(/Firefox\/([0-9.]+)/);
if (match) return `Firefox ${match[1]}`;
} else if (userAgent.includes('Safari')) {
const match = userAgent.match(/Version\/([0-9.]+)/);
if (match) return `Safari ${match[1]}`;
}
return 'Unknown Browser';
}
/**
* 检查SharedArrayBuffer是否真正可用
*/
private checkSharedArrayBufferEnabled(): boolean {
try {
new SharedArrayBuffer(8);
return true;
} catch {
return false;
}
}
}
/**
* 浏览器Worker封装
*/
class BrowserWorker implements PlatformWorker {
private _state: 'running' | 'terminated' = 'running';
private worker: Worker;
constructor(script: string, options: WorkerCreationOptions = {}) {
this.worker = this.createBrowserWorker(script, options);
}
public get state(): 'running' | 'terminated' {
return this._state;
}
public postMessage(message: any, transfer?: Transferable[]): void {
if (this._state === 'terminated') {
throw new Error('Worker已被终止');
}
try {
if (transfer && transfer.length > 0) {
this.worker.postMessage(message, transfer);
} else {
this.worker.postMessage(message);
}
} catch (error) {
throw new Error(`发送消息到Worker失败: ${(error as Error).message}`);
}
}
public onMessage(handler: (event: { data: any }) => void): void {
this.worker.onmessage = (event: MessageEvent) => {
handler({ data: event.data });
};
}
public onError(handler: (error: ErrorEvent) => void): void {
this.worker.onerror = handler;
}
public terminate(): void {
if (this._state === 'running') {
try {
this.worker.terminate();
this._state = 'terminated';
} catch (error) {
console.error('终止Worker失败:', error);
}
}
}
/**
* 创建浏览器Worker
*/
private createBrowserWorker(script: string, options: WorkerCreationOptions): Worker {
try {
// 创建Blob URL
const blob = new Blob([script], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
// 创建Worker
const worker = new Worker(url, {
type: options.type || 'classic',
credentials: options.credentials,
name: options.name
});
// 清理Blob URL延迟清理确保Worker已加载
setTimeout(() => {
URL.revokeObjectURL(url);
}, 1000);
return worker;
} catch (error) {
throw new Error(`无法创建浏览器Worker: ${(error as Error).message}`);
}
}
}
```
## 使用方法
### 1. 复制代码
将上述代码复制到你的项目中,例如 `src/platform/BrowserAdapter.ts`
### 2. 注册适配器
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { BrowserAdapter } from './platform/BrowserAdapter';
// 创建并注册浏览器适配器
const browserAdapter = new BrowserAdapter();
PlatformManager.registerAdapter(browserAdapter);
// 框架会自动检测和使用合适的适配器
```
### 3. 使用 WorkerEntitySystem
浏览器适配器与 WorkerEntitySystem 配合使用,框架会自动处理 Worker 脚本的创建:
```typescript
import { WorkerEntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends WorkerEntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity), {
enableWorker: true,
workerCount: navigator.hardwareConcurrency || 4,
useSharedArrayBuffer: true,
systemConfig: { gravity: 9.8 }
});
}
protected getDefaultEntityDataSize(): number {
return 6; // x, y, vx, vy, mass, radius
}
protected extractEntityData(entity: Entity): PhysicsData {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
return {
x: transform.x,
y: transform.y,
vx: velocity.x,
vy: velocity.y,
mass: 1,
radius: 10
};
}
// 这个函数会被自动序列化并在Worker中执行
protected workerProcess(entities, deltaTime, config) {
return entities.map(entity => {
// 应用重力
entity.vy += config.gravity * deltaTime;
// 更新位置
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
return entity;
});
}
protected applyResult(entity: Entity, result: PhysicsData): void {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
transform.x = result.x;
transform.y = result.y;
velocity.x = result.vx;
velocity.y = result.vy;
}
}
interface PhysicsData {
x: number;
y: number;
vx: number;
vy: number;
mass: number;
radius: number;
}
```
### 4. 验证适配器工作状态
```typescript
// 验证适配器是否正常工作
const adapter = new BrowserAdapter();
console.log('适配器名称:', adapter.name);
console.log('浏览器版本:', adapter.version);
console.log('Worker支持:', adapter.isWorkerSupported());
console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported());
console.log('CPU核心数:', adapter.getHardwareConcurrency());
```
## 重要注意事项
### SharedArrayBuffer 支持
SharedArrayBuffer 需要特殊的安全配置:
1. **HTTPS**: 必须在安全上下文中使用
2. **COOP/COEP 头部**: 需要设置正确的跨域隔离头部
```html
<!-- 在 HTML 中设置 -->
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin">
<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp">
```
或在服务器配置中设置:
```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```
### 浏览器兼容性
- **Worker**: 所有现代浏览器支持
- **Module Worker**: Chrome 80+, Firefox 114+
- **SharedArrayBuffer**: Chrome 68+, Firefox 79+需要COOP/COEP
- **Transferable Objects**: 所有现代浏览器支持
## 性能优化建议
1. **Worker 池**: 复用 Worker 实例,避免频繁创建和销毁
2. **数据传输**: 使用 Transferable Objects 减少数据拷贝
3. **SharedArrayBuffer**: 对于大量数据共享,使用 SharedArrayBuffer
4. **模块 Worker**: 在支持的浏览器中使用模块 Worker 来更好地组织代码
## 调试技巧
```typescript
// 检查浏览器支持情况
const adapter = new BrowserAdapter();
console.log('Worker支持:', adapter.isWorkerSupported());
console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported());
console.log('硬件并发数:', adapter.getHardwareConcurrency());
console.log('平台配置:', adapter.getPlatformConfig());
```

View File

@@ -0,0 +1,558 @@
# Node.js 适配器
## 概述
Node.js 平台适配器为 Node.js 服务器环境提供支持,适用于游戏服务器、计算服务器或其他需要 ECS 架构的服务器应用。
## 特性支持
-**Worker**: 支持(通过 `worker_threads` 模块)
-**SharedArrayBuffer**: 支持Node.js 16.17.0+
-**Transferable Objects**: 完全支持
-**高精度时间**: 使用 `process.hrtime.bigint()`
-**设备信息**: 完整的系统和进程信息
## 完整实现
```typescript
import { worker_threads, Worker, isMainThread, parentPort } from 'worker_threads';
import * as os from 'os';
import * as process from 'process';
import * as fs from 'fs';
import * as path from 'path';
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig,
NodeDeviceInfo
} from '@esengine/ecs-framework';
/**
* Node.js 平台适配器
* 支持 Node.js 服务器环境
*/
export class NodeAdapter implements IPlatformAdapter {
public readonly name = 'nodejs';
public readonly version: string;
constructor() {
this.version = process.version;
}
/**
* 检查是否支持Worker
*/
public isWorkerSupported(): boolean {
try {
// 检查 worker_threads 模块是否可用
return typeof worker_threads !== 'undefined' && typeof Worker !== 'undefined';
} catch {
return false;
}
}
/**
* 检查是否支持SharedArrayBuffer
*/
public isSharedArrayBufferSupported(): boolean {
// Node.js 支持 SharedArrayBuffer
return typeof SharedArrayBuffer !== 'undefined';
}
/**
* 获取硬件并发数CPU核心数
*/
public getHardwareConcurrency(): number {
return os.cpus().length;
}
/**
* 创建Worker
*/
public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
if (!this.isWorkerSupported()) {
throw new Error('Node.js环境不支持Worker Threads');
}
try {
return new NodeWorker(script, options);
} catch (error) {
throw new Error(`创建Node.js Worker失败: ${(error as Error).message}`);
}
}
/**
* 创建SharedArrayBuffer
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
if (!this.isSharedArrayBufferSupported()) {
return null;
}
try {
return new SharedArrayBuffer(length);
} catch (error) {
console.warn('SharedArrayBuffer创建失败:', error);
return null;
}
}
/**
* 获取高精度时间戳(纳秒)
*/
public getHighResTimestamp(): number {
// 返回毫秒,与浏览器 performance.now() 保持一致
return Number(process.hrtime.bigint()) / 1000000;
}
/**
* 获取平台配置
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: this.getHardwareConcurrency(),
supportsModuleWorker: true, // Node.js 支持 ES 模块
supportsTransferableObjects: true,
maxSharedArrayBufferSize: this.getMaxSharedArrayBufferSize(),
workerScriptPrefix: '',
limitations: {
noEval: false, // Node.js 支持 eval
requiresWorkerInit: false
},
extensions: {
platform: 'nodejs',
nodeVersion: process.version,
v8Version: process.versions.v8,
uvVersion: process.versions.uv,
zlibVersion: process.versions.zlib,
opensslVersion: process.versions.openssl,
architecture: process.arch,
endianness: os.endianness(),
pid: process.pid,
ppid: process.ppid
}
};
}
/**
* 获取Node.js设备信息
*/
public getDeviceInfo(): NodeDeviceInfo {
const cpus = os.cpus();
const networkInterfaces = os.networkInterfaces();
const userInfo = os.userInfo();
return {
// 系统信息
platform: os.platform(),
arch: os.arch(),
type: os.type(),
release: os.release(),
version: os.version(),
hostname: os.hostname(),
// CPU信息
cpus: cpus.map(cpu => ({
model: cpu.model,
speed: cpu.speed,
times: cpu.times
})),
// 内存信息
totalMemory: os.totalmem(),
freeMemory: os.freemem(),
usedMemory: os.totalmem() - os.freemem(),
// 负载信息
loadAverage: os.loadavg(),
// 网络接口
networkInterfaces: Object.fromEntries(
Object.entries(networkInterfaces).map(([name, interfaces]) => [
name,
(interfaces || []).map(iface => ({
address: iface.address,
netmask: iface.netmask,
family: iface.family as 'IPv4' | 'IPv6',
mac: iface.mac,
internal: iface.internal,
cidr: iface.cidr,
scopeid: iface.scopeid
}))
])
),
// 进程信息
process: {
pid: process.pid,
ppid: process.ppid,
version: process.version,
versions: process.versions,
uptime: process.uptime()
},
// 用户信息
userInfo: {
uid: userInfo.uid,
gid: userInfo.gid,
username: userInfo.username,
homedir: userInfo.homedir,
shell: userInfo.shell
}
};
}
/**
* 获取SharedArrayBuffer最大大小限制
*/
private getMaxSharedArrayBufferSize(): number {
const totalMemory = os.totalmem();
// 限制为系统总内存的50%
return Math.floor(totalMemory * 0.5);
}
}
/**
* Node.js Worker封装
*/
class NodeWorker implements PlatformWorker {
private _state: 'running' | 'terminated' = 'running';
private worker: Worker;
private isTemporaryFile: boolean = false;
private scriptPath: string;
constructor(script: string, options: WorkerCreationOptions = {}) {
try {
// 判断 script 是文件路径还是脚本内容
if (this.isFilePath(script)) {
// 直接使用文件路径
this.scriptPath = script;
this.isTemporaryFile = false;
} else {
// 将脚本内容写入临时文件
this.scriptPath = this.writeScriptToFile(script, options.name);
this.isTemporaryFile = true;
}
// 创建Worker
this.worker = new Worker(this.scriptPath, {
// Node.js Worker options
workerData: options.name ? { name: options.name } : undefined
});
} catch (error) {
throw new Error(`创建Node.js Worker失败: ${(error as Error).message}`);
}
}
/**
* 判断是否为文件路径
*/
private isFilePath(script: string): boolean {
// 检查是否看起来像文件路径
return (script.endsWith('.js') || script.endsWith('.mjs') || script.endsWith('.ts')) &&
!script.includes('\n') &&
!script.includes(';') &&
script.length < 500; // 文件路径通常不会太长
}
/**
* 将脚本内容写入临时文件
*/
private writeScriptToFile(script: string, name?: string): string {
const tmpDir = os.tmpdir();
const fileName = name ? `worker-${name}-${Date.now()}.js` : `worker-${Date.now()}.js`;
const filePath = path.join(tmpDir, fileName);
try {
fs.writeFileSync(filePath, script, 'utf8');
return filePath;
} catch (error) {
throw new Error(`写入Worker脚本文件失败: ${(error as Error).message}`);
}
}
public get state(): 'running' | 'terminated' {
return this._state;
}
public postMessage(message: any, transfer?: Transferable[]): void {
if (this._state === 'terminated') {
throw new Error('Worker已被终止');
}
try {
if (transfer && transfer.length > 0) {
// Node.js Worker 支持 Transferable Objects
this.worker.postMessage(message, transfer);
} else {
this.worker.postMessage(message);
}
} catch (error) {
throw new Error(`发送消息到Node.js Worker失败: ${(error as Error).message}`);
}
}
public onMessage(handler: (event: { data: any }) => void): void {
this.worker.on('message', (data: any) => {
handler({ data });
});
}
public onError(handler: (error: ErrorEvent) => void): void {
this.worker.on('error', (error: Error) => {
// 将 Error 转换为 ErrorEvent 格式
const errorEvent = {
message: error.message,
filename: '',
lineno: 0,
colno: 0,
error: error
} as ErrorEvent;
handler(errorEvent);
});
}
public terminate(): void {
if (this._state === 'running') {
try {
this.worker.terminate();
this._state = 'terminated';
// 清理临时脚本文件
this.cleanupScriptFile();
} catch (error) {
console.error('终止Node.js Worker失败:', error);
}
}
}
/**
* 清理临时脚本文件
*/
private cleanupScriptFile(): void {
// 只清理临时创建的文件,不清理用户提供的文件路径
if (this.scriptPath && this.isTemporaryFile) {
try {
fs.unlinkSync(this.scriptPath);
} catch (error) {
console.warn('清理Worker脚本文件失败:', error);
}
}
}
}
```
## 使用方法
### 1. 复制代码
将上述代码复制到你的项目中,例如 `src/platform/NodeAdapter.ts`
### 2. 注册适配器
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { NodeAdapter } from './platform/NodeAdapter';
// 检查是否在Node.js环境
if (typeof process !== 'undefined' && process.versions && process.versions.node) {
const nodeAdapter = new NodeAdapter();
PlatformManager.getInstance().registerAdapter(nodeAdapter);
}
```
### 3. 使用 WorkerEntitySystem
Node.js 适配器与 WorkerEntitySystem 配合使用,框架会自动处理 Worker 脚本的创建:
```typescript
import { WorkerEntitySystem, Matcher } from '@esengine/ecs-framework';
import * as os from 'os';
class PhysicsSystem extends WorkerEntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity), {
enableWorker: true,
workerCount: os.cpus().length, // 使用所有CPU核心
useSharedArrayBuffer: true,
systemConfig: { gravity: 9.8 }
});
}
protected getDefaultEntityDataSize(): number {
return 6; // x, y, vx, vy, mass, radius
}
protected extractEntityData(entity: Entity): PhysicsData {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
return {
x: transform.x,
y: transform.y,
vx: velocity.x,
vy: velocity.y,
mass: 1,
radius: 10
};
}
// 这个函数会被自动序列化并在Worker中执行
protected workerProcess(entities, deltaTime, config) {
return entities.map(entity => {
// 应用重力
entity.vy += config.gravity * deltaTime;
// 更新位置
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
return entity;
});
}
protected applyResult(entity: Entity, result: PhysicsData): void {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
transform.x = result.x;
transform.y = result.y;
velocity.x = result.vx;
velocity.y = result.vy;
}
}
interface PhysicsData {
x: number;
y: number;
vx: number;
vy: number;
mass: number;
radius: number;
}
```
### 4. 获取系统信息
```typescript
const manager = PlatformManager.getInstance();
if (manager.hasAdapter()) {
const adapter = manager.getAdapter();
const deviceInfo = adapter.getDeviceInfo();
console.log('Node.js版本:', deviceInfo.process?.version);
console.log('CPU核心数:', deviceInfo.cpus?.length);
console.log('总内存:', Math.round(deviceInfo.totalMemory! / 1024 / 1024), 'MB');
console.log('可用内存:', Math.round(deviceInfo.freeMemory! / 1024 / 1024), 'MB');
}
```
## 官方文档参考
Node.js Worker Threads 相关官方文档:
- [Worker Threads 官方文档](https://nodejs.org/api/worker_threads.html)
- [SharedArrayBuffer 支持](https://nodejs.org/api/globals.html#class-sharedarraybuffer)
- [OS 模块文档](https://nodejs.org/api/os.html)
- [Process 模块文档](https://nodejs.org/api/process.html)
## 重要注意事项
### Worker Threads 要求
- **Node.js版本**: 需要 Node.js 10.5.0+ (建议 12+)
- **模块类型**: 支持 CommonJS 和 ES 模块
- **线程限制**: 理论上无限制,但建议不超过 CPU 核心数的 2 倍
### 性能优化建议
#### 1. Worker 池管理
```typescript
class ServerPhysicsSystem extends WorkerEntitySystem {
constructor() {
const cpuCount = os.cpus().length;
super(Matcher.all(Transform, Velocity), {
enableWorker: true,
workerCount: Math.min(cpuCount * 2, 16), // 最多16个Worker
entitiesPerWorker: 1000, // 每个Worker处理1000个实体
useSharedArrayBuffer: true,
systemConfig: {
gravity: 9.8,
timeStep: 1/60
}
});
}
}
```
#### 2. 内存管理
```typescript
class MemoryMonitor {
public static checkMemoryUsage(): void {
const used = process.memoryUsage();
console.log('内存使用情况:');
console.log(` RSS: ${Math.round(used.rss / 1024 / 1024)} MB`);
console.log(` Heap Used: ${Math.round(used.heapUsed / 1024 / 1024)} MB`);
console.log(` Heap Total: ${Math.round(used.heapTotal / 1024 / 1024)} MB`);
console.log(` External: ${Math.round(used.external / 1024 / 1024)} MB`);
// 内存使用率过高时触发警告
if (used.heapUsed > used.heapTotal * 0.9) {
console.warn('内存使用率过高,建议优化或重启');
}
}
}
// 定期检查内存使用
setInterval(() => {
MemoryMonitor.checkMemoryUsage();
}, 30000); // 每30秒检查一次
```
#### 3. 服务器环境优化
```typescript
// 设置进程标题
process.title = 'ecs-game-server';
// 处理未捕获异常
process.on('uncaughtException', (error) => {
console.error('未捕获异常:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
});
// 优雅关闭
process.on('SIGTERM', () => {
console.log('收到SIGTERM信号正在关闭服务器...');
// 清理资源
process.exit(0);
});
```
## 调试技巧
```typescript
// 检查Node.js环境支持情况
const adapter = new NodeAdapter();
console.log('Node.js版本:', adapter.version);
console.log('Worker支持:', adapter.isWorkerSupported());
console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported());
console.log('CPU核心数:', adapter.getHardwareConcurrency());
// 获取详细配置
const config = adapter.getPlatformConfig();
console.log('平台配置:', JSON.stringify(config, null, 2));
// 系统资源监控
const deviceInfo = adapter.getDeviceInfo();
console.log('系统负载:', deviceInfo.loadAverage);
console.log('网络接口:', Object.keys(deviceInfo.networkInterfaces!));
```

View File

@@ -0,0 +1,677 @@
# 微信小游戏适配器
## 概述
微信小游戏平台适配器专为微信小游戏环境设计处理微信小游戏的特殊限制和API。
## 特性支持
-**Worker**: 支持(通过 `wx.createWorker` 创建,需要配置 game.json
-**SharedArrayBuffer**: 不支持
-**Transferable Objects**: 不支持(只支持可序列化对象)
-**高精度时间**: 使用 `Date.now()``wx.getPerformance()`
-**设备信息**: 完整的微信小游戏设备信息
## 完整实现
```typescript
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig,
WeChatDeviceInfo
} from '@esengine/ecs-framework';
/**
* 微信小游戏平台适配器
* 支持微信小游戏环境
*/
export class WeChatMiniGameAdapter implements IPlatformAdapter {
public readonly name = 'wechat-minigame';
public readonly version: string;
private systemInfo: any;
constructor() {
// 获取微信小游戏版本信息
this.systemInfo = this.getSystemInfo();
this.version = this.systemInfo.version || 'unknown';
}
/**
* 检查是否支持Worker
*/
public isWorkerSupported(): boolean {
// 微信小游戏支持Worker通过wx.createWorker创建
return typeof wx !== 'undefined' && typeof wx.createWorker === 'function';
}
/**
* 检查是否支持SharedArrayBuffer不支持
*/
public isSharedArrayBufferSupported(): boolean {
return false; // 微信小游戏不支持SharedArrayBuffer
}
/**
* 获取硬件并发数
*/
public getHardwareConcurrency(): number {
// 微信小游戏官方限制:最多只能创建 1 个 Worker
return 1;
}
/**
* 创建Worker
* @param script 脚本内容或文件路径
* @param options Worker创建选项
*/
public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
if (!this.isWorkerSupported()) {
throw new Error('微信小游戏不支持Worker');
}
try {
return new WeChatWorker(script, options);
} catch (error) {
throw new Error(`创建微信Worker失败: ${(error as Error).message}`);
}
}
/**
* 创建SharedArrayBuffer不支持
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
return null; // 微信小游戏不支持SharedArrayBuffer
}
/**
* 获取高精度时间戳
*/
public getHighResTimestamp(): number {
// 尝试使用微信的性能API否则使用Date.now()
if (typeof wx !== 'undefined' && wx.getPerformance) {
const performance = wx.getPerformance();
return performance.now();
}
return Date.now();
}
/**
* 获取平台配置
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: 1, // 微信小游戏最多支持 1 个 Worker
supportsModuleWorker: false, // 不支持模块Worker
supportsTransferableObjects: this.checkTransferableObjectsSupport(),
maxSharedArrayBufferSize: 0,
workerScriptPrefix: '',
limitations: {
noEval: true, // 微信小游戏限制eval使用
requiresWorkerInit: false,
memoryLimit: this.getMemoryLimit(),
workerNotSupported: false,
workerLimitations: [
'最多只能创建 1 个 Worker',
'创建新Worker前必须先调用 Worker.terminate()',
'Worker脚本必须为项目内相对路径',
'需要在 game.json 中配置 workers 路径',
'使用 worker.onMessage() 而不是 self.onmessage',
'需要基础库 1.9.90 及以上版本'
]
},
extensions: {
platform: 'wechat-minigame',
systemInfo: this.systemInfo,
appId: this.systemInfo.host?.appId || 'unknown'
}
};
}
/**
* 获取微信小游戏设备信息
*/
public getDeviceInfo(): WeChatDeviceInfo {
return {
// 设备基础信息
brand: this.systemInfo.brand,
model: this.systemInfo.model,
platform: this.systemInfo.platform,
system: this.systemInfo.system,
benchmarkLevel: this.systemInfo.benchmarkLevel,
cpuType: this.systemInfo.cpuType,
memorySize: this.systemInfo.memorySize,
deviceAbi: this.systemInfo.deviceAbi,
abi: this.systemInfo.abi,
// 窗口信息
screenWidth: this.systemInfo.screenWidth,
screenHeight: this.systemInfo.screenHeight,
screenTop: this.systemInfo.screenTop,
windowWidth: this.systemInfo.windowWidth,
windowHeight: this.systemInfo.windowHeight,
pixelRatio: this.systemInfo.pixelRatio,
statusBarHeight: this.systemInfo.statusBarHeight,
safeArea: this.systemInfo.safeArea,
// 应用信息
version: this.systemInfo.version,
language: this.systemInfo.language,
theme: this.systemInfo.theme,
SDKVersion: this.systemInfo.SDKVersion,
enableDebug: this.systemInfo.enableDebug,
fontSizeSetting: this.systemInfo.fontSizeSetting,
host: this.systemInfo.host
};
}
/**
* 异步获取完整的平台配置
*/
public async getPlatformConfigAsync(): Promise<PlatformConfig> {
// 可以在这里添加异步获取设备性能信息的逻辑
const baseConfig = this.getPlatformConfig();
// 尝试获取设备性能信息
try {
const benchmarkLevel = await this.getBenchmarkLevel();
baseConfig.extensions = {
...baseConfig.extensions,
benchmarkLevel
};
} catch (error) {
console.warn('获取性能基准失败:', error);
}
return baseConfig;
}
/**
* 检查是否支持Transferable Objects
*/
private checkTransferableObjectsSupport(): boolean {
// 微信小游戏不支持 Transferable Objects
// 基础库 2.20.2 之前只支持可序列化的 key-value 对象
// 2.20.2 之后支持任意类型数据,但仍然不支持 Transferable Objects
return false;
}
/**
* 获取系统信息
*/
private getSystemInfo(): any {
if (typeof wx !== 'undefined' && wx.getSystemInfoSync) {
try {
return wx.getSystemInfoSync();
} catch (error) {
console.warn('获取微信系统信息失败:', error);
return {};
}
}
return {};
}
/**
* 获取内存限制
*/
private getMemoryLimit(): number {
// 微信小游戏通常有内存限制
const memorySize = this.systemInfo.memorySize;
if (memorySize) {
// 解析内存大小字符串(如 "4GB"
const match = memorySize.match(/(\d+)([GM]B)?/i);
if (match) {
const value = parseInt(match[1], 10);
const unit = match[2]?.toUpperCase();
if (unit === 'GB') {
return value * 1024 * 1024 * 1024;
} else if (unit === 'MB') {
return value * 1024 * 1024;
}
}
}
// 默认限制为512MB
return 512 * 1024 * 1024;
}
/**
* 异步获取设备性能基准
*/
private async getBenchmarkLevel(): Promise<number> {
return new Promise((resolve) => {
if (typeof wx !== 'undefined' && wx.getDeviceInfo) {
wx.getDeviceInfo({
success: (res: any) => {
resolve(res.benchmarkLevel || 0);
},
fail: () => {
resolve(0);
}
});
} else {
resolve(this.systemInfo.benchmarkLevel || 0);
}
});
}
}
/**
* 微信Worker封装
*/
class WeChatWorker implements PlatformWorker {
private _state: 'running' | 'terminated' = 'running';
private worker: any;
private scriptPath: string;
private isTemporaryFile: boolean = false;
constructor(script: string, options: WorkerCreationOptions = {}) {
if (typeof wx === 'undefined' || typeof wx.createWorker !== 'function') {
throw new Error('微信小游戏不支持Worker');
}
try {
// 判断 script 是文件路径还是脚本内容
if (this.isFilePath(script)) {
// 直接使用文件路径
this.scriptPath = script;
this.isTemporaryFile = false;
this.worker = wx.createWorker(this.scriptPath, {
useExperimentalWorker: true // 启用实验性Worker获得更好性能
});
} else {
// 微信小游戏不支持动态脚本内容,只能使用文件路径
// 将脚本内容写入文件系统
this.scriptPath = this.writeScriptToFile(script, options.name);
this.isTemporaryFile = true;
this.worker = wx.createWorker(this.scriptPath, {
useExperimentalWorker: true
});
}
} catch (error) {
throw new Error(`创建微信Worker失败: ${(error as Error).message}`);
}
}
/**
* 判断是否为文件路径
*/
private isFilePath(script: string): boolean {
// 简单判断:如果包含 .js 后缀且不包含换行符或分号,认为是文件路径
return script.endsWith('.js') &&
!script.includes('\n') &&
!script.includes(';') &&
script.length < 200; // 文件路径通常不会太长
}
/**
* 将脚本内容写入文件系统
* 注意微信小游戏不支持blob URL只能使用文件系统
*/
private writeScriptToFile(script: string, name?: string): string {
const fs = wx.getFileSystemManager();
const fileName = name ? `worker-${name}.js` : `worker-${Date.now()}.js`;
const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`;
try {
fs.writeFileSync(filePath, script, 'utf8');
return filePath;
} catch (error) {
throw new Error(`写入Worker脚本文件失败: ${(error as Error).message}`);
}
}
public get state(): 'running' | 'terminated' {
return this._state;
}
public postMessage(message: any, transfer?: Transferable[]): void {
if (this._state === 'terminated') {
throw new Error('Worker已被终止');
}
try {
// 微信小游戏 Worker 只支持可序列化对象,忽略 transfer 参数
this.worker.postMessage(message);
} catch (error) {
throw new Error(`发送消息到微信Worker失败: ${(error as Error).message}`);
}
}
public onMessage(handler: (event: { data: any }) => void): void {
// 微信小游戏使用 onMessage 方法,不是 onmessage 属性
this.worker.onMessage((res: any) => {
handler({ data: res });
});
}
public onError(handler: (error: ErrorEvent) => void): void {
// 注意:微信小游戏 Worker 的错误处理可能与标准不同
if (this.worker.onError) {
this.worker.onError(handler);
}
}
public terminate(): void {
if (this._state === 'running') {
try {
this.worker.terminate();
this._state = 'terminated';
// 清理临时脚本文件
this.cleanupScriptFile();
} catch (error) {
console.error('终止微信Worker失败:', error);
}
}
}
/**
* 清理临时脚本文件
*/
private cleanupScriptFile(): void {
// 只清理临时创建的文件,不清理用户提供的文件路径
if (this.scriptPath && this.isTemporaryFile) {
try {
const fs = wx.getFileSystemManager();
fs.unlinkSync(this.scriptPath);
} catch (error) {
console.warn('清理Worker脚本文件失败:', error);
}
}
}
}
```
## 使用方法
### 1. 复制代码
将上述代码复制到你的项目中,例如 `src/platform/WeChatMiniGameAdapter.ts`
### 2. 注册适配器
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { WeChatMiniGameAdapter } from './platform/WeChatMiniGameAdapter';
// 检查是否在微信小游戏环境
if (typeof wx !== 'undefined') {
const wechatAdapter = new WeChatMiniGameAdapter();
PlatformManager.getInstance().registerAdapter(wechatAdapter);
}
```
### 3. WorkerEntitySystem 使用方式
微信小游戏适配器与 WorkerEntitySystem 配合使用,自动处理 Worker 脚本创建:
#### 基本使用方式(推荐)
```typescript
import { WorkerEntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
interface PhysicsData {
id: number;
x: number;
y: number;
vx: number;
vy: number;
mass: number;
}
class PhysicsSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Transform, Velocity), {
enableWorker: true,
workerCount: 1, // 微信小游戏限制只能创建1个Worker
systemConfig: { gravity: 100, friction: 0.95 }
});
}
protected getDefaultEntityDataSize(): number {
return 6; // id, x, y, vx, vy, mass
}
protected extractEntityData(entity: Entity): PhysicsData {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
const physics = entity.getComponent(Physics);
return {
id: entity.id,
x: transform.x,
y: transform.y,
vx: velocity.x,
vy: velocity.y,
mass: physics.mass
};
}
// WorkerEntitySystem 会自动将此函数序列化并写入临时文件
protected workerProcess(entities: PhysicsData[], deltaTime: number, config: any): PhysicsData[] {
return entities.map(entity => {
// 应用重力
entity.vy += config.gravity * deltaTime;
// 更新位置
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
// 应用摩擦力
entity.vx *= config.friction;
entity.vy *= config.friction;
return entity;
});
}
protected applyResult(entity: Entity, result: PhysicsData): void {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
transform.x = result.x;
transform.y = result.y;
velocity.x = result.vx;
velocity.y = result.vy;
}
}
```
#### 使用预先创建的 Worker 文件(可选)
如果你希望使用预先创建的 Worker 文件:
```typescript
// 1. 在 game.json 中配置 Worker 路径
/*
{
"workers": "workers"
}
*/
// 2. 创建 workers/physics.js 文件
// workers/physics.js 内容:
/*
// 微信小游戏 Worker 使用标准的 self.onmessage
self.onmessage = function(e) {
const { type, id, entities, deltaTime, systemConfig } = e.data;
if (entities) {
// 处理物理计算
const results = entities.map(entity => {
entity.vy += systemConfig.gravity * deltaTime;
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
return entity;
});
self.postMessage({ id, result: results });
}
};
*/
// 3. 通过平台适配器直接创建不推荐WorkerEntitySystem会自动处理
const adapter = PlatformManager.getInstance().getAdapter();
const worker = adapter.createWorker('workers/physics.js');
```
### 4. 获取设备信息
```typescript
const manager = PlatformManager.getInstance();
if (manager.hasAdapter()) {
const adapter = manager.getAdapter();
console.log('微信设备信息:', adapter.getDeviceInfo());
}
```
## 官方文档参考
在使用微信小游戏 Worker 之前,建议先阅读官方文档:
- [wx.createWorker API](https://developers.weixin.qq.com/minigame/dev/api/worker/wx.createWorker.html)
- [Worker.postMessage API](https://developers.weixin.qq.com/minigame/dev/api/worker/Worker.postMessage.html)
- [Worker.onMessage API](https://developers.weixin.qq.com/minigame/dev/api/worker/Worker.onMessage.html)
- [Worker.terminate API](https://developers.weixin.qq.com/minigame/dev/api/worker/Worker.terminate.html)
## 重要注意事项
### Worker 限制和配置
微信小游戏的 Worker 有以下限制:
- **数量限制**: 最多只能创建 1 个 Worker
- **版本要求**: 需要基础库 1.9.90 及以上版本
- **脚本支持**: 不支持 blob URL只能使用文件路径或写入文件系统
- **文件路径**: Worker 脚本路径必须为绝对路径,但不能以 "/" 开头
- **生命周期**: 创建新 Worker 前必须先调用 `Worker.terminate()` 终止当前 Worker
- **消息处理**: Worker 内使用标准的 `self.onmessage`,主线程使用 `worker.onMessage()`
- **实验性功能**: 支持 `useExperimentalWorker` 选项获得更好的 iOS 性能
#### Worker 配置(可选)
如果使用预先创建的 Worker 文件,需要在 `game.json` 中添加 workers 配置:
```json
{
"deviceOrientation": "portrait",
"showStatusBar": false,
"workers": "workers",
"subpackages": []
}
```
**注意**: 使用 WorkerEntitySystem 时无需此配置,框架会自动将脚本写入临时文件。
### 内存限制
微信小游戏有严格的内存限制:
- 通常限制在 256MB - 512MB
- 需要及时释放不用的资源
- 避免内存泄漏
### API 限制
- 不支持 `eval()` 函数
- 不支持 `Function` 构造器
- DOM API 受限
- 文件系统 API 受限
## 性能优化建议
### 1. 分帧处理
```typescript
class FramedProcessor {
private tasks: (() => void)[] = [];
private isProcessing = false;
public addTask(task: () => void): void {
this.tasks.push(task);
if (!this.isProcessing) {
this.processNextFrame();
}
}
private processNextFrame(): void {
this.isProcessing = true;
const startTime = Date.now();
const frameTime = 16; // 16ms per frame
while (this.tasks.length > 0 && Date.now() - startTime < frameTime) {
const task = this.tasks.shift();
if (task) task();
}
if (this.tasks.length > 0) {
setTimeout(() => this.processNextFrame(), 0);
} else {
this.isProcessing = false;
}
}
}
```
### 2. 内存管理
```typescript
class MemoryManager {
private static readonly MAX_MEMORY = 256 * 1024 * 1024; // 256MB
public static checkMemoryUsage(): void {
if (typeof wx !== 'undefined' && wx.getPerformance) {
const performance = wx.getPerformance();
const memoryInfo = performance.getEntries().find(
(entry: any) => entry.entryType === 'memory'
);
if (memoryInfo && memoryInfo.usedJSHeapSize > this.MAX_MEMORY * 0.8) {
console.warn('内存使用率过高,建议清理资源');
// 触发垃圾回收或资源清理
}
}
}
}
```
## 调试技巧
```typescript
// 检查微信小游戏环境
if (typeof wx !== 'undefined') {
const adapter = new WeChatMiniGameAdapter();
console.log('微信版本:', adapter.version);
console.log('设备信息:', adapter.getDeviceInfo());
console.log('平台配置:', adapter.getPlatformConfig());
// 检查功能支持
console.log('Worker支持:', adapter.isWorkerSupported());
console.log('SharedArrayBuffer支持:', adapter.isSharedArrayBufferSupported());
}
```
## 微信小游戏特殊API
```typescript
// 获取设备性能等级
if (typeof wx !== 'undefined' && wx.getDeviceInfo) {
wx.getDeviceInfo({
success: (res) => {
console.log('设备性能等级:', res.benchmarkLevel);
}
});
}
// 监听内存警告
if (typeof wx !== 'undefined' && wx.onMemoryWarning) {
wx.onMemoryWarning(() => {
console.warn('收到内存警告,开始清理资源');
// 清理不必要的资源
});
}
```

643
docs/guide/plugin-system.md Normal file
View File

@@ -0,0 +1,643 @@
# 插件系统
插件系统允许你以模块化的方式扩展 ECS Framework 的功能。通过插件,你可以封装特定功能(如网络同步、物理引擎、调试工具等),并在多个项目中复用。
## 概述
### 什么是插件
插件是实现了 `IPlugin` 接口的类,可以在运行时动态安装到框架中。插件可以:
- 注册自定义服务到服务容器
- 添加系统到场景
- 注册自定义组件
- 扩展框架功能
### 插件的优势
- **模块化**: 将功能封装为独立模块,提高代码可维护性
- **可复用**: 同一个插件可以在多个项目中使用
- **解耦**: 核心框架与扩展功能分离
- **热插拔**: 运行时动态安装和卸载插件
## 快速开始
### 创建第一个插件
创建一个简单的调试插件:
```typescript
import { IPlugin, Core, ServiceContainer } from '@esengine/ecs-framework';
class DebugPlugin implements IPlugin {
readonly name = 'debug-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
console.log('Debug plugin installed');
// 可以在这里注册服务、添加系统等
}
uninstall(): void {
console.log('Debug plugin uninstalled');
// 清理资源
}
}
```
### 安装插件
使用 `Core.installPlugin()` 安装插件:
```typescript
import { Core } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 安装插件
await Core.installPlugin(new DebugPlugin());
// 检查插件是否已安装
if (Core.isPluginInstalled('debug-plugin')) {
console.log('Debug plugin is running');
}
```
### 卸载插件
```typescript
// 卸载插件
await Core.uninstallPlugin('debug-plugin');
```
### 获取插件实例
```typescript
// 获取已安装的插件
const plugin = Core.getPlugin('debug-plugin');
if (plugin) {
console.log(`Plugin version: ${plugin.version}`);
}
```
## 插件开发
### IPlugin 接口
所有插件必须实现 `IPlugin` 接口:
```typescript
export interface IPlugin {
// 插件唯一名称
readonly name: string;
// 插件版本建议遵循semver规范
readonly version: string;
// 依赖的其他插件(可选)
readonly dependencies?: readonly string[];
// 安装插件时调用
install(core: Core, services: ServiceContainer): void | Promise<void>;
// 卸载插件时调用
uninstall(): void | Promise<void>;
}
```
### 插件生命周期
#### install 方法
在插件安装时调用,用于初始化插件:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 1. 注册服务
services.registerSingleton(MyService);
// 2. 访问当前场景
const scene = core.scene;
if (scene) {
// 3. 添加系统
scene.addSystem(new MySystem());
}
// 4. 其他初始化逻辑
console.log('Plugin initialized');
}
uninstall(): void {
// 清理逻辑
}
}
```
#### uninstall 方法
在插件卸载时调用,用于清理资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private myService?: MyService;
install(core: Core, services: ServiceContainer): void {
this.myService = new MyService();
services.registerInstance(MyService, this.myService);
}
uninstall(): void {
// 清理服务
if (this.myService) {
this.myService.dispose();
this.myService = undefined;
}
// 移除事件监听器
// 释放其他资源
}
}
```
### 异步插件
插件的 `install``uninstall` 方法都支持异步:
```typescript
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
// 异步加载资源
const config = await fetch('/plugin-config.json').then(r => r.json());
// 使用加载的配置初始化服务
const service = new MyService(config);
services.registerInstance(MyService, service);
}
async uninstall(): Promise<void> {
// 异步清理
await this.saveState();
}
private async saveState() {
// 保存插件状态
}
}
// 使用
await Core.installPlugin(new AsyncPlugin());
```
### 注册服务
插件可以向服务容器注册自己的服务:
```typescript
import { IService } from '@esengine/ecs-framework';
class NetworkService implements IService {
connect(url: string) {
console.log(`Connecting to ${url}`);
}
dispose(): void {
console.log('Network service disposed');
}
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkService);
// 解析并使用服务
const network = services.resolve(NetworkService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务容器会自动调用服务的dispose方法
}
}
```
### 添加系统
插件可以向场景添加自定义系统:
```typescript
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PhysicsBody));
}
protected process(entities: readonly Entity[]): void {
// 物理模拟逻辑
}
}
class PhysicsPlugin implements IPlugin {
readonly name = 'physics-plugin';
readonly version = '1.0.0';
private physicsSystem?: PhysicsSystem;
install(core: Core, services: ServiceContainer): void {
const scene = core.scene;
if (scene) {
this.physicsSystem = new PhysicsSystem();
scene.addSystem(this.physicsSystem);
}
}
uninstall(): void {
// 移除系统
if (this.physicsSystem) {
const scene = Core.scene;
if (scene) {
scene.removeSystem(this.physicsSystem);
}
this.physicsSystem = undefined;
}
}
}
```
## 依赖管理
### 声明依赖
插件可以声明对其他插件的依赖:
```typescript
class AdvancedPhysicsPlugin implements IPlugin {
readonly name = 'advanced-physics';
readonly version = '2.0.0';
// 声明依赖基础物理插件
readonly dependencies = ['physics-plugin'] as const;
install(core: Core, services: ServiceContainer): void {
// 可以安全地使用physics-plugin提供的服务
const physicsService = services.resolve(PhysicsService);
// ...
}
uninstall(): void {
// 清理
}
}
```
### 依赖检查
框架会自动检查依赖关系,如果依赖未满足会抛出错误:
```typescript
// 错误physics-plugin 未安装
try {
await Core.installPlugin(new AdvancedPhysicsPlugin());
} catch (error) {
console.error(error); // Plugin advanced-physics has unmet dependencies: physics-plugin
}
// 正确:先安装依赖
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
```
### 卸载顺序
框架会检查依赖关系,防止卸载被其他插件依赖的插件:
```typescript
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
// 错误physics-plugin 被 advanced-physics 依赖
try {
await Core.uninstallPlugin('physics-plugin');
} catch (error) {
console.error(error); // Cannot uninstall plugin physics-plugin: it is required by advanced-physics
}
// 正确:先卸载依赖它的插件
await Core.uninstallPlugin('advanced-physics');
await Core.uninstallPlugin('physics-plugin');
```
## 插件管理
### 通过 Core 管理
Core 类提供了便捷的插件管理方法:
```typescript
// 安装插件
await Core.installPlugin(myPlugin);
// 卸载插件
await Core.uninstallPlugin('plugin-name');
// 检查插件是否已安装
if (Core.isPluginInstalled('plugin-name')) {
// ...
}
// 获取插件实例
const plugin = Core.getPlugin('plugin-name');
```
### 通过 PluginManager 管理
也可以直接使用 PluginManager 服务:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// 获取所有插件
const allPlugins = pluginManager.getAllPlugins();
console.log(`Total plugins: ${allPlugins.length}`);
// 获取插件元数据
const metadata = pluginManager.getMetadata('my-plugin');
if (metadata) {
console.log(`State: ${metadata.state}`);
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
}
// 获取所有插件元数据
const allMetadata = pluginManager.getAllMetadata();
for (const meta of allMetadata) {
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
}
```
## 实用插件示例
### 网络同步插件
```typescript
import { IPlugin, IService, Core, ServiceContainer } from '@esengine/ecs-framework';
class NetworkSyncService implements IService {
private ws?: WebSocket;
connect(url: string) {
this.ws = new WebSocket(url);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
}
private handleMessage(data: any) {
// 处理网络消息
}
dispose(): void {
if (this.ws) {
this.ws.close();
this.ws = undefined;
}
}
}
class NetworkSyncPlugin implements IPlugin {
readonly name = 'network-sync';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkSyncService);
// 自动连接
const network = services.resolve(NetworkSyncService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务会自动dispose
}
}
```
### 性能分析插件
```typescript
class PerformanceAnalysisPlugin implements IPlugin {
readonly name = 'performance-analysis';
readonly version = '1.0.0';
private frameCount = 0;
private totalTime = 0;
install(core: Core, services: ServiceContainer): void {
const monitor = services.resolve(PerformanceMonitor);
monitor.enable();
// 定期输出性能报告
const timer = services.resolve(TimerManager);
timer.schedule(5.0, true, null, () => {
this.printReport(monitor);
});
}
uninstall(): void {
// 清理
}
private printReport(monitor: PerformanceMonitor) {
console.log('=== Performance Report ===');
console.log(`FPS: ${monitor.getFPS()}`);
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
}
}
```
## 最佳实践
### 命名规范
- 插件名称使用小写字母和连字符:`my-awesome-plugin`
- 版本号遵循语义化版本规范:`1.0.0`
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-awesome-plugin'; // 好
readonly version = '1.0.0'; // 好
}
```
### 清理资源
始终在 `uninstall` 中清理插件创建的所有资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private timerId?: number;
private listener?: () => void;
install(core: Core, services: ServiceContainer): void {
// 添加定时器
this.timerId = setInterval(() => {
// ...
}, 1000);
// 添加事件监听
this.listener = () => {};
window.addEventListener('resize', this.listener);
}
uninstall(): void {
// 清理定时器
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
}
// 移除事件监听
if (this.listener) {
window.removeEventListener('resize', this.listener);
this.listener = undefined;
}
}
}
```
### 错误处理
在插件中妥善处理错误,避免影响整个应用:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
try {
// 可能失败的操作
await this.loadConfig();
} catch (error) {
console.error('Failed to load plugin config:', error);
throw error; // 重新抛出,让框架知道安装失败
}
}
async uninstall(): Promise<void> {
try {
await this.cleanup();
} catch (error) {
console.error('Failed to cleanup plugin:', error);
// 即使清理失败也不应该阻止卸载
}
}
private async loadConfig() {
// 加载配置
}
private async cleanup() {
// 清理
}
}
```
### 配置化
允许用户配置插件行为:
```typescript
interface NetworkPluginConfig {
serverUrl: string;
autoReconnect: boolean;
timeout: number;
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
constructor(private config: NetworkPluginConfig) {}
install(core: Core, services: ServiceContainer): void {
const network = new NetworkService(this.config);
services.registerInstance(NetworkService, network);
}
uninstall(): void {
// 清理
}
}
// 使用
const plugin = new NetworkPlugin({
serverUrl: 'ws://localhost:8080',
autoReconnect: true,
timeout: 5000
});
await Core.installPlugin(plugin);
```
## 常见问题
### 插件安装失败
**问题**: 插件安装时抛出错误
**原因**:
- 依赖未满足
- install 方法中有异常
- 服务注册冲突
**解决**:
1. 检查依赖是否已安装
2. 查看错误日志
3. 确保服务名称不冲突
### 插件卸载后仍有副作用
**问题**: 卸载插件后,插件的功能仍在运行
**原因**: uninstall 方法中未正确清理资源
**解决**: 确保在 uninstall 中清理:
- 定时器
- 事件监听器
- WebSocket连接
- 系统引用
### 何时使用插件
**适合使用插件**:
- 可选功能(调试工具、性能分析)
- 第三方集成(网络库、物理引擎)
- 跨项目复用的功能模块
**不适合使用插件**:
- 核心游戏逻辑
- 简单的工具类
- 项目特定的功能
## 相关链接
- [服务容器](./service-container.md) - 在插件中使用服务容器
- [系统架构](./system.md) - 在插件中添加系统
- [快速开始](./getting-started.md) - Core 初始化和基础使用

675
docs/guide/scene-manager.md Normal file
View File

@@ -0,0 +1,675 @@
# SceneManager
SceneManager 是 ECS Framework 提供的轻量级场景管理器,适用于 95% 的游戏应用。它提供简单直观的 API支持场景切换和延迟加载。
## 适用场景
SceneManager 适合以下场景:
- 单人游戏
- 简单多人游戏
- 移动游戏
- 需要场景切换的游戏(菜单、游戏、暂停等)
- 不需要多 World 隔离的项目
## 特点
- 轻量级,零额外开销
- 简单直观的 API
- 支持延迟场景切换(避免在当前帧中途切换)
- 自动管理 ECS 流式 API
- 自动处理场景生命周期
- 集成在 Core 中,自动更新
## 基本使用
### 推荐方式:使用 Core 的静态方法
这是最简单和推荐的方式,适合大多数应用:
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// 1. 初始化 Core
Core.create({ debug: true });
// 2. 创建并设置场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 创建初始实体
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Health(100));
}
public onStart(): void {
console.log("游戏场景已启动");
}
}
// 3. 设置场景
Core.setScene(new GameScene());
// 4. 游戏循环Core.update 会自动更新场景)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有服务和场景
}
// Laya 引擎集成
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime);
});
// Cocos Creator 集成
update(deltaTime: number) {
Core.update(deltaTime);
}
```
### 高级方式:直接使用 SceneManager
如果需要更多控制,可以直接使用 SceneManager
```typescript
import { Core, SceneManager, Scene } from '@esengine/ecs-framework';
// 初始化 Core
Core.create({ debug: true });
// 获取 SceneManagerCore 已自动创建并注册)
const sceneManager = Core.services.resolve(SceneManager);
// 设置场景
const gameScene = new GameScene();
sceneManager.setScene(gameScene);
// 游戏循环(仍然使用 Core.update
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Core会自动调用sceneManager.update()
}
```
**重要**:无论使用哪种方式,游戏循环中都应该只调用 `Core.update()`,它会自动更新 SceneManager 和场景。不需要手动调用 `sceneManager.update()`
## 场景切换
### 立即切换
使用 `Core.setScene()``sceneManager.setScene()` 立即切换场景:
```typescript
// 方式1使用 Core推荐
Core.setScene(new MenuScene());
// 方式2使用 SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new MenuScene());
```
### 延迟切换
使用 `Core.loadScene()``sceneManager.loadScene()` 延迟切换场景,场景会在下一帧切换:
```typescript
// 方式1使用 Core推荐
Core.loadScene(new GameOverScene());
// 方式2使用 SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.loadScene(new GameOverScene());
```
在 System 中切换场景时,应该使用延迟切换:
```typescript
class GameOverSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
const player = entities.find(e => e.name === 'Player');
const health = player?.getComponent(Health);
if (health && health.value <= 0) {
// 延迟切换到游戏结束场景(下一帧生效)
Core.loadScene(new GameOverScene());
// 当前帧继续执行,不会中断当前系统的处理
}
}
}
```
### 完整的场景切换示例
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// 初始化
Core.create({ debug: true });
// 菜单场景
class MenuScene extends Scene {
protected initialize(): void {
this.name = "MenuScene";
// 监听开始游戏事件
this.eventSystem.on('start_game', () => {
Core.loadScene(new GameScene());
});
}
public onStart(): void {
console.log("显示菜单界面");
}
public unload(): void {
console.log("菜单场景卸载");
}
}
// 游戏场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 创建游戏实体
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Health(100));
// 监听游戏结束事件
this.eventSystem.on('game_over', () => {
Core.loadScene(new GameOverScene());
});
}
public onStart(): void {
console.log("游戏开始");
}
public unload(): void {
console.log("游戏场景卸载");
}
}
// 游戏结束场景
class GameOverScene extends Scene {
protected initialize(): void {
this.name = "GameOverScene";
// 监听返回菜单事件
this.eventSystem.on('back_to_menu', () => {
Core.loadScene(new MenuScene());
});
}
public onStart(): void {
console.log("显示游戏结束界面");
}
}
// 开始游戏
Core.setScene(new MenuScene());
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新场景
}
```
## API 参考
### Core 静态方法(推荐)
#### Core.setScene()
立即切换场景。
```typescript
public static setScene<T extends IScene>(scene: T): T
```
**参数**
- `scene` - 要设置的场景实例
**返回**
- 返回设置的场景实例
**示例**
```typescript
const gameScene = Core.setScene(new GameScene());
console.log(gameScene.name);
```
#### Core.loadScene()
延迟加载场景(下一帧切换)。
```typescript
public static loadScene<T extends IScene>(scene: T): void
```
**参数**
- `scene` - 要加载的场景实例
**示例**
```typescript
Core.loadScene(new GameOverScene());
```
#### Core.scene
获取当前活跃的场景。
```typescript
public static get scene(): IScene | null
```
**返回**
- 当前场景实例,如果没有场景则返回 null
**示例**
```typescript
const currentScene = Core.scene;
if (currentScene) {
console.log(`当前场景: ${currentScene.name}`);
}
```
#### Core.ecsAPI
获取 ECS 流式 API。
```typescript
public static get ecsAPI(): ECSFluentAPI | null
```
**返回**
- ECS API 实例,如果当前没有场景则返回 null
**示例**
```typescript
const api = Core.ecsAPI;
if (api) {
// 查询实体
const enemies = api.find(Enemy, Transform);
// 发射事件
api.emit('game:start', { level: 1 });
}
```
### SceneManager 方法(高级)
如果需要直接使用 SceneManager可以通过服务容器获取
```typescript
const sceneManager = Core.services.resolve(SceneManager);
```
#### setScene()
立即切换场景。
```typescript
public setScene<T extends IScene>(scene: T): T
```
#### loadScene()
延迟加载场景。
```typescript
public loadScene<T extends IScene>(scene: T): void
```
#### currentScene
获取当前场景。
```typescript
public get currentScene(): IScene | null
```
#### api
获取 ECS 流式 API。
```typescript
public get api(): ECSFluentAPI | null
```
#### hasScene
检查是否有活跃场景。
```typescript
public get hasScene(): boolean
```
#### hasPendingScene
检查是否有待切换的场景。
```typescript
public get hasPendingScene(): boolean
```
## 使用 ECS 流式 API
通过 `Core.ecsAPI` 可以方便地访问场景的 ECS 功能:
```typescript
const api = Core.ecsAPI;
if (!api) {
console.error('没有活跃场景');
return;
}
// 查询实体
const players = api.find(Player, Transform);
const enemies = api.find(Enemy, Health, Transform);
// 发射事件
api.emit('player:scored', { points: 100 });
// 监听事件
api.on('enemy:died', (data) => {
console.log('敌人死亡:', data);
});
```
## 最佳实践
### 1. 使用 Core 的静态方法
```typescript
// 推荐:使用 Core 的静态方法
Core.setScene(new GameScene());
Core.loadScene(new MenuScene());
const currentScene = Core.scene;
// 不推荐:除非有特殊需求,否则不需要直接使用 SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
### 2. 只调用 Core.update()
```typescript
// 正确:只调用 Core.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有服务和场景
}
// 错误:不要手动调用 sceneManager.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime);
sceneManager.update(); // 重复更新,会导致问题!
}
```
### 3. 使用延迟切换避免问题
在 System 中切换场景时,应该使用 `loadScene()` 而不是 `setScene()`
```typescript
// 推荐:延迟切换
class HealthSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health.value <= 0) {
Core.loadScene(new GameOverScene());
// 当前帧继续处理其他实体
}
}
}
}
// 不推荐:立即切换可能导致问题
class HealthSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health.value <= 0) {
Core.setScene(new GameOverScene());
// 场景立即切换,当前帧的其他实体可能无法正常处理
}
}
}
}
```
### 4. 场景职责分离
每个场景应该只负责一个特定的游戏状态:
```typescript
// 好的设计 - 职责清晰
class MenuScene extends Scene {
// 只处理菜单相关逻辑
}
class GameScene extends Scene {
// 只处理游戏玩法逻辑
}
class PauseScene extends Scene {
// 只处理暂停界面逻辑
}
// 避免的设计 - 职责混乱
class MegaScene extends Scene {
// 包含菜单、游戏、暂停等所有逻辑
}
```
### 5. 资源管理
在场景的 `unload()` 方法中清理资源:
```typescript
class GameScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
this.textures.set('player', loadTexture('player.png'));
this.sounds.set('bgm', loadSound('bgm.mp3'));
}
public unload(): void {
// 清理资源
this.textures.clear();
this.sounds.clear();
console.log('场景资源已清理');
}
}
```
### 6. 事件驱动的场景切换
使用事件系统来触发场景切换,保持代码解耦:
```typescript
class GameScene extends Scene {
protected initialize(): void {
// 监听场景切换事件
this.eventSystem.on('goto:menu', () => {
Core.loadScene(new MenuScene());
});
this.eventSystem.on('goto:gameover', (data) => {
Core.loadScene(new GameOverScene());
});
}
}
// 在 System 中触发事件
class GameLogicSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
if (levelComplete) {
this.scene.eventSystem.emitSync('goto:gameover', {
score: 1000,
level: 5
});
}
}
}
```
## 架构层次
SceneManager 在 ECS Framework 中的位置:
```
Core (全局服务)
└── SceneManager (场景管理,自动更新)
└── Scene (当前场景)
├── EntitySystem (系统)
├── Entity (实体)
└── Component (组件)
```
## 与 WorldManager 的对比
| 特性 | SceneManager | WorldManager |
|------|--------------|--------------|
| 适用场景 | 95% 的游戏应用 | 高级多世界隔离场景 |
| 复杂度 | 简单 | 复杂 |
| 场景数量 | 单场景(可切换) | 多 World每个 World 多场景 |
| 性能开销 | 最小 | 较高 |
| 使用方式 | `Core.setScene()` | `worldManager.createWorld()` |
**何时使用 SceneManager**
- 单人游戏
- 简单的多人游戏
- 移动游戏
- 场景之间需要切换但不需要同时运行
**何时使用 WorldManager**
- MMO 游戏服务器(每个房间一个 World
- 游戏大厅系统(每个游戏房间完全隔离)
- 需要运行多个完全独立的游戏实例
## 完整示例
```typescript
import { Core, Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// 定义组件
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// 定义系统
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// 定义场景
class MenuScene extends Scene {
protected initialize(): void {
this.name = "MenuScene";
console.log("菜单场景初始化");
}
public onStart(): void {
console.log("菜单场景启动");
}
}
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
}
public onStart(): void {
console.log('游戏场景启动');
}
public unload(): void {
console.log('游戏场景卸载');
}
}
// 初始化
Core.create({ debug: true });
// 设置初始场景
Core.setScene(new MenuScene());
// 游戏循环
let lastTime = 0;
function gameLoop(currentTime: number) {
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
// 只需要调用 Core.update它会自动更新场景
Core.update(deltaTime);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
// 切换到游戏场景
setTimeout(() => {
Core.loadScene(new GameScene());
}, 3000);
```
SceneManager 为大多数游戏提供了简单而强大的场景管理能力。通过 Core 的静态方法,你可以轻松地管理场景切换。如果你需要更高级的多世界隔离功能,请参考 [WorldManager](./world-manager.md) 文档。

661
docs/guide/scene.md Normal file
View File

@@ -0,0 +1,661 @@
# 场景管理
在 ECS 架构中场景Scene是游戏世界的容器负责管理实体、系统和组件的生命周期。场景提供了完整的 ECS 运行环境。
## 基本概念
场景是 ECS 框架的核心容器,提供:
- 实体的创建、管理和销毁
- 系统的注册和执行调度
- 组件的存储和查询
- 事件系统支持
- 性能监控和调试信息
## 场景管理方式
ECS Framework 提供了两种场景管理方式:
1. **[SceneManager](./scene-manager.md)** - 适用于 95% 的游戏应用
- 单人游戏、简单多人游戏、移动游戏
- 轻量级,简单直观的 API
- 支持场景切换
2. **[WorldManager](./world-manager.md)** - 适用于高级多世界隔离场景
- MMO 游戏服务器、游戏房间系统
- 多 World 管理,每个 World 可包含多个场景
- 完全隔离的独立环境
本文档重点介绍 Scene 类本身的使用方法。关于场景管理器的详细信息,请查看对应的文档。
## 创建场景
### 继承 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() 由框架自动调用
```
**生命周期方法**
1. `initialize()` - 场景初始化,设置系统和初始实体
2. `begin()` / `onStart()` - 场景开始运行
3. `update()` - 每帧更新(由场景管理器调用)
4. `end()` / `unload()` - 场景卸载,清理资源
## 实体管理
### 创建实体
```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()
});
// 发送事件(异步)
this.eventSystem.emit('async_event', {
data: "异步事件数据"
});
}
}
```
### 事件系统 API
```typescript
// 监听事件
this.eventSystem.on('event_name', callback);
// 监听一次(自动取消订阅)
this.eventSystem.once('event_name', callback);
// 取消监听
this.eventSystem.off('event_name', callback);
// 同步发送事件
this.eventSystem.emitSync('event_name', data);
// 异步发送事件
this.eventSystem.emit('event_name', data);
// 清除所有事件监听
this.eventSystem.clear();
```
## 场景统计和调试
### 获取场景统计
```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} 个实体`);
});
}
}
```
## 组件查询
Scene 提供了强大的组件查询系统:
```typescript
class QueryScene extends Scene {
protected initialize(): void {
// 创建一些实体
for (let i = 0; i < 10; i++) {
const entity = this.createEntity(`Entity_${i}`);
entity.addComponent(new Transform(i * 10, 0));
entity.addComponent(new Velocity(1, 0));
if (i % 2 === 0) {
entity.addComponent(new Renderer());
}
}
}
public queryEntities(): void {
// 通过 QuerySystem 查询
const entities = this.querySystem.query([Transform, Velocity]);
console.log(`找到 ${entities.length} 个有 Transform 和 Velocity 的实体`);
// 使用 ECS 流式 API如果通过 SceneManager
// const api = sceneManager.api;
// const entities = api?.find(Transform, Velocity);
}
}
```
## 性能监控
Scene 内置了性能监控功能:
```typescript
class PerformanceScene extends Scene {
public showPerformance(): void {
// 获取性能数据
const perfData = this.performanceMonitor?.getPerformanceData();
if (perfData) {
console.log('FPS:', perfData.fps);
console.log('帧时间:', perfData.frameTime);
console.log('实体更新时间:', perfData.entityUpdateTime);
console.log('系统更新时间:', perfData.systemUpdateTime);
}
// 获取性能报告
const report = this.performanceMonitor?.generateReport();
if (report) {
console.log('性能报告:', report);
}
}
}
```
## 最佳实践
### 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<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
// 加载场景所需资源
this.textures.set('player', this.loadTexture('player.png'));
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
}
public unload(): void {
// 清理资源
this.textures.clear();
this.sounds.clear();
console.log('场景资源已清理');
}
private loadTexture(path: string): any {
// 加载纹理
return null;
}
private loadSound(path: string): any {
// 加载音效
return null;
}
}
```
### 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 {
// 处理玩家输入
}
public unload(): void {
// 清理事件监听
this.eventSystem.clear();
}
}
```
### 5. 初始化顺序
```typescript
class ProperInitScene extends Scene {
protected initialize(): void {
// 1. 首先设置场景配置
this.name = "GameScene";
// 2. 然后添加系统(按依赖顺序)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
// 3. 最后创建实体
this.createEntities();
// 4. 设置事件监听
this.setupEvents();
}
private createEntities(): void {
// 创建实体
}
private setupEvents(): void {
// 设置事件监听
}
}
```
## 完整示例
```typescript
import { Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// 定义组件
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// 定义系统
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// 定义场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
// 设置事件监听
this.eventSystem.on('player_died', () => {
console.log('玩家死亡!');
});
}
public onStart(): void {
console.log('游戏场景启动');
}
public unload(): void {
console.log('游戏场景卸载');
this.eventSystem.clear();
}
}
// 使用场景
// 方式1通过 SceneManager推荐
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
// 方式2通过 WorldManager高级用例
import { WorldManager } from '@esengine/ecs-framework';
const worldManager = Core.services.resolve(WorldManager);
const world = worldManager.createWorld('game');
world.createScene('main', new GameScene());
world.setSceneActive('main', true);
```
## 下一步
- 了解 [SceneManager](./scene-manager.md) - 适用于大多数游戏的简单场景管理
- 了解 [WorldManager](./world-manager.md) - 适用于需要多世界隔离的高级场景
场景是 ECS 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。

823
docs/guide/serialization.md Normal file
View File

@@ -0,0 +1,823 @@
# 序列化系统
序列化系统提供了完整的场景、实体和组件数据持久化方案,支持全量序列化和增量序列化两种模式,适用于游戏存档、网络同步、场景编辑器、时间回溯等场景。
## 基本概念
序列化系统分为两个层次:
- **全量序列化**:序列化完整的场景状态,包括所有实体、组件和场景数据
- **增量序列化**:只序列化相对于基础快照的变更部分,大幅减少数据量
### 支持的数据格式
- **JSON格式**:人类可读,便于调试和编辑
- **Binary格式**使用MessagePack体积更小性能更高
> **📢 v2.2.2 重要变更**
>
> 从 v2.2.2 开始,二进制序列化格式返回 `Uint8Array` 而非 Node.js 的 `Buffer`,以确保浏览器兼容性:
> - `serialize({ format: 'binary' })` 返回 `string | Uint8Array`(原为 `string | Buffer`
> - `deserialize(data)` 接收 `string | Uint8Array`(原为 `string | Buffer`
> - `applyIncremental(data)` 接收 `IncrementalSnapshot | string | Uint8Array`(原为包含 `Buffer`
>
> **迁移影响**
> - ✅ **运行时兼容**Node.js 的 `Buffer` 继承自 `Uint8Array`,现有代码可直接运行
> - ⚠️ **类型检查**:如果你的 TypeScript 代码中显式使用了 `Buffer` 类型,需要改为 `Uint8Array`
> - ✅ **浏览器支持**`Uint8Array` 是标准 JavaScript 类型,所有现代浏览器都支持
## 全量序列化
### 基础用法
#### 1. 标记可序列化组件
使用 `@Serializable``@Serialize` 装饰器标记需要序列化的组件和字段:
```typescript
import { Component, ECSComponent, Serializable, Serialize } from '@esengine/ecs-framework';
@ECSComponent('Player')
@Serializable({ version: 1 })
class PlayerComponent extends Component {
@Serialize()
public name: string = '';
@Serialize()
public level: number = 1;
@Serialize()
public experience: number = 0;
@Serialize()
public position: { x: number; y: number } = { x: 0, y: 0 };
// 不使用 @Serialize() 的字段不会被序列化
private tempData: any = null;
}
```
#### 2. 序列化场景
```typescript
// JSON格式序列化
const jsonData = scene.serialize({
format: 'json',
pretty: true // 美化输出
});
// 保存到本地存储
localStorage.setItem('gameSave', jsonData);
// Binary格式序列化更小的体积
const binaryData = scene.serialize({
format: 'binary'
});
// 保存为文件Node.js环境
// 注意binaryData 是 Uint8Array 类型Node.js 的 fs 可以直接写入
fs.writeFileSync('save.bin', binaryData);
```
#### 3. 反序列化场景
```typescript
// 从JSON恢复
const saveData = localStorage.getItem('gameSave');
if (saveData) {
scene.deserialize(saveData, {
strategy: 'replace' // 替换当前场景内容
});
}
// 从Binary恢复
const binaryData = fs.readFileSync('save.bin');
scene.deserialize(binaryData, {
strategy: 'merge' // 合并到现有场景
});
```
### 序列化选项
#### SerializationOptions
```typescript
interface SceneSerializationOptions {
// 指定要序列化的组件类型(可选)
components?: ComponentType[];
// 序列化格式:'json' 或 'binary'
format?: 'json' | 'binary';
// JSON美化输出
pretty?: boolean;
// 包含元数据
includeMetadata?: boolean;
}
```
示例:
```typescript
// 只序列化特定组件类型
const saveData = scene.serialize({
format: 'json',
components: [PlayerComponent, InventoryComponent],
pretty: true,
includeMetadata: true
});
```
#### DeserializationOptions
```typescript
interface SceneDeserializationOptions {
// 反序列化策略
strategy?: 'merge' | 'replace';
// 组件类型注册表(可选,默认使用全局注册表)
componentRegistry?: Map<string, ComponentType>;
}
```
### 高级装饰器
#### 字段序列化选项
```typescript
@ECSComponent('Advanced')
@Serializable({ version: 1 })
class AdvancedComponent extends Component {
// 使用别名
@Serialize({ alias: 'playerName' })
public name: string = '';
// 自定义序列化器
@Serialize({
serializer: (value: Date) => value.toISOString(),
deserializer: (value: string) => new Date(value)
})
public createdAt: Date = new Date();
// 忽略序列化
@IgnoreSerialization()
public cachedData: any = null;
}
```
#### 集合类型序列化
```typescript
@ECSComponent('Collections')
@Serializable({ version: 1 })
class CollectionsComponent extends Component {
// Map序列化
@SerializeAsMap()
public inventory: Map<string, number> = new Map();
// Set序列化
@SerializeAsSet()
public acquiredSkills: Set<string> = new Set();
constructor() {
super();
this.inventory.set('gold', 100);
this.inventory.set('silver', 50);
this.acquiredSkills.add('attack');
this.acquiredSkills.add('defense');
}
}
```
### 场景自定义数据
除了实体和组件,还可以序列化场景级别的配置数据:
```typescript
// 设置场景数据
scene.sceneData.set('weather', 'rainy');
scene.sceneData.set('difficulty', 'hard');
scene.sceneData.set('checkpoint', { x: 100, y: 200 });
// 序列化时会自动包含场景数据
const saveData = scene.serialize({ format: 'json' });
// 反序列化后场景数据会恢复
scene.deserialize(saveData);
console.log(scene.sceneData.get('weather')); // 'rainy'
```
## 增量序列化
增量序列化只保存场景的变更部分,适用于网络同步、撤销/重做、时间回溯等需要频繁保存状态的场景。
### 基础用法
#### 1. 创建基础快照
```typescript
// 在需要开始记录变更前创建基础快照
scene.createIncrementalSnapshot();
```
#### 2. 修改场景
```typescript
// 添加实体
const enemy = scene.createEntity('Enemy');
enemy.addComponent(new PositionComponent(100, 200));
enemy.addComponent(new HealthComponent(50));
// 修改组件
const player = scene.findEntity('Player');
const pos = player.getComponent(PositionComponent);
pos.x = 300;
pos.y = 400;
// 删除组件
player.removeComponentByType(BuffComponent);
// 删除实体
const oldEntity = scene.findEntity('ToDelete');
oldEntity.destroy();
// 修改场景数据
scene.sceneData.set('score', 1000);
```
#### 3. 获取增量变更
```typescript
// 获取相对于基础快照的所有变更
const incremental = scene.serializeIncremental();
// 查看变更统计
const stats = IncrementalSerializer.getIncrementalStats(incremental);
console.log('总变更数:', stats.totalChanges);
console.log('新增实体:', stats.addedEntities);
console.log('删除实体:', stats.removedEntities);
console.log('新增组件:', stats.addedComponents);
console.log('更新组件:', stats.updatedComponents);
```
#### 4. 序列化增量数据
```typescript
// JSON格式默认
const jsonData = IncrementalSerializer.serializeIncremental(incremental, {
format: 'json'
});
// 二进制格式(更小的体积,更高性能)
const binaryData = IncrementalSerializer.serializeIncremental(incremental, {
format: 'binary'
});
// 美化JSON输出便于调试
const prettyJson = IncrementalSerializer.serializeIncremental(incremental, {
format: 'json',
pretty: true
});
// 发送或保存
socket.send(binaryData); // 网络传输使用二进制
localStorage.setItem('changes', jsonData); // 本地存储可用JSON
```
#### 5. 应用增量变更
```typescript
// 在另一个场景应用变更
const otherScene = new Scene();
// 直接应用增量对象
otherScene.applyIncremental(incremental);
// 从JSON字符串应用
const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
otherScene.applyIncremental(jsonData);
// 从二进制Uint8Array应用
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
otherScene.applyIncremental(binaryData);
```
### 增量快照管理
#### 更新快照基准
在应用增量变更后,可以更新快照基准:
```typescript
// 创建初始快照
scene.createIncrementalSnapshot();
// 第一次修改
entity.addComponent(new VelocityComponent(5, 0));
const incremental1 = scene.serializeIncremental();
// 更新基准(将当前状态设为新的基准)
scene.updateIncrementalSnapshot();
// 第二次修改(增量将基于更新后的基准)
entity.getComponent(VelocityComponent).dx = 10;
const incremental2 = scene.serializeIncremental();
```
#### 清除快照
```typescript
// 释放快照占用的内存
scene.clearIncrementalSnapshot();
// 检查是否有快照
if (scene.hasIncrementalSnapshot()) {
console.log('存在增量快照');
}
```
### 增量序列化选项
```typescript
interface IncrementalSerializationOptions {
// 是否进行组件数据的深度对比
// 默认true设为false可提升性能但可能漏掉组件内部字段变更
deepComponentComparison?: boolean;
// 是否跟踪场景数据变更
// 默认true
trackSceneData?: boolean;
// 是否压缩快照使用JSON序列化
// 默认false
compressSnapshot?: boolean;
// 序列化格式
// 'json': JSON格式可读性好方便调试
// 'binary': MessagePack二进制格式体积小性能高
// 默认 'json'
format?: 'json' | 'binary';
// 是否美化JSON输出仅在format='json'时有效)
// 默认false
pretty?: boolean;
}
// 使用选项
scene.createIncrementalSnapshot({
deepComponentComparison: true,
trackSceneData: true
});
```
### 增量数据结构
增量快照包含以下变更类型:
```typescript
interface IncrementalSnapshot {
version: number; // 快照版本号
timestamp: number; // 时间戳
sceneName: string; // 场景名称
baseVersion: number; // 基础版本号
entityChanges: EntityChange[]; // 实体变更
componentChanges: ComponentChange[]; // 组件变更
sceneDataChanges: SceneDataChange[]; // 场景数据变更
}
// 变更操作类型
enum ChangeOperation {
EntityAdded = 'entity_added',
EntityRemoved = 'entity_removed',
EntityUpdated = 'entity_updated',
ComponentAdded = 'component_added',
ComponentRemoved = 'component_removed',
ComponentUpdated = 'component_updated',
SceneDataUpdated = 'scene_data_updated'
}
```
## 版本迁移
当组件结构发生变化时,版本迁移系统可以自动升级旧版本的存档数据。
### 注册迁移函数
```typescript
import { VersionMigrationManager } from '@esengine/ecs-framework';
// 假设 PlayerComponent v1 有 hp 字段
// v2 改为 health 和 maxHealth 字段
// 注册从版本1到版本2的迁移
VersionMigrationManager.registerComponentMigration(
'Player',
1, // 从版本
2, // 到版本
(data) => {
// 迁移逻辑
const newData = {
...data,
health: data.hp,
maxHealth: data.hp,
};
delete newData.hp;
return newData;
}
);
```
### 使用迁移构建器
```typescript
import { MigrationBuilder } from '@esengine/ecs-framework';
new MigrationBuilder()
.forComponent('Player')
.fromVersionToVersion(2, 3)
.migrate((data) => {
// 从版本2迁移到版本3
data.experience = data.exp || 0;
delete data.exp;
return data;
});
```
### 场景级迁移
```typescript
// 注册场景级迁移
VersionMigrationManager.registerSceneMigration(
1, // 从版本
2, // 到版本
(scene) => {
// 迁移场景结构
scene.metadata = {
...scene.metadata,
migratedFrom: 1
};
return scene;
}
);
```
### 检查迁移路径
```typescript
// 检查是否可以迁移
const canMigrate = VersionMigrationManager.canMigrateComponent(
'Player',
1, // 从版本
3 // 到版本
);
if (canMigrate) {
// 可以安全迁移
scene.deserialize(oldSaveData);
}
// 获取迁移路径
const path = VersionMigrationManager.getComponentMigrationPath('Player');
console.log('可用迁移版本:', path); // [1, 2, 3]
```
## 使用场景
### 游戏存档系统
```typescript
class SaveSystem {
private static SAVE_KEY = 'game_save';
// 保存游戏
public static saveGame(scene: Scene): void {
const saveData = scene.serialize({
format: 'json',
pretty: false
});
localStorage.setItem(this.SAVE_KEY, saveData);
console.log('游戏已保存');
}
// 加载游戏
public static loadGame(scene: Scene): boolean {
const saveData = localStorage.getItem(this.SAVE_KEY);
if (saveData) {
scene.deserialize(saveData, {
strategy: 'replace'
});
console.log('游戏已加载');
return true;
}
return false;
}
// 检查是否有存档
public static hasSave(): boolean {
return localStorage.getItem(this.SAVE_KEY) !== null;
}
}
```
### 网络同步
```typescript
class NetworkSync {
private baseSnapshot?: any;
private syncInterval: number = 100; // 100ms同步一次
constructor(private scene: Scene, private socket: WebSocket) {
this.setupSync();
}
private setupSync(): void {
// 创建基础快照
this.scene.createIncrementalSnapshot();
// 定期发送增量
setInterval(() => {
this.sendIncremental();
}, this.syncInterval);
// 接收远程增量
this.socket.onmessage = (event) => {
this.receiveIncremental(event.data);
};
}
private sendIncremental(): void {
const incremental = this.scene.serializeIncremental();
const stats = IncrementalSerializer.getIncrementalStats(incremental);
// 只在有变更时发送
if (stats.totalChanges > 0) {
// 使用二进制格式减少网络传输量
const binaryData = IncrementalSerializer.serializeIncremental(incremental, {
format: 'binary'
});
this.socket.send(binaryData);
// 更新基准
this.scene.updateIncrementalSnapshot();
}
}
private receiveIncremental(data: ArrayBuffer): void {
// 直接应用二进制数据ArrayBuffer 转 Uint8Array
const uint8Array = new Uint8Array(data);
this.scene.applyIncremental(uint8Array);
}
}
```
### 撤销/重做系统
```typescript
class UndoRedoSystem {
private history: IncrementalSnapshot[] = [];
private currentIndex: number = -1;
private maxHistory: number = 50;
constructor(private scene: Scene) {
// 创建初始快照
this.scene.createIncrementalSnapshot();
this.saveState('Initial');
}
// 保存当前状态
public saveState(label: string): void {
const incremental = this.scene.serializeIncremental();
// 删除当前位置之后的历史
this.history = this.history.slice(0, this.currentIndex + 1);
// 添加新状态
this.history.push(incremental);
this.currentIndex++;
// 限制历史记录数量
if (this.history.length > this.maxHistory) {
this.history.shift();
this.currentIndex--;
}
// 更新快照基准
this.scene.updateIncrementalSnapshot();
}
// 撤销
public undo(): boolean {
if (this.currentIndex > 0) {
this.currentIndex--;
const incremental = this.history[this.currentIndex];
this.scene.applyIncremental(incremental);
return true;
}
return false;
}
// 重做
public redo(): boolean {
if (this.currentIndex < this.history.length - 1) {
this.currentIndex++;
const incremental = this.history[this.currentIndex];
this.scene.applyIncremental(incremental);
return true;
}
return false;
}
public canUndo(): boolean {
return this.currentIndex > 0;
}
public canRedo(): boolean {
return this.currentIndex < this.history.length - 1;
}
}
```
### 关卡编辑器
```typescript
class LevelEditor {
// 导出关卡
public exportLevel(scene: Scene, filename: string): void {
const levelData = scene.serialize({
format: 'json',
pretty: true,
includeMetadata: true
});
// 浏览器环境
const blob = new Blob([levelData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// 导入关卡
public importLevel(scene: Scene, fileContent: string): void {
scene.deserialize(fileContent, {
strategy: 'replace'
});
}
// 验证关卡数据
public validateLevel(saveData: string): boolean {
const validation = SceneSerializer.validate(saveData);
if (!validation.valid) {
console.error('关卡数据无效:', validation.errors);
return false;
}
return true;
}
// 获取关卡信息(不完全反序列化)
public getLevelInfo(saveData: string): any {
const info = SceneSerializer.getInfo(saveData);
return info;
}
}
```
## 性能优化建议
### 1. 选择合适的格式
- **开发阶段**使用JSON格式便于调试和查看
- **生产环境**使用Binary格式减少30-50%的数据大小
### 2. 按需序列化
```typescript
// 只序列化需要持久化的组件
const saveData = scene.serialize({
format: 'binary',
components: [PlayerComponent, InventoryComponent, QuestComponent]
});
```
### 3. 增量序列化优化
```typescript
// 对于高频同步,关闭深度对比以提升性能
scene.createIncrementalSnapshot({
deepComponentComparison: false // 只检测组件的添加/删除
});
```
### 4. 批量操作
```typescript
// 批量修改后再序列化
scene.entities.buffer.forEach(entity => {
// 批量修改
});
// 一次性序列化所有变更
const incremental = scene.serializeIncremental();
```
## 最佳实践
### 1. 明确序列化字段
```typescript
// 明确标记需要序列化的字段
@ECSComponent('Player')
@Serializable({ version: 1 })
class PlayerComponent extends Component {
@Serialize()
public name: string = '';
@Serialize()
public level: number = 1;
// 运行时数据不序列化
private _cachedSprite: any = null;
}
```
### 2. 使用版本控制
```typescript
// 为组件指定版本
@Serializable({ version: 2 })
class PlayerComponent extends Component {
// 版本2的字段
}
// 注册迁移函数确保兼容性
VersionMigrationManager.registerComponentMigration('Player', 1, 2, migrateV1ToV2);
```
### 3. 避免循环引用
```typescript
// 不要在组件中直接引用其他实体
@ECSComponent('Follower')
@Serializable({ version: 1 })
class FollowerComponent extends Component {
// 存储实体ID而不是实体引用
@Serialize()
public targetId: number = 0;
// 通过场景查找目标实体
public getTarget(scene: Scene): Entity | null {
return scene.entities.findEntityById(this.targetId);
}
}
```
### 4. 压缩大数据
```typescript
// 对于大型数据结构,使用自定义序列化
@ECSComponent('LargeData')
@Serializable({ version: 1 })
class LargeDataComponent extends Component {
@Serialize({
serializer: (data: LargeObject) => compressData(data),
deserializer: (data: CompressedData) => decompressData(data)
})
public data: LargeObject;
}
```
## API参考
### 全量序列化API
- [`Scene.serialize()`](/api/classes/Scene#serialize) - 序列化场景
- [`Scene.deserialize()`](/api/classes/Scene#deserialize) - 反序列化场景
- [`SceneSerializer`](/api/classes/SceneSerializer) - 场景序列化器
- [`ComponentSerializer`](/api/classes/ComponentSerializer) - 组件序列化器
### 增量序列化API
- [`Scene.createIncrementalSnapshot()`](/api/classes/Scene#createincrementalsnapshot) - 创建基础快照
- [`Scene.serializeIncremental()`](/api/classes/Scene#serializeincremental) - 获取增量变更
- [`Scene.applyIncremental()`](/api/classes/Scene#applyincremental) - 应用增量变更支持IncrementalSnapshot对象、JSON字符串或二进制Uint8Array
- [`Scene.updateIncrementalSnapshot()`](/api/classes/Scene#updateincrementalsnapshot) - 更新快照基准
- [`Scene.clearIncrementalSnapshot()`](/api/classes/Scene#clearincrementalsnapshot) - 清除快照
- [`Scene.hasIncrementalSnapshot()`](/api/classes/Scene#hasincrementalsnapshot) - 检查是否有快照
- [`IncrementalSerializer`](/api/classes/IncrementalSerializer) - 增量序列化器
- [`IncrementalSnapshot`](/api/interfaces/IncrementalSnapshot) - 增量快照接口
- [`IncrementalSerializationOptions`](/api/interfaces/IncrementalSerializationOptions) - 增量序列化选项
- [`IncrementalSerializationFormat`](/api/type-aliases/IncrementalSerializationFormat) - 序列化格式类型
### 版本迁移API
- [`VersionMigrationManager`](/api/classes/VersionMigrationManager) - 版本迁移管理器
- `VersionMigrationManager.registerComponentMigration()` - 注册组件迁移
- `VersionMigrationManager.registerSceneMigration()` - 注册场景迁移
- `VersionMigrationManager.canMigrateComponent()` - 检查是否可以迁移
- `VersionMigrationManager.getComponentMigrationPath()` - 获取迁移路径
序列化系统是构建完整游戏的重要基础设施,合理使用可以实现强大的功能,如存档系统、网络同步、关卡编辑器等。

View File

@@ -0,0 +1,589 @@
# 服务容器
服务容器ServiceContainer是 ECS Framework 的依赖注入容器,负责管理框架中所有服务的注册、解析和生命周期。通过服务容器,你可以实现松耦合的架构设计,提高代码的可测试性和可维护性。
## 概述
### 什么是服务容器
服务容器是一个轻量级的依赖注入DI容器它提供了
- **服务注册**: 将服务类型注册到容器中
- **服务解析**: 从容器中获取服务实例
- **生命周期管理**: 自动管理服务实例的创建和销毁
- **依赖注入**: 自动解析服务之间的依赖关系
### 核心概念
#### 服务Service
服务是实现了 `IService` 接口的类,必须提供 `dispose()` 方法用于资源清理:
```typescript
import { IService } from '@esengine/ecs-framework';
class MyService implements IService {
constructor() {
// 初始化逻辑
}
dispose(): void {
// 清理资源
}
}
```
#### 生命周期
服务容器支持两种生命周期:
- **Singleton单例**: 整个应用程序生命周期内只有一个实例,所有解析请求返回同一个实例
- **Transient瞬时**: 每次解析都创建新的实例
## 基础使用
### 访问服务容器
Core 类内置了服务容器,可以通过 `Core.services` 访问:
```typescript
import { Core } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 访问服务容器
const container = Core.services;
```
### 注册服务
#### 注册单例服务
单例服务在首次解析时创建,之后所有解析请求都返回同一个实例:
```typescript
class DataService implements IService {
private data: Map<string, any> = new Map();
getData(key: string) {
return this.data.get(key);
}
setData(key: string, value: any) {
this.data.set(key, value);
}
dispose(): void {
this.data.clear();
}
}
// 注册单例服务
Core.services.registerSingleton(DataService);
```
#### 注册瞬时服务
瞬时服务每次解析都创建新实例,适用于无状态或短生命周期的服务:
```typescript
class CommandService implements IService {
execute(command: string) {
console.log(`Executing: ${command}`);
}
dispose(): void {
// 清理资源
}
}
// 注册瞬时服务
Core.services.registerTransient(CommandService);
```
#### 注册服务实例
直接注册已创建的实例,自动视为单例:
```typescript
const config = new ConfigService();
config.load('./config.json');
// 注册实例
Core.services.registerInstance(ConfigService, config);
```
#### 使用工厂函数注册
工厂函数允许你在创建服务时执行自定义逻辑:
```typescript
Core.services.registerSingleton(LoggerService, (container) => {
const logger = new LoggerService();
logger.setLevel('debug');
return logger;
});
```
### 解析服务
#### resolve 方法
解析服务实例,如果服务未注册会抛出异常:
```typescript
// 解析服务
const dataService = Core.services.resolve(DataService);
dataService.setData('player', { name: 'Alice', score: 100 });
// 单例服务,多次解析返回同一个实例
const same = Core.services.resolve(DataService);
console.log(same === dataService); // true
```
#### tryResolve 方法
尝试解析服务,如果未注册返回 null 而不抛出异常:
```typescript
const optional = Core.services.tryResolve(OptionalService);
if (optional) {
optional.doSomething();
}
```
#### isRegistered 方法
检查服务是否已注册:
```typescript
if (Core.services.isRegistered(DataService)) {
const service = Core.services.resolve(DataService);
}
```
## 内置服务
Core 在初始化时自动注册了以下内置服务:
### TimerManager
定时器管理器,负责管理所有游戏定时器:
```typescript
const timerManager = Core.services.resolve(TimerManager);
// 创建定时器
timerManager.schedule(1.0, false, null, (timer) => {
console.log('1秒后执行');
});
```
### PerformanceMonitor
性能监控器,监控游戏性能并提供优化建议:
```typescript
const monitor = Core.services.resolve(PerformanceMonitor);
// 启用性能监控
monitor.enable();
// 获取性能数据
const fps = monitor.getFPS();
```
### SceneManager
场景管理器,管理单场景应用的场景生命周期:
```typescript
const sceneManager = Core.services.resolve(SceneManager);
// 设置当前场景
sceneManager.setScene(new GameScene());
// 获取当前场景
const currentScene = sceneManager.currentScene;
// 延迟切换场景
sceneManager.loadScene(new MenuScene());
// 更新场景
sceneManager.update();
```
### WorldManager
世界管理器,管理多个独立的 World 实例(高级用例):
```typescript
const worldManager = Core.services.resolve(WorldManager);
// 创建独立的游戏世界
const gameWorld = worldManager.createWorld('game_room_001', {
name: 'GameRoom',
maxScenes: 5
});
// 在World中创建场景
const scene = gameWorld.createScene('battle', new BattleScene());
gameWorld.setSceneActive('battle', true);
// 更新所有World
worldManager.updateAll();
```
**适用场景**:
- SceneManager: 适用于 95% 的游戏(单人游戏、简单多人游戏)
- WorldManager: 适用于 MMO 服务器、游戏房间系统等需要完全隔离的多世界应用
### PoolManager
对象池管理器,管理所有对象池:
```typescript
const poolManager = Core.services.resolve(PoolManager);
// 创建对象池
const bulletPool = poolManager.createPool('bullets', () => new Bullet(), 100);
```
### PluginManager
插件管理器,管理插件的安装和卸载:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// 获取所有已安装的插件
const plugins = pluginManager.getAllPlugins();
```
## 依赖注入
ECS Framework 提供了装饰器来简化依赖注入。
### @Injectable 装饰器
标记类为可注入的服务:
```typescript
import { Injectable, IService } from '@esengine/ecs-framework';
@Injectable()
class GameService implements IService {
constructor() {
console.log('GameService created');
}
dispose(): void {
console.log('GameService disposed');
}
}
```
### @Inject 装饰器
在构造函数中注入依赖:
```typescript
import { Injectable, Inject, IService } from '@esengine/ecs-framework';
@Injectable()
class PlayerService implements IService {
constructor(
@Inject(DataService) private data: DataService,
@Inject(GameService) private game: GameService
) {
// data 和 game 会自动从容器中解析
}
dispose(): void {
// 清理资源
}
}
```
### 注册可注入服务
使用 `registerInjectable` 自动处理依赖注入:
```typescript
import { registerInjectable } from '@esengine/ecs-framework';
// 注册服务(会自动解析@Inject依赖
registerInjectable(Core.services, PlayerService);
// 解析时会自动注入依赖
const player = Core.services.resolve(PlayerService);
```
### @Updatable 装饰器
标记服务为可更新的,使其在每帧自动被调用:
```typescript
import { Injectable, Updatable, IService, IUpdatable } from '@esengine/ecs-framework';
@Injectable()
@Updatable() // 默认优先级为0
class PhysicsService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧更新物理模拟
}
dispose(): void {
// 清理资源
}
}
// 指定更新优先级(数值越小越先执行)
@Injectable()
@Updatable(10)
class RenderService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧渲染
}
dispose(): void {
// 清理资源
}
}
```
使用 `@Updatable` 装饰器的服务会被 Core 自动调用,无需手动管理:
```typescript
// Core.update() 会自动调用所有@Updatable服务的update方法
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有可更新服务
}
```
## 自定义服务
### 创建自定义服务
实现 `IService` 接口并注册到容器:
```typescript
import { IService } from '@esengine/ecs-framework';
class AudioService implements IService {
private sounds: Map<string, HTMLAudioElement> = new Map();
play(soundId: string) {
const sound = this.sounds.get(soundId);
if (sound) {
sound.play();
}
}
load(soundId: string, url: string) {
const audio = new Audio(url);
this.sounds.set(soundId, audio);
}
dispose(): void {
// 停止所有音效并清理
for (const sound of this.sounds.values()) {
sound.pause();
sound.src = '';
}
this.sounds.clear();
}
}
// 注册自定义服务
Core.services.registerSingleton(AudioService);
// 使用服务
const audio = Core.services.resolve(AudioService);
audio.load('jump', '/sounds/jump.mp3');
audio.play('jump');
```
### 服务间依赖
服务可以依赖其他服务:
```typescript
@Injectable()
class ConfigService implements IService {
private config: any = {};
get(key: string) {
return this.config[key];
}
dispose(): void {
this.config = {};
}
}
@Injectable()
class NetworkService implements IService {
constructor(
@Inject(ConfigService) private config: ConfigService
) {
// 使用配置服务
const apiUrl = this.config.get('apiUrl');
}
dispose(): void {
// 清理网络连接
}
}
// 注册服务(按依赖顺序)
registerInjectable(Core.services, ConfigService);
registerInjectable(Core.services, NetworkService);
```
## 高级用法
### 服务替换(测试)
在测试中替换真实服务为模拟服务:
```typescript
// 测试代码
class MockDataService implements IService {
getData(key: string) {
return 'mock data';
}
dispose(): void {}
}
// 注册模拟服务(用于测试)
Core.services.registerInstance(DataService, new MockDataService());
```
### 循环依赖检测
服务容器会自动检测循环依赖:
```typescript
// A 依赖 BB 依赖 A
@Injectable()
class ServiceA implements IService {
constructor(@Inject(ServiceB) b: ServiceB) {}
dispose(): void {}
}
@Injectable()
class ServiceB implements IService {
constructor(@Inject(ServiceA) a: ServiceA) {}
dispose(): void {}
}
// 解析时会抛出错误: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
```
### 获取所有服务
```typescript
// 获取所有已注册的服务类型
const types = Core.services.getRegisteredServices();
// 获取所有已实例化的服务实例
const instances = Core.services.getAll();
```
### 服务清理
```typescript
// 注销单个服务
Core.services.unregister(MyService);
// 清空所有服务会调用每个服务的dispose方法
Core.services.clear();
```
## 最佳实践
### 服务命名
服务类名应该以 `Service``Manager` 结尾,清晰表达其职责:
```typescript
class PlayerService implements IService {}
class AudioManager implements IService {}
class NetworkService implements IService {}
```
### 资源清理
始终在 `dispose()` 方法中清理资源:
```typescript
class ResourceService implements IService {
private resources: Map<string, Resource> = new Map();
dispose(): void {
// 释放所有资源
for (const resource of this.resources.values()) {
resource.release();
}
this.resources.clear();
}
}
```
### 避免过度使用
不要把所有类都注册为服务,服务应该是:
- 全局单例或需要共享状态
- 需要在多处使用
- 生命周期需要管理
- 需要依赖注入
对于简单的工具类或数据类,直接创建实例即可。
### 依赖方向
保持清晰的依赖方向,避免循环依赖:
```
高层服务 -> 中层服务 -> 底层服务
GameLogic -> DataService -> ConfigService
```
## 常见问题
### 服务未注册错误
**问题**: `Error: Service MyService is not registered`
**解决**:
```typescript
// 确保服务已注册
Core.services.registerSingleton(MyService);
// 或者使用tryResolve
const service = Core.services.tryResolve(MyService);
if (!service) {
console.log('Service not found');
}
```
### 循环依赖错误
**问题**: `Circular dependency detected`
**解决**: 重新设计服务依赖关系,引入中间服务或使用事件系统解耦。
### 何时使用单例 vs 瞬时
- **单例**: 管理器类、配置、缓存、状态管理
- **瞬时**: 命令对象、请求处理器、临时任务
## 相关链接
- [插件系统](./plugin-system.md) - 使用服务容器注册插件服务
- [快速开始](./getting-started.md) - Core 初始化和基础使用
- [系统架构](./system.md) - 在系统中使用服务

650
docs/guide/system.md Normal file
View File

@@ -0,0 +1,650 @@
# 系统架构
在 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 {
// 保存逻辑
}
}
```
### WorkerEntitySystem - 多线程系统
基于Web Worker的多线程处理系统适用于计算密集型任务能够充分利用多核CPU性能。
Worker系统提供了真正的并行计算能力支持SharedArrayBuffer优化并具有自动降级支持。特别适合物理模拟、粒子系统、AI计算等场景。
**详细内容请参考:[Worker系统](/guide/worker-system)**
## 实体匹配器 (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 {
// 方式1传入实例
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 方式2传入类型自动依赖注入
this.addEntityProcessor(PhysicsSystem);
// 设置系统更新顺序
const movementSystem = this.getSystem(MovementSystem);
if (movementSystem) {
movementSystem.updateOrder = 1;
}
}
}
```
### 系统依赖注入
系统实现了 `IService` 接口,支持通过依赖注入获取其他服务或系统:
```typescript
import { ECSSystem, Injectable, Inject } from '@esengine/ecs-framework';
@Injectable()
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
constructor(
@Inject(CollisionService) private collision: CollisionService
) {
super(Matcher.all(Transform, RigidBody));
}
protected process(entities: readonly Entity[]): void {
// 使用注入的服务
this.collision.detectCollisions(entities);
}
// 实现 IService 接口的 dispose 方法
public dispose(): void {
// 清理资源
}
}
// 使用时传入类型即可,框架会自动注入依赖
class GameScene extends Scene {
protected initialize(): void {
// 自动依赖注入
this.addEntityProcessor(PhysicsSystem);
}
}
```
注意事项:
- 使用 `@Injectable()` 装饰器标记需要依赖注入的系统
- 在构造函数参数中使用 `@Inject()` 装饰器声明依赖
- 系统必须实现 `dispose()` 方法IService 接口要求)
- 使用 `addEntityProcessor(类型)` 而不是 `addSystem(new 类型())` 来启用依赖注入
### 系统更新顺序
```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<string, any> = new Map();
protected onDestroy(): void {
// 清理资源
for (const [key, resource] of this.resources) {
if (resource.dispose) {
resource.dispose();
}
}
this.resources.clear();
}
}
```
系统是 ECS 架构的逻辑处理核心,正确设计和使用系统能让你的游戏代码更加模块化、高效和易于维护。

View File

@@ -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<GameScene>();
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<typeof gameData>();
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<TimerControlExample>();
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<AdvancedTimerUsage>();
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<AdvancedTimerUsage>();
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<AdvancedTimerUsage>();
// 检查某个条件
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<GameStateManager>();
self.startCombatPhase();
});
}
private startCombatPhase(): void {
this.gamePhase = 'combat';
console.log('战斗阶段开始 - 60秒战斗时间');
this.phaseTimer = Core.schedule(60.0, false, this, (timer) => {
const self = timer.getContext<GameStateManager>();
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<GameStateManager>();
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<TimerContext>();
console.log(`实体 ${ctx.entityId} 的定时器完成`);
ctx.onComplete();
});
}
}
```
时间和定时器系统是游戏开发中的重要工具,正确使用这些功能能让你的游戏逻辑更加精确和可控。

608
docs/guide/worker-system.md Normal file
View File

@@ -0,0 +1,608 @@
# Worker系统
Worker系统WorkerEntitySystem是ECS框架中基于Web Worker的多线程处理系统专为计算密集型任务设计能够充分利用多核CPU性能实现真正的并行计算。
## 核心特性
- **真正的并行计算**利用Web Worker在后台线程执行计算密集型任务
- **自动负载均衡**根据CPU核心数自动分配工作负载
- **SharedArrayBuffer优化**:零拷贝数据共享,提升大规模计算性能
- **降级支持**不支持Worker时自动回退到主线程处理
- **类型安全**完整的TypeScript支持和类型检查
## 基本用法
### 简单的物理系统示例
```typescript
interface PhysicsData {
id: number;
x: number;
y: number;
vx: number;
vy: number;
mass: number;
radius: number;
}
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics), {
enableWorker: true, // 启用Worker并行处理
workerCount: 8, // Worker数量系统会自动限制在硬件支持范围内
entitiesPerWorker: 100, // 每个Worker处理的实体数量
useSharedArrayBuffer: true, // 启用SharedArrayBuffer优化
entityDataSize: 7, // 每个实体数据大小
maxEntities: 10000, // 最大实体数量
systemConfig: { // 传递给Worker的配置
gravity: 100,
friction: 0.95
}
});
}
// 数据提取将Entity转换为可序列化的数据
protected extractEntityData(entity: Entity): PhysicsData {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
const physics = entity.getComponent(Physics);
return {
id: entity.id,
x: position.x,
y: position.y,
vx: velocity.x,
vy: velocity.y,
mass: physics.mass,
radius: physics.radius
};
}
// Worker处理函数纯函数在Worker中执行
protected workerProcess(
entities: PhysicsData[],
deltaTime: number,
config: any
): PhysicsData[] {
return entities.map(entity => {
// 应用重力
entity.vy += config.gravity * deltaTime;
// 更新位置
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
// 应用摩擦力
entity.vx *= config.friction;
entity.vy *= config.friction;
return entity;
});
}
// 结果应用将Worker处理结果应用回Entity
protected applyResult(entity: Entity, result: PhysicsData): void {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
position.x = result.x;
position.y = result.y;
velocity.x = result.vx;
velocity.y = result.vy;
}
// SharedArrayBuffer优化支持
protected getDefaultEntityDataSize(): number {
return 7; // id, x, y, vx, vy, mass, radius
}
protected writeEntityToBuffer(entityData: PhysicsData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset + 0] = entityData.id;
this.sharedFloatArray[offset + 1] = entityData.x;
this.sharedFloatArray[offset + 2] = entityData.y;
this.sharedFloatArray[offset + 3] = entityData.vx;
this.sharedFloatArray[offset + 4] = entityData.vy;
this.sharedFloatArray[offset + 5] = entityData.mass;
this.sharedFloatArray[offset + 6] = entityData.radius;
}
protected readEntityFromBuffer(offset: number): PhysicsData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset + 0],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
vx: this.sharedFloatArray[offset + 3],
vy: this.sharedFloatArray[offset + 4],
mass: this.sharedFloatArray[offset + 5],
radius: this.sharedFloatArray[offset + 6]
};
}
}
```
## 配置选项
Worker系统支持丰富的配置选项
```typescript
interface WorkerSystemConfig {
/** 是否启用Worker并行处理 */
enableWorker?: boolean;
/** Worker数量默认为CPU核心数自动限制在系统最大值内 */
workerCount?: number;
/** 每个Worker处理的实体数量用于控制负载分布 */
entitiesPerWorker?: number;
/** 系统配置数据会传递给Worker */
systemConfig?: any;
/** 是否使用SharedArrayBuffer优化 */
useSharedArrayBuffer?: boolean;
/** 每个实体在SharedArrayBuffer中占用的Float32数量 */
entityDataSize?: number;
/** 最大实体数量用于预分配SharedArrayBuffer */
maxEntities?: number;
}
```
### 配置建议
```typescript
constructor() {
super(matcher, {
// 根据任务复杂度决定是否启用
enableWorker: this.shouldUseWorker(),
// Worker数量系统会自动限制在硬件支持范围内
workerCount: 8, // 请求8个Worker实际数量受CPU核心数限制
// 每个Worker处理的实体数量可选
entitiesPerWorker: 200, // 精确控制负载分布
// 大量简单计算时启用SharedArrayBuffer
useSharedArrayBuffer: this.entityCount > 1000,
// 根据实际数据结构设置
entityDataSize: 8, // 确保与数据结构匹配
// 预估最大实体数量
maxEntities: 10000,
// 传递给Worker的全局配置
systemConfig: {
gravity: 9.8,
friction: 0.95,
worldBounds: { width: 1920, height: 1080 }
}
});
}
private shouldUseWorker(): boolean {
// 根据实体数量和计算复杂度决定
return this.expectedEntityCount > 100;
}
// 获取系统信息
getSystemInfo() {
const info = this.getWorkerInfo();
console.log(`Worker数量: ${info.workerCount}/${info.maxSystemWorkerCount}`);
console.log(`每Worker实体数: ${info.entitiesPerWorker || '自动分配'}`);
console.log(`当前模式: ${info.currentMode}`);
}
```
## 处理模式
Worker系统支持两种处理模式
### 1. 传统Worker模式
数据通过序列化在主线程和Worker间传递
```typescript
// 适用于:复杂计算逻辑,实体数量适中
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: false, // 使用传统模式
workerCount: 2
});
}
protected workerProcess(entities: EntityData[], deltaTime: number): EntityData[] {
// 复杂的算法逻辑
return entities.map(entity => {
// AI决策、路径规划等复杂计算
return this.complexAILogic(entity, deltaTime);
});
}
```
### 2. SharedArrayBuffer模式
零拷贝数据共享,适合大量简单计算:
```typescript
// 适用于:大量实体的简单计算
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: true, // 启用共享内存
entityDataSize: 6,
maxEntities: 10000
});
}
protected getSharedArrayBufferProcessFunction(): SharedArrayBufferProcessFunction {
return function(sharedFloatArray: Float32Array, startIndex: number, endIndex: number, deltaTime: number, config: any) {
const entitySize = 6;
for (let i = startIndex; i < endIndex; i++) {
const offset = i * entitySize;
// 读取数据
let x = sharedFloatArray[offset];
let y = sharedFloatArray[offset + 1];
let vx = sharedFloatArray[offset + 2];
let vy = sharedFloatArray[offset + 3];
// 物理计算
vy += config.gravity * deltaTime;
x += vx * deltaTime;
y += vy * deltaTime;
// 写回数据
sharedFloatArray[offset] = x;
sharedFloatArray[offset + 1] = y;
sharedFloatArray[offset + 2] = vx;
sharedFloatArray[offset + 3] = vy;
}
};
}
```
## 完整示例:粒子物理系统
一个包含碰撞检测的完整粒子物理系统:
```typescript
interface ParticleData {
id: number;
x: number;
y: number;
dx: number;
dy: number;
mass: number;
radius: number;
bounce: number;
friction: number;
}
@ECSSystem('ParticlePhysics')
class ParticlePhysicsWorkerSystem extends WorkerEntitySystem<ParticleData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics, Renderable), {
enableWorker: true,
workerCount: 6, // 请求6个Worker自动限制在CPU核心数内
entitiesPerWorker: 150, // 每个Worker处理150个粒子
useSharedArrayBuffer: true,
entityDataSize: 9,
maxEntities: 5000,
systemConfig: {
gravity: 100,
canvasWidth: 800,
canvasHeight: 600,
groundFriction: 0.98
}
});
}
protected extractEntityData(entity: Entity): ParticleData {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
const physics = entity.getComponent(Physics);
const renderable = entity.getComponent(Renderable);
return {
id: entity.id,
x: position.x,
y: position.y,
dx: velocity.dx,
dy: velocity.dy,
mass: physics.mass,
radius: renderable.size,
bounce: physics.bounce,
friction: physics.friction
};
}
protected workerProcess(
entities: ParticleData[],
deltaTime: number,
config: any
): ParticleData[] {
const result = entities.map(e => ({ ...e }));
// 基础物理更新
for (const particle of result) {
// 应用重力
particle.dy += config.gravity * deltaTime;
// 更新位置
particle.x += particle.dx * deltaTime;
particle.y += particle.dy * deltaTime;
// 边界碰撞
if (particle.x <= particle.radius) {
particle.x = particle.radius;
particle.dx = -particle.dx * particle.bounce;
} else if (particle.x >= config.canvasWidth - particle.radius) {
particle.x = config.canvasWidth - particle.radius;
particle.dx = -particle.dx * particle.bounce;
}
if (particle.y <= particle.radius) {
particle.y = particle.radius;
particle.dy = -particle.dy * particle.bounce;
} else if (particle.y >= config.canvasHeight - particle.radius) {
particle.y = config.canvasHeight - particle.radius;
particle.dy = -particle.dy * particle.bounce;
particle.dx *= config.groundFriction;
}
// 空气阻力
particle.dx *= particle.friction;
particle.dy *= particle.friction;
}
// 粒子间碰撞检测O(n²)算法)
for (let i = 0; i < result.length; i++) {
for (let j = i + 1; j < result.length; j++) {
const p1 = result[i];
const p2 = result[j];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const minDistance = p1.radius + p2.radius;
if (distance < minDistance && distance > 0) {
// 分离粒子
const nx = dx / distance;
const ny = dy / distance;
const overlap = minDistance - distance;
p1.x -= nx * overlap * 0.5;
p1.y -= ny * overlap * 0.5;
p2.x += nx * overlap * 0.5;
p2.y += ny * overlap * 0.5;
// 弹性碰撞
const relativeVelocityX = p2.dx - p1.dx;
const relativeVelocityY = p2.dy - p1.dy;
const velocityAlongNormal = relativeVelocityX * nx + relativeVelocityY * ny;
if (velocityAlongNormal > 0) continue;
const restitution = (p1.bounce + p2.bounce) * 0.5;
const impulseScalar = -(1 + restitution) * velocityAlongNormal / (1/p1.mass + 1/p2.mass);
p1.dx -= impulseScalar * nx / p1.mass;
p1.dy -= impulseScalar * ny / p1.mass;
p2.dx += impulseScalar * nx / p2.mass;
p2.dy += impulseScalar * ny / p2.mass;
}
}
}
return result;
}
protected applyResult(entity: Entity, result: ParticleData): void {
if (!entity?.enabled) return;
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
if (position && velocity) {
position.set(result.x, result.y);
velocity.set(result.dx, result.dy);
}
}
protected getDefaultEntityDataSize(): number {
return 9;
}
protected writeEntityToBuffer(data: ParticleData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset + 0] = data.id;
this.sharedFloatArray[offset + 1] = data.x;
this.sharedFloatArray[offset + 2] = data.y;
this.sharedFloatArray[offset + 3] = data.dx;
this.sharedFloatArray[offset + 4] = data.dy;
this.sharedFloatArray[offset + 5] = data.mass;
this.sharedFloatArray[offset + 6] = data.radius;
this.sharedFloatArray[offset + 7] = data.bounce;
this.sharedFloatArray[offset + 8] = data.friction;
}
protected readEntityFromBuffer(offset: number): ParticleData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset + 0],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
dx: this.sharedFloatArray[offset + 3],
dy: this.sharedFloatArray[offset + 4],
mass: this.sharedFloatArray[offset + 5],
radius: this.sharedFloatArray[offset + 6],
bounce: this.sharedFloatArray[offset + 7],
friction: this.sharedFloatArray[offset + 8]
};
}
// 性能监控
public getPerformanceInfo(): {
enabled: boolean;
workerCount: number;
entitiesPerWorker?: number;
maxSystemWorkerCount: number;
entityCount: number;
isProcessing: boolean;
currentMode: string;
} {
const workerInfo = this.getWorkerInfo();
return {
...workerInfo,
entityCount: this.entities.length
};
}
}
```
## 适用场景
Worker系统特别适合以下场景
### 1. 物理模拟
- **重力系统**:大量实体的重力计算
- **碰撞检测**:复杂的碰撞算法
- **流体模拟**:粒子流体系统
- **布料模拟**:顶点物理计算
### 2. AI计算
- **路径寻找**A*、Dijkstra等算法
- **行为树**复杂的AI决策逻辑
- **群体智能**:鸟群、鱼群算法
- **神经网络**简单的AI推理
### 3. 数据处理
- **大量实体更新**:状态机、生命周期管理
- **统计计算**:游戏数据分析
- **图像处理**:纹理生成、效果计算
- **音频处理**:音效合成、频谱分析
## 最佳实践
### 1. Worker函数要求
```typescript
// ✅ 推荐Worker处理函数是纯函数
protected workerProcess(entities: PhysicsData[], deltaTime: number, config: any): PhysicsData[] {
// 只使用参数和标准JavaScript API
return entities.map(entity => {
// 纯计算逻辑,不依赖外部状态
entity.y += entity.velocity * deltaTime;
return entity;
});
}
// ❌ 避免在Worker函数中使用外部引用
protected workerProcess(entities: PhysicsData[], deltaTime: number): PhysicsData[] {
// this 和外部变量在Worker中不可用
return entities.map(entity => {
entity.y += this.someProperty; // ❌ 错误
return entity;
});
}
```
### 2. 数据设计
```typescript
// ✅ 推荐:合理的数据设计
interface SimplePhysicsData {
x: number;
y: number;
vx: number;
vy: number;
// 保持数据结构简单,便于序列化
}
// ❌ 避免:复杂的嵌套对象
interface ComplexData {
transform: {
position: { x: number; y: number };
rotation: { angle: number };
};
// 复杂嵌套结构增加序列化开销
}
```
### 3. Worker数量控制
```typescript
// ✅ 推荐灵活的Worker配置
constructor() {
super(matcher, {
// 直接指定需要的Worker数量系统会自动限制在硬件支持范围内
workerCount: 8, // 请求8个Worker
entitiesPerWorker: 100, // 每个Worker处理100个实体
enableWorker: this.shouldUseWorker(), // 条件启用
});
}
private shouldUseWorker(): boolean {
// 根据实体数量和复杂度决定是否使用Worker
return this.expectedEntityCount > 100;
}
// 获取实际使用的Worker信息
checkWorkerConfiguration() {
const info = this.getWorkerInfo();
console.log(`请求Worker数量: 8`);
console.log(`实际Worker数量: ${info.workerCount}`);
console.log(`系统最大支持: ${info.maxSystemWorkerCount}`);
console.log(`每Worker实体数: ${info.entitiesPerWorker || '自动分配'}`);
}
```
### 4. 性能监控
```typescript
// ✅ 推荐:性能监控
public getPerformanceMetrics(): WorkerPerformanceMetrics {
return {
...this.getWorkerInfo(),
entityCount: this.entities.length,
averageProcessTime: this.getAverageProcessTime(),
workerUtilization: this.getWorkerUtilization()
};
}
```
## 性能优化建议
### 1. 计算密集度评估
只对计算密集型任务使用Worker避免在简单计算上增加线程开销。
### 2. 数据传输优化
- 使用SharedArrayBuffer减少序列化开销
- 保持数据结构简单和扁平
- 避免频繁的大数据传输
### 3. 降级策略
始终提供主线程回退方案确保在不支持Worker的环境中正常运行。
### 4. 内存管理
及时清理Worker池和共享缓冲区避免内存泄漏。
### 5. 负载均衡
使用 `entitiesPerWorker` 参数精确控制负载分布避免某些Worker空闲而其他Worker过载。
## 在线演示
查看完整的Worker系统演示[Worker系统演示](https://esengine.github.io/ecs-framework/demos/worker-system/)
该演示展示了:
- 多线程物理计算
- 实时性能对比
- SharedArrayBuffer优化
- 大量实体的并行处理
Worker系统为ECS框架提供了强大的并行计算能力让你能够充分利用现代多核处理器的性能为复杂的游戏逻辑和计算密集型任务提供了高效的解决方案。

761
docs/guide/world-manager.md Normal file
View File

@@ -0,0 +1,761 @@
# WorldManager
WorldManager 是 ECS Framework 提供的高级世界管理器用于管理多个完全隔离的游戏世界World。每个 World 都是独立的 ECS 环境,可以包含多个场景。
## 适用场景
WorldManager 适合以下高级场景:
- MMO 游戏服务器的多房间管理
- 游戏大厅系统(每个游戏房间完全隔离)
- 服务器端的多游戏实例
- 需要完全隔离的多个游戏环境
- 需要同时运行多个独立世界的应用
## 特点
- 多 World 管理,每个 World 完全独立
- 每个 World 可以包含多个 Scene
- 支持 World 的激活/停用
- 自动清理空 World
- World 级别的全局系统
- 批量操作和查询
## 基本使用
### 初始化
WorldManager 是 Core 的内置服务,通过服务容器获取:
```typescript
import { Core, WorldManager } from '@esengine/ecs-framework';
// 初始化 Core
Core.create({ debug: true });
// 从服务容器获取 WorldManagerCore 已自动创建并注册)
const worldManager = Core.services.resolve(WorldManager);
```
### 创建 World
```typescript
// 创建游戏房间 World
const room1 = worldManager.createWorld('room_001', {
name: 'GameRoom_001',
maxScenes: 5,
debug: true
});
// 激活 World
worldManager.setWorldActive('room_001', true);
// 创建更多房间
const room2 = worldManager.createWorld('room_002', {
name: 'GameRoom_002',
maxScenes: 5
});
worldManager.setWorldActive('room_002', true);
```
### 游戏循环
在游戏循环中更新所有活跃的 World
```typescript
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 更新全局服务
worldManager.updateAll(); // 更新所有活跃的 World
}
// 启动游戏循环
let lastTime = 0;
setInterval(() => {
const currentTime = Date.now();
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
gameLoop(deltaTime);
}, 16); // 60 FPS
```
## World 管理
### 创建 World
```typescript
// 基本创建
const world = worldManager.createWorld('worldId');
// 带配置创建
const world = worldManager.createWorld('worldId', {
name: 'MyWorld',
maxScenes: 10,
autoCleanup: true,
debug: true
});
```
**配置选项IWorldConfig**
- `name?: string` - World 名称
- `maxScenes?: number` - 最大场景数量限制(默认 10
- `autoCleanup?: boolean` - 是否自动清理空场景(默认 true
- `debug?: boolean` - 是否启用调试模式(默认 false
### 获取 World
```typescript
// 通过 ID 获取
const world = worldManager.getWorld('room_001');
if (world) {
console.log(`World: ${world.name}`);
}
// 获取所有 World
const allWorlds = worldManager.getAllWorlds();
console.log(`共有 ${allWorlds.length} 个 World`);
// 获取所有 World ID
const worldIds = worldManager.getWorldIds();
console.log('World 列表:', worldIds);
// 通过名称查找
const world = worldManager.findWorldByName('GameRoom_001');
```
### 激活和停用 World
```typescript
// 激活 World开始运行和更新
worldManager.setWorldActive('room_001', true);
// 停用 World停止更新但保留数据
worldManager.setWorldActive('room_001', false);
// 检查 World 是否激活
if (worldManager.isWorldActive('room_001')) {
console.log('房间正在运行');
}
// 获取所有活跃的 World
const activeWorlds = worldManager.getActiveWorlds();
console.log(`当前有 ${activeWorlds.length} 个活跃 World`);
```
### 移除 World
```typescript
// 移除 World会自动停用并销毁
const removed = worldManager.removeWorld('room_001');
if (removed) {
console.log('World 已移除');
}
```
## World 中的场景管理
每个 World 可以包含多个 Scene 并独立管理它们的生命周期。
### 创建场景
```typescript
const world = worldManager.getWorld('room_001');
if (!world) return;
// 创建场景
const mainScene = world.createScene('main', new MainScene());
const uiScene = world.createScene('ui', new UIScene());
const hudScene = world.createScene('hud', new HUDScene());
// 激活场景
world.setSceneActive('main', true);
world.setSceneActive('ui', true);
world.setSceneActive('hud', false);
```
### 查询场景
```typescript
// 获取特定场景
const mainScene = world.getScene<MainScene>('main');
if (mainScene) {
console.log(`场景名称: ${mainScene.name}`);
}
// 获取所有场景
const allScenes = world.getAllScenes();
console.log(`World 中共有 ${allScenes.length} 个场景`);
// 获取所有场景 ID
const sceneIds = world.getSceneIds();
console.log('场景列表:', sceneIds);
// 获取活跃场景数量
const activeCount = world.getActiveSceneCount();
console.log(`当前有 ${activeCount} 个活跃场景`);
// 检查场景是否激活
if (world.isSceneActive('main')) {
console.log('主场景正在运行');
}
```
### 场景切换
World 支持多场景同时运行,也支持场景切换:
```typescript
class GameWorld {
private world: World;
constructor(worldManager: WorldManager) {
this.world = worldManager.createWorld('game', {
name: 'GameWorld',
maxScenes: 5
});
// 创建所有场景
this.world.createScene('menu', new MenuScene());
this.world.createScene('game', new GameScene());
this.world.createScene('pause', new PauseScene());
this.world.createScene('gameover', new GameOverScene());
// 激活 World
worldManager.setWorldActive('game', true);
}
public showMenu(): void {
this.deactivateAllScenes();
this.world.setSceneActive('menu', true);
}
public startGame(): void {
this.deactivateAllScenes();
this.world.setSceneActive('game', true);
}
public pauseGame(): void {
// 游戏场景继续存在但停止更新
this.world.setSceneActive('game', false);
// 显示暂停界面
this.world.setSceneActive('pause', true);
}
public resumeGame(): void {
this.world.setSceneActive('pause', false);
this.world.setSceneActive('game', true);
}
public showGameOver(): void {
this.deactivateAllScenes();
this.world.setSceneActive('gameover', true);
}
private deactivateAllScenes(): void {
const sceneIds = this.world.getSceneIds();
sceneIds.forEach(id => this.world.setSceneActive(id, false));
}
}
```
### 移除场景
```typescript
// 移除不再需要的场景
const removed = world.removeScene('oldScene');
if (removed) {
console.log('场景已移除');
}
// 场景会自动调用 end() 方法进行清理
```
## 全局系统
World 支持全局系统,这些系统在 World 级别运行,不依赖特定 Scene。
### 定义全局系统
```typescript
import { IGlobalSystem } from '@esengine/ecs-framework';
// 网络系统World 级别)
class NetworkSystem implements IGlobalSystem {
readonly name = 'NetworkSystem';
private connectionId: string;
constructor(connectionId: string) {
this.connectionId = connectionId;
}
initialize(): void {
console.log(`网络系统初始化: ${this.connectionId}`);
// 建立网络连接
}
update(deltaTime?: number): void {
// 处理网络消息,不依赖任何 Scene
// 接收和发送网络包
}
destroy(): void {
console.log(`网络系统销毁: ${this.connectionId}`);
// 关闭网络连接
}
}
// 物理系统World 级别)
class PhysicsSystem implements IGlobalSystem {
readonly name = 'PhysicsSystem';
initialize(): void {
console.log('物理系统初始化');
}
update(deltaTime?: number): void {
// 物理模拟,作用于 World 中所有场景
}
destroy(): void {
console.log('物理系统销毁');
}
}
```
### 使用全局系统
```typescript
const world = worldManager.getWorld('room_001');
if (!world) return;
// 添加全局系统
const networkSystem = world.addGlobalSystem(new NetworkSystem('conn_001'));
const physicsSystem = world.addGlobalSystem(new PhysicsSystem());
// 获取全局系统
const network = world.getGlobalSystem(NetworkSystem);
if (network) {
console.log('找到网络系统');
}
// 移除全局系统
world.removeGlobalSystem(networkSystem);
```
## 批量操作
### 更新所有 World
```typescript
// 更新所有活跃的 World应该在游戏循环中调用
worldManager.updateAll();
// 这会自动更新每个 World 的:
// 1. 全局系统
// 2. 所有活跃场景
```
### 启动和停止
```typescript
// 启动所有 World
worldManager.startAll();
// 停止所有 World
worldManager.stopAll();
// 检查是否正在运行
if (worldManager.isRunning) {
console.log('WorldManager 正在运行');
}
```
### 查找 World
```typescript
// 使用条件查找
const emptyWorlds = worldManager.findWorlds(world => {
return world.sceneCount === 0;
});
// 查找活跃的 World
const activeWorlds = worldManager.findWorlds(world => {
return world.isActive;
});
// 查找特定名称的 World
const world = worldManager.findWorldByName('GameRoom_001');
```
## 统计和监控
### 获取统计信息
```typescript
const stats = worldManager.getStats();
console.log(`总 World 数: ${stats.totalWorlds}`);
console.log(`活跃 World 数: ${stats.activeWorlds}`);
console.log(`总场景数: ${stats.totalScenes}`);
console.log(`总实体数: ${stats.totalEntities}`);
console.log(`总系统数: ${stats.totalSystems}`);
// 查看每个 World 的详细信息
stats.worlds.forEach(worldInfo => {
console.log(`World: ${worldInfo.name}`);
console.log(` 场景数: ${worldInfo.sceneCount}`);
console.log(` 是否活跃: ${worldInfo.isActive}`);
});
```
### 获取详细状态
```typescript
const status = worldManager.getDetailedStatus();
// 包含所有 World 的详细状态
status.worlds.forEach(worldStatus => {
console.log(`World ID: ${worldStatus.id}`);
console.log(`状态:`, worldStatus.status);
});
```
## 自动清理
WorldManager 支持自动清理空的 World。
### 配置清理
```typescript
// 创建带清理配置的 WorldManager
const worldManager = Core.services.resolve(WorldManager);
// WorldManager 的配置在 Core 中设置:
// {
// maxWorlds: 50,
// autoCleanup: true,
// cleanupInterval: 30000 // 30 秒
// }
```
### 手动清理
```typescript
// 手动触发清理
const cleanedCount = worldManager.cleanup();
console.log(`清理了 ${cleanedCount} 个 World`);
```
**清理条件**
- World 未激活
- 没有 Scene 或所有 Scene 都是空的
- 创建时间超过 10 分钟
## API 参考
### WorldManager API
| 方法 | 说明 |
|------|------|
| `createWorld(worldId, config?)` | 创建新 World |
| `removeWorld(worldId)` | 移除 World |
| `getWorld(worldId)` | 获取 World |
| `getAllWorlds()` | 获取所有 World |
| `getWorldIds()` | 获取所有 World ID |
| `setWorldActive(worldId, active)` | 设置 World 激活状态 |
| `isWorldActive(worldId)` | 检查 World 是否激活 |
| `getActiveWorlds()` | 获取所有活跃的 World |
| `updateAll()` | 更新所有活跃 World |
| `startAll()` | 启动所有 World |
| `stopAll()` | 停止所有 World |
| `findWorlds(predicate)` | 查找满足条件的 World |
| `findWorldByName(name)` | 根据名称查找 World |
| `getStats()` | 获取统计信息 |
| `getDetailedStatus()` | 获取详细状态信息 |
| `cleanup()` | 清理空 World |
| `destroy()` | 销毁 WorldManager |
### World API
| 方法 | 说明 |
|------|------|
| `createScene(sceneId, sceneInstance?)` | 创建并添加 Scene |
| `removeScene(sceneId)` | 移除 Scene |
| `getScene(sceneId)` | 获取 Scene |
| `getAllScenes()` | 获取所有 Scene |
| `getSceneIds()` | 获取所有 Scene ID |
| `setSceneActive(sceneId, active)` | 设置 Scene 激活状态 |
| `isSceneActive(sceneId)` | 检查 Scene 是否激活 |
| `getActiveSceneCount()` | 获取活跃 Scene 数量 |
| `addGlobalSystem(system)` | 添加全局系统 |
| `removeGlobalSystem(system)` | 移除全局系统 |
| `getGlobalSystem(type)` | 获取全局系统 |
| `start()` | 启动 World |
| `stop()` | 停止 World |
| `updateGlobalSystems()` | 更新全局系统 |
| `updateScenes()` | 更新所有激活 Scene |
| `destroy()` | 销毁 World |
| `getStatus()` | 获取 World 状态 |
| `getStats()` | 获取统计信息 |
### 属性
| 属性 | 说明 |
|------|------|
| `worldCount` | World 总数 |
| `activeWorldCount` | 活跃 World 数量 |
| `isRunning` | 是否正在运行 |
| `config` | 配置信息 |
## 完整示例
### MMO 游戏房间系统
```typescript
import { Core, WorldManager, Scene, World } from '@esengine/ecs-framework';
// 初始化
Core.create({ debug: true });
const worldManager = Core.services.resolve(WorldManager);
// 房间管理器
class RoomManager {
private worldManager: WorldManager;
private rooms: Map<string, World> = new Map();
constructor(worldManager: WorldManager) {
this.worldManager = worldManager;
}
// 创建游戏房间
public createRoom(roomId: string, maxPlayers: number): World {
const world = this.worldManager.createWorld(roomId, {
name: `Room_${roomId}`,
maxScenes: 3,
debug: true
});
// 创建房间场景
world.createScene('lobby', new LobbyScene());
world.createScene('game', new GameScene());
world.createScene('result', new ResultScene());
// 添加房间级别的系统
world.addGlobalSystem(new NetworkSystem(roomId));
world.addGlobalSystem(new RoomLogicSystem(maxPlayers));
// 激活 World 和初始场景
this.worldManager.setWorldActive(roomId, true);
world.setSceneActive('lobby', true);
this.rooms.set(roomId, world);
console.log(`房间 ${roomId} 已创建`);
return world;
}
// 玩家加入房间
public joinRoom(roomId: string, playerId: string): boolean {
const world = this.rooms.get(roomId);
if (!world) {
console.log(`房间 ${roomId} 不存在`);
return false;
}
// 在大厅场景中创建玩家实体
const lobbyScene = world.getScene('lobby');
if (lobbyScene) {
const player = lobbyScene.createEntity(`Player_${playerId}`);
// 添加玩家组件...
console.log(`玩家 ${playerId} 加入房间 ${roomId}`);
return true;
}
return false;
}
// 开始游戏
public startGame(roomId: string): void {
const world = this.rooms.get(roomId);
if (!world) return;
// 切换到游戏场景
world.setSceneActive('lobby', false);
world.setSceneActive('game', true);
console.log(`房间 ${roomId} 游戏开始`);
}
// 结束游戏
public endGame(roomId: string): void {
const world = this.rooms.get(roomId);
if (!world) return;
// 切换到结果场景
world.setSceneActive('game', false);
world.setSceneActive('result', true);
console.log(`房间 ${roomId} 游戏结束`);
}
// 关闭房间
public closeRoom(roomId: string): void {
this.worldManager.removeWorld(roomId);
this.rooms.delete(roomId);
console.log(`房间 ${roomId} 已关闭`);
}
// 获取房间列表
public getRoomList(): string[] {
return Array.from(this.rooms.keys());
}
// 获取房间统计
public getRoomStats(roomId: string) {
const world = this.rooms.get(roomId);
return world?.getStats();
}
}
// 使用房间管理器
const roomManager = new RoomManager(worldManager);
// 创建多个游戏房间
roomManager.createRoom('room_001', 4);
roomManager.createRoom('room_002', 4);
roomManager.createRoom('room_003', 2);
// 玩家加入
roomManager.joinRoom('room_001', 'player_1');
roomManager.joinRoom('room_001', 'player_2');
// 开始游戏
roomManager.startGame('room_001');
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime);
worldManager.updateAll(); // 更新所有房间
}
// 定期清理空房间
setInterval(() => {
const stats = worldManager.getStats();
console.log(`当前房间数: ${stats.totalWorlds}`);
console.log(`活跃房间数: ${stats.activeWorlds}`);
worldManager.cleanup();
}, 60000); // 每分钟清理一次
```
## 最佳实践
### 1. 合理的 World 粒度
```typescript
// 推荐:每个独立环境一个 World
const room1 = worldManager.createWorld('room_1'); // 游戏房间1
const room2 = worldManager.createWorld('room_2'); // 游戏房间2
// 不推荐:过度使用 World
const world1 = worldManager.createWorld('ui'); // UI 不需要独立 World
const world2 = worldManager.createWorld('menu'); // 菜单不需要独立 World
```
### 2. 使用全局系统处理跨场景逻辑
```typescript
// 推荐World 级别的系统
class NetworkSystem implements IGlobalSystem {
update() {
// 网络处理不依赖场景
}
}
// 不推荐:在每个场景中重复创建
class GameScene extends Scene {
initialize() {
this.addSystem(new NetworkSystem()); // 不应该在场景级别
}
}
```
### 3. 及时清理不用的 World
```typescript
// 推荐:玩家离开时清理房间
function onPlayerLeave(roomId: string) {
const world = worldManager.getWorld(roomId);
if (world && world.sceneCount === 0) {
worldManager.removeWorld(roomId);
}
}
// 或使用自动清理
worldManager.cleanup();
```
### 4. 监控资源使用
```typescript
// 定期检查资源使用情况
setInterval(() => {
const stats = worldManager.getStats();
if (stats.totalWorlds > 100) {
console.warn('World 数量过多,考虑清理');
worldManager.cleanup();
}
if (stats.totalEntities > 10000) {
console.warn('实体数量过多,检查是否有泄漏');
}
}, 30000);
```
## 与 SceneManager 的对比
| 特性 | SceneManager | WorldManager |
|------|--------------|--------------|
| 适用场景 | 95% 的游戏应用 | 高级多世界隔离场景 |
| 复杂度 | 简单 | 复杂 |
| 场景数量 | 单场景(可切换) | 多 World每个 World 多场景 |
| 场景隔离 | 无(场景切换) | 完全隔离(每个 World 独立) |
| 性能开销 | 最小 | 较高 |
| 全局系统 | 无 | 支持World 级别) |
| 使用示例 | 单人游戏、移动游戏 | MMO 服务器、游戏房间系统 |
**何时使用 WorldManager**
- MMO 游戏服务器(每个房间一个 World
- 游戏大厅系统(每个游戏房间完全隔离)
- 需要运行多个完全独立的游戏实例
- 服务器端模拟多个游戏世界
**何时使用 SceneManager**
- 单人游戏
- 简单的多人游戏
- 移动游戏
- 场景之间需要切换但不需要同时运行
## 架构层次
WorldManager 在 ECS Framework 中的位置:
```
Core (全局服务)
└── WorldManager (世界管理)
├── World 1 (游戏房间1)
│ ├── GlobalSystem (全局系统)
│ ├── Scene 1 (场景1)
│ │ ├── EntitySystem
│ │ ├── Entity
│ │ └── Component
│ └── Scene 2 (场景2)
├── World 2 (游戏房间2)
│ ├── GlobalSystem
│ └── Scene 1
└── World 3 (游戏房间3)
```
WorldManager 为需要多世界隔离的高级应用提供了强大的管理能力。如果你的应用不需要多世界隔离,建议使用更简单的 [SceneManager](./scene-manager.md)。

23
docs/index.md Normal file
View File

@@ -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: 核心功能独立打包,支持多平台
---

View File

@@ -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框架的性能表现。

View File

@@ -0,0 +1,118 @@
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
let coepCredentialless = false;
if (typeof window === 'undefined') {
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
self.addEventListener("message", (ev) => {
if (!ev.data) {
return;
} else if (ev.data.type === "deregister") {
self.registration
.unregister()
.then(() => {
return self.clients.matchAll();
})
.then(clients => {
clients.forEach((client) => client.navigate(client.url));
});
} else if (ev.data.type === "coepCredentialless") {
coepCredentialless = ev.data.value;
}
});
self.addEventListener("fetch", function (event) {
const r = event.request;
if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
return;
}
const request = (coepCredentialless && r.mode === "no-cors")
? new Request(r, {
credentials: "omit",
})
: r;
event.respondWith(
fetch(request)
.then((response) => {
if (response.status === 0) {
return response;
}
const newHeaders = new Headers(response.headers);
newHeaders.set("Cross-Origin-Embedder-Policy",
coepCredentialless ? "credentialless" : "require-corp"
);
if (!coepCredentialless) {
newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin");
}
newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
})
.catch((e) => console.error(e))
);
});
} else {
(() => {
// You can customize the behavior of this script through a global `coi` variable.
const coi = {
shouldRegister: () => true,
shouldDeregister: () => false,
coepCredentialless: () => !(window.chrome || window.netscape),
doReload: () => window.location.reload(),
quiet: false,
...window.coi
};
const n = navigator;
if (n.serviceWorker && n.serviceWorker.controller) {
n.serviceWorker.controller.postMessage({
type: "coepCredentialless",
value: coi.coepCredentialless(),
});
if (coi.shouldDeregister()) {
n.serviceWorker.controller.postMessage({ type: "deregister" });
}
}
// If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
// already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return;
if (!window.isSecureContext) {
!coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required.");
return;
}
// In some environments (e.g. Chrome incognito mode) this won't be available
if (n.serviceWorker) {
n.serviceWorker.register(window.document.currentScript.src).then(
(registration) => {
!coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope);
registration.addEventListener("updatefound", () => {
!coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
coi.doReload();
});
// If the registration is active, but it's not controlling the page
if (registration.active && !n.serviceWorker.controller) {
!coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker.");
coi.doReload();
}
},
(err) => {
!coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err);
}
);
}
})();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ECS Framework Worker System Demo</title>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
background: #1a1a1a;
color: white;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
color: #4a9eff;
}
.demo-area {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.canvas-container {
flex: 1;
}
#gameCanvas {
border: 2px solid #4a9eff;
background: #000;
display: block;
}
.controls {
width: 300px;
background: #2a2a2a;
padding: 20px;
border-radius: 8px;
}
.control-group {
margin-bottom: 15px;
}
.control-group label {
display: block;
margin-bottom: 5px;
color: #ccc;
}
.control-group input, .control-group button {
width: 100%;
padding: 8px;
margin-bottom: 5px;
border: 1px solid #555;
background: #3a3a3a;
color: white;
border-radius: 4px;
}
.control-group button {
background: #4a9eff;
cursor: pointer;
transition: background 0.3s;
}
.control-group button:hover {
background: #3a8eef;
}
.control-group button:disabled {
background: #555;
cursor: not-allowed;
}
.stats {
background: #2a2a2a;
padding: 15px;
border-radius: 8px;
font-family: monospace;
font-size: 12px;
}
.stats h3 {
margin-top: 0;
color: #4a9eff;
}
.stat-line {
margin: 5px 0;
}
.worker-enabled {
color: #4eff4a;
}
.worker-disabled {
color: #ff4a4a;
}
.performance-high {
color: #4eff4a;
}
.performance-medium {
color: #ffff4a;
}
.performance-low {
color: #ff4a4a;
}
</style>
<script type="module" crossorigin src="/ecs-framework/demos/worker-system/assets/index-CrID--xK.js"></script>
</head>
<body>
<div class="container">
<h1>ECS Framework Worker System 演示</h1>
<div class="demo-area">
<div class="canvas-container">
<canvas id="gameCanvas" width="800" height="600"></canvas>
</div>
<div class="controls">
<div class="control-group">
<label>实体数量:</label>
<input type="range" id="entityCount" min="100" max="10000" value="1000" step="100">
<span id="entityCountValue">1000</span>
</div>
<div class="control-group">
<label>Worker 设置:</label>
<button id="toggleWorker">禁用 Worker</button>
<button id="toggleSAB">禁用 SharedArrayBuffer</button>
</div>
<div class="control-group">
<button id="spawnParticles">生成粒子系统</button>
<button id="clearEntities">清空所有实体</button>
<button id="resetDemo">重置演示</button>
</div>
<div class="control-group">
<label>物理参数:</label>
<input type="range" id="gravity" min="0" max="500" value="100" step="10">
<label>重力: <span id="gravityValue">100</span></label>
<input type="range" id="friction" min="0" max="100" value="95" step="5">
<label>摩擦力: <span id="frictionValue">95%</span></label>
</div>
</div>
</div>
<div class="stats">
<h3>性能统计</h3>
<div class="stat-line">FPS: <span id="fps">0</span></div>
<div class="stat-line">实体数量: <span id="entityCountStat">0</span></div>
<div class="stat-line">Worker状态: <span id="workerStatus" class="worker-disabled">未启用</span></div>
<div class="stat-line">Worker负载: <span id="workerLoad">N/A</span></div>
<div class="stat-line">运行模式: <span id="sabStatus" class="worker-disabled">同步模式</span></div>
<div class="stat-line">物理系统耗时: <span id="physicsTime">0</span>ms</div>
<div class="stat-line">渲染系统耗时: <span id="renderTime">0</span>ms</div>
<div class="stat-line">总帧时间: <span id="frameTime">0</span>ms</div>
<div class="stat-line">内存使用: <span id="memoryUsage">0</span>MB</div>
</div>
</div>
<!-- 使用 coi-serviceworker 启用 SharedArrayBuffer 支持 -->
<script src="/ecs-framework/coi-serviceworker.js"></script>
<script>
// Check SharedArrayBuffer support and display info
function checkSharedArrayBufferSupport() {
const hasSharedArrayBuffer = typeof SharedArrayBuffer !== 'undefined';
const isCrossOriginIsolated = self.crossOriginIsolated || false;
const isLocalhost = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
const isGitHubPages = location.hostname === 'esengine.github.io';
console.log('=== SharedArrayBuffer 支持检测 ===');
console.log('SharedArrayBuffer 存在:', hasSharedArrayBuffer);
console.log('跨域隔离状态:', isCrossOriginIsolated);
console.log('是否本地环境:', isLocalhost);
console.log('是否 GitHub Pages:', isGitHubPages);
if (hasSharedArrayBuffer && isCrossOriginIsolated) {
console.log('✅ SharedArrayBuffer 功能已启用!');
console.log('系统将使用高性能的 SharedArrayBuffer 模式');
} else if (isGitHubPages) {
console.log(' 如果页面刷新,可能是 coi-serviceworker 正在设置跨域隔离');
console.log('刷新后 SharedArrayBuffer 应该可用');
}
return hasSharedArrayBuffer && isCrossOriginIsolated;
}
// Run check after page load
window.addEventListener('load', () => {
setTimeout(checkSharedArrayBufferSupport, 1000);
});
</script>
</body>
</html>

View File

@@ -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参数避免额外查询
- 定期检查查询性能和缓存命中率

File diff suppressed because it is too large Load Diff

View File

@@ -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<string, Function>;
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;
// 普通数值 (默认Float32Array4字节)
public x: number = 0;
public y: number = 0;
// 集合类型装饰器
@SerializeMap
public playerStats: Map<string, number> = new Map();
@SerializeSet
public achievements: Set<string> = 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<TransformComponent>;
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<MyComponent>;
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<string, any> = 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系统性能但滥用会带来相反的效果。建议在实际项目中先进行基准测试确认优化效果后再应用到生产环境。

View File

@@ -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());
```
通过合理使用这些系统类型,你可以构建出高性能、易维护的游戏逻辑!

View File

@@ -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<string, number> = 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<ITimer> = 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 = [];
}
}
```
定时器是游戏开发中非常有用的工具,合理使用可以让你的游戏逻辑更加优雅和高效!

View File

@@ -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<number, Entity> = 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<string, number> = 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框架在不同场景下都发挥最佳性能。

View File

@@ -0,0 +1,483 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ECS Framework Core Demos</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #0f0f23;
color: #e0e0e0;
overflow: hidden;
height: 100vh;
}
.app-container {
display: flex;
height: 100vh;
}
/* 侧边栏 */
.sidebar {
width: 280px;
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
border-right: 2px solid #0a3d62;
display: flex;
flex-direction: column;
box-shadow: 4px 0 20px rgba(0,0,0,0.5);
}
.sidebar-header {
padding: 30px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
text-align: center;
}
.sidebar-header h1 {
font-size: 1.5em;
color: white;
margin-bottom: 8px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.sidebar-header p {
font-size: 0.85em;
color: rgba(255,255,255,0.9);
font-weight: 300;
}
.demo-list {
flex: 1;
overflow-y: auto;
padding: 15px 0;
}
.demo-category {
margin-bottom: 10px;
}
.category-title {
padding: 12px 20px;
color: #8892b0;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
}
.demo-item {
padding: 14px 20px;
cursor: pointer;
transition: all 0.2s ease;
border-left: 3px solid transparent;
display: flex;
align-items: center;
gap: 12px;
}
.demo-item:hover {
background: rgba(102, 126, 234, 0.1);
border-left-color: #667eea;
}
.demo-item.active {
background: rgba(102, 126, 234, 0.2);
border-left-color: #667eea;
}
.demo-icon {
font-size: 20px;
width: 24px;
text-align: center;
}
.demo-info {
flex: 1;
}
.demo-name {
font-size: 14px;
font-weight: 500;
color: #ccd6f6;
margin-bottom: 2px;
}
.demo-item.active .demo-name {
color: #667eea;
font-weight: 600;
}
.demo-desc {
font-size: 11px;
color: #8892b0;
line-height: 1.3;
}
.sidebar-footer {
padding: 20px;
border-top: 1px solid rgba(255,255,255,0.1);
text-align: center;
}
.github-link {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: rgba(102, 126, 234, 0.2);
color: #667eea;
text-decoration: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
transition: all 0.2s;
}
.github-link:hover {
background: rgba(102, 126, 234, 0.3);
transform: translateY(-2px);
}
/* 主内容区 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.content-header {
padding: 25px 40px;
background: #1a1a2e;
border-bottom: 2px solid #0a3d62;
}
.content-header h2 {
font-size: 2em;
color: #ccd6f6;
margin-bottom: 8px;
}
.content-header p {
color: #8892b0;
font-size: 14px;
line-height: 1.6;
}
.demo-canvas-container {
flex: 1;
position: relative;
overflow: hidden;
background: #0a0a15;
}
#demoCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* 控制面板 */
.control-panel {
position: absolute;
top: 20px;
right: 20px;
width: 320px;
background: rgba(26, 26, 46, 0.95);
border-radius: 12px;
border: 1px solid rgba(102, 126, 234, 0.3);
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
backdrop-filter: blur(10px);
max-height: calc(100% - 40px);
overflow-y: auto;
}
.control-panel-header {
padding: 15px 20px;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%);
border-bottom: 1px solid rgba(102, 126, 234, 0.3);
border-radius: 12px 12px 0 0;
}
.control-panel-header h3 {
color: #667eea;
font-size: 16px;
font-weight: 600;
}
.control-panel-content {
padding: 20px;
}
.control-section {
margin-bottom: 20px;
}
.control-section:last-child {
margin-bottom: 0;
}
.control-section h4 {
color: #8892b0;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
font-weight: 600;
}
.button-group {
display: flex;
flex-direction: column;
gap: 8px;
}
button {
padding: 10px 16px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
button:active {
transform: translateY(0);
}
button.secondary {
background: rgba(102, 126, 234, 0.2);
color: #667eea;
}
button.secondary:hover {
background: rgba(102, 126, 234, 0.3);
}
button.danger {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
button.success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.input-group {
margin-bottom: 12px;
}
.input-group label {
display: block;
color: #8892b0;
font-size: 12px;
margin-bottom: 6px;
font-weight: 500;
}
.input-group input {
width: 100%;
padding: 8px 12px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
color: #ccd6f6;
font-size: 13px;
transition: all 0.2s;
}
.input-group input:focus {
outline: none;
border-color: #667eea;
background: rgba(255,255,255,0.08);
}
/* 统计信息 */
.stats-panel {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 15px;
}
.stat-item {
background: rgba(255,255,255,0.03);
padding: 12px;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.05);
}
.stat-label {
color: #8892b0;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
.stat-value {
color: #667eea;
font-size: 20px;
font-weight: 700;
}
/* Toast通知 */
.toast {
position: fixed;
bottom: 30px;
right: 30px;
background: rgba(26, 26, 46, 0.98);
border: 1px solid #667eea;
border-radius: 8px;
padding: 15px 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
transform: translateY(150%);
transition: transform 0.3s ease;
z-index: 1000;
min-width: 280px;
}
.toast.show {
transform: translateY(0);
}
.toast-content {
display: flex;
align-items: center;
gap: 12px;
}
.toast-icon {
font-size: 20px;
}
.toast-message {
color: #ccd6f6;
font-size: 14px;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255,255,255,0.05);
}
::-webkit-scrollbar-thumb {
background: rgba(102, 126, 234, 0.5);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(102, 126, 234, 0.7);
}
/* 加载动画 */
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(102, 126, 234, 0.2);
border-top-color: #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 15px;
color: #8892b0;
font-size: 14px;
}
</style>
</head>
<body>
<div class="app-container">
<!-- 侧边栏 -->
<div class="sidebar">
<div class="sidebar-header">
<h1>🎮 ECS Core Demos</h1>
<p>交互式演示集合</p>
</div>
<div class="demo-list" id="demoList">
<!-- Demo列表将通过JS动态生成 -->
</div>
<div class="sidebar-footer">
<a href="https://github.com/esengine/ecs-framework" target="_blank" class="github-link">
<span></span>
<span>GitHub</span>
</a>
</div>
</div>
<!-- 主内容区 -->
<div class="main-content">
<div class="content-header">
<h2 id="demoTitle">选择一个演示开始</h2>
<p id="demoDescription">从左侧菜单选择一个演示查看效果</p>
</div>
<div class="demo-canvas-container">
<canvas id="demoCanvas"></canvas>
<!-- 控制面板 -->
<div class="control-panel" id="controlPanel" style="display: none;">
<div class="control-panel-header">
<h3>控制面板</h3>
</div>
<div class="control-panel-content" id="controlPanelContent">
<!-- 控制内容将由各个demo动态生成 -->
</div>
</div>
<!-- 加载动画 -->
<div class="loading" id="loading" style="display: none;">
<div class="loading-spinner"></div>
<div class="loading-text">加载中...</div>
</div>
</div>
</div>
</div>
<!-- Toast通知 -->
<div class="toast" id="toast">
<div class="toast-content">
<span class="toast-icon"></span>
<span class="toast-message" id="toastMessage"></span>
</div>
</div>
<script type="module" src="src/main.ts"></script>
</body>
</html>

630
examples/core-demos/package-lock.json generated Normal file
View File

@@ -0,0 +1,630 @@
{
"name": "ecs-core-demos",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ecs-core-demos",
"version": "1.0.0",
"dependencies": {
"@esengine/ecs-framework": "file:../../packages/core"
},
"devDependencies": {
"typescript": "^5.0.0",
"vite": "^4.0.0"
}
},
"../../packages/core": {
"name": "@esengine/ecs-framework",
"version": "2.1.51",
"license": "MIT",
"dependencies": {
"msgpack-lite": "^0.1.26"
},
"devDependencies": {
"@babel/core": "^7.28.3",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
"@babel/plugin-transform-optional-chaining": "^7.27.1",
"@babel/preset-env": "^7.28.3",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/msgpack-lite": "^0.1.11",
"@types/node": "^20.19.17",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"rimraf": "^5.0.0",
"rollup": "^4.42.0",
"rollup-plugin-dts": "^6.2.1",
"ts-jest": "^29.4.0",
"typescript": "^5.8.3"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esengine/ecs-framework": {
"resolved": "../../packages/core",
"link": true
},
"node_modules/esbuild": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"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/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"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/rollup": {
"version": "3.29.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
"dev": true,
"license": "MIT",
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=14.18.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"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/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/vite": {
"version": "4.5.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
"integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
{
"name": "ecs-core-demos",
"version": "1.0.0",
"description": "ECS Framework Core Demos - Multiple Interactive Examples",
"main": "src/main.ts",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "^5.0.0",
"vite": "^4.0.0"
},
"dependencies": {
"@esengine/ecs-framework": "file:../../packages/core"
}
}

View File

@@ -0,0 +1,99 @@
import { Scene, Core } from '@esengine/ecs-framework';
export interface DemoInfo {
id: string;
name: string;
description: string;
category: string;
icon: string;
}
export abstract class DemoBase {
protected scene: Scene;
protected canvas: HTMLCanvasElement;
protected ctx: CanvasRenderingContext2D;
protected controlPanel: HTMLElement;
protected isRunning: boolean = false;
protected animationFrameId: number | null = null;
protected lastTime: number = 0;
constructor(canvas: HTMLCanvasElement, controlPanel: HTMLElement) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
this.controlPanel = controlPanel;
this.scene = new Scene({ name: this.getInfo().name });
// 设置canvas大小
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
}
abstract getInfo(): DemoInfo;
abstract setup(): void;
abstract createControls(): void;
protected resizeCanvas() {
const rect = this.canvas.getBoundingClientRect();
this.canvas.width = rect.width;
this.canvas.height = rect.height;
}
public start() {
if (this.isRunning) return;
this.isRunning = true;
this.lastTime = performance.now();
// 设置当前场景到Core
Core.setScene(this.scene);
this.scene.begin();
this.loop();
}
public stop() {
this.isRunning = false;
if (this.animationFrameId !== null) {
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = null;
}
}
public destroy() {
this.stop();
this.scene.end();
}
protected loop = () => {
if (!this.isRunning) return;
// 计算deltaTime
const currentTime = performance.now();
const deltaTime = (currentTime - this.lastTime) / 1000; // 转换为秒
this.lastTime = currentTime;
// 更新ECS框架
Core.update(deltaTime);
// 渲染
this.render();
// 继续循环
this.animationFrameId = requestAnimationFrame(this.loop);
}
protected abstract render(): void;
protected showToast(message: string, icon: string = '✅') {
const toast = document.getElementById('toast')!;
const toastMessage = document.getElementById('toastMessage')!;
const toastIcon = toast.querySelector('.toast-icon')!;
toastIcon.textContent = icon;
toastMessage.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
}

View File

@@ -0,0 +1,468 @@
import { DemoBase, DemoInfo } from './DemoBase';
import {
Component,
ECSComponent,
Entity,
EntitySystem,
Matcher,
Serializable,
Serialize,
IncrementalSerializer
} from '@esengine/ecs-framework';
// ===== 组件定义 =====
@ECSComponent('IncDemo_Position')
@Serializable({ version: 1, typeId: 'IncDemo_Position' })
class PositionComponent extends Component {
@Serialize() x: number = 0;
@Serialize() y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
@ECSComponent('IncDemo_Velocity')
@Serializable({ version: 1, typeId: 'IncDemo_Velocity' })
class VelocityComponent extends Component {
@Serialize() vx: number = 0;
@Serialize() vy: number = 0;
constructor(vx: number = 0, vy: number = 0) {
super();
this.vx = vx;
this.vy = vy;
}
}
@ECSComponent('IncDemo_Renderable')
@Serializable({ version: 1, typeId: 'IncDemo_Renderable' })
class RenderableComponent extends Component {
@Serialize() color: string = '#ffffff';
@Serialize() radius: number = 10;
constructor(color: string = '#ffffff', radius: number = 10) {
super();
this.color = color;
this.radius = radius;
}
}
// ===== 系统定义 =====
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(PositionComponent, VelocityComponent));
}
protected override process(entities: readonly Entity[]): void {
for (const entity of entities) {
const [pos, vel] = this.getComponents(entity, PositionComponent, VelocityComponent);
pos.x += vel.vx;
pos.y += vel.vy;
if (pos.x < 0 || pos.x > 1200) vel.vx *= -1;
if (pos.y < 0 || pos.y > 600) vel.vy *= -1;
}
}
}
class RenderSystem extends EntitySystem {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
constructor(canvas: HTMLCanvasElement) {
super(Matcher.all(PositionComponent, RenderableComponent));
this.canvas = canvas;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get canvas context');
this.ctx = ctx;
}
protected override process(entities: readonly Entity[]): void {
this.ctx.fillStyle = '#0a0a15';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
for (const entity of entities) {
const [pos, render] = this.getComponents(entity, PositionComponent, RenderableComponent);
this.ctx.fillStyle = render.color;
this.ctx.beginPath();
this.ctx.arc(pos.x, pos.y, render.radius, 0, Math.PI * 2);
this.ctx.fill();
this.ctx.fillStyle = 'white';
this.ctx.font = '10px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(entity.name, pos.x, pos.y - render.radius - 5);
}
}
}
export class IncrementalSerializationDemo extends DemoBase {
private renderSystem!: RenderSystem;
private incrementalHistory: any[] = [];
private autoSnapshotInterval: number | null = null;
getInfo(): DemoInfo {
return {
id: 'incremental-serialization',
name: '增量序列化',
description: '演示增量序列化功能,只保存场景变更而非完整状态,适用于网络同步和回放系统',
category: '核心功能',
icon: '🔄'
};
}
setup() {
// 创建控制面板
this.createControls();
// 添加系统
this.renderSystem = new RenderSystem(this.canvas);
this.scene.addEntityProcessor(new MovementSystem());
this.scene.addEntityProcessor(this.renderSystem);
// 创建初始实体
this.createInitialEntities();
// 创建基础快照
this.scene.createIncrementalSnapshot();
this.addToHistory('Initial State');
}
private createInitialEntities() {
// 创建玩家
const player = this.scene.createEntity('Player');
player.addComponent(new PositionComponent(600, 300));
player.addComponent(new VelocityComponent(2, 1.5));
player.addComponent(new RenderableComponent('#4a9eff', 15));
// 设置场景数据
this.scene.sceneData.set('gameTime', 0);
this.scene.sceneData.set('score', 0);
}
private createRandomEntity() {
const entity = this.scene.createEntity(`Entity_${Date.now()}`);
entity.addComponent(new PositionComponent(
Math.random() * this.canvas.width,
Math.random() * this.canvas.height
));
entity.addComponent(new VelocityComponent(
(Math.random() - 0.5) * 3,
(Math.random() - 0.5) * 3
));
const colors = ['#ff6b6b', '#4ecdc4', '#ffe66d', '#a8dadc', '#f1faee'];
entity.addComponent(new RenderableComponent(
colors[Math.floor(Math.random() * colors.length)],
5 + Math.random() * 10
));
}
private addToHistory(label: string) {
const incremental = this.scene.serializeIncremental();
const stats = IncrementalSerializer.getIncrementalStats(incremental);
// 计算JSON和二进制格式的大小
const jsonSize = IncrementalSerializer.getIncrementalSize(incremental, 'json');
const binarySize = IncrementalSerializer.getIncrementalSize(incremental, 'binary');
this.incrementalHistory.push({
label,
incremental,
stats,
timestamp: Date.now(),
jsonSize,
binarySize
});
this.scene.updateIncrementalSnapshot();
this.updateHistoryPanel();
this.updateStats();
}
createControls() {
this.controlPanel.innerHTML = `
<div class="control-section">
<h4>实体控制</h4>
<div class="button-group">
<button id="addEntity" class="secondary">添加随机实体</button>
<button id="removeEntity" class="danger">删除最后一个实体</button>
<button id="modifyEntity" class="secondary">修改实体数据</button>
</div>
</div>
<div class="control-section">
<h4>增量快照</h4>
<div class="button-group">
<button id="captureSnapshot" class="success">捕获当前状态</button>
<button id="clearHistory" class="danger">清空历史</button>
</div>
<div style="margin-top: 10px;">
<label>
<input type="checkbox" id="autoSnapshot">
自动快照每2秒
</label>
</div>
</div>
<div class="control-section">
<h4>场景数据控制</h4>
<div class="input-group">
<label>游戏时间</label>
<input type="number" id="gameTime" value="0" step="1">
</div>
<div class="input-group">
<label>分数</label>
<input type="number" id="score" value="0" step="10">
</div>
<button id="updateSceneData" class="secondary">更新场景数据</button>
</div>
<div class="stats-panel">
<div class="stat-item">
<div class="stat-label">实体数量</div>
<div class="stat-value" id="entityCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">历史记录</div>
<div class="stat-value" id="historyCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">JSON大小</div>
<div class="stat-value" id="jsonSize">0B</div>
</div>
<div class="stat-item">
<div class="stat-label">二进制大小</div>
<div class="stat-value" id="binarySize">0B</div>
</div>
<div class="stat-item">
<div class="stat-label">压缩率</div>
<div class="stat-value" id="compressionRatio">0%</div>
</div>
<div class="stat-item">
<div class="stat-label">总变更数</div>
<div class="stat-value" id="totalChanges">0</div>
</div>
</div>
<div class="control-section">
<h4>增量历史 <small style="color: #8892b0;">(点击快照查看详情)</small></h4>
<div style="max-height: 300px; overflow-y: auto; background: rgba(0,0,0,0.3); padding: 10px; border-radius: 6px;" id="historyPanel">
暂无历史记录
</div>
</div>
<div class="control-section">
<h4>快照详情</h4>
<div style="max-height: 200px; overflow-y: auto; background: rgba(0,0,0,0.3); padding: 10px; border-radius: 6px; font-family: monospace; font-size: 11px; color: #8892b0;" id="snapshotDetails">
点击历史记录查看详情...
</div>
</div>
`;
this.bindEvents();
this.updateStats();
}
private bindEvents() {
document.getElementById('addEntity')!.addEventListener('click', () => {
this.createRandomEntity();
this.addToHistory('添加实体');
this.showToast('添加了一个随机实体');
});
document.getElementById('removeEntity')!.addEventListener('click', () => {
const entities = this.scene.entities.buffer;
if (entities.length > 1) {
const lastEntity = entities[entities.length - 1];
lastEntity.destroy();
this.addToHistory('删除实体');
this.showToast('删除了最后一个实体');
} else {
this.showToast('至少保留一个实体', '⚠️');
}
});
document.getElementById('modifyEntity')!.addEventListener('click', () => {
const entities = this.scene.entities.buffer;
if (entities.length > 0) {
const randomEntity = entities[Math.floor(Math.random() * entities.length)];
const pos = randomEntity.getComponent(PositionComponent);
if (pos) {
pos.x = Math.random() * this.canvas.width;
pos.y = Math.random() * this.canvas.height;
}
this.addToHistory('修改实体位置');
this.showToast(`修改了 ${randomEntity.name} 的位置`);
}
});
document.getElementById('captureSnapshot')!.addEventListener('click', () => {
this.addToHistory('手动快照');
this.showToast('已捕获当前状态', '📸');
});
document.getElementById('clearHistory')!.addEventListener('click', () => {
this.incrementalHistory = [];
this.scene.createIncrementalSnapshot();
this.addToHistory('清空后重新开始');
this.showToast('历史记录已清空');
});
document.getElementById('autoSnapshot')!.addEventListener('change', (e) => {
const checkbox = e.target as HTMLInputElement;
if (checkbox.checked) {
this.autoSnapshotInterval = window.setInterval(() => {
this.addToHistory('自动快照');
}, 2000);
this.showToast('自动快照已启用', '⏱️');
} else {
if (this.autoSnapshotInterval !== null) {
clearInterval(this.autoSnapshotInterval);
this.autoSnapshotInterval = null;
}
this.showToast('自动快照已禁用');
}
});
document.getElementById('updateSceneData')!.addEventListener('click', () => {
const gameTime = parseInt((document.getElementById('gameTime') as HTMLInputElement).value);
const score = parseInt((document.getElementById('score') as HTMLInputElement).value);
this.scene.sceneData.set('gameTime', gameTime);
this.scene.sceneData.set('score', score);
this.addToHistory('更新场景数据');
this.showToast('场景数据已更新');
});
}
private updateHistoryPanel() {
const panel = document.getElementById('historyPanel')!;
if (this.incrementalHistory.length === 0) {
panel.innerHTML = '暂无历史记录';
return;
}
panel.innerHTML = this.incrementalHistory.map((item, index) => {
const isLatest = index === this.incrementalHistory.length - 1;
const time = new Date(item.timestamp).toLocaleTimeString();
return `
<div class="history-item" data-index="${index}" style="
padding: 8px;
margin: 4px 0;
background: ${isLatest ? 'rgba(74, 158, 255, 0.2)' : 'rgba(136, 146, 176, 0.1)'};
border-left: 3px solid ${isLatest ? '#4a9eff' : '#8892b0'};
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<strong>${item.label}</strong>
${isLatest ? '<span style="color: #4a9eff; margin-left: 8px;">●</span>' : ''}
</div>
<small style="color: #8892b0;">${time}</small>
</div>
<div style="font-size: 11px; color: #8892b0; margin-top: 4px;">
实体: +${item.stats.addedEntities} -${item.stats.removedEntities} ~${item.stats.updatedEntities} |
组件: +${item.stats.addedComponents} -${item.stats.removedComponents} ~${item.stats.updatedComponents}
</div>
<div style="font-size: 11px; color: #8892b0; margin-top: 2px;">
JSON: ${this.formatBytes(item.jsonSize)} |
Binary: ${this.formatBytes(item.binarySize)} |
<span style="color: #4ecdc4;">节省: ${((1 - item.binarySize / item.jsonSize) * 100).toFixed(1)}%</span>
</div>
</div>
`;
}).join('');
// 绑定点击事件
panel.querySelectorAll('.history-item').forEach(item => {
item.addEventListener('click', () => {
const index = parseInt(item.getAttribute('data-index')!);
this.showSnapshotDetails(index);
});
});
// 自动滚动到底部
panel.scrollTop = panel.scrollHeight;
}
private showSnapshotDetails(index: number) {
const item = this.incrementalHistory[index];
const detailsPanel = document.getElementById('snapshotDetails')!;
const compressionRatio = ((1 - item.binarySize / item.jsonSize) * 100).toFixed(1);
const details = {
版本: item.incremental.version,
基础版本: item.incremental.baseVersion,
时间戳: new Date(item.incremental.timestamp).toLocaleString(),
场景名称: item.incremental.sceneName,
: {
JSON大小: this.formatBytes(item.jsonSize),
二进制大小: this.formatBytes(item.binarySize),
: `${compressionRatio}%`,
节省字节: this.formatBytes(item.jsonSize - item.binarySize)
},
统计: item.stats,
实体变更: item.incremental.entityChanges.map((c: any) => ({
操作: c.operation,
实体ID: c.entityId,
实体名称: c.entityName
})),
组件变更: item.incremental.componentChanges.map((c: any) => ({
操作: c.operation,
实体ID: c.entityId,
组件类型: c.componentType
})),
场景数据变更: item.incremental.sceneDataChanges.map((c: any) => ({
: c.key,
: c.value,
已删除: c.deleted
}))
};
detailsPanel.textContent = JSON.stringify(details, null, 2);
}
private updateStats() {
document.getElementById('entityCount')!.textContent = this.scene.entities.count.toString();
document.getElementById('historyCount')!.textContent = this.incrementalHistory.length.toString();
if (this.incrementalHistory.length > 0) {
const lastItem = this.incrementalHistory[this.incrementalHistory.length - 1];
document.getElementById('jsonSize')!.textContent = this.formatBytes(lastItem.jsonSize);
document.getElementById('binarySize')!.textContent = this.formatBytes(lastItem.binarySize);
const compressionRatio = ((1 - lastItem.binarySize / lastItem.jsonSize) * 100).toFixed(1);
const ratioElement = document.getElementById('compressionRatio')!;
ratioElement.textContent = `${compressionRatio}%`;
ratioElement.style.color = parseFloat(compressionRatio) > 30 ? '#4ecdc4' : '#ffe66d';
document.getElementById('totalChanges')!.textContent = lastItem.stats.totalChanges.toString();
}
}
private formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
}
protected render() {
// RenderSystem会处理渲染
}
public destroy() {
if (this.autoSnapshotInterval !== null) {
clearInterval(this.autoSnapshotInterval);
}
super.destroy();
}
}

View File

@@ -0,0 +1,386 @@
import { DemoBase, DemoInfo } from './DemoBase';
import {
Component,
ECSComponent,
Entity,
EntitySystem,
Matcher,
Serializable,
Serialize,
SerializeAsMap
} from '@esengine/ecs-framework';
// ===== 组件定义 =====
@ECSComponent('SerDemo_Position')
@Serializable({ version: 1, typeId: 'SerDemo_Position' })
class PositionComponent extends Component {
@Serialize() x: number = 0;
@Serialize() y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
@ECSComponent('SerDemo_Velocity')
@Serializable({ version: 1, typeId: 'SerDemo_Velocity' })
class VelocityComponent extends Component {
@Serialize() vx: number = 0;
@Serialize() vy: number = 0;
constructor(vx: number = 0, vy: number = 0) {
super();
this.vx = vx;
this.vy = vy;
}
}
@ECSComponent('SerDemo_Renderable')
@Serializable({ version: 1, typeId: 'SerDemo_Renderable' })
class RenderableComponent extends Component {
@Serialize() color: string = '#ffffff';
@Serialize() radius: number = 10;
constructor(color: string = '#ffffff', radius: number = 10) {
super();
this.color = color;
this.radius = radius;
}
}
@ECSComponent('SerDemo_Player')
@Serializable({ version: 1, typeId: 'SerDemo_Player' })
class PlayerComponent extends Component {
@Serialize() name: string = 'Player';
@Serialize() level: number = 1;
@Serialize() health: number = 100;
@SerializeAsMap() inventory: Map<string, number> = new Map();
constructor(name: string = 'Player') {
super();
this.name = name;
}
}
// ===== 系统定义 =====
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(PositionComponent, VelocityComponent));
}
protected override process(entities: readonly Entity[]): void {
for (const entity of entities) {
const [pos, vel] = this.getComponents(entity, PositionComponent, VelocityComponent);
pos.x += vel.vx;
pos.y += vel.vy;
// 边界反弹
if (pos.x < 0 || pos.x > 1200) vel.vx *= -1;
if (pos.y < 0 || pos.y > 600) vel.vy *= -1;
}
}
}
class RenderSystem extends EntitySystem {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
constructor(canvas: HTMLCanvasElement) {
super(Matcher.all(PositionComponent, RenderableComponent));
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
}
protected override process(entities: readonly Entity[]): void {
// 清空画布
this.ctx.fillStyle = '#0a0a15';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// 渲染所有实体
for (const entity of entities) {
const [pos, render] = this.getComponents(entity, PositionComponent, RenderableComponent);
this.ctx.fillStyle = render.color;
this.ctx.beginPath();
this.ctx.arc(pos.x, pos.y, render.radius, 0, Math.PI * 2);
this.ctx.fill();
// 如果是玩家,显示名字
const player = entity.getComponent(PlayerComponent);
if (player) {
this.ctx.fillStyle = 'white';
this.ctx.font = '12px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(player.name, pos.x, pos.y - render.radius - 5);
}
}
}
}
export class SerializationDemo extends DemoBase {
private renderSystem!: RenderSystem;
private jsonData: string = '';
private binaryData: Buffer | null = null;
getInfo(): DemoInfo {
return {
id: 'serialization',
name: '场景序列化',
description: '演示场景的序列化和反序列化功能支持JSON和二进制格式',
category: '核心功能',
icon: '💾'
};
}
setup() {
// @ECSComponent装饰器会自动注册组件到ComponentRegistry
// ComponentRegistry会被序列化系统自动使用无需手动注册
// 添加系统
this.renderSystem = new RenderSystem(this.canvas);
this.scene.addEntityProcessor(new MovementSystem());
this.scene.addEntityProcessor(this.renderSystem);
// 创建初始实体
this.createInitialEntities();
// 创建控制面板
this.createControls();
}
private createInitialEntities() {
// 创建玩家
const player = this.scene.createEntity('Player');
player.addComponent(new PositionComponent(600, 300));
player.addComponent(new VelocityComponent(2, 1.5));
player.addComponent(new RenderableComponent('#4a9eff', 15));
const playerComp = new PlayerComponent('Hero');
playerComp.level = 5;
playerComp.health = 100;
playerComp.inventory.set('sword', 1);
playerComp.inventory.set('potion', 5);
player.addComponent(playerComp);
// 创建一些随机实体
for (let i = 0; i < 5; i++) {
this.createRandomEntity();
}
// 设置场景数据
this.scene.sceneData.set('weather', 'sunny');
this.scene.sceneData.set('gameTime', 12.5);
this.scene.sceneData.set('difficulty', 'normal');
}
private createRandomEntity() {
const entity = this.scene.createEntity(`Entity_${Date.now()}`);
entity.addComponent(new PositionComponent(
Math.random() * this.canvas.width,
Math.random() * this.canvas.height
));
entity.addComponent(new VelocityComponent(
(Math.random() - 0.5) * 3,
(Math.random() - 0.5) * 3
));
const colors = ['#ff6b6b', '#4ecdc4', '#ffe66d', '#a8dadc', '#f1faee'];
entity.addComponent(new RenderableComponent(
colors[Math.floor(Math.random() * colors.length)],
5 + Math.random() * 10
));
}
createControls() {
this.controlPanel.innerHTML = `
<div class="control-section">
<h4>实体控制</h4>
<div class="button-group">
<button id="addEntity" class="secondary">添加随机实体</button>
<button id="clearEntities" class="danger">清空所有实体</button>
</div>
</div>
<div class="control-section">
<h4>序列化操作</h4>
<div class="button-group">
<button id="serializeJSON">序列化为JSON</button>
<button id="serializeBinary" class="success">序列化为二进制</button>
<button id="deserialize" class="secondary">反序列化恢复</button>
</div>
</div>
<div class="control-section">
<h4>本地存储</h4>
<div class="button-group">
<button id="saveLocal" class="success">保存到LocalStorage</button>
<button id="loadLocal" class="secondary">从LocalStorage加载</button>
</div>
</div>
<div class="control-section">
<h4>场景数据</h4>
<div class="input-group">
<label>天气</label>
<input type="text" id="weather" value="sunny" placeholder="sunny/rainy/snowy">
</div>
<div class="input-group">
<label>游戏时间</label>
<input type="number" id="gameTime" value="12.5" step="0.1" min="0" max="24">
</div>
<button id="updateSceneData" class="secondary">更新场景数据</button>
</div>
<div class="stats-panel">
<div class="stat-item">
<div class="stat-label">实体数量</div>
<div class="stat-value" id="entityCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">JSON大小</div>
<div class="stat-value" id="jsonSize">0B</div>
</div>
<div class="stat-item">
<div class="stat-label">二进制大小</div>
<div class="stat-value" id="binarySize">0B</div>
</div>
<div class="stat-item">
<div class="stat-label">压缩率</div>
<div class="stat-value" id="compressionRatio">0%</div>
</div>
</div>
<div class="control-section">
<h4>序列化数据预览</h4>
<div style="max-height: 200px; overflow-y: auto; background: rgba(0,0,0,0.3); padding: 10px; border-radius: 6px; font-family: monospace; font-size: 11px; color: #8892b0; word-break: break-all;" id="dataPreview">
点击序列化按钮查看数据...
</div>
</div>
`;
// 绑定事件
this.bindEvents();
}
private bindEvents() {
document.getElementById('addEntity')!.addEventListener('click', () => {
this.createRandomEntity();
this.updateStats();
this.showToast('添加了一个随机实体');
});
document.getElementById('clearEntities')!.addEventListener('click', () => {
this.scene.destroyAllEntities();
this.createInitialEntities();
this.updateStats();
this.showToast('场景已重置');
});
document.getElementById('serializeJSON')!.addEventListener('click', () => {
this.jsonData = this.scene.serialize({ format: 'json', pretty: true }) as string;
this.updateDataPreview(this.jsonData, 'json');
this.updateStats();
this.showToast('已序列化为JSON格式');
});
document.getElementById('serializeBinary')!.addEventListener('click', () => {
this.binaryData = this.scene.serialize({ format: 'binary' }) as Buffer;
const base64 = this.binaryData.toString('base64');
this.updateDataPreview(`Binary Data (Base64):\n${base64.substring(0, 500)}...`, 'binary');
this.updateStats();
this.showToast('已序列化为二进制格式', '🔐');
});
document.getElementById('deserialize')!.addEventListener('click', () => {
const data = this.binaryData || this.jsonData;
if (!data) {
this.showToast('请先执行序列化操作', '⚠️');
return;
}
this.scene.deserialize(data, {
strategy: 'replace'
// componentRegistry会自动从ComponentRegistry获取无需手动传入
});
this.updateStats();
this.showToast('场景已恢复');
});
document.getElementById('saveLocal')!.addEventListener('click', () => {
const jsonData = this.scene.serialize({ format: 'json' }) as string;
localStorage.setItem('ecs_demo_scene', jsonData);
this.showToast('已保存到LocalStorage', '💾');
});
document.getElementById('loadLocal')!.addEventListener('click', () => {
const data = localStorage.getItem('ecs_demo_scene');
if (!data) {
this.showToast('LocalStorage中没有保存的场景', '⚠️');
return;
}
this.scene.deserialize(data, {
strategy: 'replace'
// componentRegistry会自动从ComponentRegistry获取无需手动传入
});
this.updateStats();
this.showToast('已从LocalStorage加载', '📂');
});
document.getElementById('updateSceneData')!.addEventListener('click', () => {
const weather = (document.getElementById('weather') as HTMLInputElement).value;
const gameTime = parseFloat((document.getElementById('gameTime') as HTMLInputElement).value);
this.scene.sceneData.set('weather', weather);
this.scene.sceneData.set('gameTime', gameTime);
this.showToast('场景数据已更新');
});
// 初始更新统计
this.updateStats();
}
private updateDataPreview(data: string, format: string) {
const preview = document.getElementById('dataPreview')!;
if (format === 'json') {
const truncated = data.length > 1000 ? data.substring(0, 1000) + '\n...(truncated)' : data;
preview.textContent = truncated;
} else {
preview.textContent = data;
}
}
private updateStats() {
const entityCount = this.scene.entities.count;
document.getElementById('entityCount')!.textContent = entityCount.toString();
// 计算JSON大小
if (this.jsonData) {
const jsonSize = new Blob([this.jsonData]).size;
document.getElementById('jsonSize')!.textContent = this.formatBytes(jsonSize);
}
// 计算二进制大小
if (this.binaryData) {
const binarySize = this.binaryData.length;
document.getElementById('binarySize')!.textContent = this.formatBytes(binarySize);
// 计算压缩率
if (this.jsonData) {
const jsonSize = new Blob([this.jsonData]).size;
const ratio = ((1 - binarySize / jsonSize) * 100).toFixed(1);
document.getElementById('compressionRatio')!.textContent = `${ratio}%`;
}
}
}
private formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
}
protected render() {
// RenderSystem会处理渲染
}
}

View File

@@ -0,0 +1,832 @@
import { DemoBase, DemoInfo } from './DemoBase';
import { Component, ECSComponent, WorkerEntitySystem, EntitySystem, Matcher, Entity, ECSSystem, PlatformManager, Time } from '@esengine/ecs-framework';
import { BrowserAdapter } from '../platform/BrowserAdapter';
// ============ 组件定义 ============
@ECSComponent('WorkerDemo_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;
}
set(x: number, y: number): void {
this.x = x;
this.y = y;
}
}
@ECSComponent('WorkerDemo_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;
}
set(dx: number, dy: number): void {
this.dx = dx;
this.dy = dy;
}
}
@ECSComponent('WorkerDemo_Physics')
class Physics extends Component {
mass: number = 1;
bounce: number = 0.8;
friction: number = 0.95;
constructor(mass: number = 1, bounce: number = 0.8, friction: number = 0.95) {
super();
this.mass = mass;
this.bounce = bounce;
this.friction = friction;
}
}
@ECSComponent('WorkerDemo_Renderable')
class Renderable extends Component {
color: string = '#ffffff';
size: number = 5;
shape: 'circle' | 'square' = 'circle';
constructor(color: string = '#ffffff', size: number = 5, shape: 'circle' | 'square' = 'circle') {
super();
this.color = color;
this.size = size;
this.shape = shape;
}
}
@ECSComponent('WorkerDemo_Lifetime')
class Lifetime extends Component {
maxAge: number = 5;
currentAge: number = 0;
constructor(maxAge: number = 5) {
super();
this.maxAge = maxAge;
this.currentAge = 0;
}
isDead(): boolean {
return this.currentAge >= this.maxAge;
}
}
// ============ 系统定义 ============
interface PhysicsEntityData {
id: number;
x: number;
y: number;
dx: number;
dy: number;
mass: number;
bounce: number;
friction: number;
radius: number;
}
interface PhysicsConfig {
gravity: number;
canvasWidth: number;
canvasHeight: number;
groundFriction: number;
}
@ECSSystem('PhysicsWorkerSystem')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsEntityData> {
private physicsConfig: PhysicsConfig;
constructor(enableWorker: boolean, canvasWidth: number, canvasHeight: number) {
const defaultConfig = {
gravity: 100,
canvasWidth,
canvasHeight,
groundFriction: 0.98
};
const isSharedArrayBufferAvailable = typeof SharedArrayBuffer !== 'undefined' && self.crossOriginIsolated;
super(
Matcher.empty().all(Position, Velocity, Physics),
{
enableWorker,
workerCount: isSharedArrayBufferAvailable ? (navigator.hardwareConcurrency || 2) : 1,
systemConfig: defaultConfig,
useSharedArrayBuffer: true
}
);
this.physicsConfig = defaultConfig;
}
protected extractEntityData(entity: Entity): PhysicsEntityData {
const position = entity.getComponent(Position)!;
const velocity = entity.getComponent(Velocity)!;
const physics = entity.getComponent(Physics)!;
const renderable = entity.getComponent(Renderable)!;
return {
id: entity.id,
x: position.x,
y: position.y,
dx: velocity.dx,
dy: velocity.dy,
mass: physics.mass,
bounce: physics.bounce,
friction: physics.friction,
radius: renderable.size
};
}
protected workerProcess(
entities: PhysicsEntityData[],
deltaTime: number,
systemConfig?: PhysicsConfig
): PhysicsEntityData[] {
const config = systemConfig || this.physicsConfig;
const result = entities.map(e => ({ ...e }));
for (let i = 0; i < result.length; i++) {
const entity = result[i];
entity.dy += config.gravity * deltaTime;
entity.x += entity.dx * deltaTime;
entity.y += entity.dy * deltaTime;
if (entity.x <= entity.radius) {
entity.x = entity.radius;
entity.dx = -entity.dx * entity.bounce;
} else if (entity.x >= config.canvasWidth - entity.radius) {
entity.x = config.canvasWidth - entity.radius;
entity.dx = -entity.dx * entity.bounce;
}
if (entity.y <= entity.radius) {
entity.y = entity.radius;
entity.dy = -entity.dy * entity.bounce;
} else if (entity.y >= config.canvasHeight - entity.radius) {
entity.y = config.canvasHeight - entity.radius;
entity.dy = -entity.dy * entity.bounce;
entity.dx *= config.groundFriction;
}
entity.dx *= entity.friction;
entity.dy *= entity.friction;
}
for (let i = 0; i < result.length; i++) {
for (let j = i + 1; j < result.length; j++) {
const ball1 = result[i];
const ball2 = result[j];
const dx = ball2.x - ball1.x;
const dy = ball2.y - ball1.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const minDistance = ball1.radius + ball2.radius;
if (distance < minDistance && distance > 0) {
const nx = dx / distance;
const ny = dy / distance;
const overlap = minDistance - distance;
const separationX = nx * overlap * 0.5;
const separationY = ny * overlap * 0.5;
ball1.x -= separationX;
ball1.y -= separationY;
ball2.x += separationX;
ball2.y += separationY;
const relativeVelocityX = ball2.dx - ball1.dx;
const relativeVelocityY = ball2.dy - ball1.dy;
const velocityAlongNormal = relativeVelocityX * nx + relativeVelocityY * ny;
if (velocityAlongNormal > 0) continue;
const restitution = (ball1.bounce + ball2.bounce) * 0.5;
const impulseScalar = -(1 + restitution) * velocityAlongNormal / (1/ball1.mass + 1/ball2.mass);
const impulseX = impulseScalar * nx;
const impulseY = impulseScalar * ny;
ball1.dx -= impulseX / ball1.mass;
ball1.dy -= impulseY / ball1.mass;
ball2.dx += impulseX / ball2.mass;
ball2.dy += impulseY / ball2.mass;
const energyLoss = 0.98;
ball1.dx *= energyLoss;
ball1.dy *= energyLoss;
ball2.dx *= energyLoss;
ball2.dy *= energyLoss;
}
}
}
return result;
}
protected applyResult(entity: Entity, result: PhysicsEntityData): void {
if (!entity || !entity.enabled) return;
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
if (!position || !velocity) return;
position.set(result.x, result.y);
velocity.set(result.dx, result.dy);
}
public updatePhysicsConfig(newConfig: Partial<PhysicsConfig>): void {
Object.assign(this.physicsConfig, newConfig);
this.updateConfig({ systemConfig: this.physicsConfig });
}
public getPhysicsConfig(): PhysicsConfig {
return { ...this.physicsConfig };
}
protected getDefaultEntityDataSize(): number {
return 9;
}
protected writeEntityToBuffer(entityData: PhysicsEntityData, offset: number): void {
const sharedArray = (this as any).sharedFloatArray as Float32Array;
if (!sharedArray) return;
// 在第一个位置存储当前实体数量
const currentEntityCount = Math.floor(offset / 9) + 1;
sharedArray[0] = currentEntityCount;
// 数据从索引9开始存储第一个9个位置用作元数据区域
const dataOffset = offset + 9;
sharedArray[dataOffset + 0] = entityData.id;
sharedArray[dataOffset + 1] = entityData.x;
sharedArray[dataOffset + 2] = entityData.y;
sharedArray[dataOffset + 3] = entityData.dx;
sharedArray[dataOffset + 4] = entityData.dy;
sharedArray[dataOffset + 5] = entityData.mass;
sharedArray[dataOffset + 6] = entityData.bounce;
sharedArray[dataOffset + 7] = entityData.friction;
sharedArray[dataOffset + 8] = entityData.radius;
}
protected readEntityFromBuffer(offset: number): PhysicsEntityData | null {
const sharedArray = (this as any).sharedFloatArray as Float32Array;
if (!sharedArray) return null;
// 数据从索引9开始存储
const dataOffset = offset + 9;
return {
id: sharedArray[dataOffset + 0],
x: sharedArray[dataOffset + 1],
y: sharedArray[dataOffset + 2],
dx: sharedArray[dataOffset + 3],
dy: sharedArray[dataOffset + 4],
mass: sharedArray[dataOffset + 5],
bounce: sharedArray[dataOffset + 6],
friction: sharedArray[dataOffset + 7],
radius: sharedArray[dataOffset + 8]
};
}
protected getSharedArrayBufferProcessFunction(): any {
return function(sharedFloatArray: Float32Array, startIndex: number, endIndex: number, deltaTime: number, systemConfig?: any) {
const config = systemConfig || {
gravity: 100,
canvasWidth: 800,
canvasHeight: 600,
groundFriction: 0.98
};
const actualEntityCount = sharedFloatArray[0];
// 基础物理更新
for (let i = startIndex; i < endIndex && i < actualEntityCount; i++) {
const offset = i * 9 + 9;
const id = sharedFloatArray[offset + 0];
if (id === 0) continue;
let x = sharedFloatArray[offset + 1];
let y = sharedFloatArray[offset + 2];
let dx = sharedFloatArray[offset + 3];
let dy = sharedFloatArray[offset + 4];
const bounce = sharedFloatArray[offset + 6];
const friction = sharedFloatArray[offset + 7];
const radius = sharedFloatArray[offset + 8];
// 应用重力
dy += config.gravity * deltaTime;
// 更新位置
x += dx * deltaTime;
y += dy * deltaTime;
// 边界碰撞
if (x <= radius) {
x = radius;
dx = -dx * bounce;
} else if (x >= config.canvasWidth - radius) {
x = config.canvasWidth - radius;
dx = -dx * bounce;
}
if (y <= radius) {
y = radius;
dy = -dy * bounce;
} else if (y >= config.canvasHeight - radius) {
y = config.canvasHeight - radius;
dy = -dy * bounce;
dx *= config.groundFriction;
}
// 空气阻力
dx *= friction;
dy *= friction;
// 写回数据
sharedFloatArray[offset + 1] = x;
sharedFloatArray[offset + 2] = y;
sharedFloatArray[offset + 3] = dx;
sharedFloatArray[offset + 4] = dy;
}
// 碰撞检测
for (let i = startIndex; i < endIndex && i < actualEntityCount; i++) {
const offset1 = i * 9 + 9;
const id1 = sharedFloatArray[offset1 + 0];
if (id1 === 0) continue;
let x1 = sharedFloatArray[offset1 + 1];
let y1 = sharedFloatArray[offset1 + 2];
let dx1 = sharedFloatArray[offset1 + 3];
let dy1 = sharedFloatArray[offset1 + 4];
const mass1 = sharedFloatArray[offset1 + 5];
const bounce1 = sharedFloatArray[offset1 + 6];
const radius1 = sharedFloatArray[offset1 + 8];
for (let j = 0; j < actualEntityCount; j++) {
if (i === j) continue;
const offset2 = j * 9 + 9;
const id2 = sharedFloatArray[offset2 + 0];
if (id2 === 0) continue;
const x2 = sharedFloatArray[offset2 + 1];
const y2 = sharedFloatArray[offset2 + 2];
const dx2 = sharedFloatArray[offset2 + 3];
const dy2 = sharedFloatArray[offset2 + 4];
const mass2 = sharedFloatArray[offset2 + 5];
const bounce2 = sharedFloatArray[offset2 + 6];
const radius2 = sharedFloatArray[offset2 + 8];
if (isNaN(x2) || isNaN(y2) || isNaN(radius2) || radius2 <= 0) continue;
const deltaX = x2 - x1;
const deltaY = y2 - y1;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const minDistance = radius1 + radius2;
if (distance < minDistance && distance > 0) {
const nx = deltaX / distance;
const ny = deltaY / distance;
const overlap = minDistance - distance;
const separationX = nx * overlap * 0.5;
const separationY = ny * overlap * 0.5;
x1 -= separationX;
y1 -= separationY;
const relativeVelocityX = dx2 - dx1;
const relativeVelocityY = dy2 - dy1;
const velocityAlongNormal = relativeVelocityX * nx + relativeVelocityY * ny;
if (velocityAlongNormal > 0) continue;
const restitution = (bounce1 + bounce2) * 0.5;
const impulseScalar = -(1 + restitution) * velocityAlongNormal / (1/mass1 + 1/mass2);
const impulseX = impulseScalar * nx;
const impulseY = impulseScalar * ny;
dx1 -= impulseX / mass1;
dy1 -= impulseY / mass1;
const energyLoss = 0.98;
dx1 *= energyLoss;
dy1 *= energyLoss;
}
}
sharedFloatArray[offset1 + 1] = x1;
sharedFloatArray[offset1 + 2] = y1;
sharedFloatArray[offset1 + 3] = dx1;
sharedFloatArray[offset1 + 4] = dy1;
}
};
}
}
@ECSSystem('RenderSystem')
class RenderSystem extends EntitySystem {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
constructor(canvas: HTMLCanvasElement) {
super(Matcher.all(Position, Renderable));
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
}
protected override process(entities: readonly Entity[]): void {
this.ctx.fillStyle = '#000';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
for (const entity of entities) {
const position = this.requireComponent(entity, Position);
const renderable = this.requireComponent(entity, Renderable);
this.ctx.fillStyle = renderable.color;
this.ctx.beginPath();
this.ctx.arc(position.x, position.y, renderable.size, 0, Math.PI * 2);
this.ctx.fill();
}
}
}
@ECSSystem('LifetimeSystem')
class LifetimeSystem extends EntitySystem {
constructor() {
super(Matcher.all(Lifetime));
}
protected override process(entities: readonly Entity[]): void {
const deltaTime = Time.deltaTime;
for (const entity of entities) {
const lifetime = this.requireComponent(entity, Lifetime);
lifetime.currentAge += deltaTime;
if (lifetime.isDead()) {
entity.destroy();
}
}
}
}
// ============ Demo类 ============
export class WorkerSystemDemo extends DemoBase {
private physicsSystem!: PhysicsWorkerSystem;
private renderSystem!: RenderSystem;
private lifetimeSystem!: LifetimeSystem;
private currentFPS = 0;
private frameCount = 0;
private fpsUpdateTime = 0;
private elements: { [key: string]: HTMLElement } = {};
getInfo(): DemoInfo {
return {
id: 'worker-system',
name: 'Worker System',
description: '演示 ECS 框架中的多线程物理计算能力',
category: '核心功能',
icon: '⚙️'
};
}
setup(): void {
// 注册浏览器平台适配器
const browserAdapter = new BrowserAdapter();
PlatformManager.getInstance().registerAdapter(browserAdapter);
// 初始化系统
this.physicsSystem = new PhysicsWorkerSystem(true, this.canvas.width, this.canvas.height);
this.renderSystem = new RenderSystem(this.canvas);
this.lifetimeSystem = new LifetimeSystem();
this.physicsSystem.updateOrder = 1;
this.lifetimeSystem.updateOrder = 2;
this.renderSystem.updateOrder = 3;
this.scene.addSystem(this.physicsSystem);
this.scene.addSystem(this.lifetimeSystem);
this.scene.addSystem(this.renderSystem);
// 创建控制面板
this.createControls();
// 初始化UI元素引用
this.initializeUIElements();
this.bindEvents();
// 生成初始实体
this.spawnInitialEntities(1000);
}
createControls(): void {
this.controlPanel.innerHTML = `
<div style="background: #2a2a2a; padding: 20px; border-radius: 8px; height: 100%; overflow-y: auto;">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #ccc;">实体数量:</label>
<input type="range" id="entityCount" min="100" max="10000" value="1000" step="100"
style="width: 100%; margin-bottom: 5px;">
<span id="entityCountValue" style="color: #fff;">1000</span>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #ccc;">Worker 设置:</label>
<button id="toggleWorker" style="width: 100%; padding: 8px; margin-bottom: 5px;
background: #4a9eff; color: white; border: none; border-radius: 4px; cursor: pointer;">
禁用 Worker
</button>
</div>
<div style="margin-bottom: 15px;">
<button id="spawnParticles" style="width: 100%; padding: 8px; margin-bottom: 5px;
background: #4a9eff; color: white; border: none; border-radius: 4px; cursor: pointer;">
生成粒子爆炸
</button>
<button id="clearEntities" style="width: 100%; padding: 8px; margin-bottom: 5px;
background: #4a9eff; color: white; border: none; border-radius: 4px; cursor: pointer;">
清空所有实体
</button>
<button id="resetDemo" style="width: 100%; padding: 8px;
background: #4a9eff; color: white; border: none; border-radius: 4px; cursor: pointer;">
重置演示
</button>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #ccc;">物理参数:</label>
<input type="range" id="gravity" min="0" max="500" value="100" step="10"
style="width: 100%; margin-bottom: 5px;">
<label style="color: #ccc;">重力: <span id="gravityValue">100</span></label>
<input type="range" id="friction" min="0" max="100" value="95" step="5"
style="width: 100%; margin-top: 10px; margin-bottom: 5px;">
<label style="color: #ccc;">摩擦力: <span id="frictionValue">95%</span></label>
</div>
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; font-family: monospace; font-size: 12px;">
<h3 style="margin-top: 0; color: #4a9eff;">性能统计</h3>
<div style="margin: 5px 0; color: #ccc;">FPS: <span id="fps" style="color: #4eff4a;">0</span></div>
<div style="margin: 5px 0; color: #ccc;">实体数量: <span id="entityCountStat" style="color: #fff;">0</span></div>
<div style="margin: 5px 0; color: #ccc;">Worker状态: <span id="workerStatus" style="color: #ff4a4a;">未启用</span></div>
<div style="margin: 5px 0; color: #ccc;">Worker负载: <span id="workerLoad" style="color: #fff;">N/A</span></div>
</div>
</div>
`;
}
protected render(): void {
this.frameCount++;
const currentTime = performance.now();
if (currentTime - this.fpsUpdateTime >= 1000) {
this.currentFPS = this.frameCount;
this.frameCount = 0;
this.fpsUpdateTime = currentTime;
}
this.updateUI();
}
private initializeUIElements(): void {
const elementIds = [
'entityCount', 'entityCountValue', 'toggleWorker',
'gravity', 'gravityValue', 'friction', 'frictionValue', 'spawnParticles',
'clearEntities', 'resetDemo', 'fps', 'entityCountStat', 'workerStatus', 'workerLoad'
];
for (const id of elementIds) {
const element = document.getElementById(id);
if (element) {
this.elements[id] = element;
}
}
}
private bindEvents(): void {
if (this.elements.entityCount && this.elements.entityCountValue) {
const slider = this.elements.entityCount as HTMLInputElement;
slider.addEventListener('input', () => {
this.elements.entityCountValue.textContent = slider.value;
});
slider.addEventListener('change', () => {
const count = parseInt(slider.value);
this.spawnInitialEntities(count);
});
}
if (this.elements.toggleWorker) {
this.elements.toggleWorker.addEventListener('click', () => {
const workerEnabled = this.toggleWorker();
this.elements.toggleWorker.textContent = workerEnabled ? '禁用 Worker' : '启用 Worker';
});
}
if (this.elements.gravity && this.elements.gravityValue) {
const slider = this.elements.gravity as HTMLInputElement;
slider.addEventListener('input', () => {
this.elements.gravityValue.textContent = slider.value;
});
slider.addEventListener('change', () => {
const gravity = parseInt(slider.value);
this.updateWorkerConfig({ gravity });
});
}
if (this.elements.friction && this.elements.frictionValue) {
const slider = this.elements.friction as HTMLInputElement;
slider.addEventListener('input', () => {
const value = parseInt(slider.value);
this.elements.frictionValue.textContent = `${value}%`;
});
slider.addEventListener('change', () => {
const friction = parseInt(slider.value) / 100;
this.updateWorkerConfig({ friction });
});
}
if (this.elements.spawnParticles) {
this.elements.spawnParticles.addEventListener('click', () => {
const centerX = this.canvas.width / 2;
const centerY = this.canvas.height / 2;
this.spawnParticleExplosion(centerX, centerY, 100);
});
}
if (this.elements.clearEntities) {
this.elements.clearEntities.addEventListener('click', () => {
this.clearAllEntities();
});
}
if (this.elements.resetDemo) {
this.elements.resetDemo.addEventListener('click', () => {
(this.elements.entityCount as HTMLInputElement).value = '1000';
this.elements.entityCountValue.textContent = '1000';
(this.elements.gravity as HTMLInputElement).value = '100';
this.elements.gravityValue.textContent = '100';
(this.elements.friction as HTMLInputElement).value = '95';
this.elements.frictionValue.textContent = '95%';
this.spawnInitialEntities(1000);
this.updateWorkerConfig({ gravity: 100, friction: 0.95 });
});
}
this.canvas.addEventListener('click', (event) => {
const rect = this.canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
this.spawnParticleExplosion(x, y, 30);
});
}
private updateUI(): void {
const workerInfo = this.physicsSystem.getWorkerInfo();
if (this.elements.fps) {
this.elements.fps.textContent = this.currentFPS.toString();
}
if (this.elements.entityCountStat) {
this.elements.entityCountStat.textContent = this.scene.entities.count.toString();
}
if (this.elements.workerStatus) {
if (workerInfo.enabled) {
this.elements.workerStatus.textContent = `启用 (${workerInfo.workerCount} Workers)`;
this.elements.workerStatus.style.color = '#4eff4a';
} else {
this.elements.workerStatus.textContent = '禁用';
this.elements.workerStatus.style.color = '#ff4a4a';
}
}
if (this.elements.workerLoad) {
const entityCount = this.scene.entities.count;
if (workerInfo.enabled && entityCount > 0) {
const entitiesPerWorker = Math.ceil(entityCount / workerInfo.workerCount);
this.elements.workerLoad.textContent = `${entitiesPerWorker}/Worker (共${workerInfo.workerCount}个)`;
} else {
this.elements.workerLoad.textContent = 'N/A';
}
}
}
private spawnInitialEntities(count: number = 1000): void {
this.clearAllEntities();
for (let i = 0; i < count; i++) {
this.createParticle();
}
}
private createParticle(): void {
const entity = this.scene.createEntity(`Particle_${Date.now()}_${Math.random()}`);
const x = Math.random() * (this.canvas.width - 20) + 10;
const y = Math.random() * (this.canvas.height - 20) + 10;
const dx = (Math.random() - 0.5) * 200;
const dy = (Math.random() - 0.5) * 200;
const mass = Math.random() * 3 + 2;
const bounce = 0.85 + Math.random() * 0.15;
const friction = 0.998 + Math.random() * 0.002;
const colors = [
'#ff4444', '#44ff44', '#4444ff', '#ffff44', '#ff44ff', '#44ffff', '#ffffff',
'#ff8844', '#88ff44', '#4488ff', '#ff4488', '#88ff88', '#8888ff', '#ffaa44'
];
const color = colors[Math.floor(Math.random() * colors.length)];
const size = Math.random() * 6 + 3;
entity.addComponent(new Position(x, y));
entity.addComponent(new Velocity(dx, dy));
entity.addComponent(new Physics(mass, bounce, friction));
entity.addComponent(new Renderable(color, size, 'circle'));
entity.addComponent(new Lifetime(5 + Math.random() * 10));
}
private spawnParticleExplosion(centerX: number, centerY: number, count: number = 50): void {
for (let i = 0; i < count; i++) {
const entity = this.scene.createEntity(`Explosion_${Date.now()}_${i}`);
const angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.5;
const distance = Math.random() * 30;
const x = centerX + Math.cos(angle) * distance;
const y = centerY + Math.sin(angle) * distance;
const speed = 100 + Math.random() * 150;
const dx = Math.cos(angle) * speed;
const dy = Math.sin(angle) * speed;
const mass = 0.5 + Math.random() * 1;
const bounce = 0.8 + Math.random() * 0.2;
const colors = ['#ffaa00', '#ff6600', '#ff0066', '#ff3300', '#ffff00'];
const color = colors[Math.floor(Math.random() * colors.length)];
const size = Math.random() * 4 + 2;
entity.addComponent(new Position(x, y));
entity.addComponent(new Velocity(dx, dy));
entity.addComponent(new Physics(mass, bounce, 0.999));
entity.addComponent(new Renderable(color, size, 'circle'));
entity.addComponent(new Lifetime(2 + Math.random() * 3));
}
}
private clearAllEntities(): void {
const entities = [...this.scene.entities.buffer];
for (const entity of entities) {
entity.destroy();
}
}
private toggleWorker(): boolean {
const workerInfo = this.physicsSystem.getWorkerInfo();
const newWorkerEnabled = !workerInfo.enabled;
// 保存当前物理配置
const currentConfig = this.physicsSystem.getPhysicsConfig();
this.scene.removeSystem(this.physicsSystem);
this.physicsSystem = new PhysicsWorkerSystem(newWorkerEnabled, this.canvas.width, this.canvas.height);
this.physicsSystem.updateOrder = 1;
// 恢复物理配置
this.physicsSystem.updatePhysicsConfig(currentConfig);
this.scene.addSystem(this.physicsSystem);
return newWorkerEnabled;
}
private updateWorkerConfig(config: { gravity?: number; friction?: number }): void {
if (config.gravity !== undefined || config.friction !== undefined) {
const physicsConfig = this.physicsSystem.getPhysicsConfig();
this.physicsSystem.updatePhysicsConfig({
gravity: config.gravity ?? physicsConfig.gravity,
groundFriction: config.friction ?? physicsConfig.groundFriction
});
}
}
}

View File

@@ -0,0 +1,13 @@
import { DemoBase } from './DemoBase';
import { SerializationDemo } from './SerializationDemo';
import { IncrementalSerializationDemo } from './IncrementalSerializationDemo';
import { WorkerSystemDemo } from './WorkerSystemDemo';
export { DemoBase, SerializationDemo, IncrementalSerializationDemo, WorkerSystemDemo };
// Demo注册表
export const DEMO_REGISTRY: typeof DemoBase[] = [
SerializationDemo,
IncrementalSerializationDemo,
WorkerSystemDemo
];

View File

@@ -0,0 +1,171 @@
import { DEMO_REGISTRY, DemoBase } from './demos';
import { Core } from '@esengine/ecs-framework';
class DemoManager {
private demos: Map<string, typeof DemoBase> = new Map();
private currentDemo: DemoBase | null = null;
private canvas: HTMLCanvasElement;
private controlPanel: HTMLElement;
constructor() {
// 初始化ECS Core
Core.create({
debug: true,
enableEntitySystems: true
});
this.canvas = document.getElementById('demoCanvas') as HTMLCanvasElement;
this.controlPanel = document.getElementById('controlPanel') as HTMLElement;
// 注册所有demos
for (const DemoClass of DEMO_REGISTRY) {
const tempInstance = new DemoClass(this.canvas, this.controlPanel);
const info = tempInstance.getInfo();
this.demos.set(info.id, DemoClass);
tempInstance.destroy();
}
// 渲染demo列表
this.renderDemoList();
// 自动加载第一个demo
const firstDemo = DEMO_REGISTRY[0];
if (firstDemo) {
const tempInstance = new firstDemo(this.canvas, this.controlPanel);
const info = tempInstance.getInfo();
tempInstance.destroy();
this.loadDemo(info.id);
}
}
private renderDemoList() {
const demoList = document.getElementById('demoList')!;
// 按分类组织demos
const categories = new Map<string, typeof DemoBase[]>();
for (const DemoClass of DEMO_REGISTRY) {
const tempInstance = new DemoClass(this.canvas, this.controlPanel);
const info = tempInstance.getInfo();
tempInstance.destroy();
if (!categories.has(info.category)) {
categories.set(info.category, []);
}
categories.get(info.category)!.push(DemoClass);
}
// 渲染分类和demos
let html = '';
for (const [category, demoClasses] of categories) {
html += `<div class="demo-category">`;
html += `<div class="category-title">${category}</div>`;
for (const DemoClass of demoClasses) {
const tempInstance = new DemoClass(this.canvas, this.controlPanel);
const info = tempInstance.getInfo();
tempInstance.destroy();
html += `
<div class="demo-item" data-demo-id="${info.id}">
<div class="demo-icon">${info.icon}</div>
<div class="demo-info">
<div class="demo-name">${info.name}</div>
<div class="demo-desc">${info.description}</div>
</div>
</div>
`;
}
html += `</div>`;
}
demoList.innerHTML = html;
// 绑定点击事件
demoList.querySelectorAll('.demo-item').forEach(item => {
item.addEventListener('click', () => {
const demoId = item.getAttribute('data-demo-id')!;
this.loadDemo(demoId);
});
});
}
private loadDemo(demoId: string) {
// 停止并销毁当前demo
if (this.currentDemo) {
this.currentDemo.destroy();
this.currentDemo = null;
}
// 显示加载动画
const loading = document.getElementById('loading')!;
loading.style.display = 'block';
// 延迟加载,给用户反馈
setTimeout(() => {
const DemoClass = this.demos.get(demoId);
if (!DemoClass) {
console.error(`Demo ${demoId} not found`);
loading.style.display = 'none';
return;
}
try {
// 创建新demo
this.currentDemo = new DemoClass(this.canvas, this.controlPanel);
const info = this.currentDemo.getInfo();
// 更新页面标题和描述
document.getElementById('demoTitle')!.textContent = info.name;
document.getElementById('demoDescription')!.textContent = info.description;
// 设置demo
this.currentDemo.setup();
// 显示控制面板
this.controlPanel.style.display = 'block';
// 启动demo
this.currentDemo.start();
// 更新菜单选中状态
document.querySelectorAll('.demo-item').forEach(item => {
item.classList.remove('active');
if (item.getAttribute('data-demo-id') === demoId) {
item.classList.add('active');
}
});
loading.style.display = 'none';
console.log(`✅ Demo "${info.name}" loaded successfully`);
} catch (error) {
console.error(`Failed to load demo ${demoId}:`, error);
loading.style.display = 'none';
this.showError('加载演示失败:' + (error as Error).message);
}
}, 300);
}
private showError(message: string) {
const toast = document.getElementById('toast')!;
const toastMessage = document.getElementById('toastMessage')!;
const toastIcon = toast.querySelector('.toast-icon')!;
toastIcon.textContent = '❌';
toastMessage.textContent = message;
toast.style.borderColor = '#f5576c';
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
toast.style.borderColor = '#667eea';
}, 3000);
}
}
// 初始化
window.addEventListener('DOMContentLoaded', () => {
new DemoManager();
});

View File

@@ -0,0 +1,204 @@
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig
} from '@esengine/ecs-framework';
/**
* 浏览器平台适配器
*/
export class BrowserAdapter implements IPlatformAdapter {
public readonly name = 'browser';
public readonly version: string;
constructor() {
this.version = this.getBrowserInfo();
}
/**
* 检查是否支持Worker
*/
public isWorkerSupported(): boolean {
return typeof Worker !== 'undefined';
}
/**
* 检查是否支持SharedArrayBuffer
*/
public isSharedArrayBufferSupported(): boolean {
return typeof SharedArrayBuffer !== 'undefined' && this.checkSharedArrayBufferEnabled();
}
/**
* 获取硬件并发数CPU核心数
*/
public getHardwareConcurrency(): number {
return navigator.hardwareConcurrency || 4;
}
/**
* 创建Worker
*/
public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
if (!this.isWorkerSupported()) {
throw new Error('浏览器不支持Worker');
}
try {
return new BrowserWorker(script, options);
} catch (error) {
throw new Error(`创建浏览器Worker失败: ${(error as Error).message}`);
}
}
/**
* 创建SharedArrayBuffer
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
if (!this.isSharedArrayBufferSupported()) {
return null;
}
try {
return new SharedArrayBuffer(length);
} catch (error) {
console.warn('SharedArrayBuffer创建失败:', error);
return null;
}
}
/**
* 获取高精度时间戳
*/
public getHighResTimestamp(): number {
return performance.now();
}
/**
* 获取平台配置
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: this.getHardwareConcurrency(),
supportsModuleWorker: false,
supportsTransferableObjects: true,
maxSharedArrayBufferSize: 1024 * 1024 * 1024, // 1GB
workerScriptPrefix: '',
limitations: {
noEval: false,
requiresWorkerInit: false
}
};
}
/**
* 获取浏览器信息
*/
private getBrowserInfo(): string {
const userAgent = navigator.userAgent;
if (userAgent.includes('Chrome')) {
const match = userAgent.match(/Chrome\/([0-9.]+)/);
return match ? `Chrome ${match[1]}` : 'Chrome';
} else if (userAgent.includes('Firefox')) {
const match = userAgent.match(/Firefox\/([0-9.]+)/);
return match ? `Firefox ${match[1]}` : 'Firefox';
} else if (userAgent.includes('Safari')) {
const match = userAgent.match(/Version\/([0-9.]+)/);
return match ? `Safari ${match[1]}` : 'Safari';
}
return 'Unknown Browser';
}
/**
* 检查SharedArrayBuffer是否真正可用
*/
private checkSharedArrayBufferEnabled(): boolean {
try {
new SharedArrayBuffer(8);
return true;
} catch {
return false;
}
}
}
/**
* 浏览器Worker封装
*/
class BrowserWorker implements PlatformWorker {
private _state: 'running' | 'terminated' = 'running';
private worker: Worker;
constructor(script: string, options: WorkerCreationOptions = {}) {
this.worker = this.createBrowserWorker(script, options);
}
public get state(): 'running' | 'terminated' {
return this._state;
}
public postMessage(message: any, transfer?: Transferable[]): void {
if (this._state === 'terminated') {
throw new Error('Worker已被终止');
}
try {
if (transfer && transfer.length > 0) {
this.worker.postMessage(message, transfer);
} else {
this.worker.postMessage(message);
}
} catch (error) {
throw new Error(`发送消息到Worker失败: ${(error as Error).message}`);
}
}
public onMessage(handler: (event: { data: any }) => void): void {
this.worker.onmessage = (event: MessageEvent) => {
handler({ data: event.data });
};
}
public onError(handler: (error: ErrorEvent) => void): void {
this.worker.onerror = handler;
}
public terminate(): void {
if (this._state === 'running') {
try {
this.worker.terminate();
this._state = 'terminated';
} catch (error) {
console.error('终止Worker失败:', error);
}
}
}
/**
* 创建浏览器Worker
*/
private createBrowserWorker(script: string, options: WorkerCreationOptions): Worker {
try {
// 创建Blob URL
const blob = new Blob([script], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
// 创建Worker
const worker = new Worker(url, {
type: options.type || 'classic',
credentials: options.credentials,
name: options.name
});
// 清理Blob URL延迟清理确保Worker已加载
setTimeout(() => {
URL.revokeObjectURL(url);
}, 1000);
return worker;
} catch (error) {
throw new Error(`无法创建浏览器Worker: ${(error as Error).message}`);
}
}
}

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src"]
}

View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'vite';
export default defineConfig({
server: {
port: 3003,
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
}
},
build: {
target: 'es2020',
outDir: 'dist'
}
});

2583
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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,19 @@
"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",
"update:worker-demo": "npm run build:core && cd examples/worker-system-demo && npm run build && cd ../.. && npm run copy:worker-demo",
"copy:worker-demo": "node scripts/update-worker-demo.js"
},
"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 +74,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"
@@ -78,6 +90,8 @@
"dependencies": {
"@types/multer": "^1.4.13",
"@types/ws": "^8.18.1",
"coi-serviceworker": "^0.1.7",
"minigame-api-typings": "^3.8.12",
"protobufjs": "^7.5.3",
"reflect-metadata": "^0.2.2",
"ws": "^8.18.2"

View File

@@ -64,12 +64,7 @@ function generatePackageJson() {
'index.umd.js.map',
'index.es5.js',
'index.es5.js.map',
'index.d.ts',
'README.md',
'LICENSE',
'SECURITY.md',
'COCOS_USAGE.md',
'.npmignore'
'index.d.ts'
],
keywords: [
'ecs',
@@ -96,11 +91,7 @@ function generatePackageJson() {
function copyFiles() {
const filesToCopy = [
{ src: './README.md', dest: './dist/README.md' },
{ src: './LICENSE', dest: './dist/LICENSE' },
{ src: './SECURITY.md', dest: './dist/SECURITY.md' },
{ src: './COCOS_USAGE.md', dest: './dist/COCOS_USAGE.md' },
{ src: './.npmignore', dest: './dist/.npmignore' }
// 移除不存在的文件以避免警告
];
filesToCopy.forEach(({ src, dest }) => {
@@ -111,6 +102,10 @@ function copyFiles() {
console.log(` ⚠️ 文件不存在: ${src}`);
}
});
if (filesToCopy.length === 0) {
console.log(' 没有需要复制的文件');
}
}
function showBuildResults() {

View File

@@ -51,8 +51,7 @@ module.exports = {
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
// 测试超时设置
testTimeout: 10000,
testTimeout: 0,
// 清除模块缓存
clearMocks: true,
restoreMocks: true,

View File

@@ -17,7 +17,7 @@ module.exports = {
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
testTimeout: 30000,
testTimeout: 0,
clearMocks: true,
restoreMocks: true,
modulePathIgnorePatterns: [

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/ecs-framework",
"version": "2.1.46",
"version": "2.2.3",
"description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架",
"main": "bin/index.js",
"types": "bin/index.d.ts",
@@ -15,9 +15,7 @@
}
},
"files": [
"bin/**/*",
"README.md",
"LICENSE"
"bin/**/*"
],
"keywords": [
"ecs",
@@ -60,7 +58,7 @@
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/node": "^20.19.0",
"@types/node": "^20.19.17",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"rimraf": "^5.0.0",
@@ -77,5 +75,9 @@
"type": "git",
"url": "https://github.com/esengine/ecs-framework.git",
"directory": "packages/core"
},
"dependencies": {
"@msgpack/msgpack": "^3.0.0",
"tslib": "^2.8.1"
}
}

View File

@@ -52,6 +52,13 @@ module.exports = [
})
],
external,
onwarn(warning, warn) {
// 忽略 msgpack-lite 的循环依赖警告
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false,
@@ -78,6 +85,12 @@ module.exports = [
})
],
external,
onwarn(warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false
}
@@ -103,6 +116,12 @@ module.exports = [
})
],
external: [],
onwarn(warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false
}
@@ -157,6 +176,12 @@ module.exports = [
})
],
external: [],
onwarn(warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false
}

View File

@@ -1,149 +1,150 @@
import { GlobalManager } from './Utils/GlobalManager';
import { TimerManager } from './Utils/Timers/TimerManager';
import { ITimer } from './Utils/Timers/ITimer';
import { Timer } from './Utils/Timers/Timer';
import { Time } from './Utils/Time';
import { PerformanceMonitor } from './Utils/PerformanceMonitor';
import { PoolManager } from './Utils/Pool/PoolManager';
import { ECSFluentAPI, createECSAPI } from './ECS/Core/FluentAPI';
import { IScene } from './ECS/IScene';
import { WorldManager } from './ECS/WorldManager';
import { DebugManager } from './Utils/Debug';
import { ICoreConfig, IECSDebugConfig } from './Types';
import { ICoreConfig, IECSDebugConfig, IUpdatable, isUpdatable } from './Types';
import { createLogger } from './Utils/Logger';
import { SceneManager } from './ECS/SceneManager';
import { IScene } from './ECS/IScene';
import { ServiceContainer } from './Core/ServiceContainer';
import { PluginManager } from './Core/PluginManager';
import { IPlugin } from './Core/Plugin';
import { WorldManager } from './ECS/WorldManager';
/**
* 游戏引擎核心类
*
* 负责管理游戏的生命周期、场景切换、全局管理器和定时器系统。
* 提供统一的游戏循环管理。
*
*
* 职责:
* - 提供全局服务Timer、Performance、Pool等
* - 管理场景生命周期内置SceneManager
* - 管理全局管理器的生命周期
* - 提供统一的游戏循环更新入口
*
* @example
* ```typescript
* // 创建核心实例
* const core = Core.create(true);
*
* // 设置场景
* Core.scene = new MyScene();
*
* // 在游戏循环中更新Laya引擎示例
* Laya.timer.frameLoop(1, this, () => {
* const deltaTime = Laya.timer.delta / 1000;
* // 初始化并设置场景
* Core.create({ debug: true });
* Core.setScene(new GameScene());
*
* // 游戏循环(自动更新全局服务和场景)
* function gameLoop(deltaTime: number) {
* Core.update(deltaTime);
* });
*
* // 调度定时器
* }
*
* // 使用定时器
* Core.schedule(1.0, false, null, (timer) => {
* Core._logger.info("1秒后执行");
* console.log("1秒后执行");
* });
*
* // 切换场景
* Core.loadScene(new MenuScene()); // 延迟切换
* Core.setScene(new GameScene()); // 立即切换
*
* // 获取当前场景
* const currentScene = Core.scene;
* ```
*/
export class Core {
/**
* 游戏暂停状态
*
*
* 当设置为true时游戏循环将暂停执行。
*/
public static paused = false;
/**
* 默认World ID
*
* 用于单Scene模式的默认World标识
*/
private static readonly DEFAULT_WORLD_ID = '__default__';
/**
* 默认Scene ID
*
* 用于单Scene模式的默认Scene标识
*/
private static readonly DEFAULT_SCENE_ID = '__main__';
/**
* 全局核心实例
*
* 可能为null表示Core尚未初始化或已被销毁
*/
private static _instance: Core;
private static _instance: Core | null = null;
/**
* Core专用日志器
*/
private static _logger = createLogger('Core');
/**
* 实体系统启用状态
*
*
* 控制是否启用ECS实体系统功能。
*/
public static entitySystemsEnabled: boolean;
/**
* 调试模式标志
*
*
* 在调试模式下会启用额外的性能监控和错误检查。
*/
public readonly debug: boolean;
/**
* 全局管理器集合
*
* 存储所有注册的全局管理器实例
* 服务容器
*
* 管理所有服务的注册、解析和生命周期
*/
public _globalManagers: GlobalManager[] = [];
private _serviceContainer: ServiceContainer;
/**
* 定时器管理器
*
*
* 负责管理所有的游戏定时器。
*/
public _timerManager: TimerManager;
/**
* 性能监控器
*
*
* 监控游戏性能并提供优化建议。
*/
public _performanceMonitor: PerformanceMonitor;
/**
* 对象池管理器
*
*
* 管理所有对象池的生命周期。
*/
public _poolManager: PoolManager;
/**
* ECS流式API
*
* 提供便捷的ECS操作接口。
*/
public _ecsAPI?: ECSFluentAPI;
/**
* 调试管理器
*
*
* 负责收集和发送调试数据。
*/
public _debugManager?: DebugManager;
/**
* World管理器
*
* 管理多个World实例支持多房间/多世界架构
* 场景管理器
*
* 管理当前场景的生命周期
*/
public _worldManager?: WorldManager;
private _sceneManager: SceneManager;
/**
* World管理器
*
* 管理多个独立的World实例可选
*/
private _worldManager: WorldManager;
/**
* 插件管理器
*
* 管理所有插件的生命周期。
*/
private _pluginManager: PluginManager;
/**
* Core配置
*/
private _config: ICoreConfig;
/**
* 创建核心实例
*
*
* @param config - Core配置对象
*/
private constructor(config: ICoreConfig = {}) {
@@ -156,37 +157,67 @@ export class Core {
...config
};
// 初始化服务容器
this._serviceContainer = new ServiceContainer();
// 初始化管理器
// 初始化定时器管理器
this._timerManager = new TimerManager();
Core.registerGlobalManager(this._timerManager);
this._serviceContainer.registerInstance(TimerManager, this._timerManager);
// 初始化性能监控器
this._performanceMonitor = PerformanceMonitor.instance;
this._performanceMonitor = new PerformanceMonitor();
this._serviceContainer.registerInstance(PerformanceMonitor, this._performanceMonitor);
// 在调试模式下启用性能监控
if (this._config.debug) {
this._performanceMonitor.enable();
}
// 初始化对象池管理器
this._poolManager = PoolManager.getInstance();
this._poolManager = new PoolManager();
this._serviceContainer.registerInstance(PoolManager, this._poolManager);
// 初始化场景管理器
this._sceneManager = new SceneManager();
this._serviceContainer.registerInstance(SceneManager, this._sceneManager);
// 设置场景切换回调,通知调试管理器
this._sceneManager.setSceneChangedCallback(() => {
if (this._debugManager) {
this._debugManager.onSceneChanged();
}
});
// 初始化World管理器
this._worldManager = new WorldManager();
this._serviceContainer.registerInstance(WorldManager, this._worldManager);
// 初始化插件管理器
this._pluginManager = new PluginManager();
this._pluginManager.initialize(this, this._serviceContainer);
this._serviceContainer.registerInstance(PluginManager, this._pluginManager);
Core.entitySystemsEnabled = this._config.enableEntitySystems ?? true;
this.debug = this._config.debug ?? true;
// 初始化调试管理器
if (this._config.debugConfig?.enabled) {
this._debugManager = new DebugManager(this, this._config.debugConfig);
// 使用DI容器创建DebugManager前两个参数从容器解析config手动传入
const config = this._config.debugConfig;
this._debugManager = new DebugManager(
this._serviceContainer.resolve(SceneManager),
this._serviceContainer.resolve(PerformanceMonitor),
config
);
this._serviceContainer.registerInstance(DebugManager, this._debugManager);
}
this.initialize();
}
/**
* 获取核心实例
*
*
* @returns 全局核心实例
*/
public static get Instance() {
@@ -194,104 +225,196 @@ export class Core {
}
/**
* 获取当前活动的场景(属性访问器)
*
* @returns 当前场景实例如果没有则返回null
* 获取服务容器
*
* 用于注册和解析自定义服务。
*
* @returns 服务容器实例
* @throws 如果Core实例未创建
*
* @example
* ```typescript
* // 注册自定义服务
* Core.services.registerSingleton(MyService);
*
* // 解析服务
* const myService = Core.services.resolve(MyService);
* ```
*/
public static get scene(): IScene | null {
return this.getScene();
public static get services(): ServiceContainer {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
return this._instance._serviceContainer;
}
/**
* 获取当前活动的场景(方法调用)
*
* @returns 当前场景实例如果没有则返回null
* 获取World管理器
*
* 用于管理多个独立的World实例高级用户
*
* @returns WorldManager实例
* @throws 如果Core实例未创建
*
* @example
* ```typescript
* // 创建多个游戏房间
* const wm = Core.worldManager;
* const room1 = wm.createWorld('room_001');
* room1.createScene('game', new GameScene());
* room1.start();
* ```
*/
public static getScene<T extends IScene>(): T | null {
public static get worldManager(): WorldManager {
if (!this._instance) {
return null;
throw new Error('Core实例未创建请先调用Core.create()');
}
// 确保默认World存在
this._instance.ensureDefaultWorld();
const defaultWorld = this._instance._worldManager!.getWorld(this.DEFAULT_WORLD_ID);
return defaultWorld?.getScene(this.DEFAULT_SCENE_ID) as T || null;
return this._instance._worldManager;
}
/**
* 设置当前场景
*
* @param scene - 要设置的场景实例
* @returns 设置的场景实例,便于链式调用
*/
public static setScene<T extends IScene>(scene: T): T {
if (!this._instance) {
throw new Error("Core实例未创建请先调用Core.create()");
}
// 确保默认World存在
this._instance.ensureDefaultWorld();
const defaultWorld = this._instance._worldManager!.getWorld(this.DEFAULT_WORLD_ID)!;
// 移除旧的主Scene如果存在
if (defaultWorld.getScene(this.DEFAULT_SCENE_ID)) {
defaultWorld.removeScene(this.DEFAULT_SCENE_ID);
}
// 添加新Scene到默认World
defaultWorld.createScene(this.DEFAULT_SCENE_ID, scene);
defaultWorld.setSceneActive(this.DEFAULT_SCENE_ID, true);
// 触发场景切换回调
this._instance.onSceneChanged();
return scene;
}
/**
* 创建Core实例
*
*
* 如果实例已存在,则返回现有实例。
*
*
* @param config - Core配置也可以直接传入boolean表示debug模式向后兼容
* @returns Core实例
*
* @example
* ```typescript
* // 方式1使用配置对象
* Core.create({
* debug: true,
* enableEntitySystems: true,
* debugConfig: {
* enabled: true,
* websocketUrl: 'ws://localhost:9229'
* }
* });
*
* // 方式2简单模式向后兼容
* Core.create(true); // debug = true
* ```
*/
public static create(config: ICoreConfig | boolean = true): Core {
if (this._instance == null) {
// 向后兼容如果传入boolean转换为配置对象
const coreConfig: ICoreConfig = typeof config === 'boolean'
const coreConfig: ICoreConfig = typeof config === 'boolean'
? { debug: config, enableEntitySystems: true }
: config;
this._instance = new Core(coreConfig);
} else {
this._logger.warn('Core实例已创建返回现有实例');
}
return this._instance;
}
/**
* 更新游戏逻辑
*
* 此方法应该在游戏引擎的更新循环中调用。
*
* @param deltaTime - 外部引擎提供的帧时间间隔(秒)
*
* 设置当前场景
*
* @param scene - 要设置的场景
* @returns 设置的场景实例
*
* @example
* ```typescript
* // Laya引擎
* Core.create({ debug: true });
*
* // 创建并设置场景
* const gameScene = new GameScene();
* Core.setScene(gameScene);
* ```
*/
public static setScene<T extends IScene>(scene: T): T {
if (!this._instance) {
Core._logger.warn("Core实例未创建请先调用Core.create()");
throw new Error("Core实例未创建");
}
return this._instance._sceneManager.setScene(scene);
}
/**
* 获取当前场景
*
* @returns 当前场景如果没有场景则返回null
*/
public static get scene(): IScene | null {
if (!this._instance) {
return null;
}
return this._instance._sceneManager.currentScene;
}
/**
* 获取ECS流式API
*
* @returns ECS API实例如果当前没有场景则返回null
*
* @example
* ```typescript
* // 使用流式API创建实体
* const player = Core.ecsAPI?.createEntity('Player')
* .addComponent(Position, 100, 100)
* .addComponent(Velocity, 50, 0);
*
* // 查询实体
* const enemies = Core.ecsAPI?.query(Enemy, Transform);
*
* // 发射事件
* Core.ecsAPI?.emit('game:start', { level: 1 });
* ```
*/
public static get ecsAPI() {
if (!this._instance) {
return null;
}
return this._instance._sceneManager.api;
}
/**
* 延迟加载场景(下一帧切换)
*
* @param scene - 要加载的场景
*
* @example
* ```typescript
* // 延迟切换场景(在下一帧生效)
* Core.loadScene(new MenuScene());
* ```
*/
public static loadScene<T extends IScene>(scene: T): void {
if (!this._instance) {
Core._logger.warn("Core实例未创建请先调用Core.create()");
return;
}
this._instance._sceneManager.loadScene(scene);
}
/**
* 更新游戏逻辑
*
* 此方法应该在游戏引擎的更新循环中调用。
* 会自动更新全局服务和当前场景。
*
* @param deltaTime - 外部引擎提供的帧时间间隔(秒)
*
* @example
* ```typescript
* // 初始化
* Core.create({ debug: true });
* Core.setScene(new GameScene());
*
* // Laya引擎集成
* Laya.timer.frameLoop(1, this, () => {
* const deltaTime = Laya.timer.delta / 1000;
* Core.update(deltaTime);
* Core.update(deltaTime); // 自动更新全局服务和场景
* });
*
* // Cocos Creator
*
* // Cocos Creator集成
* update(deltaTime: number) {
* Core.update(deltaTime);
* Core.update(deltaTime); // 自动更新全局服务和场景
* }
*
* ```
*/
public static update(deltaTime: number): void {
@@ -299,78 +422,49 @@ export class Core {
Core._logger.warn("Core实例未创建请先调用Core.create()");
return;
}
this._instance.updateInternal(deltaTime);
}
/**
* 注册全局管理器
*
* 将管理器添加到全局管理器列表中,并启用它。
*
* @param manager - 要注册的全局管理器
*/
public static registerGlobalManager(manager: GlobalManager) {
this._instance._globalManagers.push(manager);
manager.enabled = true;
}
/**
* 注销全局管理器
*
* 从全局管理器列表中移除管理器,并禁用它。
*
* @param manager - 要注销的全局管理器
*/
public static unregisterGlobalManager(manager: GlobalManager) {
this._instance._globalManagers.splice(this._instance._globalManagers.indexOf(manager), 1);
manager.enabled = false;
}
/**
* 获取指定类型的全局管理器
*
* @param type - 管理器类型构造函数
* @returns 管理器实例如果未找到则返回null
*/
public static getGlobalManager<T extends GlobalManager>(type: new (...args: unknown[]) => T): T | null {
for (const manager of this._instance._globalManagers) {
if (manager instanceof type)
return manager as T;
}
return null;
}
/**
* 调度定时器
*
*
* 创建一个定时器,在指定时间后执行回调函数。
*
*
* @param timeInSeconds - 延迟时间(秒)
* @param repeats - 是否重复执行默认为false
* @param context - 回调函数的上下文默认为null
* @param onTime - 定时器触发时的回调函数
* @returns 创建的定时器实例
* @throws 如果Core实例未创建或onTime回调未提供
*
* @example
* ```typescript
* // 一次性定时器
* Core.schedule(1.0, false, null, (timer) => {
* console.log("1秒后执行一次");
* });
*
* // 重复定时器
* Core.schedule(0.5, true, null, (timer) => {
* console.log("每0.5秒执行一次");
* });
* ```
*/
public static schedule<TContext = unknown>(timeInSeconds: number, repeats: boolean = false, context?: TContext, onTime?: (timer: ITimer<TContext>) => void): Timer<TContext> {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
if (!onTime) {
throw new Error('onTime callback is required');
}
return this._instance._timerManager.schedule(timeInSeconds, repeats, context as TContext, onTime);
}
/**
* 获取ECS流式API
*
* @returns ECS API实例如果未初始化则返回null
*/
public static get ecsAPI(): ECSFluentAPI | null {
return this._instance?._ecsAPI || null;
}
/**
* 启用调试功能
*
*
* @param config 调试配置
*/
public static enableDebug(config: IECSDebugConfig): void {
@@ -382,7 +476,13 @@ export class Core {
if (this._instance._debugManager) {
this._instance._debugManager.updateConfig(config);
} else {
this._instance._debugManager = new DebugManager(this._instance, config);
// 使用DI容器创建DebugManager
this._instance._debugManager = new DebugManager(
this._instance._serviceContainer.resolve(SceneManager),
this._instance._serviceContainer.resolve(PerformanceMonitor),
config
);
this._instance._serviceContainer.registerInstance(DebugManager, this._instance._debugManager);
}
// 更新Core配置
@@ -408,7 +508,7 @@ export class Core {
/**
* 获取调试数据
*
*
* @returns 当前调试数据如果调试未启用则返回null
*/
public static getDebugData(): unknown {
@@ -421,110 +521,114 @@ export class Core {
/**
* 检查调试是否启用
*
*
* @returns 调试状态
*/
public static get isDebugEnabled(): boolean {
return this._instance?._config.debugConfig?.enabled || false;
}
/**
* 获取WorldManager实例
*
* @returns WorldManager实例如果未初始化则自动创建
* 安装插件
*
* @param plugin - 插件实例
* @throws 如果Core实例未创建或插件安装失败
*
* @example
* ```typescript
* Core.create({ debug: true });
*
* // 安装插件
* await Core.installPlugin(new MyPlugin());
* ```
*/
public static getWorldManager(): WorldManager {
public static async installPlugin(plugin: IPlugin): Promise<void> {
if (!this._instance) {
throw new Error("Core实例未创建请先调用Core.create()");
throw new Error('Core实例未创建请先调用Core.create()');
}
if (!this._instance._worldManager) {
// 多World模式的配置用户主动获取WorldManager
this._instance._worldManager = WorldManager.getInstance({
maxWorlds: 50,
autoCleanup: true,
cleanupInterval: 60000,
debug: this._instance._config.debug
});
}
return this._instance._worldManager;
await this._instance._pluginManager.install(plugin);
}
/**
* 启用World管理
*
* 显式启用World功能用于多房间/多世界架构
* 卸载插件
*
* @param name - 插件名称
* @throws 如果Core实例未创建或插件卸载失败
*
* @example
* ```typescript
* await Core.uninstallPlugin('my-plugin');
* ```
*/
public static enableWorldManager(): WorldManager {
return this.getWorldManager();
public static async uninstallPlugin(name: string): Promise<void> {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
await this._instance._pluginManager.uninstall(name);
}
/**
* 确保默认World存在
*
* 内部方法用于懒初始化默认World
* 获取插件实例
*
* @param name - 插件名称
* @returns 插件实例如果未安装则返回undefined
*
* @example
* ```typescript
* const myPlugin = Core.getPlugin('my-plugin');
* if (myPlugin) {
* console.log(myPlugin.version);
* }
* ```
*/
private ensureDefaultWorld(): void {
if (!this._worldManager) {
this._worldManager = WorldManager.getInstance({
maxWorlds: 1, // 单场景用户只需要1个World
autoCleanup: false, // 单场景不需要自动清理
cleanupInterval: 0, // 禁用清理定时器
debug: this._config.debug
});
public static getPlugin(name: string): IPlugin | undefined {
if (!this._instance) {
return undefined;
}
// 检查默认World是否存在
if (!this._worldManager.getWorld(Core.DEFAULT_WORLD_ID)) {
this._worldManager.createWorld(Core.DEFAULT_WORLD_ID, {
name: 'DefaultWorld',
maxScenes: 1,
autoCleanup: false
});
this._worldManager.setWorldActive(Core.DEFAULT_WORLD_ID, true);
}
return this._instance._pluginManager.getPlugin(name);
}
/**
* 场景切换回调
*
* 在场景切换时调用,用于重置时间系统等。
* 检查插件是否已安装
*
* @param name - 插件名称
* @returns 是否已安装
*
* @example
* ```typescript
* if (Core.isPluginInstalled('my-plugin')) {
* console.log('Plugin is installed');
* }
* ```
*/
public onSceneChanged() {
Time.sceneChanged();
// 获取当前Scene从默认World
const currentScene = Core.getScene();
// 初始化ECS API如果场景支持
if (currentScene && currentScene.querySystem && currentScene.eventSystem) {
this._ecsAPI = createECSAPI(currentScene, currentScene.querySystem, currentScene.eventSystem);
public static isPluginInstalled(name: string): boolean {
if (!this._instance) {
return false;
}
// 延迟调试管理器通知,避免在场景初始化过程中干扰属性
if (this._debugManager) {
queueMicrotask(() => {
this._debugManager?.onSceneChanged();
});
}
return this._instance._pluginManager.isInstalled(name);
}
/**
* 初始化核心系统
*
*
* 执行核心系统的初始化逻辑。
*/
protected initialize() {
// 核心系统初始化
Core._logger.info('Core initialized', {
debug: this.debug,
entitySystemsEnabled: Core.entitySystemsEnabled,
debugEnabled: this._config.debugConfig?.enabled || false
});
}
/**
* 内部更新方法
*
*
* @param deltaTime - 帧时间间隔(秒)
*/
private updateInternal(deltaTime: number): void {
@@ -541,37 +645,19 @@ export class Core {
this._performanceMonitor.updateFPS(Time.deltaTime);
}
// 更新全局管理器
const managersStartTime = this._performanceMonitor.startMonitoring('GlobalManagers.update');
for (const globalManager of this._globalManagers) {
if (globalManager.enabled)
globalManager.update();
}
this._performanceMonitor.endMonitoring('GlobalManagers.update', managersStartTime, this._globalManagers.length);
// 更新所有可更新的服务
const servicesStartTime = this._performanceMonitor.startMonitoring('Services.update');
this._serviceContainer.updateAll(deltaTime);
this._performanceMonitor.endMonitoring('Services.update', servicesStartTime, this._serviceContainer.getUpdatableCount());
// 更新对象池管理器
this._poolManager.update();
// 更新所有World
if (this._worldManager) {
const worldsStartTime = this._performanceMonitor.startMonitoring('Worlds.update');
const activeWorlds = this._worldManager.getActiveWorlds();
let totalWorldEntities = 0;
// 更新默认场景(通过 SceneManager
this._sceneManager.update();
for (const world of activeWorlds) {
// 更新World的全局System
world.updateGlobalSystems();
// 更新World中的所有Scene
world.updateScenes();
// 统计实体数量(用于性能监控)
const worldStats = world.getStats();
totalWorldEntities += worldStats.totalEntities;
}
this._performanceMonitor.endMonitoring('Worlds.update', worldsStartTime, totalWorldEntities);
}
// 更新额外的 WorldManager
this._worldManager.updateAll();
// 更新调试管理器基于FPS的数据发送
if (this._debugManager) {
@@ -581,4 +667,26 @@ export class Core {
// 结束性能监控
this._performanceMonitor.endMonitoring('Core.update', frameStartTime);
}
/**
* 销毁Core实例
*
* 清理所有资源,通常在应用程序关闭时调用。
*/
public static destroy(): void {
if (!this._instance) return;
// 停止调试管理器
if (this._instance._debugManager) {
this._instance._debugManager.stop();
}
// 清理所有服务
this._instance._serviceContainer.clear();
Core._logger.info('Core destroyed');
// 清空实例引用允许重新创建Core实例
this._instance = null;
}
}

View File

@@ -0,0 +1,364 @@
/**
* 依赖注入装饰器
*
* 提供 @Injectable、@Inject 和 @Updatable 装饰器,用于标记可注入的类和依赖注入点
*/
import type { ServiceContainer } from '../ServiceContainer';
import type { IService, ServiceType } from '../ServiceContainer';
/**
* 依赖注入元数据键
*/
const INJECTABLE_METADATA_KEY = Symbol('injectable');
const INJECT_METADATA_KEY = Symbol('inject');
const INJECT_PARAMS_METADATA_KEY = Symbol('inject:params');
const UPDATABLE_METADATA_KEY = Symbol('updatable');
/**
* 依赖注入元数据存储
*/
const injectableMetadata = new WeakMap<any, InjectableMetadata>();
const injectMetadata = new WeakMap<any, Map<number, ServiceType<any> | string | symbol>>();
const updatableMetadata = new WeakMap<any, UpdatableMetadata>();
/**
* 可注入元数据接口
*/
export interface InjectableMetadata {
/**
* 是否可注入
*/
injectable: boolean;
/**
* 依赖列表
*/
dependencies: Array<ServiceType<any> | string | symbol>;
/**
* 属性注入映射
* key: 属性名, value: 服务类型
*/
properties?: Map<string | symbol, ServiceType<any>>;
}
/**
* 可更新元数据接口
*/
export interface UpdatableMetadata {
/**
* 是否可更新
*/
updatable: boolean;
/**
* 更新优先级数值越小越先执行默认0
*/
priority: number;
}
/**
* @Injectable() 装饰器
*
* 标记类为可注入的服务使其可以通过ServiceContainer进行依赖注入
*
* @example
* ```typescript
* @Injectable()
* class TimeService implements IService {
* constructor() {}
* dispose() {}
* }
*
* @Injectable()
* class PhysicsSystem extends EntitySystem {
* constructor(
* @Inject(TimeService) private timeService: TimeService
* ) {
* super();
* }
* }
* ```
*/
export function Injectable(): ClassDecorator {
return function <T extends Function>(target: T): T {
const existing = injectableMetadata.get(target);
injectableMetadata.set(target, {
injectable: true,
dependencies: [],
properties: existing?.properties
});
return target;
};
}
/**
* @Updatable() 装饰器
*
* 标记服务类为可更新的使其在每帧自动被ServiceContainer调用update方法。
* 使用此装饰器的类必须实现IUpdatable接口包含update方法
*
* @param priority - 更新优先级数值越小越先执行默认0
* @throws 如果类没有实现update方法将在运行时抛出错误
*
* @example
* ```typescript
* @Injectable()
* @Updatable()
* class TimerManager implements IService, IUpdatable {
* update(deltaTime?: number) {
* // 每帧更新逻辑
* }
* dispose() {}
* }
*
* // 指定优先级
* @Injectable()
* @Updatable(10)
* class PhysicsManager implements IService, IUpdatable {
* update() { }
* dispose() {}
* }
* ```
*/
export function Updatable(priority: number = 0): ClassDecorator {
return function <T extends Function>(target: T): T {
// 验证类原型上是否有update方法
const prototype = (target as any).prototype;
if (!prototype || typeof prototype.update !== 'function') {
throw new Error(
`@Updatable() decorator requires class ${target.name} to implement IUpdatable interface with update() method. ` +
`Please add 'implements IUpdatable' and define update(deltaTime?: number): void method.`
);
}
// 标记为可更新
updatableMetadata.set(target, {
updatable: true,
priority
});
return target;
};
}
/**
* @Inject() 装饰器
*
* 标记构造函数参数需要注入的服务类型
*
* @param serviceType 服务类型标识符
*/
export function Inject(serviceType: ServiceType<any> | string | symbol): ParameterDecorator {
return function (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) {
// 获取或创建注入元数据
let params = injectMetadata.get(target);
if (!params) {
params = new Map();
injectMetadata.set(target, params);
}
// 记录参数索引和服务类型的映射
params.set(parameterIndex, serviceType);
};
}
/**
* @InjectProperty() 装饰器
*
* 通过属性装饰器注入依赖
*
* 注入时机在构造函数执行后、onInitialize() 调用前完成
*
* @param serviceType 服务类型
*/
export function InjectProperty(serviceType: ServiceType<any>): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
let metadata = injectableMetadata.get(target.constructor);
if (!metadata) {
metadata = {
injectable: true,
dependencies: []
};
injectableMetadata.set(target.constructor, metadata);
}
if (!metadata.properties) {
metadata.properties = new Map();
}
metadata.properties.set(propertyKey, serviceType);
};
}
/**
* 检查类是否标记为可注入
*
* @param target 目标类
* @returns 是否可注入
*/
export function isInjectable(target: any): boolean {
const metadata = injectableMetadata.get(target);
return metadata?.injectable ?? false;
}
/**
* 获取类的依赖注入元数据
*
* @param target 目标类
* @returns 依赖注入元数据
*/
export function getInjectableMetadata(target: any): InjectableMetadata | undefined {
return injectableMetadata.get(target);
}
/**
* 获取构造函数参数的注入元数据
*
* @param target 目标类
* @returns 参数索引到服务类型的映射
*/
export function getInjectMetadata(target: any): Map<number, ServiceType<any> | string | symbol> {
return injectMetadata.get(target) || new Map();
}
/**
* 创建实例并自动注入依赖
*
* @param constructor 构造函数
* @param container 服务容器
* @returns 创建的实例
*
* @example
* ```typescript
* const instance = createInstance(MySystem, container);
* ```
*/
export function createInstance<T>(
constructor: new (...args: any[]) => T,
container: ServiceContainer
): T {
// 获取参数注入元数据
const injectParams = getInjectMetadata(constructor);
// 解析依赖
const dependencies: any[] = [];
// 获取构造函数参数数量
const paramCount = constructor.length;
for (let i = 0; i < paramCount; i++) {
const serviceType = injectParams.get(i);
if (serviceType) {
// 如果有显式的@Inject标记使用标记的类型
if (typeof serviceType === 'string' || typeof serviceType === 'symbol') {
// 字符串或Symbol类型的服务标识
throw new Error(
`String and Symbol service identifiers are not yet supported in constructor injection. ` +
`Please use class types for ${constructor.name} parameter ${i}`
);
} else {
// 类类型
dependencies.push(container.resolve(serviceType as ServiceType<any>));
}
} else {
// 没有@Inject标记传入undefined
dependencies.push(undefined);
}
}
// 创建实例
return new constructor(...dependencies);
}
/**
* 为实例注入属性依赖
*
* @param instance 目标实例
* @param container 服务容器
*/
export function injectProperties<T>(instance: T, container: ServiceContainer): void {
const constructor = (instance as any).constructor;
const metadata = getInjectableMetadata(constructor);
if (!metadata?.properties || metadata.properties.size === 0) {
return;
}
for (const [propertyKey, serviceType] of metadata.properties) {
const service = container.resolve(serviceType);
if (service !== null) {
(instance as any)[propertyKey] = service;
}
}
}
/**
* 检查类是否标记为可更新
*
* @param target 目标类
* @returns 是否可更新
*/
export function isUpdatable(target: any): boolean {
const metadata = updatableMetadata.get(target);
return metadata?.updatable ?? false;
}
/**
* 获取类的可更新元数据
*
* @param target 目标类
* @returns 可更新元数据
*/
export function getUpdatableMetadata(target: any): UpdatableMetadata | undefined {
return updatableMetadata.get(target);
}
/**
* 注册可注入的服务到容器
*
* 自动检测@Injectable装饰器并注册服务
*
* @param container 服务容器
* @param serviceType 服务类型
* @param singleton 是否注册为单例默认true
*
* @example
* ```typescript
* @Injectable()
* class MyService implements IService {
* dispose() {}
* }
*
* // 自动注册
* registerInjectable(Core.services, MyService);
* ```
*/
export function registerInjectable<T extends IService>(
container: ServiceContainer,
serviceType: ServiceType<T>,
singleton: boolean = true
): void {
if (!isInjectable(serviceType)) {
throw new Error(
`${serviceType.name} is not marked as @Injectable(). ` +
`Please add @Injectable() decorator to the class.`
);
}
// 创建工厂函数使用createInstance自动解析依赖
const factory = (c: ServiceContainer) => createInstance(serviceType, c);
// 注册到容器
if (singleton) {
container.registerSingleton(serviceType, factory);
} else {
container.registerTransient(serviceType, factory);
}
}

View File

@@ -0,0 +1,22 @@
/**
* 依赖注入模块
*
* 提供装饰器和工具函数,用于实现依赖注入模式
*/
export {
Injectable,
Inject,
InjectProperty,
Updatable,
isInjectable,
getInjectableMetadata,
getInjectMetadata,
isUpdatable,
getUpdatableMetadata,
createInstance,
injectProperties,
registerInjectable
} from './Decorators';
export type { InjectableMetadata, UpdatableMetadata } from './Decorators';

View File

@@ -0,0 +1,124 @@
import type { Core } from '../Core';
import type { ServiceContainer } from './ServiceContainer';
/**
* 插件状态
*/
export enum PluginState {
/**
* 未安装
*/
NotInstalled = 'not_installed',
/**
* 已安装
*/
Installed = 'installed',
/**
* 安装失败
*/
Failed = 'failed'
}
/**
* 插件接口
*
* 所有插件都必须实现此接口。
* 插件提供了一种扩展框架功能的标准方式。
*
* @example
* ```typescript
* class MyPlugin implements IPlugin {
* readonly name = 'my-plugin';
* readonly version = '1.0.0';
* readonly dependencies = ['other-plugin'];
*
* async install(core: Core, services: ServiceContainer) {
* // 注册服务
* services.registerSingleton(MyService);
*
* // 添加系统
* const world = core.getWorld();
* if (world) {
* world.addSystem(new MySystem());
* }
* }
*
* async uninstall() {
* // 清理资源
* }
* }
* ```
*/
export interface IPlugin {
/**
* 插件唯一名称
*
* 用于依赖解析和插件管理。
*/
readonly name: string;
/**
* 插件版本
*
* 遵循语义化版本规范 (semver)。
*/
readonly version: string;
/**
* 依赖的其他插件名称列表
*
* 这些插件必须在当前插件之前安装。
*/
readonly dependencies?: readonly string[];
/**
* 安装插件
*
* 在此方法中初始化插件,注册服务、系统等。
* 可以是同步或异步的。
*
* @param core - Core实例用于访问World等
* @param services - 服务容器,用于注册服务
*/
install(core: Core, services: ServiceContainer): void | Promise<void>;
/**
* 卸载插件
*
* 清理插件占用的资源。
* 可以是同步或异步的。
*/
uninstall(): void | Promise<void>;
}
/**
* 插件元数据
*/
export interface IPluginMetadata {
/**
* 插件名称
*/
name: string;
/**
* 插件版本
*/
version: string;
/**
* 插件状态
*/
state: PluginState;
/**
* 安装时间戳
*/
installedAt?: number;
/**
* 错误信息(如果安装失败)
*/
error?: string;
}

View File

@@ -0,0 +1,266 @@
import { IPlugin, IPluginMetadata, PluginState } from './Plugin';
import type { IService } from './ServiceContainer';
import type { Core } from '../Core';
import type { ServiceContainer } from './ServiceContainer';
import { createLogger } from '../Utils/Logger';
const logger = createLogger('PluginManager');
/**
* 插件管理器
*
* 负责插件的注册、安装、卸载和生命周期管理。
* 支持依赖检查和异步加载。
*
* @example
* ```typescript
* const core = Core.create();
* const pluginManager = core.getService(PluginManager);
*
* // 注册插件
* await pluginManager.install(new MyPlugin());
*
* // 查询插件
* const plugin = pluginManager.getPlugin('my-plugin');
*
* // 卸载插件
* await pluginManager.uninstall('my-plugin');
* ```
*/
export class PluginManager implements IService {
/**
* 已安装的插件
*/
private _plugins: Map<string, IPlugin> = new Map();
/**
* 插件元数据
*/
private _metadata: Map<string, IPluginMetadata> = new Map();
/**
* Core实例引用
*/
private _core: Core | null = null;
/**
* 服务容器引用
*/
private _services: ServiceContainer | null = null;
/**
* 初始化插件管理器
*
* @param core - Core实例
* @param services - 服务容器
*/
public initialize(core: Core, services: ServiceContainer): void {
this._core = core;
this._services = services;
logger.info('PluginManager initialized');
}
/**
* 安装插件
*
* 会自动检查依赖并按正确顺序安装。
*
* @param plugin - 插件实例
* @throws 如果依赖检查失败或安装失败
*/
public async install(plugin: IPlugin): Promise<void> {
if (!this._core || !this._services) {
throw new Error('PluginManager not initialized. Call initialize() first.');
}
// 检查是否已安装
if (this._plugins.has(plugin.name)) {
logger.warn(`Plugin ${plugin.name} is already installed`);
return;
}
// 检查依赖
if (plugin.dependencies && plugin.dependencies.length > 0) {
this._checkDependencies(plugin);
}
// 创建元数据
const metadata: IPluginMetadata = {
name: plugin.name,
version: plugin.version,
state: PluginState.NotInstalled,
installedAt: Date.now()
};
this._metadata.set(plugin.name, metadata);
try {
// 调用插件的安装方法
logger.info(`Installing plugin: ${plugin.name} v${plugin.version}`);
await plugin.install(this._core, this._services);
// 标记为已安装
this._plugins.set(plugin.name, plugin);
metadata.state = PluginState.Installed;
logger.info(`Plugin ${plugin.name} installed successfully`);
} catch (error) {
// 安装失败
metadata.state = PluginState.Failed;
metadata.error = error instanceof Error ? error.message : String(error);
logger.error(`Failed to install plugin ${plugin.name}:`, error);
throw error;
}
}
/**
* 卸载插件
*
* @param name - 插件名称
* @throws 如果插件未安装或卸载失败
*/
public async uninstall(name: string): Promise<void> {
const plugin = this._plugins.get(name);
if (!plugin) {
throw new Error(`Plugin ${name} is not installed`);
}
// 检查是否有其他插件依赖此插件
this._checkDependents(name);
try {
logger.info(`Uninstalling plugin: ${name}`);
await plugin.uninstall();
// 从注册表中移除
this._plugins.delete(name);
this._metadata.delete(name);
logger.info(`Plugin ${name} uninstalled successfully`);
} catch (error) {
logger.error(`Failed to uninstall plugin ${name}:`, error);
throw error;
}
}
/**
* 获取插件实例
*
* @param name - 插件名称
* @returns 插件实例如果未安装则返回undefined
*/
public getPlugin(name: string): IPlugin | undefined {
return this._plugins.get(name);
}
/**
* 获取插件元数据
*
* @param name - 插件名称
* @returns 插件元数据如果未安装则返回undefined
*/
public getMetadata(name: string): IPluginMetadata | undefined {
return this._metadata.get(name);
}
/**
* 获取所有已安装的插件
*
* @returns 插件列表
*/
public getAllPlugins(): IPlugin[] {
return Array.from(this._plugins.values());
}
/**
* 获取所有插件元数据
*
* @returns 元数据列表
*/
public getAllMetadata(): IPluginMetadata[] {
return Array.from(this._metadata.values());
}
/**
* 检查插件是否已安装
*
* @param name - 插件名称
* @returns 是否已安装
*/
public isInstalled(name: string): boolean {
return this._plugins.has(name);
}
/**
* 检查插件依赖
*
* @param plugin - 插件实例
* @throws 如果依赖未满足
*/
private _checkDependencies(plugin: IPlugin): void {
if (!plugin.dependencies) {
return;
}
const missingDeps: string[] = [];
for (const dep of plugin.dependencies) {
if (!this._plugins.has(dep)) {
missingDeps.push(dep);
}
}
if (missingDeps.length > 0) {
throw new Error(
`Plugin ${plugin.name} has unmet dependencies: ${missingDeps.join(', ')}`
);
}
}
/**
* 检查是否有其他插件依赖指定插件
*
* @param name - 插件名称
* @throws 如果有其他插件依赖此插件
*/
private _checkDependents(name: string): void {
const dependents: string[] = [];
for (const plugin of this._plugins.values()) {
if (plugin.dependencies && plugin.dependencies.includes(name)) {
dependents.push(plugin.name);
}
}
if (dependents.length > 0) {
throw new Error(
`Cannot uninstall plugin ${name}: it is required by ${dependents.join(', ')}`
);
}
}
/**
* 释放资源
*/
public dispose(): void {
// 卸载所有插件(逆序,先卸载依赖项)
const plugins = Array.from(this._plugins.values()).reverse();
for (const plugin of plugins) {
try {
logger.info(`Disposing plugin: ${plugin.name}`);
plugin.uninstall();
} catch (error) {
logger.error(`Error disposing plugin ${plugin.name}:`, error);
}
}
this._plugins.clear();
this._metadata.clear();
this._core = null;
this._services = null;
logger.info('PluginManager disposed');
}
}

View File

@@ -0,0 +1,423 @@
import { createLogger } from '../Utils/Logger';
import { isUpdatable as checkUpdatable, getUpdatableMetadata } from './DI';
const logger = createLogger('ServiceContainer');
/**
* 服务基础接口
* 所有通过 ServiceContainer 管理的服务都应该实现此接口
*/
export interface IService {
/**
* 释放服务占用的资源
* 当服务被注销或容器被清空时调用
*/
dispose(): void;
}
/**
* 服务类型
*
* 支持任意构造函数签名,以便与依赖注入装饰器配合使用
*/
export type ServiceType<T extends IService> = new (...args: any[]) => T;
/**
* 服务生命周期
*/
export enum ServiceLifetime {
/**
* 单例模式 - 整个应用生命周期内只有一个实例
*/
Singleton = 'singleton',
/**
* 瞬时模式 - 每次请求都创建新实例
*/
Transient = 'transient'
}
/**
* 服务注册信息
*/
interface ServiceRegistration<T extends IService> {
/**
* 服务类型
*/
type: ServiceType<T>;
/**
* 服务实例(单例模式)
*/
instance?: T;
/**
* 工厂函数
*/
factory?: (container: ServiceContainer) => T;
/**
* 生命周期
*/
lifetime: ServiceLifetime;
}
/**
* 服务容器
*
* 负责管理所有服务的注册、解析和生命周期。
* 支持依赖注入和服务定位模式。
*
* 特点:
* - 单例和瞬时两种生命周期
* - 支持工厂函数注册
* - 支持实例注册
* - 类型安全的服务解析
*
* @example
* ```typescript
* const container = new ServiceContainer();
*
* // 注册单例服务
* container.registerSingleton(TimerManager);
*
* // 注册工厂函数
* container.registerSingleton(Logger, (c) => createLogger('App'));
*
* // 注册实例
* container.registerInstance(Config, new Config());
*
* // 解析服务
* const timer = container.resolve(TimerManager);
* ```
*/
export class ServiceContainer {
/**
* 服务注册表
*/
private _services: Map<ServiceType<IService>, ServiceRegistration<IService>> = new Map();
/**
* 正在解析的服务栈(用于循环依赖检测)
*/
private _resolving: Set<ServiceType<IService>> = new Set();
/**
* 可更新的服务列表
*
* 自动收集所有使用@Updatable装饰器标记的服务供Core统一更新
* 按优先级排序(数值越小越先执行)
*/
private _updatableServices: Array<{ instance: any; priority: number }> = [];
/**
* 注册单例服务
*
* @param type - 服务类型
* @param factory - 可选的工厂函数
*
* @example
* ```typescript
* // 直接注册类型
* container.registerSingleton(TimerManager);
*
* // 使用工厂函数
* container.registerSingleton(Logger, (c) => {
* return createLogger('App');
* });
* ```
*/
public registerSingleton<T extends IService>(
type: ServiceType<T>,
factory?: (container: ServiceContainer) => T
): void {
if (this._services.has(type as ServiceType<IService>)) {
logger.warn(`Service ${type.name} is already registered`);
return;
}
this._services.set(type as ServiceType<IService>, {
type: type as ServiceType<IService>,
factory: factory as ((container: ServiceContainer) => IService) | undefined,
lifetime: ServiceLifetime.Singleton
});
logger.debug(`Registered singleton service: ${type.name}`);
}
/**
* 注册瞬时服务
*
* 每次解析都会创建新实例。
*
* @param type - 服务类型
* @param factory - 可选的工厂函数
*
* @example
* ```typescript
* // 每次解析都创建新实例
* container.registerTransient(Command);
* ```
*/
public registerTransient<T extends IService>(
type: ServiceType<T>,
factory?: (container: ServiceContainer) => T
): void {
if (this._services.has(type as ServiceType<IService>)) {
logger.warn(`Service ${type.name} is already registered`);
return;
}
this._services.set(type as ServiceType<IService>, {
type: type as ServiceType<IService>,
factory: factory as ((container: ServiceContainer) => IService) | undefined,
lifetime: ServiceLifetime.Transient
});
logger.debug(`Registered transient service: ${type.name}`);
}
/**
* 注册服务实例
*
* 直接注册已创建的实例,自动视为单例。
*
* @param type - 服务类型(构造函数,仅用作标识)
* @param instance - 服务实例
*
* @example
* ```typescript
* const config = new Config();
* container.registerInstance(Config, config);
* ```
*/
public registerInstance<T extends IService>(type: new (...args: any[]) => T, instance: T): void {
if (this._services.has(type as ServiceType<IService>)) {
logger.warn(`Service ${type.name} is already registered`);
return;
}
this._services.set(type as ServiceType<IService>, {
type: type as ServiceType<IService>,
instance: instance as IService,
lifetime: ServiceLifetime.Singleton
});
// 如果使用了@Updatable装饰器添加到可更新列表
if (checkUpdatable(type)) {
const metadata = getUpdatableMetadata(type);
const priority = metadata?.priority ?? 0;
this._updatableServices.push({ instance, priority });
// 按优先级排序(数值越小越先执行)
this._updatableServices.sort((a, b) => a.priority - b.priority);
logger.debug(`Service ${type.name} is updatable (priority: ${priority}), added to update list`);
}
logger.debug(`Registered service instance: ${type.name}`);
}
/**
* 解析服务
*
* @param type - 服务类型
* @returns 服务实例
* @throws 如果服务未注册或存在循环依赖
*
* @example
* ```typescript
* const timer = container.resolve(TimerManager);
* ```
*/
public resolve<T extends IService>(type: ServiceType<T>): T {
const registration = this._services.get(type as ServiceType<IService>);
if (!registration) {
throw new Error(`Service ${type.name} is not registered`);
}
// 检测循环依赖
if (this._resolving.has(type as ServiceType<IService>)) {
const chain = Array.from(this._resolving).map(t => t.name).join(' -> ');
throw new Error(`Circular dependency detected: ${chain} -> ${type.name}`);
}
// 如果是单例且已经有实例,直接返回
if (registration.lifetime === ServiceLifetime.Singleton && registration.instance) {
return registration.instance as T;
}
// 添加到解析栈
this._resolving.add(type as ServiceType<IService>);
try {
// 创建实例
let instance: IService;
if (registration.factory) {
// 使用工厂函数
instance = registration.factory(this);
} else {
// 直接构造
instance = new (registration.type)();
}
// 如果是单例,缓存实例
if (registration.lifetime === ServiceLifetime.Singleton) {
registration.instance = instance;
// 如果使用了@Updatable装饰器添加到可更新列表
if (checkUpdatable(registration.type)) {
const metadata = getUpdatableMetadata(registration.type);
const priority = metadata?.priority ?? 0;
this._updatableServices.push({ instance, priority });
// 按优先级排序(数值越小越先执行)
this._updatableServices.sort((a, b) => a.priority - b.priority);
logger.debug(`Service ${type.name} is updatable (priority: ${priority}), added to update list`);
}
}
return instance as T;
} finally {
// 从解析栈移除
this._resolving.delete(type as ServiceType<IService>);
}
}
/**
* 尝试解析服务
*
* 如果服务未注册返回null而不是抛出异常。
*
* @param type - 服务类型
* @returns 服务实例或null
*
* @example
* ```typescript
* const timer = container.tryResolve(TimerManager);
* if (timer) {
* timer.schedule(...);
* }
* ```
*/
public tryResolve<T extends IService>(type: ServiceType<T>): T | null {
try {
return this.resolve(type);
} catch {
return null;
}
}
/**
* 检查服务是否已注册
*
* @param type - 服务类型
* @returns 是否已注册
*/
public isRegistered<T extends IService>(type: ServiceType<T>): boolean {
return this._services.has(type as ServiceType<IService>);
}
/**
* 注销服务
*
* @param type - 服务类型
* @returns 是否成功注销
*/
public unregister<T extends IService>(type: ServiceType<T>): boolean {
const registration = this._services.get(type as ServiceType<IService>);
if (!registration) {
return false;
}
// 如果有单例实例,调用 dispose
if (registration.instance) {
// 从可更新列表中移除
const index = this._updatableServices.findIndex(item => item.instance === registration.instance);
if (index !== -1) {
this._updatableServices.splice(index, 1);
}
registration.instance.dispose();
}
this._services.delete(type as ServiceType<IService>);
logger.debug(`Unregistered service: ${type.name}`);
return true;
}
/**
* 清空所有服务
*/
public clear(): void {
// 清理所有单例实例
for (const [, registration] of this._services) {
if (registration.instance) {
registration.instance.dispose();
}
}
this._services.clear();
this._updatableServices = [];
logger.debug('Cleared all services');
}
/**
* 获取所有已注册的服务类型
*
* @returns 服务类型数组
*/
public getRegisteredServices(): ServiceType<IService>[] {
return Array.from(this._services.keys());
}
/**
* 更新所有使用@Updatable装饰器标记的服务
*
* 此方法会按优先级顺序遍历所有可更新的服务并调用它们的update方法。
* 所有服务在注册时已经由@Updatable装饰器验证过必须实现IUpdatable接口。
* 通常在Core的主更新循环中调用。
*
* @param deltaTime - 可选的帧时间间隔(秒)
*
* @example
* ```typescript
* // 在Core的update方法中
* this._serviceContainer.updateAll(deltaTime);
* ```
*/
public updateAll(deltaTime?: number): void {
for (const { instance } of this._updatableServices) {
instance.update(deltaTime);
}
}
/**
* 获取所有可更新的服务数量
*
* @returns 可更新服务的数量
*/
public getUpdatableCount(): number {
return this._updatableServices.length;
}
/**
* 获取所有已实例化的服务实例
*
* @returns 所有服务实例的数组
*/
public getAll(): IService[] {
const instances: IService[] = [];
for (const descriptor of this._services.values()) {
if (descriptor.instance) {
instances.push(descriptor.instance);
}
}
return instances;
}
}

View File

@@ -2,19 +2,29 @@ import type { IComponent } from '../Types';
/**
* 游戏组件基类
*
* ECS架构中的组件Component,用于实现具体的游戏功能
* 组件包含数据和行为,可以被添加到实体上以扩展实体的功能
*
*
* ECS架构中的组件Component应该是纯数据容器
* 所有游戏逻辑应该在 EntitySystem 中实现,而不是在组件内部
*
* @example
* 推荐做法:纯数据组件
* ```typescript
* class HealthComponent extends Component {
* public health: number = 100;
*
* public takeDamage(damage: number): void {
* this.health -= damage;
* if (this.health <= 0) {
* this.entity.destroy();
* public maxHealth: number = 100;
* }
* ```
*
* @example
* 推荐做法:在 System 中处理逻辑
* ```typescript
* class HealthSystem extends EntitySystem {
* process(entities: Entity[]): void {
* for (const entity of entities) {
* const health = entity.getComponent(HealthComponent);
* if (health && health.health <= 0) {
* entity.destroy();
* }
* }
* }
* }
@@ -23,137 +33,56 @@ import type { IComponent } from '../Types';
export abstract class Component implements IComponent {
/**
* 组件ID生成器
*
*
* 用于为每个组件分配唯一的ID。
*/
public static _idGenerator: number = 0;
/**
* 组件唯一标识符
*
*
* 在整个游戏生命周期中唯一的数字ID。
*/
public readonly id: number;
/**
* 组件所属实体
*
* 指向拥有此组件的实体实例
* 所属实体ID
*
* 存储实体ID而非引用避免循环引用符合ECS数据导向设计
*/
public entity!: Entity;
/**
* 组件启用状态
*
* 控制组件是否参与更新循环。
*/
private _enabled: boolean = true;
/**
* 更新顺序
*
* 决定组件在更新循环中的执行顺序。
*/
private _updateOrder: number = 0;
public entityId: number | null = null;
/**
* 创建组件实例
*
*
* 自动分配唯一ID给组件。
*/
constructor() {
this.id = Component._idGenerator++;
}
/**
* 获取组件启用状态
*
* 组件的实际启用状态取决于自身状态和所属实体的状态。
*
* @returns 如果组件和所属实体都启用则返回true
*/
public get enabled(): boolean {
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
}
/**
* 设置组件启用状态
*
* 当状态改变时会触发相应的生命周期回调。
*
* @param value - 新的启用状态
*/
public set enabled(value: boolean) {
if (this._enabled !== value) {
this._enabled = value;
if (this._enabled) {
this.onEnabled();
} else {
this.onDisabled();
}
}
}
/**
* 获取更新顺序
*
* @returns 组件的更新顺序值
*/
public get updateOrder(): number {
return this._updateOrder;
}
/**
* 设置更新顺序
*
* @param value - 新的更新顺序值
*/
public set updateOrder(value: number) {
this._updateOrder = value;
}
/**
* 组件添加到实体时的回调
*
*
* 当组件被添加到实体时调用,可以在此方法中进行初始化操作。
*
* @remarks
* 这是一个生命周期钩子,用于组件的初始化逻辑。
* 虽然保留此方法,但建议将复杂的初始化逻辑放在 System 中处理。
*/
public onAddedToEntity(): void {
}
/**
* 组件从实体移除时的回调
*
*
* 当组件从实体中移除时调用,可以在此方法中进行清理操作。
*
* @remarks
* 这是一个生命周期钩子,用于组件的清理逻辑。
* 虽然保留此方法,但建议将复杂的清理逻辑放在 System 中处理。
*/
public onRemovedFromEntity(): void {
}
/**
* 组件启用时的回调
*
* 当组件被启用时调用。
*/
public onEnabled(): void {
}
/**
* 组件禁用时的回调
*
* 当组件被禁用时调用。
*/
public onDisabled(): void {
}
/**
* 更新组件
*
* 每帧调用,用于更新组件的逻辑。
* 子类应该重写此方法来实现具体的更新逻辑。
*/
public update(): void {
}
}
// 避免循环引用在文件末尾导入Entity
import type { Entity } from './Entity';
}

View File

@@ -1,27 +0,0 @@
/**
* 可更新接口
* 当添加到组件时只要组件和实体被启用就会在每帧调用update方法
*/
export interface IUpdatable {
enabled: boolean;
updateOrder: number;
update(): void;
}
/**
* 用于比较组件更新排序的比较器
*/
export class IUpdatableComparer {
public compare(a: IUpdatable, b: IUpdatable): number {
return a.updateOrder - b.updateOrder;
}
}
/**
* 检查对象是否实现了IUpdatable接口
* @param props 要检查的对象
* @returns 如果实现了IUpdatable接口返回true否则返回false
*/
export function isIUpdatable(props: any): props is IUpdatable {
return typeof (props as IUpdatable)['update'] !== 'undefined';
}

View File

@@ -1,64 +0,0 @@
import type { Scene } from '../Scene';
/**
* 场景组件基类
* 附加到场景的组件,用于实现场景级别的功能
*/
export class SceneComponent {
/** 组件所属的场景 */
public scene!: Scene;
/** 更新顺序 */
public updateOrder: number = 0;
/** 是否启用 */
private _enabled: boolean = true;
/** 获取是否启用 */
public get enabled(): boolean {
return this._enabled;
}
/** 设置是否启用 */
public set enabled(value: boolean) {
if (this._enabled !== value) {
this._enabled = value;
if (this._enabled) {
this.onEnabled();
} else {
this.onDisabled();
}
}
}
/**
* 当组件启用时调用
*/
public onEnabled(): void {
}
/**
* 当组件禁用时调用
*/
public onDisabled(): void {
}
/**
* 当组件从场景中移除时调用
*/
public onRemovedFromScene(): void {
}
/**
* 每帧更新
*/
public update(): void {
}
/**
* 比较组件的更新顺序
* @param other 其他组件
* @returns 比较结果
*/
public compare(other: SceneComponent): number {
return this.updateOrder - other.updateOrder;
}
}

View File

@@ -1,11 +1,12 @@
import { Entity } from '../Entity';
import { ComponentType } from './ComponentStorage';
import { getComponentTypeName } from '../Decorators';
import { BitMask64Data, BitMask64Utils, ComponentTypeManager } from "../Utils";
import { BitMaskHashMap } from "../Utils/BitMaskHashMap";
/**
* 原型标识符
*/
export type ArchetypeId = string;
export type ArchetypeId = BitMask64Data;
/**
* 原型数据结构
@@ -15,12 +16,8 @@ export interface Archetype {
id: ArchetypeId;
/** 包含的组件类型 */
componentTypes: ComponentType[];
/** 属于该原型的实体列表 */
entities: Entity[];
/** 原型创建时间 */
createdAt: number;
/** 最后更新时间 */
updatedAt: number;
/** 属于该原型的实体集合 */
entities: Set<Entity>;
}
/**
@@ -31,10 +28,6 @@ export interface ArchetypeQueryResult {
archetypes: Archetype[];
/** 所有匹配实体的总数 */
totalEntities: number;
/** 查询执行时间(毫秒) */
executionTime: number;
/** 是否使用了缓存 */
fromCache: boolean;
}
/**
@@ -44,22 +37,19 @@ export interface ArchetypeQueryResult {
*/
export class ArchetypeSystem {
/** 所有原型的映射表 */
private _archetypes = new Map<ArchetypeId, Archetype>();
private _archetypes = new BitMaskHashMap<Archetype>();
/** 实体到原型的映射 */
private _entityToArchetype = new Map<Entity, Archetype>();
/** 组件类型到原型的映射 */
private _componentToArchetypes = new Map<ComponentType, Set<Archetype>>();
/** 查询缓存 */
private _queryCache = new Map<string, {
result: ArchetypeQueryResult;
timestamp: number;
}>();
private _cacheTimeout = 5000;
private _maxCacheSize = 100;
/** 实体组件类型缓存 */
private _entityComponentTypesCache = new Map<Entity, ComponentType[]>();
/** 所有原型 */
private _allArchetypes: Archetype[] = [];
/**
* 添加实体到原型系统
@@ -67,18 +57,14 @@ export class ArchetypeSystem {
public addEntity(entity: Entity): void {
const componentTypes = this.getEntityComponentTypes(entity);
const archetypeId = this.generateArchetypeId(componentTypes);
let archetype = this._archetypes.get(archetypeId);
if (!archetype) {
archetype = this.createArchetype(componentTypes);
}
archetype.entities.push(entity);
archetype.updatedAt = Date.now();
archetype.entities.add(entity);
this._entityToArchetype.set(entity, archetype);
this.updateComponentIndexes(archetype, componentTypes, true);
this.invalidateQueryCache();
}
/**
@@ -87,48 +73,111 @@ export class ArchetypeSystem {
public removeEntity(entity: Entity): void {
const archetype = this._entityToArchetype.get(entity);
if (!archetype) return;
const index = archetype.entities.indexOf(entity);
if (index !== -1) {
archetype.entities.splice(index, 1);
archetype.updatedAt = Date.now();
}
archetype.entities.delete(entity);
// 清理实体相关缓存
this._entityComponentTypesCache.delete(entity);
this._entityToArchetype.delete(entity);
this.invalidateQueryCache();
}
/**
* 更新实体的原型归属
*
* 当实体的组件组合发生变化时调用此方法,将实体从旧原型移动到新原型。
* 如果新的组件组合对应的原型不存在,将自动创建新原型。
*
* @param entity 要更新的实体
*/
public updateEntity(entity: Entity): void {
const currentArchetype = this._entityToArchetype.get(entity);
// 清理实体组件类型缓存,强制重新计算
this._entityComponentTypesCache.delete(entity);
const newComponentTypes = this.getEntityComponentTypes(entity);
const newArchetypeId = this.generateArchetypeId(newComponentTypes);
// 如果实体已在正确的原型中,无需更新
if (currentArchetype && currentArchetype.id === newArchetypeId) {
return;
}
// 从旧原型中移除实体
if (currentArchetype) {
currentArchetype.entities.delete(entity);
}
// 获取或创建新原型
let newArchetype = this._archetypes.get(newArchetypeId);
if (!newArchetype) {
newArchetype = this.createArchetype(newComponentTypes);
}
// 将实体添加到新原型
newArchetype.entities.add(entity);
this._entityToArchetype.set(entity, newArchetype);
}
/**
* 查询包含指定组件组合的原型
*
* @param componentTypes 要查询的组件类型列表
* @param operation 查询操作类型:'AND'(包含所有)或 'OR'(包含任意)
* @returns 匹配的原型列表及实体总数
*/
public queryArchetypes(componentTypes: ComponentType[], operation: 'AND' | 'OR' = 'AND'): ArchetypeQueryResult {
const startTime = performance.now();
const cacheKey = `${operation}:${componentTypes.map(t => getComponentTypeName(t)).sort().join(',')}`;
// 检查缓存
const cached = this._queryCache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp < this._cacheTimeout)) {
return {
...cached.result,
executionTime: performance.now() - startTime,
fromCache: true
};
}
const matchingArchetypes: Archetype[] = [];
let totalEntities = 0;
if (operation === 'AND') {
for (const archetype of this._archetypes.values()) {
if (this.archetypeContainsAllComponents(archetype, componentTypes)) {
if (componentTypes.length === 0) {
for (const archetype of this._allArchetypes) {
matchingArchetypes.push(archetype);
totalEntities += archetype.entities.length;
totalEntities += archetype.entities.size;
}
return { archetypes: matchingArchetypes, totalEntities };
}
if (componentTypes.length === 1) {
const archetypes = this._componentToArchetypes.get(componentTypes[0]);
if (archetypes) {
for (const archetype of archetypes) {
matchingArchetypes.push(archetype);
totalEntities += archetype.entities.size;
}
}
return { archetypes: matchingArchetypes, totalEntities };
}
let smallestSet: Set<Archetype> | undefined;
let smallestSize = Infinity;
for (const componentType of componentTypes) {
const archetypes = this._componentToArchetypes.get(componentType);
if (!archetypes || archetypes.size === 0) {
return { archetypes: [], totalEntities: 0 };
}
if (archetypes.size < smallestSize) {
smallestSize = archetypes.size;
smallestSet = archetypes;
}
}
const queryMask = this.generateArchetypeId(componentTypes);
if (smallestSet) {
for (const archetype of smallestSet) {
if (BitMask64Utils.hasAll(archetype.id, queryMask)) {
matchingArchetypes.push(archetype);
totalEntities += archetype.entities.size;
}
}
}
} else {
const foundArchetypes = new Set<Archetype>();
for (const componentType of componentTypes) {
const archetypes = this._componentToArchetypes.get(componentType);
if (archetypes) {
@@ -137,27 +186,17 @@ export class ArchetypeSystem {
}
}
}
for (const archetype of foundArchetypes) {
matchingArchetypes.push(archetype);
totalEntities += archetype.entities.length;
totalEntities += archetype.entities.size;
}
}
const result: ArchetypeQueryResult = {
return {
archetypes: matchingArchetypes,
totalEntities,
executionTime: performance.now() - startTime,
fromCache: false
totalEntities
};
// 缓存结果
this._queryCache.set(cacheKey, {
result,
timestamp: Date.now()
});
return result;
}
/**
@@ -171,9 +210,27 @@ export class ArchetypeSystem {
* 获取所有原型
*/
public getAllArchetypes(): Archetype[] {
return Array.from(this._archetypes.values());
return this._allArchetypes.slice();
}
/**
* 获取包含指定组件类型的所有实体
*/
public getEntitiesByComponent(componentType: ComponentType): Entity[] {
const archetypes = this._componentToArchetypes.get(componentType);
if (!archetypes || archetypes.size === 0) {
return [];
}
const entities: Entity[] = [];
for (const archetype of archetypes) {
for (const entity of archetype.entities) {
entities.push(entity);
}
}
return entities;
}
/**
* 清空所有数据
*/
@@ -181,24 +238,38 @@ export class ArchetypeSystem {
this._archetypes.clear();
this._entityToArchetype.clear();
this._componentToArchetypes.clear();
this._queryCache.clear();
this._entityComponentTypesCache.clear();
this._allArchetypes = [];
}
/**
* 更新所有原型数组
*/
private updateAllArchetypeArrays(): void {
this._allArchetypes = [];
for (let archetype of this._archetypes.values()) {
this._allArchetypes.push(archetype);
}
}
/**
* 获取实体的组件类型列表
*/
private getEntityComponentTypes(entity: Entity): ComponentType[] {
return entity.components.map(component => component.constructor as ComponentType);
let componentTypes = this._entityComponentTypesCache.get(entity);
if (!componentTypes) {
componentTypes = entity.components.map(component => component.constructor as ComponentType);
this._entityComponentTypesCache.set(entity, componentTypes);
}
return componentTypes;
}
/**
* 生成原型ID
*/
private generateArchetypeId(componentTypes: ComponentType[]): ArchetypeId {
return componentTypes
.map(type => getComponentTypeName(type))
.sort()
.join('|');
let entityBits = ComponentTypeManager.instance.getEntityBits(componentTypes);
return entityBits.getValue();
}
/**
@@ -206,57 +277,26 @@ export class ArchetypeSystem {
*/
private createArchetype(componentTypes: ComponentType[]): Archetype {
const id = this.generateArchetypeId(componentTypes);
const archetype: Archetype = {
id,
componentTypes: [...componentTypes],
entities: [],
createdAt: Date.now(),
updatedAt: Date.now()
entities: new Set<Entity>()
};
this._archetypes.set(id, archetype);
return archetype;
}
/**
* 检查原型是否包含所有指定组件
*/
private archetypeContainsAllComponents(archetype: Archetype, componentTypes: ComponentType[]): boolean {
for (const componentType of componentTypes) {
if (!archetype.componentTypes.includes(componentType)) {
return false;
}
}
return true;
}
/**
* 更新组件索引
*/
private updateComponentIndexes(archetype: Archetype, componentTypes: ComponentType[], add: boolean): void {
this._archetypes.set(id,archetype);
this.updateAllArchetypeArrays();
for (const componentType of componentTypes) {
let archetypes = this._componentToArchetypes.get(componentType);
if (!archetypes) {
archetypes = new Set();
this._componentToArchetypes.set(componentType, archetypes);
}
if (add) {
archetypes.add(archetype);
} else {
archetypes.delete(archetype);
if (archetypes.size === 0) {
this._componentToArchetypes.delete(componentType);
}
}
archetypes.add(archetype);
}
return archetype;
}
/**
* 使查询缓存失效
*/
private invalidateQueryCache(): void {
this._queryCache.clear();
}
}

View File

@@ -1,181 +0,0 @@
import { Entity } from '../Entity';
import { ComponentType } from './ComponentStorage';
import { ComponentSparseSet } from '../Utils/ComponentSparseSet';
/**
* 索引统计信息
*/
export interface IndexStats {
/** 索引大小 */
size: number;
/** 内存使用量(字节) */
memoryUsage: number;
/** 查询次数 */
queryCount: number;
/** 平均查询时间(毫秒) */
avgQueryTime: number;
/** 最后更新时间 */
lastUpdated: number;
}
/**
* 组件索引接口
*/
export interface IComponentIndex {
/** 添加实体到索引 */
addEntity(entity: Entity): void;
/** 从索引中移除实体 */
removeEntity(entity: Entity): void;
/** 查询包含指定组件的实体 */
query(componentType: ComponentType): Set<Entity>;
/** 批量查询多个组件 */
queryMultiple(componentTypes: ComponentType[], operation: 'AND' | 'OR'): Set<Entity>;
/** 清空索引 */
clear(): void;
/** 获取索引统计信息 */
getStats(): IndexStats;
}
/**
* 通用组件索引实现
*
* 基于Sparse Set算法
* - O(1)的实体添加、删除、查找
* - 高效的位运算查询
* - 内存紧凑的存储结构
* - 缓存友好的遍历性能
*/
export class ComponentIndex implements IComponentIndex {
/**
* 组件稀疏集合
*
* 核心存储结构,处理所有实体和组件的索引操作。
*/
private _sparseSet: ComponentSparseSet;
// 性能统计
private _queryCount = 0;
private _totalQueryTime = 0;
private _lastUpdated = Date.now();
constructor() {
this._sparseSet = new ComponentSparseSet();
}
public addEntity(entity: Entity): void {
this._sparseSet.addEntity(entity);
this._lastUpdated = Date.now();
}
public removeEntity(entity: Entity): void {
this._sparseSet.removeEntity(entity);
this._lastUpdated = Date.now();
}
public query(componentType: ComponentType): Set<Entity> {
const startTime = performance.now();
const result = this._sparseSet.queryByComponent(componentType);
this._queryCount++;
this._totalQueryTime += performance.now() - startTime;
return result;
}
public queryMultiple(componentTypes: ComponentType[], operation: 'AND' | 'OR'): Set<Entity> {
const startTime = performance.now();
let result: Set<Entity>;
if (componentTypes.length === 0) {
result = new Set();
} else if (componentTypes.length === 1) {
result = this.query(componentTypes[0]);
} else if (operation === 'AND') {
result = this._sparseSet.queryMultipleAnd(componentTypes);
} else {
result = this._sparseSet.queryMultipleOr(componentTypes);
}
this._queryCount++;
this._totalQueryTime += performance.now() - startTime;
return result;
}
public clear(): void {
this._sparseSet.clear();
this._lastUpdated = Date.now();
}
public getStats(): IndexStats {
const memoryStats = this._sparseSet.getMemoryStats();
return {
size: this._sparseSet.size,
memoryUsage: memoryStats.totalMemory,
queryCount: this._queryCount,
avgQueryTime: this._queryCount > 0 ? this._totalQueryTime / this._queryCount : 0,
lastUpdated: this._lastUpdated
};
}
}
/**
* 组件索引管理器
*
* 使用统一的组件索引实现,自动优化查询性能。
*/
export class ComponentIndexManager {
private _index: ComponentIndex;
constructor() {
this._index = new ComponentIndex();
}
/**
* 添加实体到索引
*/
public addEntity(entity: Entity): void {
this._index.addEntity(entity);
}
/**
* 从索引中移除实体
*/
public removeEntity(entity: Entity): void {
this._index.removeEntity(entity);
}
/**
* 查询包含指定组件的实体
*/
public query(componentType: ComponentType): Set<Entity> {
return this._index.query(componentType);
}
/**
* 批量查询多个组件
*/
public queryMultiple(componentTypes: ComponentType[], operation: 'AND' | 'OR'): Set<Entity> {
return this._index.queryMultiple(componentTypes, operation);
}
/**
* 获取索引统计信息
*/
public getStats(): IndexStats {
return this._index.getStats();
}
/**
* 清空索引
*/
public clear(): void {
this._index.clear();
}
}

View File

@@ -8,24 +8,41 @@ export class ComponentPool<T extends Component> {
private createFn: () => T;
private resetFn?: (component: T) => void;
private maxSize: number;
private minSize: number;
private growthFactor: number;
private stats = {
totalCreated: 0,
totalAcquired: 0,
totalReleased: 0
};
constructor(
createFn: () => T,
resetFn?: (component: T) => void,
maxSize: number = 1000
maxSize: number = 1000,
minSize: number = 10,
growthFactor: number = 1.5
) {
this.createFn = createFn;
this.resetFn = resetFn;
this.maxSize = maxSize;
this.minSize = Math.max(1, minSize);
this.growthFactor = Math.max(1.1, growthFactor);
}
/**
* 获取一个组件实例
*/
acquire(): T {
this.stats.totalAcquired++;
if (this.pool.length > 0) {
return this.pool.pop()!;
}
this.stats.totalCreated++;
return this.createFn();
}
@@ -33,20 +50,41 @@ export class ComponentPool<T extends Component> {
* 释放一个组件实例回池中
*/
release(component: T): void {
if (this.pool.length < this.maxSize) {
if (this.resetFn) {
this.resetFn(component);
}
this.pool.push(component);
this.stats.totalReleased++;
if (this.pool.length >= this.maxSize) {
return;
}
if (this.resetFn) {
this.resetFn(component);
}
this.pool.push(component);
}
/**
* 预填充对象池
*/
prewarm(count: number): void {
for (let i = 0; i < count && this.pool.length < this.maxSize; i++) {
this.pool.push(this.createFn());
const targetCount = Math.min(count, this.maxSize);
for (let i = this.pool.length; i < targetCount; i++) {
const component = this.createFn();
if (this.resetFn) {
this.resetFn(component);
}
this.pool.push(component);
this.stats.totalCreated++;
}
}
/**
* 自动收缩池大小
*/
shrink(): void {
while (this.pool.length > this.minSize) {
this.pool.pop();
}
}
@@ -70,6 +108,35 @@ export class ComponentPool<T extends Component> {
getMaxSize(): number {
return this.maxSize;
}
/**
* 获取统计信息
*/
getStats() {
const hitRate = this.stats.totalAcquired === 0
? 0
: (this.stats.totalAcquired - this.stats.totalCreated) / this.stats.totalAcquired;
return {
totalCreated: this.stats.totalCreated,
totalAcquired: this.stats.totalAcquired,
totalReleased: this.stats.totalReleased,
hitRate: hitRate,
currentSize: this.pool.length,
maxSize: this.maxSize,
minSize: this.minSize,
utilizationRate: this.pool.length / this.maxSize
};
}
}
/**
* 组件使用追踪
*/
interface ComponentUsageTracker {
createCount: number;
releaseCount: number;
lastAccessTime: number;
}
/**
@@ -78,6 +145,10 @@ export class ComponentPool<T extends Component> {
export class ComponentPoolManager {
private static instance: ComponentPoolManager;
private pools = new Map<string, ComponentPool<any>>();
private usageTracker = new Map<string, ComponentUsageTracker>();
private autoCleanupInterval = 60000;
private lastCleanupTime = 0;
private constructor() {}
@@ -95,9 +166,16 @@ export class ComponentPoolManager {
componentName: string,
createFn: () => T,
resetFn?: (component: T) => void,
maxSize?: number
maxSize?: number,
minSize?: number
): void {
this.pools.set(componentName, new ComponentPool(createFn, resetFn, maxSize));
this.pools.set(componentName, new ComponentPool(createFn, resetFn, maxSize, minSize));
this.usageTracker.set(componentName, {
createCount: 0,
releaseCount: 0,
lastAccessTime: Date.now()
});
}
/**
@@ -105,6 +183,9 @@ export class ComponentPoolManager {
*/
acquireComponent<T extends Component>(componentName: string): T | null {
const pool = this.pools.get(componentName);
this.trackUsage(componentName, 'create');
return pool ? pool.acquire() : null;
}
@@ -113,11 +194,71 @@ export class ComponentPoolManager {
*/
releaseComponent<T extends Component>(componentName: string, component: T): void {
const pool = this.pools.get(componentName);
this.trackUsage(componentName, 'release');
if (pool) {
pool.release(component);
}
}
/**
* 追踪使用情况
*/
private trackUsage(componentName: string, action: 'create' | 'release'): void {
let tracker = this.usageTracker.get(componentName);
if (!tracker) {
tracker = {
createCount: 0,
releaseCount: 0,
lastAccessTime: Date.now()
};
this.usageTracker.set(componentName, tracker);
}
if (action === 'create') {
tracker.createCount++;
} else {
tracker.releaseCount++;
}
tracker.lastAccessTime = Date.now();
}
/**
* 自动清理(定期调用)
*/
public update(): void {
const now = Date.now();
if (now - this.lastCleanupTime < this.autoCleanupInterval) {
return;
}
for (const [name, tracker] of this.usageTracker.entries()) {
const inactive = now - tracker.lastAccessTime > 120000;
if (inactive) {
const pool = this.pools.get(name);
if (pool) {
pool.shrink();
}
}
}
this.lastCleanupTime = now;
}
/**
* 获取热点组件列表
*/
public getHotComponents(threshold: number = 100): string[] {
return Array.from(this.usageTracker.entries())
.filter(([_, tracker]) => tracker.createCount > threshold)
.map(([name]) => name);
}
/**
* 预热所有池
*/
@@ -137,10 +278,28 @@ export class ComponentPoolManager {
}
/**
* 重置管理器,移除所有注册的池
* 重置管理器
*/
reset(): void {
this.pools.clear();
this.usageTracker.clear();
}
/**
* 获取全局统计信息
*/
getGlobalStats() {
const stats: any[] = [];
for (const [name, pool] of this.pools.entries()) {
stats.push({
componentName: name,
poolStats: pool.getStats(),
usage: this.usageTracker.get(name)
});
}
return stats;
}
/**
@@ -158,7 +317,7 @@ export class ComponentPoolManager {
}
/**
* 获取池利用率信息(用于调试)
* 获取池利用率信息
*/
getPoolUtilization(): Map<string, { used: number; total: number; utilization: number }> {
const utilization = new Map();
@@ -167,7 +326,7 @@ export class ComponentPoolManager {
const maxSize = pool.getMaxSize();
const used = maxSize - available;
const utilRate = maxSize > 0 ? (used / maxSize * 100) : 0;
utilization.set(name, {
used: used,
total: maxSize,
@@ -183,11 +342,11 @@ export class ComponentPoolManager {
getComponentUtilization(componentName: string): number {
const pool = this.pools.get(componentName);
if (!pool) return 0;
const available = pool.getAvailableCount();
const maxSize = pool.getMaxSize();
const used = maxSize - available;
return maxSize > 0 ? (used / maxSize * 100) : 0;
}
}

View File

@@ -1,12 +1,11 @@
import { Component } from '../Component';
import { BitMask64Utils, BitMask64Data } from '../Utils/BigIntCompatibility';
import { SoAStorage, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from './SoAStorage';
import { SoAStorage, SupportedTypedArray } from './SoAStorage';
import { createLogger } from '../../Utils/Logger';
import { getComponentTypeName } from '../Decorators';
import { ComponentRegistry, ComponentType } from './ComponentStorage/ComponentRegistry';
// 重新导出装饰器和核心类型
export { EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy };
// 导出核心类型
export { ComponentRegistry, ComponentType };
@@ -203,9 +202,9 @@ export class ComponentStorageManager {
* @returns TypedArray或null
*/
public getFieldArray<T extends Component>(
componentType: ComponentType<T>,
componentType: ComponentType<T>,
fieldName: string
): Float32Array | Float64Array | Int32Array | null {
): SupportedTypedArray | null {
const soaStorage = this.getSoAStorage(componentType);
return soaStorage ? soaStorage.getFieldArray(fieldName) : null;
}
@@ -217,9 +216,9 @@ export class ComponentStorageManager {
* @returns TypedArray或null
*/
public getTypedFieldArray<T extends Component, K extends keyof T>(
componentType: ComponentType<T>,
componentType: ComponentType<T>,
fieldName: K
): Float32Array | Float64Array | Int32Array | null {
): SupportedTypedArray | null {
const soaStorage = this.getSoAStorage(componentType);
return soaStorage ? soaStorage.getTypedFieldArray(fieldName) : null;
}

View File

@@ -15,11 +15,11 @@ export type ComponentType<T extends Component = Component> = new (...args: any[]
export class ComponentRegistry {
protected static readonly _logger = createLogger('ComponentStorage');
private static componentTypes = new Map<Function, number>();
private static bitIndexToType = new Map<number, Function>();
private static componentNameToType = new Map<string, Function>();
private static componentNameToId = new Map<string, number>();
private static maskCache = new Map<string, BitMask64Data>();
private static nextBitIndex = 0;
private static maxComponents = 64; // 支持最多64种组件类型
/**
* 注册组件类型并分配位掩码
@@ -28,21 +28,18 @@ export class ComponentRegistry {
*/
public static register<T extends Component>(componentType: ComponentType<T>): number {
const typeName = getComponentTypeName(componentType);
if (this.componentTypes.has(componentType)) {
const existingIndex = this.componentTypes.get(componentType)!;
return existingIndex;
}
if (this.nextBitIndex >= this.maxComponents) {
throw new Error(`Maximum number of component types (${this.maxComponents}) exceeded`);
}
const bitIndex = this.nextBitIndex++;
this.componentTypes.set(componentType, bitIndex);
this.bitIndexToType.set(bitIndex, componentType);
this.componentNameToType.set(typeName, componentType);
this.componentNameToId.set(typeName, bitIndex);
return bitIndex;
}
@@ -83,6 +80,23 @@ export class ComponentRegistry {
return this.componentTypes.has(componentType);
}
/**
* 通过位索引获取组件类型
* @param bitIndex 位索引
* @returns 组件类型构造函数或null
*/
public static getTypeByBitIndex(bitIndex: number): ComponentType | null {
return (this.bitIndexToType.get(bitIndex) as ComponentType) || null;
}
/**
* 获取当前已注册的组件类型数量
* @returns 已注册数量
*/
public static getRegisteredCount(): number {
return this.nextBitIndex;
}
/**
* 通过名称获取组件类型
* @param componentName 组件名称
@@ -127,10 +141,6 @@ export class ComponentRegistry {
return this.componentNameToId.get(componentName)!;
}
if (this.nextBitIndex >= this.maxComponents) {
throw new Error(`Maximum number of component types (${this.maxComponents}) exceeded`);
}
const bitIndex = this.nextBitIndex++;
this.componentNameToId.set(componentName, bitIndex);
return bitIndex;
@@ -196,6 +206,7 @@ export class ComponentRegistry {
*/
public static reset(): void {
this.componentTypes.clear();
this.bitIndexToType.clear();
this.componentNameToType.clear();
this.componentNameToId.clear();
this.maskCache.clear();

View File

@@ -1,377 +0,0 @@
import { Entity } from '../Entity';
import { Component } from '../Component';
import { ComponentType } from './ComponentStorage';
import { createLogger } from '../../Utils/Logger';
/**
* 脏标记类型
*/
export enum DirtyFlag {
/** 组件数据已修改 */
COMPONENT_MODIFIED = 1 << 0,
/** 组件已添加 */
COMPONENT_ADDED = 1 << 1,
/** 组件已移除 */
COMPONENT_REMOVED = 1 << 2,
/** 实体位置已改变 */
TRANSFORM_CHANGED = 1 << 3,
/** 实体状态已改变 */
STATE_CHANGED = 1 << 4,
/** 自定义标记1 */
CUSTOM_1 = 1 << 8,
/** 自定义标记2 */
CUSTOM_2 = 1 << 9,
/** 自定义标记3 */
CUSTOM_3 = 1 << 10,
/** 所有标记 */
ALL = 0xFFFFFFFF
}
/**
* 脏标记数据
*/
export interface DirtyData {
/** 实体引用 */
entity: Entity;
/** 脏标记位 */
flags: number;
/** 修改的组件类型列表 */
modifiedComponents: Set<ComponentType>;
/** 标记时间戳 */
timestamp: number;
/** 帧编号 */
frameNumber: number;
}
/**
* 脏标记监听器
*/
export interface DirtyListener {
/** 感兴趣的标记类型 */
flags: number;
/** 回调函数 */
callback: (dirtyData: DirtyData) => void;
/** 监听器优先级(数字越小优先级越高) */
priority?: number;
}
/**
* 脏标记统计信息
*/
export interface DirtyStats {
/** 当前脏实体数量 */
dirtyEntityCount: number;
/** 总标记次数 */
totalMarkings: number;
/** 总清理次数 */
totalCleanups: number;
/** 监听器数量 */
listenerCount: number;
/** 平均每帧脏实体数量 */
avgDirtyPerFrame: number;
/** 内存使用量估算 */
estimatedMemoryUsage: number;
}
/**
* 脏标记追踪系统
*
* 提供高效的组件和实体变更追踪,避免不必要的计算和更新。
* 支持细粒度的脏标记和批量处理机制。
*
* @example
* ```typescript
* const dirtySystem = new DirtyTrackingSystem();
*
* // 标记实体的位置组件已修改
* dirtySystem.markDirty(entity, DirtyFlag.TRANSFORM_CHANGED, [PositionComponent]);
*
* // 监听位置变化
* dirtySystem.addListener({
* flags: DirtyFlag.TRANSFORM_CHANGED,
* callback: (data) => {
* logger.debug('Entity position changed:', data.entity.name);
* }
* });
*
* // 处理所有脏标记
* dirtySystem.processDirtyEntities();
* ```
*/
export class DirtyTrackingSystem {
private static readonly _logger = createLogger('DirtyTrackingSystem');
/** 脏实体映射表 */
private _dirtyEntities = new Map<Entity, DirtyData>();
/** 脏标记监听器 */
private _listeners: DirtyListener[] = [];
/** 性能统计 */
private _stats = {
totalMarkings: 0,
totalCleanups: 0,
frameCount: 0,
totalDirtyPerFrame: 0
};
/** 当前帧编号 */
private _currentFrame = 0;
private _batchSize = 100;
private _maxProcessingTime = 16;
/** 延迟处理队列 */
private _processingQueue: DirtyData[] = [];
private _isProcessing = false;
/**
* 标记实体为脏状态
*
* @param entity 要标记的实体
* @param flags 脏标记位
* @param modifiedComponents 修改的组件类型列表
*/
public markDirty(entity: Entity, flags: DirtyFlag, modifiedComponents: ComponentType[] = []): void {
this._stats.totalMarkings++;
let dirtyData = this._dirtyEntities.get(entity);
if (!dirtyData) {
dirtyData = {
entity,
flags: 0,
modifiedComponents: new Set(),
timestamp: performance.now(),
frameNumber: this._currentFrame
};
this._dirtyEntities.set(entity, dirtyData);
}
dirtyData.flags |= flags;
dirtyData.timestamp = performance.now();
dirtyData.frameNumber = this._currentFrame;
for (const componentType of modifiedComponents) {
dirtyData.modifiedComponents.add(componentType);
}
this.notifyListeners(dirtyData, flags);
}
/**
* 检查实体是否有指定的脏标记
*
* @param entity 要检查的实体
* @param flags 要检查的标记位
* @returns 是否有指定的脏标记
*/
public isDirty(entity: Entity, flags: DirtyFlag = DirtyFlag.ALL): boolean {
const dirtyData = this._dirtyEntities.get(entity);
return dirtyData ? (dirtyData.flags & flags) !== 0 : false;
}
/**
* 清除实体的脏标记
*
* @param entity 要清除的实体
* @param flags 要清除的标记位,默认清除所有
*/
public clearDirty(entity: Entity, flags: DirtyFlag = DirtyFlag.ALL): void {
const dirtyData = this._dirtyEntities.get(entity);
if (!dirtyData) return;
if (flags === DirtyFlag.ALL) {
this._dirtyEntities.delete(entity);
} else {
dirtyData.flags &= ~flags;
if (dirtyData.flags === 0) {
this._dirtyEntities.delete(entity);
}
}
this._stats.totalCleanups++;
}
/**
* 获取所有脏实体
*
* @param flags 过滤标记位,只返回包含指定标记的实体
* @returns 脏实体数据数组
*/
public getDirtyEntities(flags: DirtyFlag = DirtyFlag.ALL): DirtyData[] {
const result: DirtyData[] = [];
for (const dirtyData of this._dirtyEntities.values()) {
if ((dirtyData.flags & flags) !== 0) {
result.push(dirtyData);
}
}
return result;
}
/**
* 批量处理脏实体
*
* 使用时间分片的方式处理脏实体,避免单帧卡顿
*/
public processDirtyEntities(): void {
if (this._isProcessing) return;
this._isProcessing = true;
const startTime = performance.now();
if (this._processingQueue.length === 0) {
this._processingQueue.push(...this._dirtyEntities.values());
}
let processed = 0;
while (this._processingQueue.length > 0 && processed < this._batchSize) {
const elapsed = performance.now() - startTime;
if (elapsed > this._maxProcessingTime) {
break;
}
const dirtyData = this._processingQueue.shift()!;
this.processEntity(dirtyData);
processed++;
}
if (this._processingQueue.length === 0) {
this._isProcessing = false;
this.onFrameEnd();
}
}
/**
* 添加脏标记监听器
*
* @param listener 监听器配置
*/
public addListener(listener: DirtyListener): void {
this._listeners.push(listener);
this._listeners.sort((a, b) => (a.priority || 100) - (b.priority || 100));
}
/**
* 移除脏标记监听器
*
* @param callback 要移除的回调函数
*/
public removeListener(callback: (dirtyData: DirtyData) => void): void {
const index = this._listeners.findIndex(l => l.callback === callback);
if (index !== -1) {
this._listeners.splice(index, 1);
}
}
/**
* 开始新的帧
*/
public beginFrame(): void {
this._currentFrame++;
}
/**
* 结束当前帧
*/
public endFrame(): void {
if (!this._isProcessing) {
this.processDirtyEntities();
}
}
/**
* 获取统计信息
*/
public getStats(): DirtyStats {
return {
dirtyEntityCount: this._dirtyEntities.size,
totalMarkings: this._stats.totalMarkings,
totalCleanups: this._stats.totalCleanups,
listenerCount: this._listeners.length,
avgDirtyPerFrame: this._stats.frameCount > 0 ?
this._stats.totalDirtyPerFrame / this._stats.frameCount : 0,
estimatedMemoryUsage: this.estimateMemoryUsage()
};
}
/**
* 清空所有脏标记和统计信息
*/
public clear(): void {
this._dirtyEntities.clear();
this._processingQueue.length = 0;
this._isProcessing = false;
this._stats = {
totalMarkings: 0,
totalCleanups: 0,
frameCount: 0,
totalDirtyPerFrame: 0
};
}
/**
* 配置批量处理参数
*
* @param batchSize 每次处理的最大实体数量
* @param maxProcessingTime 每帧最大处理时间(毫秒)
*/
public configureBatchProcessing(batchSize: number, maxProcessingTime: number): void {
this._batchSize = batchSize;
this._maxProcessingTime = maxProcessingTime;
}
/**
* 处理单个脏实体
*/
private processEntity(dirtyData: DirtyData): void {
for (const listener of this._listeners) {
if ((dirtyData.flags & listener.flags) !== 0) {
try {
listener.callback(dirtyData);
} catch (error) {
DirtyTrackingSystem._logger.error('脏数据监听器错误:', error);
}
}
}
this.clearDirty(dirtyData.entity);
}
/**
* 通知监听器
*/
private notifyListeners(dirtyData: DirtyData, newFlags: DirtyFlag): void {
for (const listener of this._listeners) {
if ((newFlags & listener.flags) !== 0) {
try {
listener.callback(dirtyData);
} catch (error) {
DirtyTrackingSystem._logger.error('脏数据监听器通知错误:', error);
}
}
}
}
/**
* 帧结束时的统计更新
*/
private onFrameEnd(): void {
this._stats.frameCount++;
this._stats.totalDirtyPerFrame += this._dirtyEntities.size;
}
/**
* 估算内存使用量
*/
private estimateMemoryUsage(): number {
let usage = 0;
usage += this._dirtyEntities.size * 100;
usage += this._listeners.length * 50;
usage += this._processingQueue.length * 8;
return usage;
}
}

View File

@@ -1,759 +0,0 @@
import { Entity } from '../Entity';
import { Component } from '../Component';
import { ComponentType } from './ComponentStorage';
import { IdentifierPool } from '../Utils/IdentifierPool';
import { ComponentIndexManager } from './ComponentIndex';
import { ArchetypeSystem } from './ArchetypeSystem';
import { DirtyTrackingSystem, DirtyFlag } from './DirtyTrackingSystem';
import { EventBus } from './EventBus';
/**
* 实体查询构建器
*
* 提供流式API来构建复杂的实体查询条件。支持组件过滤、标签过滤、状态过滤和自定义条件。
*
* @example
* ```typescript
* const results = entityManager.query()
* .withAll(PositionComponent, HealthComponent)
* .without(VelocityComponent)
* .withTag(1)
* .active()
* .where(entity => entity.name.startsWith("Player"))
* .execute();
* ```
*/
export class EntityQueryBuilder {
/** 必须包含的组件类型 */
private _allComponents: ComponentType[] = [];
/** 至少包含一个的组件类型 */
private _anyComponents: ComponentType[] = [];
/** 不能包含的组件类型 */
private _withoutComponents: ComponentType[] = [];
/** 必须包含的标签 */
private _withTags: number[] = [];
/** 不能包含的标签 */
private _withoutTags: number[] = [];
/** 是否只查询激活状态的实体 */
private _activeOnly: boolean = false;
/** 是否只查询启用状态的实体 */
private _enabledOnly: boolean = false;
/** 自定义过滤条件 */
private _customPredicates: Array<(entity: Entity) => boolean> = [];
/**
* 创建查询构建器实例
* @param entityManager 实体管理器实例
*/
constructor(private entityManager: EntityManager) {}
/**
* 添加必须包含的组件条件
*
* 返回的实体必须包含所有指定的组件类型。
*
* @param componentTypes 组件类型列表
* @returns 查询构建器实例,支持链式调用
*/
public withAll(...componentTypes: ComponentType[]): EntityQueryBuilder {
this._allComponents.push(...componentTypes);
return this;
}
/**
* 添加至少包含一个的组件条件
*
* 返回的实体必须至少包含其中一个指定的组件类型。
*
* @param componentTypes 组件类型列表
* @returns 查询构建器实例,支持链式调用
*/
public withAny(...componentTypes: ComponentType[]): EntityQueryBuilder {
this._anyComponents.push(...componentTypes);
return this;
}
/**
* 添加不能包含的组件条件
*
* 返回的实体不能包含任何指定的组件类型。
*
* @param componentTypes 组件类型列表
* @returns 查询构建器实例,支持链式调用
*/
public without(...componentTypes: ComponentType[]): EntityQueryBuilder {
this._withoutComponents.push(...componentTypes);
return this;
}
/**
* 添加必须包含的标签条件
*
* 返回的实体必须具有指定的标签。
*
* @param tag 标签值
* @returns 查询构建器实例,支持链式调用
*/
public withTag(tag: number): EntityQueryBuilder {
this._withTags.push(tag);
return this;
}
/**
* 添加不能包含的标签条件
*
* 返回的实体不能具有指定的标签。
*
* @param tag 标签值
* @returns 查询构建器实例,支持链式调用
*/
public withoutTag(tag: number): EntityQueryBuilder {
this._withoutTags.push(tag);
return this;
}
/**
* 添加激活状态过滤条件
*
* 返回的实体必须处于激活状态active = true
*
* @returns 查询构建器实例,支持链式调用
*/
public active(): EntityQueryBuilder {
this._activeOnly = true;
return this;
}
/**
* 添加启用状态过滤条件
*
* 返回的实体必须处于启用状态enabled = true
*
* @returns 查询构建器实例,支持链式调用
*/
public enabled(): EntityQueryBuilder {
this._enabledOnly = true;
return this;
}
/**
* 添加自定义过滤条件
*
* 允许用户定义复杂的过滤逻辑。
*
* @param predicate 自定义过滤函数,接收实体作为参数,返回布尔值
* @returns 查询构建器实例,支持链式调用
*
* @example
* ```typescript
* .where(entity => entity.name.startsWith("Player"))
* .where(entity => entity.components.length > 5)
* ```
*/
public where(predicate: (entity: Entity) => boolean): EntityQueryBuilder {
this._customPredicates.push(predicate);
return this;
}
/**
* 执行查询并返回所有匹配的实体
*
* @returns 符合所有查询条件的实体数组
*/
public execute(): Entity[] {
let candidates: Entity[] = [];
if (this._allComponents.length > 0) {
const indexResult = this.entityManager.queryWithComponentIndex(this._allComponents, 'AND');
candidates = Array.from(indexResult);
} else if (this._anyComponents.length > 0) {
const indexResult = this.entityManager.queryWithComponentIndex(this._anyComponents, 'OR');
candidates = Array.from(indexResult);
} else {
candidates = this.entityManager.getAllEntities();
}
return candidates.filter(entity => this.matchesEntity(entity));
}
/**
* 执行查询并返回第一个匹配的实体
*
* @returns 第一个符合查询条件的实体如果没有找到则返回null
*/
public first(): Entity | null {
const entities = this.entityManager.getAllEntities();
return entities.find(entity => this.matchesEntity(entity)) || null;
}
/**
* 执行查询并返回匹配实体的数量
*
* @returns 符合查询条件的实体数量
*/
public count(): number {
const entities = this.entityManager.getAllEntities();
return entities.filter(entity => this.matchesEntity(entity)).length;
}
/**
* 对所有匹配的实体执行指定操作
*
* @param action 要执行的操作函数,接收匹配的实体作为参数
*/
public forEach(action: (entity: Entity) => void): void {
const entities = this.entityManager.getAllEntities();
entities.forEach(entity => {
if (this.matchesEntity(entity)) {
action(entity);
}
});
}
/**
* 检查实体是否匹配所有查询条件
*
* 按优先级顺序检查各种过滤条件一旦发现不匹配立即返回false。
*
* @param entity 要检查的实体
* @returns 实体是否匹配所有查询条件
*/
private matchesEntity(entity: Entity): boolean {
// 检查激活状态
if (this._activeOnly && !entity.active) {
return false;
}
// 检查启用状态
if (this._enabledOnly && !entity.enabled) {
return false;
}
// 检查必须包含的组件
if (this._allComponents.length > 0) {
for (const componentType of this._allComponents) {
if (!entity.hasComponent(componentType)) {
return false;
}
}
}
// 检查至少包含一个的组件
if (this._anyComponents.length > 0) {
let hasAny = false;
for (const componentType of this._anyComponents) {
if (entity.hasComponent(componentType)) {
hasAny = true;
break;
}
}
if (!hasAny) {
return false;
}
}
// 检查不能包含的组件
if (this._withoutComponents.length > 0) {
for (const componentType of this._withoutComponents) {
if (entity.hasComponent(componentType)) {
return false;
}
}
}
// 检查必须包含的标签
if (this._withTags.length > 0) {
if (!this._withTags.includes(entity.tag)) {
return false;
}
}
// 检查不能包含的标签
if (this._withoutTags.length > 0) {
if (this._withoutTags.includes(entity.tag)) {
return false;
}
}
// 检查自定义条件
if (this._customPredicates.length > 0) {
for (const predicate of this._customPredicates) {
if (!predicate(entity)) {
return false;
}
}
}
return true;
}
}
/**
* 实体管理器
*
* 提供统一的实体管理和查询机制,支持高效的实体操作。
* 包括实体的创建、销毁、查询和索引管理功能。
*
* @example
* ```typescript
* const entityManager = new EntityManager();
*
* // 创建实体
* const player = entityManager.createEntity("Player");
*
* // 查询实体
* const playerEntity = entityManager.getEntityByName("Player");
*
* // 复杂查询
* const results = entityManager.query()
* .withAll(HealthComponent, PositionComponent)
* .active()
* .execute();
* ```
*/
export class EntityManager {
/** 主要实体存储使用ID作为键 */
private _entities: Map<number, Entity> = new Map();
/** 按名称索引的实体映射 */
private _entitiesByName: Map<string, Entity[]> = new Map();
/** 按标签索引的实体映射 */
private _entitiesByTag: Map<number, Entity[]> = new Map();
/** 实体ID分配器 */
private _identifierPool: IdentifierPool;
/** 已销毁实体的ID集合 */
private _destroyedEntities: Set<number> = new Set();
/** 性能优化系统 */
private _componentIndexManager: ComponentIndexManager;
private _archetypeSystem: ArchetypeSystem;
private _dirtyTrackingSystem: DirtyTrackingSystem;
/** 事件总线 */
private _eventBus: EventBus;
/**
* 创建实体管理器实例
*
* 初始化内部数据结构和ID分配器。
*/
constructor() {
this._identifierPool = new IdentifierPool();
// 初始化性能优化系统
this._componentIndexManager = new ComponentIndexManager();
this._archetypeSystem = new ArchetypeSystem();
this._dirtyTrackingSystem = new DirtyTrackingSystem();
this._eventBus = new EventBus(false);
// 设置Entity的静态事件总线引用
Entity.eventBus = this._eventBus;
// 监听组件事件来同步更新索引
this._eventBus.on('component:added', (data: any) => {
const entity = this._entities.get(data.entityId);
if (entity) {
this._componentIndexManager.addEntity(entity);
this._archetypeSystem.addEntity(entity);
}
});
this._eventBus.on('component:removed', (data: any) => {
const entity = this._entities.get(data.entityId);
if (entity) {
this._componentIndexManager.removeEntity(entity);
this._archetypeSystem.removeEntity(entity);
}
});
}
/**
* 获取实体总数
*
* @returns 当前管理的实体总数量
*/
public get entityCount(): number {
return this._entities.size;
}
/**
* 获取激活状态的实体数量
*
* 只计算同时满足激活状态且未被销毁的实体。
*
* @returns 激活状态的实体数量
*/
public get activeEntityCount(): number {
let count = 0;
for (const entity of this._entities.values()) {
if (entity.active && !entity.isDestroyed) {
count++;
}
}
return count;
}
/**
* 创建新实体
*
* 分配唯一ID并将实体添加到管理系统中同时更新相关索引。
*
* @param name 实体名称,如果未指定则使用时间戳生成默认名称
* @returns 创建的实体实例
*
* @example
* ```typescript
* const player = entityManager.createEntity("Player");
* const enemy = entityManager.createEntity(); // 使用默认名称
* ```
*/
public createEntity(name?: string): Entity {
const id = this._identifierPool.checkOut();
if (!name) {
name = `Entity_${id}`;
}
const entity = new Entity(name, id);
this._entities.set(id, entity);
this.updateNameIndex(entity, true);
this.updateTagIndex(entity, true);
if (entity.components.length > 0) {
this._componentIndexManager.addEntity(entity);
this._archetypeSystem.addEntity(entity);
this._dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_ADDED);
}
// 发射实体创建事件
this._eventBus.emitEntityCreated({
timestamp: Date.now(),
source: 'EntityManager',
entityId: entity.id,
entityName: entity.name,
entityTag: entity.tag?.toString()
});
return entity;
}
/**
* 批量创建实体
*
* 为了优化大量实体创建的性能,批量处理索引更新和事件发射。
* 适用于需要创建大量实体的场景,如子弹、粒子等。
*
* @param count 要创建的实体数量
* @param namePrefix 实体名称前缀,默认为 Entity
* @param skipEvents 是否跳过事件发射以提升性能,默认为 false
* @returns 创建的实体数组
*
* @example
* const bullets = entityManager.createEntitiesBatch(100, "Bullet", true);
* const particles = entityManager.createEntitiesBatch(500, "Particle");
*/
public createEntitiesBatch(
count: number,
namePrefix: string = "Entity",
skipEvents: boolean = false
): Entity[] {
if (count <= 0) return [];
const entities: Entity[] = [];
// 批量分配ID和创建Entity对象
for (let i = 0; i < count; i++) {
const id = this._identifierPool.checkOut();
const name = `${namePrefix}_${id}`;
const entity = new Entity(name, id);
entities.push(entity);
this._entities.set(id, entity);
}
// 批量更新索引
for (const entity of entities) {
this.updateNameIndex(entity, true);
this.updateTagIndex(entity, true);
if (entity.components.length > 0) {
this._componentIndexManager.addEntity(entity);
this._archetypeSystem.addEntity(entity);
this._dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_ADDED);
}
}
// 批量发射事件
if (!skipEvents) {
const timestamp = Date.now();
for (const entity of entities) {
this._eventBus.emitEntityCreated({
timestamp,
source: 'EntityManager',
entityId: entity.id,
entityName: entity.name,
entityTag: entity.tag?.toString()
});
}
}
return entities;
}
/**
* 销毁实体
*
* 支持通过实体对象、名称或ID来销毁实体。
* 会清理所有相关索引并回收ID。
*
* @param entityOrId 要销毁的实体可以是实体对象、名称字符串或ID数字
* @returns 是否成功销毁实体
*
* @example
* ```typescript
* // 通过实体对象销毁
* entityManager.destroyEntity(player);
*
* // 通过名称销毁
* entityManager.destroyEntity("Enemy_1");
*
* // 通过ID销毁
* entityManager.destroyEntity(123);
* ```
*/
public destroyEntity(entityOrId: Entity | string | number): boolean {
let entity: Entity | null = null;
if (typeof entityOrId === 'string') {
entity = this.getEntityByName(entityOrId);
} else if (typeof entityOrId === 'number') {
entity = this._entities.get(entityOrId) || null;
} else {
entity = this._entities.get(entityOrId.id) || null;
}
if (!entity) {
return false;
}
this._destroyedEntities.add(entity.id);
this.updateNameIndex(entity, false);
this.updateTagIndex(entity, false);
this._componentIndexManager.removeEntity(entity);
this._archetypeSystem.removeEntity(entity);
this._dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_REMOVED);
// 发射实体销毁事件
this._eventBus.emitEntityDestroyed({
timestamp: Date.now(),
source: 'EntityManager',
entityId: entity.id,
entityName: entity.name,
entityTag: entity.tag?.toString()
});
entity.destroy();
this._entities.delete(entity.id);
this._identifierPool.checkIn(entity.id);
return true;
}
/**
* 获取所有实体
*
* 返回当前管理的所有实体的副本数组。
*
* @returns 所有实体的数组
*/
public getAllEntities(): Entity[] {
return Array.from(this._entities.values());
}
/**
* 根据ID获取实体
*
* 支持字符串和数字类型的ID。
*
* @param id 实体ID可以是字符串或数字
* @returns 对应的实体如果不存在则返回null
*/
public getEntity(id: string | number): Entity | null {
const numId = typeof id === 'string' ? parseInt(id) : id;
return this._entities.get(numId) || null;
}
/**
* 根据名称获取实体
*
* 如果存在多个同名实体,返回第一个找到的实体。
*
* @param name 实体名称
* @returns 匹配的实体如果不存在则返回null
*/
public getEntityByName(name: string): Entity | null {
const entities = this._entitiesByName.get(name);
return entities && entities.length > 0 ? entities[0] : null;
}
/**
* 根据标签获取实体列表
*
* 返回所有具有指定标签的实体。
*
* @param tag 标签值
* @returns 具有指定标签的实体数组
*/
public getEntitiesByTag(tag: number): Entity[] {
return Array.from(this._entities.values())
.filter(entity => entity.tag === tag);
}
/**
* 获取包含指定组件的所有实体
*
* 遍历所有实体,查找包含指定组件类型的实体。
*
* @param componentType 组件类型
* @returns 包含指定组件的实体数组
*
* @example
* ```typescript
* const entitiesWithHealth = entityManager.getEntitiesWithComponent(HealthComponent);
* ```
*/
public getEntitiesWithComponent<T extends Component>(componentType: ComponentType<T>): Entity[] {
const indexResult = this._componentIndexManager.query(componentType);
return Array.from(indexResult);
}
/**
* 创建查询构建器
*
* 返回一个新的查询构建器实例,用于构建复杂的实体查询。
*
* @returns 新的查询构建器实例
*
* @example
* ```typescript
* const results = entityManager.query()
* .withAll(PositionComponent, HealthComponent)
* .without(VelocityComponent)
* .active()
* .execute();
* ```
*/
public query(): EntityQueryBuilder {
return new EntityQueryBuilder(this);
}
/**
* 使用组件索引进行多组件查询
*
* @param componentTypes 组件类型数组
* @param operation 查询操作:'AND' 或 'OR'
* @returns 匹配的实体集合
*/
public queryWithComponentIndex(componentTypes: ComponentType[], operation: 'AND' | 'OR'): Set<Entity> {
return this._componentIndexManager.queryMultiple(componentTypes, operation);
}
/**
* 标记实体组件已修改
*
* @param entity 修改的实体
* @param componentTypes 修改的组件类型
*/
public markEntityDirty(entity: Entity, componentTypes: ComponentType[]): void {
this._dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_MODIFIED, componentTypes);
}
/**
* 获取性能优化统计信息
*/
public getOptimizationStats(): any {
return {
componentIndex: this._componentIndexManager.getStats(),
archetypeSystem: this._archetypeSystem.getAllArchetypes().map(a => ({
id: a.id,
componentTypes: a.componentTypes.map(t => t.name),
entityCount: a.entities.length
})),
dirtyTracking: this._dirtyTrackingSystem.getStats()
};
}
/**
* 获取事件总线实例
*
* 允许外部代码监听和发射ECS相关事件。
*
* @returns 事件总线实例
*/
public get eventBus(): EventBus {
return this._eventBus;
}
/**
* 更新名称索引
*
* 维护按名称查找实体的索引结构。支持添加和移除操作。
*
* @param entity 要更新索引的实体
* @param isAdd true表示添加到索引false表示从索引中移除
*/
private updateNameIndex(entity: Entity, isAdd: boolean): void {
if (!entity.name) {
return;
}
if (isAdd) {
let entities = this._entitiesByName.get(entity.name);
if (!entities) {
entities = [];
this._entitiesByName.set(entity.name, entities);
}
entities.push(entity);
} else {
const entities = this._entitiesByName.get(entity.name);
if (entities) {
const index = entities.indexOf(entity);
if (index !== -1) {
entities.splice(index, 1);
if (entities.length === 0) {
this._entitiesByName.delete(entity.name);
}
}
}
}
}
/**
* 更新标签索引
*
* 维护按标签查找实体的索引结构。支持添加和移除操作。
*
* @param entity 要更新索引的实体
* @param isAdd true表示添加到索引false表示从索引中移除
*/
private updateTagIndex(entity: Entity, isAdd: boolean): void {
if (isAdd) {
let entities = this._entitiesByTag.get(entity.tag);
if (!entities) {
entities = [];
this._entitiesByTag.set(entity.tag, entities);
}
entities.push(entity);
} else {
const entities = this._entitiesByTag.get(entity.tag);
if (entities) {
const index = entities.indexOf(entity);
if (index !== -1) {
entities.splice(index, 1);
if (entities.length === 0) {
this._entitiesByTag.delete(entity.tag);
}
}
}
}
}
}

View File

@@ -41,36 +41,36 @@ export class EventBus implements IEventBus {
* 发射事件
* @param eventType 事件类型
* @param data 事件数据
* @param enhance 是否增强事件数据添加timestamp、eventId等默认false提升性能
*/
public emit<T>(eventType: string, data: T): void {
public emit<T>(eventType: string, data: T, enhance: boolean = false): void {
this.validateEventType(eventType);
// 增强事件数据
const enhancedData = this.enhanceEventData(eventType, data);
const finalData = enhance ? this.enhanceEventData(eventType, data) : data;
if (this.isDebugMode) {
EventBus._logger.info(`发射事件: ${eventType}`, enhancedData);
EventBus._logger.info(`发射事件: ${eventType}`, finalData);
}
this.eventSystem.emitSync(eventType, enhancedData);
this.eventSystem.emitSync(eventType, finalData);
}
/**
* 异步发射事件
* @param eventType 事件类型
* @param data 事件数据
* @param enhance 是否增强事件数据添加timestamp、eventId等默认false提升性能
*/
public async emitAsync<T>(eventType: string, data: T): Promise<void> {
public async emitAsync<T>(eventType: string, data: T, enhance: boolean = false): Promise<void> {
this.validateEventType(eventType);
// 增强事件数据
const enhancedData = this.enhanceEventData(eventType, data);
const finalData = enhance ? this.enhanceEventData(eventType, data) : data;
if (this.isDebugMode) {
EventBus._logger.info(`发射异步事件: ${eventType}`, enhancedData);
EventBus._logger.info(`发射异步事件: ${eventType}`, finalData);
}
await this.eventSystem.emit(eventType, enhancedData);
await this.eventSystem.emit(eventType, finalData);
}
/**
@@ -265,7 +265,7 @@ export class EventBus implements IEventBus {
public emitEntityCreated(entityData: IEntityEventData): void {
this.emit(ECSEventType.ENTITY_CREATED, entityData);
}
/**
* 发射实体销毁事件
* @param entityData 实体事件数据
@@ -273,7 +273,7 @@ export class EventBus implements IEventBus {
public emitEntityDestroyed(entityData: IEntityEventData): void {
this.emit(ECSEventType.ENTITY_DESTROYED, entityData);
}
/**
* 发射组件添加事件
* @param componentData 组件事件数据
@@ -281,7 +281,7 @@ export class EventBus implements IEventBus {
public emitComponentAdded(componentData: IComponentEventData): void {
this.emit(ECSEventType.COMPONENT_ADDED, componentData);
}
/**
* 发射组件移除事件
* @param componentData 组件事件数据
@@ -289,7 +289,7 @@ export class EventBus implements IEventBus {
public emitComponentRemoved(componentData: IComponentEventData): void {
this.emit(ECSEventType.COMPONENT_REMOVED, componentData);
}
/**
* 发射系统添加事件
* @param systemData 系统事件数据
@@ -297,7 +297,7 @@ export class EventBus implements IEventBus {
public emitSystemAdded(systemData: ISystemEventData): void {
this.emit(ECSEventType.SYSTEM_ADDED, systemData);
}
/**
* 发射系统移除事件
* @param systemData 系统事件数据
@@ -305,7 +305,7 @@ export class EventBus implements IEventBus {
public emitSystemRemoved(systemData: ISystemEventData): void {
this.emit(ECSEventType.SYSTEM_REMOVED, systemData);
}
/**
* 发射场景变化事件
* @param sceneData 场景事件数据
@@ -313,7 +313,7 @@ export class EventBus implements IEventBus {
public emitSceneChanged(sceneData: ISceneEventData): void {
this.emit(ECSEventType.SCENE_ACTIVATED, sceneData);
}
/**
* 发射性能警告事件
* @param performanceData 性能事件数据
@@ -375,16 +375,15 @@ export class EventBus implements IEventBus {
// 私有方法
/**
* 验证事件类型
* 验证事件类型仅在debug模式下执行提升性能
* @param eventType 事件类型
*/
private validateEventType(eventType: string): void {
if (!EventTypeValidator.isValid(eventType)) {
if (this.isDebugMode) {
// 只在debug模式下进行验证提升生产环境性能
if (this.isDebugMode) {
if (!EventTypeValidator.isValid(eventType)) {
EventBus._logger.warn(`未知事件类型: ${eventType}`);
}
// 在调试模式下添加自定义事件类型
if (this.isDebugMode) {
// 在调试模式下添加自定义事件类型
EventTypeValidator.addCustomType(eventType);
}
}
@@ -468,41 +467,4 @@ export class GlobalEventBus {
}
}
/**
* 事件装饰器工厂
* 用于自动注册事件监听器
*/
export function EventHandler(eventType: string, config: IEventListenerConfig = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
// 在类实例化时自动注册监听器
const initMethod = target.constructor.prototype.initEventListeners || function() {};
target.constructor.prototype.initEventListeners = function() {
initMethod.call(this);
const eventBus = GlobalEventBus.getInstance();
eventBus.on(eventType, originalMethod.bind(this), config);
};
return descriptor;
};
}
/**
* 异步事件装饰器工厂
* 用于自动注册异步事件监听器
*/
export function AsyncEventHandler(eventType: string, config: IEventListenerConfig = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const initMethod = target.constructor.prototype.initEventListeners || function() {};
target.constructor.prototype.initEventListeners = function() {
initMethod.call(this);
const eventBus = GlobalEventBus.getInstance();
eventBus.onAsync(eventType, originalMethod.bind(this), config);
};
return descriptor;
};
}

View File

@@ -578,37 +578,4 @@ export class TypeSafeEventSystem {
*/
export const GlobalEventSystem = new TypeSafeEventSystem();
/**
* 事件装饰器 - 用于自动注册事件监听器
* @param eventType 事件类型
* @param config 监听器配置
*/
export function EventListener(eventType: string, config: EventListenerConfig = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
// 在类实例化时自动注册监听器
const initMethod = target.constructor.prototype.initEventListeners || function () { };
target.constructor.prototype.initEventListeners = function () {
initMethod.call(this);
GlobalEventSystem.on(eventType, originalMethod.bind(this), config);
};
};
}
/**
* 异步事件装饰器
* @param eventType 事件类型
* @param config 监听器配置
*/
export function AsyncEventListener(eventType: string, config: EventListenerConfig = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const initMethod = target.constructor.prototype.initEventListeners || function () { };
target.constructor.prototype.initEventListeners = function () {
initMethod.call(this);
GlobalEventSystem.onAsync(eventType, originalMethod.bind(this), config);
};
};
}

View File

@@ -1,2 +1,2 @@
export { EventBus, GlobalEventBus, EventHandler, AsyncEventHandler } from '../EventBus';
export { EventBus, GlobalEventBus } from '../EventBus';
export { TypeSafeEventSystem, EventListenerConfig, EventStats } from '../EventSystem';

View File

@@ -62,11 +62,11 @@ export class ECSFluentAPI {
}
/**
* 查找实体(简化版)
* 查找实体
* @param componentTypes 组件类型
* @returns 实体数组
*/
public find(...componentTypes: ComponentType[]): Entity[] {
public find(...componentTypes: ComponentType[]): readonly Entity[] {
return this.querySystem.queryAll(...componentTypes).entities;
}

View File

@@ -14,7 +14,9 @@ export class EntityBuilder {
constructor(scene: IScene, storageManager: ComponentStorageManager) {
this.scene = scene;
this.storageManager = storageManager;
this.entity = new Entity("", scene.identifierPool.checkOut());
const id = scene.identifierPool.checkOut();
this.entity = new Entity("", id);
this.entity.scene = this.scene as any;
}
/**

View File

@@ -1,21 +0,0 @@
export {
ComponentIndexManager,
ComponentIndex,
IComponentIndex,
IndexStats
} from '../ComponentIndex';
export {
ArchetypeSystem,
Archetype,
ArchetypeQueryResult
} from '../ArchetypeSystem';
export {
DirtyTrackingSystem,
DirtyFlag,
DirtyData,
DirtyListener
} from '../DirtyTrackingSystem';

View File

@@ -0,0 +1,404 @@
/**
* 类型安全的Query查询系统
*
* 提供完整的TypeScript类型推断在编译时确保类型安全
*/
import type { Entity } from '../../Entity';
import type { ComponentConstructor, ComponentInstance, ComponentTypeMap } from '../../../Types/TypeHelpers';
import { Matcher, type QueryCondition } from '../../Utils/Matcher';
/**
* 类型安全的查询结果
*
* 根据查询条件自动推断实体必定拥有的组件类型
*/
export class TypedQueryResult<TAll extends readonly ComponentConstructor[]> {
private _entities: readonly Entity[];
private _componentTypes: TAll;
constructor(entities: readonly Entity[], componentTypes: TAll) {
this._entities = entities;
this._componentTypes = componentTypes;
}
/**
* 获取实体列表
*/
get entities(): readonly Entity[] {
return this._entities;
}
/**
* 实体数量
*/
get length(): number {
return this._entities.length;
}
/**
* 遍历所有实体
*
* @example
* ```typescript
* query.forEach((entity) => {
* // entity.getComponent返回类型自动推断
* const pos = entity.getComponent(Position); // Position类型
* const vel = entity.getComponent(Velocity); // Velocity类型
* });
* ```
*/
forEach(callback: (entity: Entity, index: number) => void): void {
this._entities.forEach(callback);
}
/**
* 映射转换实体
*/
map<R>(callback: (entity: Entity, index: number) => R): R[] {
return this._entities.map(callback);
}
/**
* 过滤实体
*/
filter(predicate: (entity: Entity, index: number) => boolean): TypedQueryResult<TAll> {
return new TypedQueryResult(this._entities.filter(predicate), this._componentTypes);
}
/**
* 查找第一个匹配的实体
*/
find(predicate: (entity: Entity, index: number) => boolean): Entity | undefined {
return this._entities.find(predicate);
}
/**
* 检查是否存在匹配的实体
*/
some(predicate: (entity: Entity, index: number) => boolean): boolean {
return this._entities.some(predicate);
}
/**
* 检查是否所有实体都匹配
*/
every(predicate: (entity: Entity, index: number) => boolean): boolean {
return this._entities.every(predicate);
}
/**
* 获取指定索引的实体
*/
get(index: number): Entity | undefined {
return this._entities[index];
}
/**
* 获取第一个实体
*/
get first(): Entity | undefined {
return this._entities[0];
}
/**
* 获取最后一个实体
*/
get last(): Entity | undefined {
return this._entities[this._entities.length - 1];
}
/**
* 检查查询结果是否为空
*/
get isEmpty(): boolean {
return this._entities.length === 0;
}
/**
* 转换为数组
*/
toArray(): Entity[] {
return [...this._entities];
}
/**
* 获取组件类型信息(用于调试)
*/
getComponentTypes(): readonly ComponentConstructor[] {
return this._componentTypes;
}
/**
* 迭代器支持
*/
[Symbol.iterator](): Iterator<Entity> {
return this._entities[Symbol.iterator]();
}
}
/**
* 类型安全的查询构建器
*
* 支持链式调用,自动推断查询结果的类型
*
* @example
* ```typescript
* // 基础查询
* const query = new TypedQueryBuilder()
* .withAll(Position, Velocity)
* .build();
*
* // 复杂查询
* const complexQuery = new TypedQueryBuilder()
* .withAll(Transform, Renderer)
* .withAny(BoxCollider, CircleCollider)
* .withNone(Disabled)
* .withTag(EntityTags.Enemy)
* .build();
* ```
*/
export class TypedQueryBuilder<
TAll extends readonly ComponentConstructor[] = [],
TAny extends readonly ComponentConstructor[] = [],
TNone extends readonly ComponentConstructor[] = []
> {
private _all: TAll;
private _any: TAny;
private _none: TNone;
private _tag?: number;
private _name?: string;
constructor(
all?: TAll,
any?: TAny,
none?: TNone,
tag?: number,
name?: string
) {
this._all = (all || []) as TAll;
this._any = (any || []) as TAny;
this._none = (none || []) as TNone;
this._tag = tag;
this._name = name;
}
/**
* 要求实体拥有所有指定的组件
*
* @param types 组件类型
* @returns 新的查询构建器,类型参数更新
*/
withAll<TNewAll extends readonly ComponentConstructor[]>(
...types: TNewAll
): TypedQueryBuilder<
readonly [...TAll, ...TNewAll],
TAny,
TNone
> {
return new TypedQueryBuilder(
[...this._all, ...types] as readonly [...TAll, ...TNewAll],
this._any,
this._none,
this._tag,
this._name
);
}
/**
* 要求实体至少拥有一个指定的组件
*
* @param types 组件类型
* @returns 新的查询构建器
*/
withAny<TNewAny extends readonly ComponentConstructor[]>(
...types: TNewAny
): TypedQueryBuilder<
TAll,
readonly [...TAny, ...TNewAny],
TNone
> {
return new TypedQueryBuilder(
this._all,
[...this._any, ...types] as readonly [...TAny, ...TNewAny],
this._none,
this._tag,
this._name
);
}
/**
* 排除拥有指定组件的实体
*
* @param types 组件类型
* @returns 新的查询构建器
*/
withNone<TNewNone extends readonly ComponentConstructor[]>(
...types: TNewNone
): TypedQueryBuilder<
TAll,
TAny,
readonly [...TNone, ...TNewNone]
> {
return new TypedQueryBuilder(
this._all,
this._any,
[...this._none, ...types] as readonly [...TNone, ...TNewNone],
this._tag,
this._name
);
}
/**
* 按标签过滤实体
*
* @param tag 标签值
* @returns 新的查询构建器
*/
withTag(tag: number): TypedQueryBuilder<TAll, TAny, TNone> {
return new TypedQueryBuilder(
this._all,
this._any,
this._none,
tag,
this._name
);
}
/**
* 按名称过滤实体
*
* @param name 实体名称
* @returns 新的查询构建器
*/
withName(name: string): TypedQueryBuilder<TAll, TAny, TNone> {
return new TypedQueryBuilder(
this._all,
this._any,
this._none,
this._tag,
name
);
}
/**
* 构建Matcher对象
*
* @returns Matcher实例用于传统查询API
*/
buildMatcher(): Matcher {
let matcher = Matcher.complex();
if (this._all.length > 0) {
matcher = matcher.all(...(this._all as unknown as ComponentConstructor[]));
}
if (this._any.length > 0) {
matcher = matcher.any(...(this._any as unknown as ComponentConstructor[]));
}
if (this._none.length > 0) {
matcher = matcher.none(...(this._none as unknown as ComponentConstructor[]));
}
if (this._tag !== undefined) {
matcher = matcher.withTag(this._tag);
}
if (this._name !== undefined) {
matcher = matcher.withName(this._name);
}
return matcher;
}
/**
* 获取查询条件
*
* @returns 查询条件对象
*/
getCondition(): QueryCondition {
return {
all: [...this._all] as ComponentConstructor[],
any: [...this._any] as ComponentConstructor[],
none: [...this._none] as ComponentConstructor[],
tag: this._tag,
name: this._name
};
}
/**
* 获取required组件类型用于类型推断
*/
getRequiredTypes(): TAll {
return this._all;
}
/**
* 克隆查询构建器
*/
clone(): TypedQueryBuilder<TAll, TAny, TNone> {
return new TypedQueryBuilder(
[...this._all] as unknown as TAll,
[...this._any] as unknown as TAny,
[...this._none] as unknown as TNone,
this._tag,
this._name
);
}
}
/**
* 创建类型安全的查询构建器
*
* @example
* ```typescript
* const query = createQuery()
* .withAll(Position, Velocity)
* .withNone(Disabled);
*
* // 在System或Scene中使用
* const entities = scene.query(query);
* entities.forEach(entity => {
* const pos = entity.getComponent(Position); // 自动推断为Position
* const vel = entity.getComponent(Velocity); // 自动推断为Velocity
* });
* ```
*/
export function createQuery(): TypedQueryBuilder<[], [], []> {
return new TypedQueryBuilder();
}
/**
* 创建单组件查询的便捷方法
*
* @param componentType 组件类型
* @returns 查询构建器
*
* @example
* ```typescript
* const healthEntities = queryFor(HealthComponent);
* ```
*/
export function queryFor<T extends ComponentConstructor>(
componentType: T
): TypedQueryBuilder<readonly [T], [], []> {
return new TypedQueryBuilder([componentType] as readonly [T]);
}
/**
* 创建多组件查询的便捷方法
*
* @param types 组件类型数组
* @returns 查询构建器
*
* @example
* ```typescript
* const movableEntities = queryForAll(Position, Velocity);
* ```
*/
export function queryForAll<T extends readonly ComponentConstructor[]>(
...types: T
): TypedQueryBuilder<T, [], []> {
return new TypedQueryBuilder(types);
}

View File

@@ -1,3 +1,3 @@
export { QuerySystem } from '../QuerySystem';
export { ECSFluentAPI, createECSAPI } from '../FluentAPI';
export { EntityManager, EntityQueryBuilder } from '../EntityManager';

View File

@@ -4,11 +4,8 @@ import { ComponentRegistry, ComponentType } from './ComponentStorage';
import { BitMask64Utils, BitMask64Data } from '../Utils/BigIntCompatibility';
import { createLogger } from '../../Utils/Logger';
import { getComponentTypeName } from '../Decorators';
import { ComponentPoolManager } from './ComponentPool';
import { ComponentIndexManager } from './ComponentIndex';
import { ArchetypeSystem, Archetype, ArchetypeQueryResult } from './ArchetypeSystem';
import { DirtyTrackingSystem, DirtyFlag } from './DirtyTrackingSystem';
import { Archetype, ArchetypeSystem } from './ArchetypeSystem';
import { ComponentTypeManager } from "../Utils";
/**
* 查询条件类型
@@ -35,7 +32,7 @@ export interface QueryCondition {
* 实体查询结果接口
*/
export interface QueryResult {
entities: Entity[];
entities: readonly Entity[];
count: number;
/** 查询执行时间(毫秒) */
executionTime: number;
@@ -47,8 +44,6 @@ export interface QueryResult {
* 实体索引结构
*/
interface EntityIndex {
byMask: Map<string, Set<Entity>>;
byComponentType: Map<ComponentType, Set<Entity>>;
byTag: Map<number, Set<Entity>>;
byName: Map<string, Set<Entity>>;
}
@@ -57,22 +52,16 @@ interface EntityIndex {
* 查询缓存条目
*/
interface QueryCacheEntry {
entities: Entity[];
entities: readonly Entity[];
timestamp: number;
hitCount: number;
version: number;
}
/**
* 高性能实体查询系统
*
* 提供快速的实体查询功能,支持按组件类型、标签、名称等多种方式查询实体。
* 系统采用多级索引和智能缓存机制,确保在大量实体场景下的查询性能。
*
* 主要特性:
* - 支持单组件和多组件查询
* - 自动索引管理和缓存优化
* - WebAssembly计算加速如果可用
* - 详细的性能统计信息
*
* @example
* ```typescript
@@ -87,25 +76,17 @@ export class QuerySystem {
private _logger = createLogger('QuerySystem');
private entities: Entity[] = [];
private entityIndex: EntityIndex;
private indexDirty = true;
// 版本号,用于缓存失效
private _version = 0;
// 查询缓存系统
private queryCache = new Map<string, QueryCacheEntry>();
private cacheMaxSize = 1000;
private cacheTimeout = 5000; // 5秒缓存过期
private cacheTimeout = 5000;
// 优化组件
private componentPoolManager: ComponentPoolManager;
private componentMaskCache = new Map<string, BitMask64Data>();
// 新增性能优化系统
private componentIndexManager: ComponentIndexManager;
private archetypeSystem: ArchetypeSystem;
private dirtyTrackingSystem: DirtyTrackingSystem;
// 性能统计
private queryStats = {
totalQueries: 0,
cacheHits: 0,
@@ -115,23 +96,31 @@ export class QuerySystem {
dirtyChecks: 0
};
private resultArrayPool: Entity[][] = [];
private poolMaxSize = 50;
constructor() {
this.entityIndex = {
byMask: new Map(),
byComponentType: new Map(),
byTag: new Map(),
byName: new Map()
};
// 初始化优化组件
this.componentPoolManager = ComponentPoolManager.getInstance();
// 初始化新的性能优化系统
this.componentIndexManager = new ComponentIndexManager();
this.archetypeSystem = new ArchetypeSystem();
this.dirtyTrackingSystem = new DirtyTrackingSystem();
}
private acquireResultArray(): Entity[] {
if (this.resultArrayPool.length > 0) {
return this.resultArrayPool.pop()!;
}
return [];
}
private releaseResultArray(array: Entity[]): void {
if (this.resultArrayPool.length < this.poolMaxSize) {
array.length = 0;
this.resultArrayPool.push(array);
}
}
/**
* 设置实体列表并重建索引
@@ -161,9 +150,7 @@ export class QuerySystem {
this.entities.push(entity);
this.addEntityToIndexes(entity);
this.componentIndexManager.addEntity(entity);
this.archetypeSystem.addEntity(entity);
this.dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_ADDED);
// 只有在非延迟模式下才立即清理缓存
@@ -195,6 +182,10 @@ export class QuerySystem {
if (!existingIds.has(entity.id)) {
this.entities.push(entity);
this.addEntityToIndexes(entity);
// 更新索引管理器
this.archetypeSystem.addEntity(entity);
existingIds.add(entity.id);
addedCount++;
}
@@ -225,6 +216,9 @@ export class QuerySystem {
// 批量更新索引
for (const entity of entities) {
this.addEntityToIndexes(entity);
// 更新索引管理器
this.archetypeSystem.addEntity(entity);
}
// 清理缓存
@@ -233,9 +227,9 @@ export class QuerySystem {
/**
* 从查询系统移除实体
*
*
* 从查询系统中移除指定实体,并清理相关索引。
*
*
* @param entity 要移除的实体
*/
public removeEntity(entity: Entity): void {
@@ -244,95 +238,81 @@ export class QuerySystem {
this.entities.splice(index, 1);
this.removeEntityFromIndexes(entity);
this.componentIndexManager.removeEntity(entity);
this.archetypeSystem.removeEntity(entity);
this.dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_REMOVED);
this.clearQueryCache();
// 更新版本号
this._version++;
}
}
/**
* 将实体添加到各种索引中(优化版本)
* 更新实体在查询系统中的索引
*
* 当实体的组件组合发生变化时调用此方法,高效地更新实体在查询系统中的索引。
*
* @param entity 要更新的实体
*/
public updateEntity(entity: Entity): void {
// 检查实体是否在查询系统中
if (!this.entities.includes(entity)) {
// 如果实体不在系统中,直接添加
this.addEntity(entity);
return;
}
// 先从索引中移除实体的旧状态
this.removeEntityFromIndexes(entity);
// 更新ArchetypeSystem中的实体状态
this.archetypeSystem.updateEntity(entity);
// 重新添加实体到索引(基于新的组件状态)
this.addEntityToIndexes(entity);
// 清理查询缓存,因为实体组件状态已改变
this.clearQueryCache();
// 更新版本号以使缓存失效
this._version++;
}
/**
* 将实体添加到各种索引中
*/
private addEntityToIndexes(entity: Entity): void {
const mask = entity.componentMask;
// 组件掩码索引 - 优化Map操作
const maskKey = mask.toString();
let maskSet = this.entityIndex.byMask.get(maskKey);
if (!maskSet) {
maskSet = new Set();
this.entityIndex.byMask.set(maskKey, maskSet);
}
maskSet.add(entity);
// 组件类型索引 - 批量处理
const components = entity.components;
for (let i = 0; i < components.length; i++) {
const componentType = components[i].constructor as ComponentType;
let typeSet = this.entityIndex.byComponentType.get(componentType);
if (!typeSet) {
typeSet = new Set();
this.entityIndex.byComponentType.set(componentType, typeSet);
}
typeSet.add(entity);
}
// 标签索引 - 只在有标签时处理
// 标签索引
const tag = entity.tag;
if (tag !== undefined) {
let tagSet = this.entityIndex.byTag.get(tag);
if (!tagSet) {
tagSet = new Set();
this.entityIndex.byTag.set(tag, tagSet);
}
const tagSet = this.entityIndex.byTag.get(tag) || this.createAndSetTagIndex(tag);
tagSet.add(entity);
}
// 名称索引 - 只在有名称时处理
// 名称索引
const name = entity.name;
if (name) {
let nameSet = this.entityIndex.byName.get(name);
if (!nameSet) {
nameSet = new Set();
this.entityIndex.byName.set(name, nameSet);
}
const nameSet = this.entityIndex.byName.get(name) || this.createAndSetNameIndex(name);
nameSet.add(entity);
}
}
private createAndSetTagIndex(tag: number): Set<Entity> {
const set = new Set<Entity>();
this.entityIndex.byTag.set(tag, set);
return set;
}
private createAndSetNameIndex(name: string): Set<Entity> {
const set = new Set<Entity>();
this.entityIndex.byName.set(name, set);
return set;
}
/**
* 从各种索引中移除实体
*/
private removeEntityFromIndexes(entity: Entity): void {
const mask = entity.componentMask;
// 从组件掩码索引移除
const maskKey = mask.toString();
const maskSet = this.entityIndex.byMask.get(maskKey);
if (maskSet) {
maskSet.delete(entity);
if (maskSet.size === 0) {
this.entityIndex.byMask.delete(maskKey);
}
}
// 从组件类型索引移除
for (const component of entity.components) {
const componentType = component.constructor as ComponentType;
const typeSet = this.entityIndex.byComponentType.get(componentType);
if (typeSet) {
typeSet.delete(entity);
if (typeSet.size === 0) {
this.entityIndex.byComponentType.delete(componentType);
}
}
}
// 从标签索引移除
if (entity.tag !== undefined) {
const tagSet = this.entityIndex.byTag.get(entity.tag);
@@ -358,27 +338,21 @@ export class QuerySystem {
/**
* 重建所有索引
*
*
* 清空并重新构建所有查询索引。
* 通常在大量实体变更后调用以确保索引一致性。
*/
private rebuildIndexes(): void {
this.entityIndex.byMask.clear();
this.entityIndex.byComponentType.clear();
this.entityIndex.byTag.clear();
this.entityIndex.byName.clear();
// 清理ArchetypeSystem和ComponentIndexManager
this.archetypeSystem.clear();
this.componentIndexManager.clear();
for (const entity of this.entities) {
this.addEntityToIndexes(entity);
this.componentIndexManager.addEntity(entity);
this.archetypeSystem.addEntity(entity);
}
this.indexDirty = false;
}
/**
@@ -402,7 +376,7 @@ export class QuerySystem {
this.queryStats.totalQueries++;
// 生成缓存键
const cacheKey = `all:${componentTypes.map(t => getComponentTypeName(t)).sort().join(',')}`;
const cacheKey = this.generateCacheKey('all', componentTypes);
// 检查缓存
const cached = this.getFromCache(cacheKey);
@@ -416,26 +390,13 @@ export class QuerySystem {
};
}
let entities: Entity[] = [];
this.queryStats.archetypeHits++;
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND');
if (archetypeResult.archetypes.length > 0) {
this.queryStats.archetypeHits++;
for (const archetype of archetypeResult.archetypes) {
entities.push(...archetype.entities);
}
} else {
try {
if (componentTypes.length === 1) {
this.queryStats.indexHits++;
const indexResult = this.componentIndexManager.query(componentTypes[0]);
entities = Array.from(indexResult);
} else {
const indexResult = this.componentIndexManager.queryMultiple(componentTypes, 'AND');
entities = Array.from(indexResult);
}
} catch (error) {
entities = [];
const entities: Entity[] = [];
for (const archetype of archetypeResult.archetypes) {
for (const entity of archetype.entities) {
entities.push(entity);
}
}
@@ -459,31 +420,11 @@ export class QuerySystem {
* @returns 匹配的实体列表
*/
private queryMultipleComponents(componentTypes: ComponentType[]): Entity[] {
// 找到最小的组件集合作为起点
let smallestSet: Set<Entity> | null = null;
let smallestSize = Infinity;
for (const componentType of componentTypes) {
const set = this.entityIndex.byComponentType.get(componentType);
if (!set || set.size === 0) {
return []; // 如果任何组件没有实体,直接返回空结果
}
if (set.size < smallestSize) {
smallestSize = set.size;
smallestSet = set;
}
}
if (!smallestSet) {
return []; // 如果没有找到任何组件集合,返回空结果
}
// 从最小集合开始,逐步过滤
const mask = this.createComponentMask(componentTypes);
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND');
const result: Entity[] = [];
for (const entity of smallestSet) {
if (BitMask64Utils.hasAll(entity.componentMask, mask)) {
for (const archetype of archetypeResult.archetypes) {
for (const entity of archetype.entities) {
result.push(entity);
}
}
@@ -513,7 +454,7 @@ export class QuerySystem {
const startTime = performance.now();
this.queryStats.totalQueries++;
const cacheKey = `any:${componentTypes.map(t => getComponentTypeName(t)).sort().join(',')}`;
const cacheKey = this.generateCacheKey('any', componentTypes);
// 检查缓存
const cached = this.getFromCache(cacheKey);
@@ -527,25 +468,24 @@ export class QuerySystem {
};
}
this.queryStats.archetypeHits++;
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'OR');
let entities: Entity[];
if (archetypeResult.archetypes.length > 0) {
this.queryStats.archetypeHits++;
entities = [];
for (const archetype of archetypeResult.archetypes) {
entities.push(...archetype.entities);
const entities = this.acquireResultArray();
for (const archetype of archetypeResult.archetypes) {
for (const entity of archetype.entities) {
entities.push(entity);
}
} else {
const indexResult = this.componentIndexManager.queryMultiple(componentTypes, 'OR');
entities = Array.from(indexResult);
}
this.addToCache(cacheKey, entities);
const frozenEntities = [...entities];
this.releaseResultArray(entities);
this.addToCache(cacheKey, frozenEntities);
return {
entities,
count: entities.length,
entities: frozenEntities,
count: frozenEntities.length,
executionTime: performance.now() - startTime,
fromCache: false
};
@@ -571,7 +511,7 @@ export class QuerySystem {
const startTime = performance.now();
this.queryStats.totalQueries++;
const cacheKey = `none:${componentTypes.map(t => getComponentTypeName(t)).sort().join(',')}`;
const cacheKey = this.generateCacheKey('none', componentTypes);
// 检查缓存
const cached = this.getFromCache(cacheKey);
@@ -715,7 +655,7 @@ export class QuerySystem {
const startTime = performance.now();
this.queryStats.totalQueries++;
const cacheKey = `component:${getComponentTypeName(componentType)}`;
const cacheKey = this.generateCacheKey('component', [componentType]);
// 检查缓存
const cached = this.getFromCache(cacheKey);
@@ -729,9 +669,8 @@ export class QuerySystem {
};
}
// 使用索引查询
this.queryStats.indexHits++;
const entities = Array.from(this.entityIndex.byComponentType.get(componentType) || []);
const entities = this.archetypeSystem.getEntitiesByComponent(componentType);
// 缓存结果
this.addToCache(cacheKey, entities);
@@ -747,12 +686,12 @@ export class QuerySystem {
/**
* 从缓存获取查询结果
*/
private getFromCache(cacheKey: string): Entity[] | null {
private getFromCache(cacheKey: string): readonly Entity[] | null {
const entry = this.queryCache.get(cacheKey);
if (!entry) return null;
// 检查缓存是否过期
if (Date.now() - entry.timestamp > this.cacheTimeout) {
// 检查缓存是否过期或版本过期
if (Date.now() - entry.timestamp > this.cacheTimeout || entry.version !== this._version) {
this.queryCache.delete(cacheKey);
return null;
}
@@ -771,9 +710,10 @@ export class QuerySystem {
}
this.queryCache.set(cacheKey, {
entities: [...entities], // 复制数组避免引用问题
entities: entities, // 直接使用引用,通过版本号控制失效
timestamp: Date.now(),
hitCount: 0
hitCount: 0,
version: this._version
});
}
@@ -791,12 +731,22 @@ export class QuerySystem {
// 如果还是太满,移除最少使用的条目
if (this.queryCache.size >= this.cacheMaxSize) {
const entries = Array.from(this.queryCache.entries());
entries.sort((a, b) => a[1].hitCount - b[1].hitCount);
let minHitCount = Infinity;
let oldestKey = '';
let oldestTimestamp = Infinity;
const toRemove = Math.floor(this.cacheMaxSize * 0.2); // 移除20%
for (let i = 0; i < toRemove && i < entries.length; i++) {
this.queryCache.delete(entries[i][0]);
// 单次遍历找到最少使用或最旧的条目
for (const [key, entry] of this.queryCache.entries()) {
if (entry.hitCount < minHitCount ||
(entry.hitCount === minHitCount && entry.timestamp < oldestTimestamp)) {
minHitCount = entry.hitCount;
oldestKey = key;
oldestTimestamp = entry.timestamp;
}
}
if (oldestKey) {
this.queryCache.delete(oldestKey);
}
}
}
@@ -806,10 +756,32 @@ export class QuerySystem {
*/
private clearQueryCache(): void {
this.queryCache.clear();
this.componentMaskCache.clear();
}
/**
* 公共方法:清理查询缓存
* 高效的缓存键生成
*/
private generateCacheKey(prefix: string, componentTypes: ComponentType[]): string {
// 快速路径:单组件查询
if (componentTypes.length === 1) {
const name = getComponentTypeName(componentTypes[0]);
return `${prefix}:${name}`;
}
// 多组件查询:使用排序后的类型名称创建键
const sortKey = componentTypes.map(t => {
const name = getComponentTypeName(t);
return name;
}).sort().join(',');
const fullKey = `${prefix}:${sortKey}`;
return fullKey;
}
/**
* 清理查询缓存
*
* 用于外部调用清理缓存,通常在批量操作后使用。
*/
@@ -817,81 +789,31 @@ export class QuerySystem {
this.clearQueryCache();
}
/**
* 批量更新实体组件
*
* 对大量实体进行批量组件更新操作。
*
* @param updates 更新操作列表包含实体ID和新的组件掩码
*
* @example
* ```typescript
* // 批量更新实体的组件配置
* const updates = [
* { entityId: 1, componentMask: BigInt(0b1011) },
* { entityId: 2, componentMask: BigInt(0b1101) }
* ];
* querySystem.batchUpdateComponents(updates);
* ```
*/
public batchUpdateComponents(updates: Array<{ entityId: number, componentMask: bigint }>): void {
// 批量处理更新,先从索引中移除,再重新添加
const entitiesToUpdate: Entity[] = [];
for (const update of updates) {
const entity = this.entities.find(e => e.id === update.entityId);
if (entity) {
// 先从所有索引中移除
this.removeEntityFromIndexes(entity);
entitiesToUpdate.push(entity);
}
}
// 重新添加到索引中(此时实体的组件掩码已经更新)
for (const entity of entitiesToUpdate) {
this.addEntityToIndexes(entity);
}
// 标记脏实体进行处理
for (const entity of entitiesToUpdate) {
this.dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_MODIFIED, []);
}
// 批量更新后清除缓存
this.clearQueryCache();
}
/**
* 创建组件掩码
*
*
* 根据组件类型列表生成对应的位掩码。
* 使用位掩码优化器进行缓存和预计算。
*
* 使用缓存避免重复计算。
*
* @param componentTypes 组件类型列表
* @returns 生成的位掩码
*/
private createComponentMask(componentTypes: ComponentType[]): BitMask64Data {
let mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
let hasValidComponents = false;
for (const type of componentTypes) {
try {
const bitMask = ComponentRegistry.getBitMask(type);
BitMask64Utils.orInPlace(mask, bitMask);
hasValidComponents = true;
} catch (error) {
this._logger.warn(`组件类型 ${getComponentTypeName(type)} 未注册,跳过`);
}
// 生成缓存键
const cacheKey = componentTypes.map(t => {
return getComponentTypeName(t);
}).sort().join(',');
// 检查缓存
const cached = this.componentMaskCache.get(cacheKey);
if (cached) {
return cached;
}
// 如果没有有效的组件类型,返回一个不可能匹配的掩码
if (!hasValidComponents) {
return { lo: 0xFFFFFFFF, hi: 0xFFFFFFFF }; // 所有位都是1不可能与任何实体匹配
}
return mask;
let mask = ComponentTypeManager.instance.getEntityBits(componentTypes);
// 缓存结果
this.componentMaskCache.set(cacheKey, mask.getValue());
return mask.getValue();
}
/**
@@ -904,8 +826,8 @@ export class QuerySystem {
/**
* 获取所有实体
*/
public getAllEntities(): Entity[] {
return [...this.entities];
public getAllEntities(): readonly Entity[] {
return this.entities;
}
/**
@@ -919,7 +841,6 @@ export class QuerySystem {
public getStats(): {
entityCount: number;
indexStats: {
maskIndexSize: number;
componentIndexSize: number;
tagIndexSize: number;
nameIndexSize: number;
@@ -934,9 +855,7 @@ export class QuerySystem {
cacheHitRate: string;
};
optimizationStats: {
componentIndex: any;
archetypeSystem: any;
dirtyTracking: any;
};
cacheStats: {
size: number;
@@ -946,8 +865,7 @@ export class QuerySystem {
return {
entityCount: this.entities.length,
indexStats: {
maskIndexSize: this.entityIndex.byMask.size,
componentIndexSize: this.entityIndex.byComponentType.size,
componentIndexSize: this.archetypeSystem.getAllArchetypes().length,
tagIndexSize: this.entityIndex.byTag.size,
nameIndexSize: this.entityIndex.byName.size
},
@@ -957,13 +875,11 @@ export class QuerySystem {
(this.queryStats.cacheHits / this.queryStats.totalQueries * 100).toFixed(2) + '%' : '0%'
},
optimizationStats: {
componentIndex: this.componentIndexManager.getStats(),
archetypeSystem: this.archetypeSystem.getAllArchetypes().map(a => ({
id: a.id,
componentTypes: a.componentTypes.map(t => getComponentTypeName(t)),
entityCount: a.entities.length
})),
dirtyTracking: this.dirtyTrackingSystem.getStats()
entityCount: a.entities.size
}))
},
cacheStats: {
size: this.queryCache.size,
@@ -973,54 +889,6 @@ export class QuerySystem {
};
}
/**
* 配置脏标记系统
*
* @param batchSize 批处理大小
* @param maxProcessingTime 最大处理时间
*/
public configureDirtyTracking(batchSize: number, maxProcessingTime: number): void {
this.dirtyTrackingSystem.configureBatchProcessing(batchSize, maxProcessingTime);
}
/**
* 手动触发性能优化
*/
public optimizePerformance(): void {
this.dirtyTrackingSystem.processDirtyEntities();
this.cleanupCache();
const stats = this.componentIndexManager.getStats();
// 基于SparseSet的索引已自动优化无需手动切换索引类型
}
/**
* 开始新的帧
*/
public beginFrame(): void {
this.dirtyTrackingSystem.beginFrame();
}
/**
* 结束当前帧
*/
public endFrame(): void {
this.dirtyTrackingSystem.endFrame();
}
/**
* 标记实体组件已修改(用于脏标记追踪)
*
* @param entity 修改的实体
* @param componentTypes 修改的组件类型
*/
public markEntityDirty(entity: Entity, componentTypes: ComponentType[]): void {
this.queryStats.dirtyChecks++;
this.dirtyTrackingSystem.markDirty(entity, DirtyFlag.COMPONENT_MODIFIED, componentTypes);
this.clearQueryCache();
}
/**
* 获取实体所属的原型信息
*

View File

@@ -0,0 +1,345 @@
import { Component } from '../Component';
import type { Entity } from '../Entity';
import type { IScene } from '../IScene';
import { createLogger } from '../../Utils/Logger';
const logger = createLogger('ReferenceTracker');
/**
* WeakRef 接口定义
*
* 用于 ES2020 环境下的类型定义
*/
interface IWeakRef<T extends object> {
deref(): T | undefined;
}
/**
* WeakRef Polyfill for ES2020 compatibility
*
* 为了兼容 Cocos Creator、Laya、微信小游戏等目标平台仅支持 ES2020
* 提供 WeakRef 的 Polyfill 实现。
*
* - 现代浏览器:自动使用原生 WeakRef (自动 GC)
* - 旧环境:使用 Polyfill (无自动 GC但 Scene 销毁时会手动清理)
*/
class WeakRefPolyfill<T extends object> implements IWeakRef<T> {
private _target: T;
constructor(target: T) {
this._target = target;
}
deref(): T | undefined {
return this._target;
}
}
/**
* WeakRef 构造函数类型
*/
interface IWeakRefConstructor {
new <T extends object>(target: T): IWeakRef<T>;
}
/**
* WeakRef 实现
*
* 优先使用原生 WeakRef不支持时降级到 Polyfill
*/
const WeakRefImpl: IWeakRefConstructor = (
(typeof globalThis !== 'undefined' && (globalThis as any).WeakRef) ||
(typeof global !== 'undefined' && (global as any).WeakRef) ||
(typeof window !== 'undefined' && (window as any).WeakRef) ||
WeakRefPolyfill
) as IWeakRefConstructor;
/**
* Entity引用记录
*/
export interface EntityRefRecord {
component: IWeakRef<Component>;
propertyKey: string;
}
/**
* 全局EntityID到Scene的映射
*
* 使用全局Map记录每个Entity ID对应的Scene用于装饰器通过Component.entityId查找Scene。
*/
const globalEntitySceneMap = new Map<number, IWeakRef<IScene>>();
/**
* 通过Entity ID获取Scene
*
* @param entityId Entity ID
* @returns Scene实例如果不存在则返回null
*/
export function getSceneByEntityId(entityId: number): IScene | null {
const sceneRef = globalEntitySceneMap.get(entityId);
return sceneRef?.deref() || null;
}
/**
* Entity引用追踪器
*
* 追踪Component中对Entity的引用当Entity被销毁时自动清理所有引用。
*
* @example
* ```typescript
* const tracker = new ReferenceTracker();
* tracker.registerReference(targetEntity, component, 'parent');
* targetEntity.destroy(); // 自动将 component.parent 设为 null
* ```
*/
export class ReferenceTracker {
/**
* Entity ID -> 引用该Entity的所有组件记录
*/
private _references: Map<number, Set<EntityRefRecord>> = new Map();
/**
* 当前Scene的引用
*/
private _scene: IWeakRef<IScene> | null = null;
/**
* 注册Entity引用
*
* @param entity 被引用的Entity
* @param component 持有引用的Component
* @param propertyKey Component中存储引用的属性名
*/
public registerReference(entity: Entity, component: Component, propertyKey: string): void {
const entityId = entity.id;
let records = this._references.get(entityId);
if (!records) {
records = new Set();
this._references.set(entityId, records);
}
const existingRecord = this._findRecord(records, component, propertyKey);
if (existingRecord) {
return;
}
records.add({
component: new WeakRefImpl(component),
propertyKey
});
}
/**
* 注销Entity引用
*
* @param entity 被引用的Entity
* @param component 持有引用的Component
* @param propertyKey Component中存储引用的属性名
*/
public unregisterReference(entity: Entity, component: Component, propertyKey: string): void {
const entityId = entity.id;
const records = this._references.get(entityId);
if (!records) {
return;
}
const record = this._findRecord(records, component, propertyKey);
if (record) {
records.delete(record);
if (records.size === 0) {
this._references.delete(entityId);
}
}
}
/**
* 清理所有指向指定Entity的引用
*
* 将所有引用该Entity的Component属性设为null。
*
* @param entityId 被销毁的Entity ID
*/
public clearReferencesTo(entityId: number): void {
const records = this._references.get(entityId);
if (!records) {
return;
}
const validRecords: EntityRefRecord[] = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push(record);
}
}
for (const record of validRecords) {
const component = record.component.deref();
if (component) {
(component as any)[record.propertyKey] = null;
}
}
this._references.delete(entityId);
}
/**
* 清理Component的所有引用注册
*
* 当Component被移除时调用清理该Component注册的所有引用。
*
* @param component 被移除的Component
*/
public clearComponentReferences(component: Component): void {
for (const [entityId, records] of this._references.entries()) {
const toDelete: EntityRefRecord[] = [];
for (const record of records) {
const comp = record.component.deref();
if (!comp || comp === component) {
toDelete.push(record);
}
}
for (const record of toDelete) {
records.delete(record);
}
if (records.size === 0) {
this._references.delete(entityId);
}
}
}
/**
* 获取指向指定Entity的所有引用记录
*
* @param entityId Entity ID
* @returns 引用记录数组(仅包含有效引用)
*/
public getReferencesTo(entityId: number): EntityRefRecord[] {
const records = this._references.get(entityId);
if (!records) {
return [];
}
const validRecords: EntityRefRecord[] = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push(record);
}
}
return validRecords;
}
/**
* 清理所有失效的WeakRef引用
*
* 遍历所有记录移除已被GC回收的Component引用。
*/
public cleanup(): void {
const entitiesToDelete: number[] = [];
for (const [entityId, records] of this._references.entries()) {
const toDelete: EntityRefRecord[] = [];
for (const record of records) {
if (!record.component.deref()) {
toDelete.push(record);
}
}
for (const record of toDelete) {
records.delete(record);
}
if (records.size === 0) {
entitiesToDelete.push(entityId);
}
}
for (const entityId of entitiesToDelete) {
this._references.delete(entityId);
}
}
/**
* 设置Scene引用
*
* @param scene Scene实例
*/
public setScene(scene: IScene): void {
this._scene = new WeakRefImpl(scene);
}
/**
* 注册Entity到Scene的映射
*
* @param entityId Entity ID
* @param scene Scene实例
*/
public registerEntityScene(entityId: number, scene: IScene): void {
globalEntitySceneMap.set(entityId, new WeakRefImpl(scene));
}
/**
* 注销Entity到Scene的映射
*
* @param entityId Entity ID
*/
public unregisterEntityScene(entityId: number): void {
globalEntitySceneMap.delete(entityId);
}
/**
* 获取调试信息
*/
public getDebugInfo(): object {
const info: Record<string, any> = {};
for (const [entityId, records] of this._references.entries()) {
const validRecords = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push({
componentId: component.id,
propertyKey: record.propertyKey
});
}
}
if (validRecords.length > 0) {
info[`entity_${entityId}`] = validRecords;
}
}
return info;
}
/**
* 查找指定的引用记录
*/
private _findRecord(
records: Set<EntityRefRecord>,
component: Component,
propertyKey: string
): EntityRefRecord | undefined {
for (const record of records) {
const comp = record.component.deref();
if (comp === component && record.propertyKey === propertyKey) {
return record;
}
}
return undefined;
}
}

View File

@@ -60,6 +60,78 @@ export function Int32(target: any, propertyKey: string | symbol): void {
target.constructor.__int32Fields.add(key);
}
/**
* 32位无符号整数装饰器
* 标记字段使用Uint32Array存储适用于无符号整数如ID、标志位等
*/
export function Uint32(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint32Fields) {
target.constructor.__uint32Fields = new Set();
}
target.constructor.__uint32Fields.add(key);
}
/**
* 16位整数装饰器
* 标记字段使用Int16Array存储适用于小范围整数
*/
export function Int16(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__int16Fields) {
target.constructor.__int16Fields = new Set();
}
target.constructor.__int16Fields.add(key);
}
/**
* 16位无符号整数装饰器
* 标记字段使用Uint16Array存储适用于小范围无符号整数
*/
export function Uint16(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint16Fields) {
target.constructor.__uint16Fields = new Set();
}
target.constructor.__uint16Fields.add(key);
}
/**
* 8位整数装饰器
* 标记字段使用Int8Array存储适用于很小的整数值
*/
export function Int8(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__int8Fields) {
target.constructor.__int8Fields = new Set();
}
target.constructor.__int8Fields.add(key);
}
/**
* 8位无符号整数装饰器
* 标记字段使用Uint8Array存储适用于字节值、布尔标志等
*/
export function Uint8(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint8Fields) {
target.constructor.__uint8Fields = new Set();
}
target.constructor.__uint8Fields.add(key);
}
/**
* 8位夹紧整数装饰器
* 标记字段使用Uint8ClampedArray存储适用于颜色值等需要夹紧的数据
*/
export function Uint8Clamped(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint8ClampedFields) {
target.constructor.__uint8ClampedFields = new Set();
}
target.constructor.__uint8ClampedFields.add(key);
}
/**
* 序列化Map装饰器
@@ -109,13 +181,161 @@ export function DeepCopy(target: any, propertyKey: string | symbol): void {
target.constructor.__deepCopyFields.add(key);
}
/**
* 自动类型推断装饰器
* 根据字段的默认值和数值范围自动选择最优的TypedArray类型
*
* @param options 类型推断选项
* @param options.minValue 数值的最小值(用于范围优化)
* @param options.maxValue 数值的最大值(用于范围优化)
* @param options.precision 是否需要浮点精度true: 使用浮点数组, false: 使用整数数组)
* @param options.signed 是否需要符号位(仅在整数模式下有效)
*/
export function AutoTyped(options?: {
minValue?: number;
maxValue?: number;
precision?: boolean;
signed?: boolean;
}) {
return function (target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__autoTypedFields) {
target.constructor.__autoTypedFields = new Map();
}
target.constructor.__autoTypedFields.set(key, options || {});
};
}
/**
* 自动类型推断器
* 根据数值类型和范围自动选择最优的TypedArray类型
*/
export class TypeInference {
/**
* 根据数值范围推断最优的TypedArray类型
*/
public static inferOptimalType(value: any, options: {
minValue?: number;
maxValue?: number;
precision?: boolean;
signed?: boolean;
} = {}): string {
const type = typeof value;
if (type === 'boolean') {
return 'uint8'; // 布尔值使用最小的无符号整数
}
if (type !== 'number') {
return 'float32'; // 非数值类型默认使用Float32
}
const { minValue, maxValue, precision, signed } = options;
// 如果显式要求精度,使用浮点数
if (precision === true) {
// 检查是否需要双精度
if (Math.abs(value) > 3.4028235e+38 || (minValue !== undefined && Math.abs(minValue) > 3.4028235e+38) || (maxValue !== undefined && Math.abs(maxValue) > 3.4028235e+38)) {
return 'float64';
}
return 'float32';
}
// 如果显式禁用精度,或者是整数值,尝试使用整数数组
if (precision === false || Number.isInteger(value)) {
const actualMin = minValue !== undefined ? minValue : value;
const actualMax = maxValue !== undefined ? maxValue : value;
const needsSigned = signed !== false && (actualMin < 0 || value < 0);
// 根据范围选择最小的整数类型
if (needsSigned) {
// 有符号整数
if (actualMin >= -128 && actualMax <= 127) {
return 'int8';
} else if (actualMin >= -32768 && actualMax <= 32767) {
return 'int16';
} else if (actualMin >= -2147483648 && actualMax <= 2147483647) {
return 'int32';
} else {
return 'float64'; // 超出int32范围使用双精度浮点
}
} else {
// 无符号整数
if (actualMax <= 255) {
return 'uint8';
} else if (actualMax <= 65535) {
return 'uint16';
} else if (actualMax <= 4294967295) {
return 'uint32';
} else {
return 'float64'; // 超出uint32范围使用双精度浮点
}
}
}
// 默认情况:检查是否为小数
if (!Number.isInteger(value)) {
return 'float32';
}
// 整数值,但没有指定范围,根据值的大小选择
if (value >= 0 && value <= 255) {
return 'uint8';
} else if (value >= -128 && value <= 127) {
return 'int8';
} else if (value >= 0 && value <= 65535) {
return 'uint16';
} else if (value >= -32768 && value <= 32767) {
return 'int16';
} else if (value >= 0 && value <= 4294967295) {
return 'uint32';
} else if (value >= -2147483648 && value <= 2147483647) {
return 'int32';
} else {
return 'float64';
}
}
/**
* 根据推断的类型名创建对应的TypedArray构造函数
*/
public static getTypedArrayConstructor(typeName: string): typeof Float32Array | typeof Float64Array | typeof Int32Array | typeof Uint32Array | typeof Int16Array | typeof Uint16Array | typeof Int8Array | typeof Uint8Array | typeof Uint8ClampedArray {
switch (typeName) {
case 'float32': return Float32Array;
case 'float64': return Float64Array;
case 'int32': return Int32Array;
case 'uint32': return Uint32Array;
case 'int16': return Int16Array;
case 'uint16': return Uint16Array;
case 'int8': return Int8Array;
case 'uint8': return Uint8Array;
case 'uint8clamped': return Uint8ClampedArray;
default: return Float32Array;
}
}
}
/**
* SoA存储器支持的TypedArray类型
*/
export type SupportedTypedArray =
| Float32Array
| Float64Array
| Int32Array
| Uint32Array
| Int16Array
| Uint16Array
| Int8Array
| Uint8Array
| Uint8ClampedArray;
/**
* SoA存储器需要装饰器启用
* 使用Structure of Arrays存储模式在大规模批量操作时提供优异性能
*/
export class SoAStorage<T extends Component> {
private static readonly _logger = createLogger('SoAStorage');
private fields = new Map<string, Float32Array | Float64Array | Int32Array>();
private fields = new Map<string, SupportedTypedArray>();
private stringFields = new Map<string, string[]>(); // 专门存储字符串
private serializedFields = new Map<string, string[]>(); // 序列化存储Map/Set/Array
private complexFields = new Map<number, Map<string, any>>(); // 存储复杂对象
@@ -137,6 +357,13 @@ export class SoAStorage<T extends Component> {
const float64Fields = (componentType as any).__float64Fields || new Set();
const float32Fields = (componentType as any).__float32Fields || new Set();
const int32Fields = (componentType as any).__int32Fields || new Set();
const uint32Fields = (componentType as any).__uint32Fields || new Set();
const int16Fields = (componentType as any).__int16Fields || new Set();
const uint16Fields = (componentType as any).__uint16Fields || new Set();
const int8Fields = (componentType as any).__int8Fields || new Set();
const uint8Fields = (componentType as any).__uint8Fields || new Set();
const uint8ClampedFields = (componentType as any).__uint8ClampedFields || new Set();
const autoTypedFields = (componentType as any).__autoTypedFields || new Map();
const serializeMapFields = (componentType as any).__serializeMapFields || new Set();
const serializeSetFields = (componentType as any).__serializeSetFields || new Set();
const serializeArrayFields = (componentType as any).__serializeArrayFields || new Set();
@@ -151,22 +378,52 @@ export class SoAStorage<T extends Component> {
if (highPrecisionFields.has(key)) {
// 标记为高精度,作为复杂对象处理
// 不添加到fields会在updateComponentAtIndex中自动添加到complexFields
} else if (autoTypedFields.has(key)) {
// 使用自动类型推断
const options = autoTypedFields.get(key);
const inferredType = TypeInference.inferOptimalType(value, options);
const ArrayConstructor = TypeInference.getTypedArrayConstructor(inferredType);
this.fields.set(key, new ArrayConstructor(this._capacity));
SoAStorage._logger.info(`字段 ${key} 自动推断为 ${inferredType} 类型,值: ${value}, 选项:`, options);
} else if (float64Fields.has(key)) {
// 使用Float64Array存储
// 使用Float64Array存储(高精度浮点数)
this.fields.set(key, new Float64Array(this._capacity));
} else if (int32Fields.has(key)) {
// 使用Int32Array存储
// 使用Int32Array存储32位有符号整数
this.fields.set(key, new Int32Array(this._capacity));
} else if (uint32Fields.has(key)) {
// 使用Uint32Array存储32位无符号整数
this.fields.set(key, new Uint32Array(this._capacity));
} else if (int16Fields.has(key)) {
// 使用Int16Array存储16位有符号整数
this.fields.set(key, new Int16Array(this._capacity));
} else if (uint16Fields.has(key)) {
// 使用Uint16Array存储16位无符号整数
this.fields.set(key, new Uint16Array(this._capacity));
} else if (int8Fields.has(key)) {
// 使用Int8Array存储8位有符号整数
this.fields.set(key, new Int8Array(this._capacity));
} else if (uint8Fields.has(key)) {
// 使用Uint8Array存储8位无符号整数
this.fields.set(key, new Uint8Array(this._capacity));
} else if (uint8ClampedFields.has(key)) {
// 使用Uint8ClampedArray存储8位夹紧无符号整数
this.fields.set(key, new Uint8ClampedArray(this._capacity));
} else if (float32Fields.has(key)) {
// 使用Float32Array存储
// 使用Float32Array存储32位浮点数
this.fields.set(key, new Float32Array(this._capacity));
} else {
// 默认使用Float32Array
this.fields.set(key, new Float32Array(this._capacity));
}
} else if (type === 'boolean') {
// 布尔值使用Float32Array存储为0/1
this.fields.set(key, new Float32Array(this._capacity));
// 布尔值默认使用Uint8Array存储为0/1(更节省内存)
if (uint8Fields.has(key) || (!float32Fields.has(key) && !float64Fields.has(key))) {
this.fields.set(key, new Uint8Array(this._capacity));
} else {
// 兼容性:如果显式指定浮点类型则使用原有方式
this.fields.set(key, new Float32Array(this._capacity));
}
} else if (type === 'string') {
// 字符串专门处理
this.stringFields.set(key, new Array(this._capacity));
@@ -430,16 +687,32 @@ export class SoAStorage<T extends Component> {
private resize(newCapacity: number): void {
// 调整数值字段的TypedArray
for (const [fieldName, oldArray] of this.fields.entries()) {
let newArray: Float32Array | Float64Array | Int32Array;
let newArray: SupportedTypedArray;
if (oldArray instanceof Float32Array) {
newArray = new Float32Array(newCapacity);
} else if (oldArray instanceof Float64Array) {
newArray = new Float64Array(newCapacity);
} else {
} else if (oldArray instanceof Int32Array) {
newArray = new Int32Array(newCapacity);
} else if (oldArray instanceof Uint32Array) {
newArray = new Uint32Array(newCapacity);
} else if (oldArray instanceof Int16Array) {
newArray = new Int16Array(newCapacity);
} else if (oldArray instanceof Uint16Array) {
newArray = new Uint16Array(newCapacity);
} else if (oldArray instanceof Int8Array) {
newArray = new Int8Array(newCapacity);
} else if (oldArray instanceof Uint8Array) {
newArray = new Uint8Array(newCapacity);
} else if (oldArray instanceof Uint8ClampedArray) {
newArray = new Uint8ClampedArray(newCapacity);
} else {
// 默认回退到Float32Array
newArray = new Float32Array(newCapacity);
SoAStorage._logger.warn(`未知的TypedArray类型用于字段 ${fieldName}回退到Float32Array`);
}
newArray.set(oldArray);
this.fields.set(fieldName, newArray);
}
@@ -469,11 +742,11 @@ export class SoAStorage<T extends Component> {
return Array.from(this.entityToIndex.values());
}
public getFieldArray(fieldName: string): Float32Array | Float64Array | Int32Array | null {
public getFieldArray(fieldName: string): SupportedTypedArray | null {
return this.fields.get(fieldName) || null;
}
public getTypedFieldArray<K extends keyof T>(fieldName: K): Float32Array | Float64Array | Int32Array | null {
public getTypedFieldArray<K extends keyof T>(fieldName: K): SupportedTypedArray | null {
return this.fields.get(String(fieldName)) || null;
}
@@ -566,16 +839,38 @@ export class SoAStorage<T extends Component> {
for (const [fieldName, array] of this.fields.entries()) {
let bytesPerElement: number;
let typeName: string;
if (array instanceof Float32Array) {
bytesPerElement = 4;
typeName = 'float32';
} else if (array instanceof Float64Array) {
bytesPerElement = 8;
typeName = 'float64';
} else {
} else if (array instanceof Int32Array) {
bytesPerElement = 4;
typeName = 'int32';
} else if (array instanceof Uint32Array) {
bytesPerElement = 4;
typeName = 'uint32';
} else if (array instanceof Int16Array) {
bytesPerElement = 2;
typeName = 'int16';
} else if (array instanceof Uint16Array) {
bytesPerElement = 2;
typeName = 'uint16';
} else if (array instanceof Int8Array) {
bytesPerElement = 1;
typeName = 'int8';
} else if (array instanceof Uint8Array) {
bytesPerElement = 1;
typeName = 'uint8';
} else if (array instanceof Uint8ClampedArray) {
bytesPerElement = 1;
typeName = 'uint8clamped';
} else {
// 默认回退
bytesPerElement = 4;
typeName = 'unknown';
}
const memory = array.length * bytesPerElement;
@@ -603,7 +898,7 @@ export class SoAStorage<T extends Component> {
* 执行向量化批量操作
* @param operation 操作函数,接收字段数组和活跃索引
*/
public performVectorizedOperation(operation: (fieldArrays: Map<string, Float32Array | Float64Array | Int32Array>, activeIndices: number[]) => void): void {
public performVectorizedOperation(operation: (fieldArrays: Map<string, SupportedTypedArray>, activeIndices: number[]) => void): void {
const activeIndices = this.getActiveIndices();
operation(this.fields, activeIndices);
}

View File

@@ -0,0 +1,49 @@
/**
* 统一的存储装饰器导出文件
*
* 用户可以从这里导入所有的SoA存储装饰器而不需要知道内部实现细节
*
* @example
* ```typescript
* import { EnableSoA, Float32, Int16, Uint8 } from './ECS/Core/StorageDecorators';
*
* @EnableSoA
* class TransformComponent extends Component {
* @Float32 x: number = 0;
* @Float32 y: number = 0;
* @Int16 layer: number = 0;
* @Uint8 visible: boolean = true;
* }
* ```
*/
// 从SoAStorage导入所有装饰器和类型
export {
// 启用装饰器
EnableSoA,
// 数值类型装饰器
HighPrecision,
Float64,
Float32,
Int32,
Uint32,
Int16,
Uint16,
Int8,
Uint8,
Uint8Clamped,
// 自动类型推断
AutoTyped,
TypeInference,
// 序列化装饰器
SerializeMap,
SerializeSet,
SerializeArray,
DeepCopy,
// 类型定义
SupportedTypedArray
} from './SoAStorage';

View File

@@ -0,0 +1,147 @@
import type { Entity } from '../Entity';
import type { Component } from '../Component';
import { getSceneByEntityId } from '../Core/ReferenceTracker';
import { createLogger } from '../../Utils/Logger';
const logger = createLogger('EntityRefDecorator');
/**
* EntityRef元数据的Symbol键
*/
export const ENTITY_REF_METADATA = Symbol('EntityRefMetadata');
/**
* EntityRef值存储的Symbol键
*/
const ENTITY_REF_VALUES = Symbol('EntityRefValues');
/**
* EntityRef元数据
*/
export interface EntityRefMetadata {
properties: Set<string>;
}
/**
* 获取或创建组件的EntityRef值存储Map
*/
function getValueMap(component: Component): Map<string, Entity | null> {
let map = (component as any)[ENTITY_REF_VALUES];
if (!map) {
map = new Map<string, Entity | null>();
(component as any)[ENTITY_REF_VALUES] = map;
}
return map;
}
/**
* Entity引用装饰器
*
* 标记Component属性为Entity引用自动追踪引用关系。
* 当被引用的Entity销毁时该属性会自动设为null。
*
* @example
* ```typescript
* class ParentComponent extends Component {
* @EntityRef() parent: Entity | null = null;
* }
*
* const parent = scene.createEntity('Parent');
* const child = scene.createEntity('Child');
* const comp = child.addComponent(new ParentComponent());
*
* comp.parent = parent;
* parent.destroy(); // comp.parent 自动变为 null
* ```
*/
export function EntityRef(): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
const constructor = target.constructor;
let metadata: EntityRefMetadata = constructor[ENTITY_REF_METADATA];
if (!metadata) {
metadata = {
properties: new Set()
};
constructor[ENTITY_REF_METADATA] = metadata;
}
const propKeyString = typeof propertyKey === 'symbol' ? propertyKey.toString() : propertyKey;
metadata.properties.add(propKeyString);
Object.defineProperty(target, propertyKey, {
get: function (this: Component) {
const valueMap = getValueMap(this);
return valueMap.get(propKeyString) || null;
},
set: function (this: Component, newValue: Entity | null) {
const valueMap = getValueMap(this);
const oldValue = valueMap.get(propKeyString) || null;
if (oldValue === newValue) {
return;
}
const scene = this.entityId !== null ? getSceneByEntityId(this.entityId) : null;
if (!scene || !scene.referenceTracker) {
valueMap.set(propKeyString, newValue);
return;
}
const tracker = scene.referenceTracker;
if (oldValue) {
tracker.unregisterReference(oldValue, this, propKeyString);
}
if (newValue) {
if (newValue.scene !== scene) {
logger.error(`Cannot reference Entity from different Scene. Entity: ${newValue.name}, Scene: ${newValue.scene?.name || 'null'}`);
return;
}
if (newValue.isDestroyed) {
logger.warn(`Cannot reference destroyed Entity: ${newValue.name}`);
valueMap.set(propKeyString, null);
return;
}
tracker.registerReference(newValue, this, propKeyString);
}
valueMap.set(propKeyString, newValue);
},
enumerable: true,
configurable: true
});
};
}
/**
* 获取Component的EntityRef元数据
*
* @param component Component实例或Component类
* @returns EntityRef元数据如果不存在则返回null
*/
export function getEntityRefMetadata(component: any): EntityRefMetadata | null {
if (!component) {
return null;
}
const constructor = typeof component === 'function'
? component
: component.constructor;
return constructor[ENTITY_REF_METADATA] || null;
}
/**
* 检查Component是否有EntityRef属性
*
* @param component Component实例或Component类
* @returns 如果有EntityRef属性返回true
*/
export function hasEntityRef(component: any): boolean {
return getEntityRefMetadata(component) !== null;
}

View File

@@ -1,5 +1,6 @@
import type { Component } from '../Component';
import type { EntitySystem } from '../Systems/EntitySystem';
import type {Component} from '../Component';
import type {EntitySystem} from '../Systems';
import {ComponentType} from "../../Types";
/**
* 存储组件类型名称的Symbol键
@@ -38,42 +39,80 @@ export function ECSComponent(typeName: string) {
};
}
/**
* System元数据配置
*/
export interface SystemMetadata {
/**
* 更新顺序数值越小越先执行默认0
*/
updateOrder?: number;
/**
* 是否默认启用默认true
*/
enabled?: boolean;
}
/**
* 系统类型装饰器
* 用于为系统类指定固定的类型名称,避免在代码混淆后失效
*
*
* @param typeName 系统类型名称
* @param metadata 系统元数据配置
* @example
* ```typescript
* // 基本使用
* @ECSSystem('Movement')
* class MovementSystem extends EntitySystem {
* protected process(entities: Entity[]): void {
* // 系统逻辑
* }
* }
*
* // 配置更新顺序
* @Injectable()
* @ECSSystem('Physics', { updateOrder: 10 })
* class PhysicsSystem extends EntitySystem {
* constructor(@Inject(CollisionSystem) private collision: CollisionSystem) {
* super(Matcher.of(Transform, RigidBody));
* }
* }
* ```
*/
export function ECSSystem(typeName: string) {
export function ECSSystem(typeName: string, metadata?: SystemMetadata) {
return function <T extends new (...args: any[]) => EntitySystem>(target: T): T {
if (!typeName || typeof typeName !== 'string') {
throw new Error('ECSSystem装饰器必须提供有效的类型名称');
}
// 在构造函数上存储类型名称
(target as any)[SYSTEM_TYPE_NAME] = typeName;
// 存储元数据
if (metadata) {
(target as any).__systemMetadata__ = metadata;
}
return target;
};
}
/**
* 获取System的元数据
*/
export function getSystemMetadata(systemType: new (...args: any[]) => EntitySystem): SystemMetadata | undefined {
return (systemType as any).__systemMetadata__;
}
/**
* 获取组件类型的名称,优先使用装饰器指定的名称
*
* @param componentType 组件构造函数
* @returns 组件类型名称
*/
export function getComponentTypeName<T extends Component>(
componentType: new (...args: any[]) => T
export function getComponentTypeName(
componentType: ComponentType
): string {
// 优先使用装饰器指定的名称
const decoratorName = (componentType as any)[COMPONENT_TYPE_NAME];
@@ -111,7 +150,7 @@ export function getSystemTypeName<T extends EntitySystem>(
* @returns 组件类型名称
*/
export function getComponentInstanceTypeName(component: Component): string {
return getComponentTypeName(component.constructor as new (...args: any[]) => Component);
return getComponentTypeName(component.constructor as ComponentType);
}
/**

View File

@@ -5,6 +5,18 @@ export {
getSystemTypeName,
getComponentInstanceTypeName,
getSystemInstanceTypeName,
getSystemMetadata,
COMPONENT_TYPE_NAME,
SYSTEM_TYPE_NAME
} from './TypeDecorators';
} from './TypeDecorators';
export type { SystemMetadata } from './TypeDecorators';
export {
EntityRef,
getEntityRefMetadata,
hasEntityRef,
ENTITY_REF_METADATA
} from './EntityRefDecorator';
export type { EntityRefMetadata } from './EntityRefDecorator';

View File

@@ -71,6 +71,19 @@ export class Entity {
* 用于发射组件相关事件
*/
public static eventBus: EventBus | null = null;
/**
* 通知Scene中的QuerySystem实体组件发生变动
*
* @param entity 发生组件变动的实体
*/
private static notifyQuerySystems(entity: Entity): void {
// 只通知Scene中的QuerySystem
if (entity.scene && entity.scene.querySystem) {
entity.scene.querySystem.updateEntity(entity);
entity.scene.clearSystemEntityCaches();
}
}
/**
* 实体名称
@@ -82,21 +95,11 @@ export class Entity {
*/
public readonly id: number;
/**
* 组件集合
*/
public readonly components: Component[] = [];
/**
* 所属场景引用
*/
public scene: IScene | null = null;
/**
* 更新间隔
*/
public updateInterval: number = 1;
/**
* 销毁状态标志
*/
@@ -116,36 +119,31 @@ export class Entity {
* 激活状态
*/
private _active: boolean = true;
/**
* 实体标签
*/
private _tag: number = 0;
/**
* 启用状态
*/
private _enabled: boolean = true;
/**
* 更新顺序
*/
private _updateOrder: number = 0;
/**
* 组件位掩码
* 组件位掩码(用于快速 hasComponent 检查)
*/
private _componentMask: BitMask64Data = BitMask64Utils.clone(BitMask64Utils.ZERO);
/**
* 按组件类型ID直址的稀疏数组
* 懒加载的组件数组缓存
*/
private _componentsByTypeId: (Component | undefined)[] = [];
/**
* typeId到components数组中密集索引的映射表
*/
private _componentDenseIndexByTypeId: number[] = [];
private _componentCache: Component[] | null = null;
/**
* 构造函数
@@ -166,6 +164,50 @@ export class Entity {
return this._isDestroyed;
}
/**
* 获取组件数组(懒加载)
* @returns 只读的组件数组
*/
public get components(): readonly Component[] {
if (this._componentCache === null) {
this._rebuildComponentCache();
}
return this._componentCache!;
}
/**
* 从存储重建组件缓存
*/
private _rebuildComponentCache(): void {
const components: Component[] = [];
if (!this.scene?.componentStorageManager) {
this._componentCache = components;
return;
}
const mask = this._componentMask;
const maxBitIndex = ComponentRegistry.getRegisteredCount();
for (let bitIndex = 0; bitIndex < maxBitIndex; bitIndex++) {
if (BitMask64Utils.getBit(mask, bitIndex)) {
const componentType = ComponentRegistry.getTypeByBitIndex(bitIndex);
if (componentType) {
const component = this.scene.componentStorageManager.getComponent(
this.id,
componentType
);
if (component) {
components.push(component);
}
}
}
}
this._componentCache = components;
}
/**
* 获取父实体
* @returns 父实体如果没有父实体则返回null
@@ -293,13 +335,19 @@ export class Entity {
/**
* 创建并添加组件
*
* @param componentType - 组件类型
*
* @param componentType - 组件类型构造函数
* @param args - 组件构造函数参数
* @returns 创建的组件实例
*
* @example
* ```typescript
* const position = entity.createComponent(Position, 100, 200);
* const health = entity.createComponent(Health, 100);
* ```
*/
public createComponent<T extends Component>(
componentType: ComponentType<T>,
componentType: ComponentType<T>,
...args: any[]
): T {
const component = new componentType(...args);
@@ -308,7 +356,7 @@ export class Entity {
/**
* 内部添加组件方法(不进行重复检查,用于初始化)
*
*
* @param component - 要添加的组件实例
* @returns 添加的组件实例
*/
@@ -319,44 +367,54 @@ export class Entity {
ComponentRegistry.register(componentType);
}
const typeId = ComponentRegistry.getBitIndex(componentType);
component.entity = this;
this._componentsByTypeId[typeId] = component;
const denseIndex = this.components.length;
this._componentDenseIndexByTypeId[typeId] = denseIndex;
this.components.push(component);
// 更新位掩码
const componentMask = ComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(this._componentMask, componentMask);
// 使缓存失效
this._componentCache = null;
return component;
}
/**
* 添加组件到实体
*
*
* @param component - 要添加的组件实例
* @returns 添加的组件实例
* @throws {Error} 如果组件类型已存在
* @throws {Error} 如果实体已存在该类型的组件
*
* @example
* ```typescript
* const position = new Position(100, 200);
* entity.addComponent(position);
* ```
*/
public addComponent<T extends Component>(component: T): T {
const componentType = component.constructor as ComponentType<T>;
if (!this.scene) {
throw new Error(`Entity must be added to Scene before adding components. Use scene.createEntity() instead of new Entity()`);
}
if (!this.scene.componentStorageManager) {
throw new Error(`Scene does not have componentStorageManager`);
}
if (this.hasComponent(componentType)) {
throw new Error(`Entity ${this.name} already has component ${getComponentTypeName(componentType)}`);
}
this.addComponentInternal(component);
if (this.scene && this.scene.componentStorageManager) {
this.scene.componentStorageManager.addComponent(this.id, component);
}
this.scene.componentStorageManager.addComponent(this.id, component);
component.entityId = this.id;
if (this.scene.referenceTracker) {
this.scene.referenceTracker.registerEntityScene(this.id, this.scene);
}
component.onAddedToEntity();
if (Entity.eventBus) {
Entity.eventBus.emitComponentAdded({
timestamp: Date.now(),
@@ -368,72 +426,60 @@ export class Entity {
component: component
});
}
if (this.scene && this.scene.querySystem) {
this.scene.querySystem.removeEntity(this);
this.scene.querySystem.addEntity(this);
}
// 通知所有相关的QuerySystem组件已变动
Entity.notifyQuerySystems(this);
return component;
}
/**
* 获取指定类型的组件
*
* @param type - 组件类型
* @returns 组件实例null
*
* @param type - 组件类型构造函数
* @returns 组件实例,如果不存在则返回null
*
* @example
* ```typescript
* const position = entity.getComponent(Position);
* if (position) {
* position.x += 10;
* position.y += 20;
* }
* ```
*/
public getComponent<T extends Component>(type: ComponentType<T>): T | null {
if (!ComponentRegistry.isRegistered(type)) {
// 快速检查:位掩码
if (!this.hasComponent(type)) {
return null;
}
const mask = ComponentRegistry.getBitMask(type);
if (BitMask64Utils.hasNone(this._componentMask, mask)) {
// 从Scene存储获取
if (!this.scene?.componentStorageManager) {
return null;
}
const typeId = ComponentRegistry.getBitIndex(type);
const component = this._componentsByTypeId[typeId];
if (component && component.constructor === type) {
return component as T;
}
if (this.scene && this.scene.componentStorageManager) {
const storageComponent = this.scene.componentStorageManager.getComponent(this.id, type);
if (storageComponent) {
this._componentsByTypeId[typeId] = storageComponent;
if (!this.components.includes(storageComponent)) {
const denseIndex = this.components.length;
this._componentDenseIndexByTypeId[typeId] = denseIndex;
this.components.push(storageComponent);
}
return storageComponent;
}
}
for (let i = 0; i < this.components.length; i++) {
const component = this.components[i];
if (component instanceof type) {
this._componentsByTypeId[typeId] = component;
this._componentDenseIndexByTypeId[typeId] = i;
return component as T;
}
}
return null;
const component = this.scene.componentStorageManager.getComponent(this.id, type);
return component as T | null;
}
/**
* 检查实体是否有指定类型的组件
*
* @param type - 组件类型
* @returns 如果有该组件返回true
* 检查实体是否有指定类型的组件
*
* @param type - 组件类型构造函数
* @returns 如果实体拥有该组件返回true否则返回false
*
* @example
* ```typescript
* if (entity.hasComponent(Position)) {
* const position = entity.getComponent(Position)!;
* position.x += 10;
* }
* ```
*/
public hasComponent<T extends Component>(type: ComponentType<T>): boolean {
if (!ComponentRegistry.isRegistered(type)) {
@@ -446,13 +492,22 @@ export class Entity {
/**
* 获取或创建指定类型的组件
*
* @param type - 组件类型
* @param args - 组件构造函数参数(仅在创建时使用)
*
* 如果组件已存在则返回现有组件,否则创建新组件并添加到实体
*
* @param type - 组件类型构造函数
* @param args - 组件构造函数参数(仅在创建新组件时使用)
* @returns 组件实例
*
* @example
* ```typescript
* // 确保实体拥有Position组件
* const position = entity.getOrCreateComponent(Position, 0, 0);
* position.x = 100;
* ```
*/
public getOrCreateComponent<T extends Component>(
type: ComponentType<T>,
type: ComponentType<T>,
...args: any[]
): T {
let component = this.getComponent(type);
@@ -464,48 +519,39 @@ export class Entity {
/**
* 移除指定的组件
*
*
* @param component - 要移除的组件实例
*/
public removeComponent(component: Component): void {
const componentType = component.constructor as ComponentType;
if (!ComponentRegistry.isRegistered(componentType)) {
return;
}
const typeId = ComponentRegistry.getBitIndex(componentType);
this._componentsByTypeId[typeId] = undefined;
BitMask64Utils.clearBit(this._componentMask, typeId);
const denseIndex = this._componentDenseIndexByTypeId[typeId];
if (denseIndex !== undefined && denseIndex < this.components.length) {
const lastIndex = this.components.length - 1;
if (denseIndex !== lastIndex) {
const lastComponent = this.components[lastIndex];
this.components[denseIndex] = lastComponent;
const lastComponentType = lastComponent.constructor as ComponentType;
const lastTypeId = ComponentRegistry.getBitIndex(lastComponentType);
this._componentDenseIndexByTypeId[lastTypeId] = denseIndex;
}
this.components.pop();
}
this._componentDenseIndexByTypeId[typeId] = -1;
const bitIndex = ComponentRegistry.getBitIndex(componentType);
if (this.scene && this.scene.componentStorageManager) {
// 更新位掩码
BitMask64Utils.clearBit(this._componentMask, bitIndex);
// 使缓存失效
this._componentCache = null;
// 从Scene存储移除
if (this.scene?.componentStorageManager) {
this.scene.componentStorageManager.removeComponent(this.id, componentType);
}
if (this.scene?.referenceTracker) {
this.scene.referenceTracker.clearComponentReferences(component);
}
if (component.onRemovedFromEntity) {
component.onRemovedFromEntity();
}
component.entityId = null;
if (Entity.eventBus) {
Entity.eventBus.emitComponentRemoved({
timestamp: Date.now(),
@@ -517,13 +563,9 @@ export class Entity {
component: component
});
}
component.entity = null as any;
if (this.scene && this.scene.querySystem) {
this.scene.querySystem.removeEntity(this);
this.scene.querySystem.addEntity(this);
}
// 通知所有相关的QuerySystem组件已变动
Entity.notifyQuerySystems(this);
}
/**
@@ -546,24 +588,25 @@ export class Entity {
*/
public removeAllComponents(): void {
const componentsToRemove = [...this.components];
this._componentsByTypeId.length = 0;
this._componentDenseIndexByTypeId.length = 0;
// 清除位掩码
BitMask64Utils.clear(this._componentMask);
// 使缓存失效
this._componentCache = null;
for (const component of componentsToRemove) {
const componentType = component.constructor as ComponentType;
if (this.scene && this.scene.componentStorageManager) {
if (this.scene?.componentStorageManager) {
this.scene.componentStorageManager.removeComponent(this.id, componentType);
}
component.onRemovedFromEntity();
component.entity = null as any;
}
this.components.length = 0;
// 通知所有相关的QuerySystem组件已全部移除
Entity.notifyQuerySystems(this);
}
/**
@@ -821,31 +864,11 @@ export class Entity {
}
}
/**
* 更新实体
*
* 调用所有组件的更新方法,并更新子实体。
*/
public update(): void {
if (!this.activeInHierarchy || this._isDestroyed) {
return;
}
for (const component of this.components) {
if (component.enabled) {
component.update();
}
}
for (const child of this._children) {
child.update();
}
}
/**
* 销毁实体
*
* 移除所有组件、子实体并标记为已销毁
*
* 移除所有组件、子实体并标记为已销毁
*/
public destroy(): void {
if (this._isDestroyed) {
@@ -853,29 +876,71 @@ export class Entity {
}
this._isDestroyed = true;
if (this.scene && this.scene.referenceTracker) {
this.scene.referenceTracker.clearReferencesTo(this.id);
this.scene.referenceTracker.unregisterEntityScene(this.id);
}
const childrenToDestroy = [...this._children];
for (const child of childrenToDestroy) {
child.destroy();
}
if (this._parent) {
this._parent.removeChild(this);
}
this.removeAllComponents();
if (this.scene) {
if (this.scene.querySystem) {
this.scene.querySystem.removeEntity(this);
}
if (this.scene.entities) {
this.scene.entities.remove(this);
}
}
}
/**
* 批量销毁所有子实体
*/
public destroyAllChildren(): void {
if (this._children.length === 0) return;
const scene = this.scene;
const toDestroy: Entity[] = [];
const collectChildren = (entity: Entity) => {
for (const child of entity._children) {
toDestroy.push(child);
collectChildren(child);
}
};
collectChildren(this);
for (const entity of toDestroy) {
entity._isDestroyed = true;
}
for (const entity of toDestroy) {
entity.removeAllComponents();
}
if (scene) {
for (const entity of toDestroy) {
scene.entities.remove(entity);
scene.querySystem.removeEntity(entity);
}
scene.clearSystemEntityCaches();
}
this._children.length = 0;
}
/**
* 比较实体
*
@@ -914,8 +979,7 @@ export class Entity {
childCount: number;
childIds: number[];
depth: number;
indexMappingSize: number;
denseIndexMappingSize: number;
cacheBuilt: boolean;
} {
return {
name: this.name,
@@ -931,8 +995,7 @@ export class Entity {
childCount: this._children.length,
childIds: this._children.map(c => c.id),
depth: this.getDepth(),
indexMappingSize: this._componentsByTypeId.filter(c => c !== undefined).length,
denseIndexMappingSize: this._componentDenseIndexByTypeId.filter(idx => idx !== -1 && idx !== undefined).length
cacheBuilt: this._componentCache !== null
};
}
}

View File

@@ -1,15 +1,15 @@
import { Entity } from './Entity';
import { EntityList } from './Utils/EntityList';
import { EntityProcessorList } from './Utils/EntityProcessorList';
import { IdentifierPool } from './Utils/IdentifierPool';
import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem';
import type { ReferenceTracker } from './Core/ReferenceTracker';
/**
* 场景接口定义
*
*
* 定义场景应该实现的核心功能和属性,使用接口而非继承提供更灵活的实现方式。
*/
export interface IScene {
@@ -18,16 +18,30 @@ export interface IScene {
*/
name: string;
/**
* 场景自定义数据
*
* 用于存储场景级别的配置和状态数据,例如:
* - 天气状态
* - 时间设置
* - 游戏难度
* - 音频配置
* - 关卡检查点
*
* @example
* ```typescript
* scene.sceneData.set('weather', 'rainy');
* scene.sceneData.set('timeOfDay', 14.5);
* scene.sceneData.set('checkpoint', { x: 100, y: 200 });
* ```
*/
readonly sceneData: Map<string, any>;
/**
* 场景中的实体集合
*/
readonly entities: EntityList;
/**
* 实体系统处理器集合
*/
readonly entityProcessors: EntityProcessorList;
/**
* 标识符池
*/
@@ -48,6 +62,11 @@ export interface IScene {
*/
readonly eventSystem: TypeSafeEventSystem;
/**
* 引用追踪器
*/
readonly referenceTracker: ReferenceTracker;
/**
* 获取系统列表
*/
@@ -88,6 +107,11 @@ export interface IScene {
*/
createEntity(name: string): Entity;
/**
* 清除所有EntitySystem的实体缓存
*/
clearSystemEntityCaches(): void;
/**
* 添加实体
*/
@@ -147,8 +171,12 @@ export interface ISceneConfig {
* 场景名称
*/
name?: string;
/**
* 调试配置
* 性能监控器实例(可选)
*
* 如果不提供Scene会自动从Core.services获取全局PerformanceMonitor。
* 提供此参数可以实现场景级别的独立性能监控。
*/
debug?: boolean;
performanceMonitor?: any;
}

View File

@@ -1,14 +1,23 @@
import { Entity } from './Entity';
import { EntityList } from './Utils/EntityList';
import { EntityProcessorList } from './Utils/EntityProcessorList';
import { IdentifierPool } from './Utils/IdentifierPool';
import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager } from './Core/ComponentStorage';
import { ComponentStorageManager, ComponentRegistry } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem';
import { EventBus } from './Core/EventBus';
import { ReferenceTracker } from './Core/ReferenceTracker';
import { IScene, ISceneConfig } from './IScene';
import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decorators';
import { getComponentInstanceTypeName, getSystemInstanceTypeName, getSystemMetadata } from "./Decorators";
import { TypedQueryBuilder } from './Core/Query/TypedQuery';
import { SceneSerializer, SceneSerializationOptions, SceneDeserializationOptions } from './Serialization/SceneSerializer';
import { IncrementalSerializer, IncrementalSnapshot, IncrementalSerializationOptions } from './Serialization/IncrementalSerializer';
import { ComponentPoolManager } from './Core/ComponentPool';
import { PerformanceMonitor } from '../Utils/PerformanceMonitor';
import { ServiceContainer, type ServiceType } from '../Core/ServiceContainer';
import { createInstance, isInjectable, injectProperties } from '../Core/DI';
import { isUpdatable, getUpdatableMetadata } from '../Core/DI/Decorators';
import { createLogger } from '../Utils/Logger';
/**
* 游戏场景默认实现类
@@ -19,24 +28,25 @@ import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decor
export class Scene implements IScene {
/**
* 场景名称
*
*
* 用于标识和调试的友好名称。
*/
public name: string = "";
/**
* 场景自定义数据
*
* 用于存储场景级别的配置和状态数据。
*/
public readonly sceneData: Map<string, any> = new Map();
/**
* 场景中的实体集合
*
*
* 管理场景内所有实体的生命周期。
*/
public readonly entities: EntityList;
/**
* 实体系统处理器集合
*
* 管理场景内所有实体系统的执行。
*/
public readonly entityProcessors: EntityProcessorList;
/**
* 实体ID池
@@ -61,36 +71,121 @@ export class Scene implements IScene {
/**
* 事件系统
*
*
* 类型安全的事件系统。
*/
public readonly eventSystem: TypeSafeEventSystem;
/**
* 引用追踪器
*
* 追踪Component中对Entity的引用当Entity销毁时自动清理引用。
*/
public readonly referenceTracker: ReferenceTracker;
/**
* 服务容器
*
* 场景级别的依赖注入容器用于管理EntitySystem和其他服务的生命周期。
* 每个Scene拥有独立的服务容器实现场景间的隔离。
*/
private readonly _services: ServiceContainer;
/**
* 日志记录器
*/
private readonly logger: ReturnType<typeof createLogger>;
/**
* 性能监控器
*
* 用于监控场景和系统的性能。可以在构造函数中注入如果不提供则从Core获取。
*/
private readonly _performanceMonitor: PerformanceMonitor;
/**
* 场景是否已开始运行
*/
private _didSceneBegin: boolean = false;
/**
* 获取系统列表(兼容性属性)
* 获取场景中所有已注册的EntitySystem
*
* 按updateOrder排序。
*
* @returns 系统列表
*/
public get systems(): EntitySystem[] {
return this.entityProcessors.processors;
// 从ServiceContainer获取所有EntitySystem实例
const services = this._services.getAll();
const systems: EntitySystem[] = [];
for (const service of services) {
if (service instanceof EntitySystem) {
systems.push(service);
}
}
// 按updateOrder排序
systems.sort((a, b) => a.updateOrder - b.updateOrder);
return systems;
}
/**
* 通过类型获取System实例
*
* @param systemType System类型
* @returns System实例如果未找到则返回null
*
* @example
* ```typescript
* const physics = scene.getSystem(PhysicsSystem);
* if (physics) {
* physics.doSomething();
* }
* ```
*/
public getSystem<T extends EntitySystem>(systemType: ServiceType<T>): T | null {
return this._services.tryResolve(systemType) as T | null;
}
/**
* 获取场景的服务容器
*
* 用于注册和解析场景级别的服务如EntitySystem
*
* @example
* ```typescript
* // 注册服务
* scene.services.registerSingleton(PhysicsSystem);
*
* // 解析服务
* const physics = scene.services.resolve(PhysicsSystem);
* ```
*/
public get services(): ServiceContainer {
return this._services;
}
/**
* 创建场景实例
*/
constructor(config?: ISceneConfig) {
this.entities = new EntityList(this);
this.entityProcessors = new EntityProcessorList();
this.identifierPool = new IdentifierPool();
this.componentStorageManager = new ComponentStorageManager();
this.querySystem = new QuerySystem();
this.eventSystem = new TypeSafeEventSystem();
this.referenceTracker = new ReferenceTracker();
this._services = new ServiceContainer();
this.logger = createLogger('Scene');
// 从配置获取 PerformanceMonitor如果未提供则创建一个新实例
// Scene 应该是独立的,不依赖于 Core通过构造函数参数明确依赖关系
this._performanceMonitor = config?.performanceMonitor || new PerformanceMonitor();
// 应用配置
if (config?.name) {
this.name = config.name;
}
@@ -98,7 +193,7 @@ export class Scene implements IScene {
if (!Entity.eventBus) {
Entity.eventBus = new EventBus(false);
}
if (Entity.eventBus) {
Entity.eventBus.onComponentAdded((data: unknown) => {
this.eventSystem.emitSync('component:added', data);
@@ -136,10 +231,6 @@ export class Scene implements IScene {
* 这个方法会启动场景。它将启动实体处理器等并调用onStart方法。
*/
public begin() {
// 启动实体处理器
if (this.entityProcessors != null)
this.entityProcessors.begin();
// 标记场景已开始运行并调用onStart方法
this._didSceneBegin = true;
this.onStart();
@@ -163,31 +254,43 @@ export class Scene implements IScene {
// 清空组件存储
this.componentStorageManager.clear();
// 结束实体处理器
if (this.entityProcessors)
this.entityProcessors.end();
// 清空服务容器会调用所有服务的dispose方法包括所有EntitySystem
this._services.clear();
// 调用卸载方法
this.unload();
}
/**
* 更新场景,更新实体组件、实体处理器等
* 更新场景
*/
public update() {
// 更新实体列表
ComponentPoolManager.getInstance().update();
this.entities.updateLists();
// 更新实体处理器
if (this.entityProcessors != null)
this.entityProcessors.update();
// 更新所有EntitySystem
const systems = this.systems;
for (const system of systems) {
if (system.enabled) {
try {
system.update();
} catch (error) {
this.logger.error(`Error in system ${system.constructor.name}.update():`, error);
}
}
}
// 更新实体组
this.entities.update();
// 更新实体处理器的后处理方法
if (this.entityProcessors != null)
this.entityProcessors.lateUpdate();
// LateUpdate
for (const system of systems) {
if (system.enabled) {
try {
system.lateUpdate();
} catch (error) {
this.logger.error(`Error in system ${system.constructor.name}.lateUpdate():`, error);
}
}
}
}
/**
@@ -202,6 +305,16 @@ export class Scene implements IScene {
return this.addEntity(entity);
}
/**
* 清除所有EntitySystem的实体缓存
* 当实体或组件发生变化时调用
*/
public clearSystemEntityCaches(): void {
for (const system of this.systems) {
system.clearEntityCache();
}
}
/**
* 在场景的实体列表中添加一个实体
* @param entity 要添加的实体
@@ -210,13 +323,18 @@ export class Scene implements IScene {
public addEntity(entity: Entity, deferCacheClear: boolean = false) {
this.entities.add(entity);
entity.scene = this;
// 将实体添加到查询系统(可延迟缓存清理)
this.querySystem.addEntity(entity, deferCacheClear);
// 清除系统缓存以确保系统能及时发现新实体
if (!deferCacheClear) {
this.clearSystemEntityCaches();
}
// 触发实体添加事件
this.eventSystem.emitSync('entity:added', { entity, scene: this });
return entity;
}
@@ -251,13 +369,35 @@ export class Scene implements IScene {
}
/**
* 批量销毁实体
*/
public destroyEntities(entities: Entity[]): void {
if (entities.length === 0) return;
for (const entity of entities) {
entity._isDestroyed = true;
}
for (const entity of entities) {
entity.removeAllComponents();
}
for (const entity of entities) {
this.entities.remove(entity);
this.querySystem.removeEntity(entity);
}
this.querySystem.clearCache();
this.clearSystemEntityCaches();
}
/**
* 从场景中删除所有实体
*/
public destroyAllEntities() {
this.entities.removeAllEntities();
// 清理查询系统中的实体引用和缓存
this.querySystem.setEntities([]);
}
@@ -308,19 +448,187 @@ export class Scene implements IScene {
}
/**
* 在场景中添加一个EntitySystem处理器
* @param processor 处理器
* 查询拥有所有指定组件的实体
*
* @param componentTypes - 组件类型数组
* @returns 查询结果
*
* @example
* ```typescript
* const result = scene.queryAll(Position, Velocity);
* for (const entity of result.entities) {
* const pos = entity.getComponent(Position);
* const vel = entity.getComponent(Velocity);
* }
* ```
*/
public addEntityProcessor(processor: EntitySystem) {
if (this.entityProcessors.processors.includes(processor)) {
return processor;
public queryAll(...componentTypes: any[]): { entities: readonly Entity[] } {
return this.querySystem.queryAll(...componentTypes);
}
/**
* 查询拥有任意一个指定组件的实体
*
* @param componentTypes - 组件类型数组
* @returns 查询结果
*/
public queryAny(...componentTypes: any[]): { entities: readonly Entity[] } {
return this.querySystem.queryAny(...componentTypes);
}
/**
* 查询不包含指定组件的实体
*
* @param componentTypes - 组件类型数组
* @returns 查询结果
*/
public queryNone(...componentTypes: any[]): { entities: readonly Entity[] } {
return this.querySystem.queryNone(...componentTypes);
}
/**
* 创建类型安全的查询构建器
*
* @returns 查询构建器,支持链式调用
*
* @example
* ```typescript
* // 使用查询构建器
* const matcher = scene.query()
* .withAll(Position, Velocity)
* .withNone(Disabled)
* .buildMatcher();
*
* // 在System中使用
* class MovementSystem extends EntitySystem {
* constructor() {
* super(matcher);
* }
* }
* ```
*/
public query(): TypedQueryBuilder {
return new TypedQueryBuilder();
}
/**
* 在场景中添加一个EntitySystem处理器
*
* 支持两种使用方式:
* 1. 传入类型推荐自动使用DI创建实例支持@Injectable和@Inject装饰器
* 2. 传入实例:直接使用提供的实例
*
* @param systemTypeOrInstance 系统类型或系统实例
* @returns 添加的处理器实例
*
* @example
* ```typescript
* // 方式1传入类型自动DI推荐
* @Injectable()
* class PhysicsSystem extends EntitySystem {
* constructor(@Inject(CollisionSystem) private collision: CollisionSystem) {
* super(Matcher.of(Transform));
* }
* }
* scene.addEntityProcessor(PhysicsSystem);
*
* // 方式2传入实例
* const system = new MySystem();
* scene.addEntityProcessor(system);
* ```
*/
public addEntityProcessor<T extends EntitySystem>(
systemTypeOrInstance: ServiceType<T> | T
): T {
let system: T;
let constructor: any;
if (typeof systemTypeOrInstance === 'function') {
constructor = systemTypeOrInstance;
if (this._services.isRegistered(constructor)) {
return this._services.resolve(constructor) as T;
}
if (isInjectable(constructor)) {
system = createInstance(constructor, this._services) as T;
} else {
system = new (constructor as any)() as T;
}
} else {
system = systemTypeOrInstance;
constructor = system.constructor;
if (this._services.isRegistered(constructor)) {
return system;
}
}
processor.scene = this;
this.entityProcessors.add(processor);
processor.initialize();
processor.setUpdateOrder(this.entityProcessors.count - 1);
return processor;
system.scene = this;
system.setPerformanceMonitor(this._performanceMonitor);
const metadata = getSystemMetadata(constructor);
if (metadata?.updateOrder !== undefined) {
system.setUpdateOrder(metadata.updateOrder);
}
if (metadata?.enabled !== undefined) {
system.enabled = metadata.enabled;
}
this._services.registerInstance(constructor, system);
injectProperties(system, this._services);
system.initialize();
return system;
}
/**
* 批量注册EntitySystem到场景使用DI
*
* 自动按照依赖顺序注册多个System。
* 所有System必须使用@Injectable装饰器标记。
*
* @param systemTypes System类型数组
* @returns 注册的System实例数组
*
* @example
* ```typescript
* @Injectable()
* @ECSSystem('Collision', { updateOrder: 5 })
* class CollisionSystem extends EntitySystem implements IService {
* constructor() { super(Matcher.of(Collider)); }
* dispose() {}
* }
*
* @Injectable()
* @ECSSystem('Physics', { updateOrder: 10 })
* class PhysicsSystem extends EntitySystem implements IService {
* constructor(@Inject(CollisionSystem) private collision: CollisionSystem) {
* super(Matcher.of(Transform, RigidBody));
* }
* dispose() {}
* }
*
* // 批量注册(自动解析依赖顺序)
* scene.registerSystems([
* CollisionSystem,
* PhysicsSystem, // 自动注入CollisionSystem
* RenderSystem
* ]);
* ```
*/
public registerSystems(systemTypes: Array<ServiceType<EntitySystem>>): EntitySystem[] {
const registeredSystems: EntitySystem[] = [];
for (const systemType of systemTypes) {
const system = this.addEntityProcessor(systemType);
registeredSystems.push(system);
}
return registeredSystems;
}
/**
@@ -335,18 +643,44 @@ export class Scene implements IScene {
* 从场景中删除EntitySystem处理器
* @param processor 要删除的处理器
*/
public removeEntityProcessor(processor: EntitySystem) {
this.entityProcessors.remove(processor);
public removeEntityProcessor(processor: EntitySystem): void {
const constructor = processor.constructor as any;
// 从ServiceContainer移除
this._services.unregister(constructor);
// 重置System状态
processor.reset();
processor.scene = null;
}
/**
* 从场景中删除系统removeEntityProcessor的别名
* @param system 系统
*/
public removeSystem(system: EntitySystem) {
this.removeEntityProcessor(system);
}
/**
* 获取指定类型的EntitySystem处理器
*
* @deprecated 推荐使用依赖注入代替此方法。使用 `scene.services.resolve(SystemType)` 或在System构造函数中使用 `@Inject(SystemType)` 装饰器。
*
* @param type 处理器类型
* @returns 处理器实例如果未找到则返回null
*
* @example
* ```typescript
* @Injectable()
* class MySystem extends EntitySystem {
* constructor(@Inject(PhysicsSystem) private physics: PhysicsSystem) {
* super();
* }
* }
* ```
*/
public getEntityProcessor<T extends EntitySystem>(type: new (...args: unknown[]) => T): T | null {
return this.entityProcessors.getProcessor(type);
return this._services.tryResolve(type as any) as T | null;
}
/**
@@ -359,7 +693,7 @@ export class Scene implements IScene {
} {
return {
entityCount: this.entities.count,
processorCount: this.entityProcessors.count,
processorCount: this.systems.length,
componentStorageStats: this.componentStorageManager.getAllStats()
};
}
@@ -386,10 +720,11 @@ export class Scene implements IScene {
}>;
componentStats: Map<string, any>;
} {
const systems = this.systems;
return {
name: this.name || this.constructor.name,
entityCount: this.entities.count,
processorCount: this.entityProcessors.count,
processorCount: systems.length,
isRunning: this._didSceneBegin,
entities: this.entities.buffer.map(entity => ({
name: entity.name,
@@ -397,7 +732,7 @@ export class Scene implements IScene {
componentCount: entity.components.length,
componentTypes: entity.components.map(c => getComponentInstanceTypeName(c))
})),
processors: this.entityProcessors.processors.map(processor => ({
processors: systems.map(processor => ({
name: getSystemInstanceTypeName(processor),
updateOrder: processor.updateOrder,
entityCount: (processor as any)._entities?.length || 0
@@ -405,4 +740,200 @@ export class Scene implements IScene {
componentStats: this.componentStorageManager.getAllStats()
};
}
/**
* 序列化场景
*
* 将场景及其所有实体、组件序列化为JSON字符串或二进制Uint8Array
*
* @param options 序列化选项
* @returns 序列化后的数据JSON字符串或二进制Uint8Array
*
* @example
* ```typescript
* // JSON格式
* const jsonData = scene.serialize({
* format: 'json',
* pretty: true
* });
*
* // 二进制格式(更小、更快)
* const binaryData = scene.serialize({
* format: 'binary'
* });
* ```
*/
public serialize(options?: SceneSerializationOptions): string | Uint8Array {
return SceneSerializer.serialize(this, options);
}
/**
* 反序列化场景
*
* 从序列化数据恢复场景状态
*
* @param saveData 序列化的数据JSON字符串或二进制Uint8Array
* @param options 反序列化选项
*
* @example
* ```typescript
* // 从JSON恢复自动从ComponentRegistry获取组件类型
* scene.deserialize(jsonData, {
* strategy: 'replace'
* });
*
* // 从二进制恢复
* scene.deserialize(binaryData, {
* strategy: 'replace'
* });
* ```
*/
public deserialize(saveData: string | Uint8Array, options?: SceneDeserializationOptions): void {
SceneSerializer.deserialize(this, saveData, options);
}
// ==================== 增量序列化 API ====================
/** 增量序列化的基础快照 */
private _incrementalBaseSnapshot?: any;
/**
* 创建增量序列化的基础快照
*
* 在需要进行增量序列化前,先调用此方法创建基础快照
*
* @param options 序列化选项
*
* @example
* ```typescript
* // 创建基础快照
* scene.createIncrementalSnapshot();
*
* // 进行一些修改...
* entity.addComponent(new PositionComponent(100, 200));
*
* // 计算增量变更
* const incremental = scene.serializeIncremental();
* ```
*/
public createIncrementalSnapshot(options?: IncrementalSerializationOptions): void {
this._incrementalBaseSnapshot = IncrementalSerializer.createSnapshot(this, options);
}
/**
* 增量序列化场景
*
* 只序列化相对于基础快照的变更部分
*
* @param options 序列化选项
* @returns 增量快照对象
*
* @example
* ```typescript
* // 创建基础快照
* scene.createIncrementalSnapshot();
*
* // 修改场景
* const entity = scene.createEntity('NewEntity');
* entity.addComponent(new PositionComponent(50, 100));
*
* // 获取增量变更
* const incremental = scene.serializeIncremental();
* console.log(`变更数量: ${incremental.entityChanges.length}`);
*
* // 序列化为JSON
* const json = IncrementalSerializer.serializeIncremental(incremental);
* ```
*/
public serializeIncremental(options?: IncrementalSerializationOptions): IncrementalSnapshot {
if (!this._incrementalBaseSnapshot) {
throw new Error('必须先调用 createIncrementalSnapshot() 创建基础快照');
}
return IncrementalSerializer.computeIncremental(
this,
this._incrementalBaseSnapshot,
options
);
}
/**
* 应用增量变更到场景
*
* @param incremental 增量快照数据IncrementalSnapshot对象、JSON字符串或二进制Uint8Array
* @param componentRegistry 组件类型注册表(可选,默认使用全局注册表)
*
* @example
* ```typescript
* // 应用增量变更对象
* scene.applyIncremental(incrementalSnapshot);
*
* // 从JSON字符串应用
* const jsonData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'json' });
* scene.applyIncremental(jsonData);
*
* // 从二进制Uint8Array应用
* const binaryData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'binary' });
* scene.applyIncremental(binaryData);
* ```
*/
public applyIncremental(
incremental: IncrementalSnapshot | string | Uint8Array,
componentRegistry?: Map<string, any>
): void {
const isSerializedData = typeof incremental === 'string' ||
incremental instanceof Uint8Array;
const snapshot = isSerializedData
? IncrementalSerializer.deserializeIncremental(incremental as string | Uint8Array)
: incremental as IncrementalSnapshot;
const registry = componentRegistry || ComponentRegistry.getAllComponentNames() as Map<string, any>;
IncrementalSerializer.applyIncremental(this, snapshot, registry);
}
/**
* 更新增量快照基准
*
* 将当前场景状态设为新的增量序列化基准
*
* @param options 序列化选项
*
* @example
* ```typescript
* // 创建初始快照
* scene.createIncrementalSnapshot();
*
* // 进行一些修改并序列化
* const incremental1 = scene.serializeIncremental();
*
* // 更新基准,之后的增量将基于当前状态
* scene.updateIncrementalSnapshot();
*
* // 继续修改
* const incremental2 = scene.serializeIncremental();
* ```
*/
public updateIncrementalSnapshot(options?: IncrementalSerializationOptions): void {
this.createIncrementalSnapshot(options);
}
/**
* 清除增量快照
*
* 释放快照占用的内存
*/
public clearIncrementalSnapshot(): void {
this._incrementalBaseSnapshot = undefined;
}
/**
* 检查是否有增量快照
*
* @returns 如果已创建增量快照返回true
*/
public hasIncrementalSnapshot(): boolean {
return this._incrementalBaseSnapshot !== undefined;
}
}

View File

@@ -0,0 +1,262 @@
import { IScene } from './IScene';
import { ECSFluentAPI, createECSAPI } from './Core/FluentAPI';
import { Time } from '../Utils/Time';
import { createLogger } from '../Utils/Logger';
import type { IService } from '../Core/ServiceContainer';
import { World } from './World';
/**
* 单场景管理器
*
* 适用场景:
* - 单人游戏
* - 简单场景切换
* - 不需要多World隔离的项目
*
* 特点:
* - 轻量级,零额外开销
* - 简单直观的API
* - 支持延迟场景切换
* - 自动管理ECS API
*
* @example
* ```typescript
* // 初始化Core
* Core.create({ debug: true });
*
* // 创建场景管理器
* const sceneManager = new SceneManager();
*
* // 设置场景
* class GameScene extends Scene {
* initialize() {
* const player = this.createEntity('Player');
* player.addComponent(new Transform(100, 100));
* }
* }
*
* sceneManager.setScene(new GameScene());
*
* // 游戏循环
* function gameLoop(deltaTime: number) {
* Core.update(deltaTime); // 更新全局服务
* sceneManager.update(); // 更新场景
* }
*
* // 延迟切换场景(下一帧生效)
* sceneManager.loadScene(new MenuScene());
* ```
*/
export class SceneManager implements IService {
/**
* 内部默认World
*/
private _defaultWorld: World;
/**
* 待切换的下一个场景(延迟切换用)
*/
private _nextScene: IScene | null = null;
/**
* ECS流式API
*/
private _ecsAPI: ECSFluentAPI | null = null;
/**
* 日志器
*/
private _logger = createLogger('SceneManager');
/**
* 场景切换回调函数
*/
private _onSceneChangedCallback?: () => void;
/**
* 默认场景ID
*/
private static readonly DEFAULT_SCENE_ID = '__main__';
constructor() {
this._defaultWorld = new World({ name: '__default__' });
this._defaultWorld.start();
}
/**
* 设置场景切换回调
*
* @param callback 场景切换时的回调函数
* @internal
*/
public setSceneChangedCallback(callback: () => void): void {
this._onSceneChangedCallback = callback;
}
/**
* 设置当前场景(立即切换)
*
* 会自动处理旧场景的结束和新场景的初始化。
*
* @param scene - 要设置的场景实例
* @returns 返回设置的场景实例,便于链式调用
*
* @example
* ```typescript
* const gameScene = sceneManager.setScene(new GameScene());
* console.log(gameScene.name); // 可以立即使用返回的场景
* ```
*/
public setScene<T extends IScene>(scene: T): T {
// 移除旧场景
this._defaultWorld.removeAllScenes();
// 通过 World 创建新场景
this._defaultWorld.createScene(SceneManager.DEFAULT_SCENE_ID, scene);
this._defaultWorld.setSceneActive(SceneManager.DEFAULT_SCENE_ID, true);
// 重建ECS API
if (scene.querySystem && scene.eventSystem) {
this._ecsAPI = createECSAPI(scene, scene.querySystem, scene.eventSystem);
} else {
this._ecsAPI = null;
}
// 触发场景切换回调
Time.sceneChanged();
// 通知调试管理器(通过回调)
if (this._onSceneChangedCallback) {
this._onSceneChangedCallback();
}
this._logger.info(`Scene changed to: ${scene.name}`);
return scene;
}
/**
* 延迟加载场景(下一帧切换)
*
* 场景不会立即切换,而是在下一次调用 update() 时切换。
* 这对于避免在当前帧的中途切换场景很有用。
*
* @param scene - 要加载的场景实例
*
* @example
* ```typescript
* // 在某个System中触发场景切换
* class GameOverSystem extends EntitySystem {
* process(entities: readonly Entity[]) {
* if (playerHealth <= 0) {
* sceneManager.loadScene(new GameOverScene());
* // 当前帧继续执行,场景将在下一帧切换
* }
* }
* }
* ```
*/
public loadScene<T extends IScene>(scene: T): void {
this._nextScene = scene;
this._logger.info(`Scheduled scene load: ${scene.name}`);
}
/**
* 获取当前活跃的场景
*
* @returns 当前场景实例如果没有场景则返回null
*/
public get currentScene(): IScene | null {
return this._defaultWorld.getScene(SceneManager.DEFAULT_SCENE_ID);
}
/**
* 获取ECS流式API
*
* 提供便捷的实体查询、事件发射等功能。
*
* @returns ECS API实例如果当前没有场景则返回null
*
* @example
* ```typescript
* const api = sceneManager.api;
* if (api) {
* // 查询所有敌人
* const enemies = api.find(Enemy, Transform);
*
* // 发射事件
* api.emit('game:start', { level: 1 });
* }
* ```
*/
public get api(): ECSFluentAPI | null {
return this._ecsAPI;
}
/**
* 更新场景
*
* 应该在每帧的游戏循环中调用。
* 会自动处理延迟场景切换。
*
* @example
* ```typescript
* function gameLoop(deltaTime: number) {
* Core.update(deltaTime);
* sceneManager.update(); // 每帧调用
* }
* ```
*/
public update(): void {
// 处理延迟场景切换
if (this._nextScene) {
this.setScene(this._nextScene);
this._nextScene = null;
}
// 通过 World 统一更新
this._defaultWorld.updateGlobalSystems();
this._defaultWorld.updateScenes();
}
/**
* 销毁场景管理器
*
* 会自动结束当前场景并清理所有资源。
* 通常在应用程序关闭时调用。
*/
public destroy(): void {
this._logger.info('SceneManager destroying');
this._defaultWorld.destroy();
this._nextScene = null;
this._ecsAPI = null;
this._logger.info('SceneManager destroyed');
}
/**
* 检查是否有活跃场景
*
* @returns 如果有活跃场景返回true否则返回false
*/
public get hasScene(): boolean {
return this._defaultWorld.getScene(SceneManager.DEFAULT_SCENE_ID) !== null;
}
/**
* 检查是否有待切换的场景
*
* @returns 如果有待切换场景返回true否则返回false
*/
public get hasPendingScene(): boolean {
return this._nextScene !== null;
}
/**
* 释放资源IService接口
*/
public dispose(): void {
this.destroy();
}
}

Some files were not shown because too many files have changed in this diff Show More