Compare commits

...

440 Commits

Author SHA1 Message Date
yhh
566e1977fd refactor: 改用 Imgur 图床上传图片 2025-12-04 09:56:10 +08:00
yhh
17f6259f43 chore: 删除测试图片 2025-12-04 09:54:46 +08:00
yhh
5d3483fc65 chore: 更新 pnpm-lock.yaml 2025-12-04 09:47:12 +08:00
yhh
d07a5d81fc Merge branch 'master' into feat/github-forum 2025-12-04 09:46:22 +08:00
yhh
6a4e6fbc04 feat(editor): 添加 GitHub Discussions 社区论坛功能 2025-12-04 09:45:16 +08:00
YHH
4b8d22ac32 Upload forum image: 1764812524764-ic2nic.png 2025-12-04 09:42:06 +08:00
YHH
9cd873da14 Merge pull request #260 from esengine/fix/docs-dead-links
fix(docs): 修复英文文档dead links问题
2025-12-03 23:02:48 +08:00
yhh
c1799bf7b3 fix(docs): 修复英文文档dead links问题 2025-12-03 23:01:31 +08:00
YHH
85be826b62 Merge pull request #259 from esengine/feat/docs-i18n-clean
feat(docs): 添加中英文国际化支持
2025-12-03 22:51:54 +08:00
yhh
dd1ae97de7 feat(docs): 添加中英文国际化支持 2025-12-03 22:48:22 +08:00
YHH
63f006ab62 feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)
* feat(platform-common): 添加WASM加载器和环境检测API

* feat(rapier2d): 新增Rapier2D WASM绑定包

* feat(physics-rapier2d): 添加跨平台WASM加载器

* feat(asset-system): 添加运行时资产目录和bundle格式

* feat(asset-system-editor): 新增编辑器资产管理包

* feat(editor-core): 添加构建系统和模块管理

* feat(editor-app): 重构浏览器预览使用import maps

* feat(platform-web): 添加BrowserRuntime和资产读取

* feat(engine): 添加材质系统和着色器管理

* feat(material): 新增材质系统和着色器编辑器

* feat(tilemap): 增强tilemap编辑器和动画系统

* feat(modules): 添加module.json配置

* feat(core): 添加module.json和类型定义更新

* chore: 更新依赖和构建配置

* refactor(plugins): 更新插件模板使用ModuleManifest

* chore: 添加第三方依赖库

* chore: 移除BehaviourTree-ai和ecs-astar子模块

* docs: 更新README和文档主题样式

* fix: 修复Rust文档测试和添加rapier2d WASM绑定

* fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题

* feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea)

* fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖

* fix: 添加缺失的包依赖修复CI构建

* fix: 修复CodeQL检测到的代码问题

* fix: 修复构建错误和缺失依赖

* fix: 修复类型检查错误

* fix(material-system): 修复tsconfig配置支持TypeScript项目引用

* fix(editor-core): 修复Rollup构建配置添加tauri external

* fix: 修复CodeQL检测到的代码问题

* fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00
YHH
caf7622aa0 Merge pull request #257 from esengine/feat/system-stable-sorting
feat(ecs): 添加系统稳定排序支持
2025-12-03 21:01:52 +08:00
yhh
d746cf3bb8 feat(ecs): 添加系统稳定排序支持 2025-12-03 20:54:34 +08:00
github-actions[bot]
88af781d78 chore(editor): bump version to 1.0.13 (#255)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-12-02 00:07:52 +08:00
yhh
15d5d37e50 fix(docs): 修复 hierarchy.md 中的死链接
将不存在的 ./transform.md 链接替换为 ./component.md
2025-12-01 23:36:20 +08:00
imgbot[bot]
b9aaf894d7 [ImgBot] Optimize images (#252)
*Total -- 1,159.60kb -> 862.13kb (25.65%)

/screenshots/main_screetshot.png -- 175.29kb -> 84.97kb (51.52%)
/screenshots/settings.png -- 44.17kb -> 25.59kb (42.06%)
/screenshots/plugin_manager.png -- 60.70kb -> 37.42kb (38.35%)
/screenshots/about.png -- 32.89kb -> 23.61kb (28.21%)
/screenshots/performance_profiler.png -- 564.36kb -> 420.67kb (25.46%)
/packages/editor-app/src-tauri/icons/128x128.png -- 4.76kb -> 4.40kb (7.55%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-512@2x.png -- 58.30kb -> 54.30kb (6.86%)
/packages/editor-app/src-tauri/icons/Square107x107Logo.png -- 3.96kb -> 3.73kb (5.75%)
/packages/editor-app/src-tauri/icons/Square142x142Logo.png -- 5.44kb -> 5.13kb (5.69%)
/packages/editor-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -- 3.16kb -> 2.98kb (5.69%)
/packages/editor-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -- 3.16kb -> 2.98kb (5.69%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -- 2.61kb -> 2.47kb (5.35%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-40x40@2x.png -- 2.61kb -> 2.47kb (5.35%)
/packages/editor-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -- 4.06kb -> 3.87kb (4.73%)
/packages/editor-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -- 7.68kb -> 7.34kb (4.41%)
/packages/editor-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -- 7.68kb -> 7.34kb (4.41%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-40x40@3x.png -- 4.58kb -> 4.38kb (4.33%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-60x60@2x.png -- 4.58kb -> 4.38kb (4.33%)
/packages/editor-app/src-tauri/icons/64x64.png -- 2.11kb -> 2.02kb (4.21%)
/packages/editor-app/src-tauri/icons/Square310x310Logo.png -- 13.92kb -> 13.36kb (4.04%)
/packages/editor-app/src-tauri/icons/Square89x89Logo.png -- 2.99kb -> 2.87kb (3.95%)
/packages/editor-app/src-tauri/icons/Square284x284Logo.png -- 12.53kb -> 12.04kb (3.89%)
/packages/editor-app/src-tauri/icons/Square150x150Logo.png -- 5.84kb -> 5.62kb (3.83%)
/packages/editor-app/src-tauri/icons/icon.png -- 25.58kb -> 24.61kb (3.81%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -- 6.73kb -> 6.47kb (3.75%)
/packages/editor-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -- 6.27kb -> 6.03kb (3.72%)
/packages/editor-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -- 14.68kb -> 14.15kb (3.59%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-76x76@2x.png -- 6.06kb -> 5.85kb (3.51%)
/packages/editor-app/src-tauri/icons/128x128@2x.png -- 10.88kb -> 10.53kb (3.21%)
/packages/editor-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -- 5.66kb -> 5.48kb (3.21%)
/packages/editor-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -- 5.66kb -> 5.48kb (3.21%)
/packages/editor-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -- 9.02kb -> 8.78kb (2.74%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-60x60@3x.png -- 7.22kb -> 7.02kb (2.65%)
/packages/editor-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -- 20.92kb -> 20.40kb (2.49%)
/packages/editor-app/src-tauri/icons/Square71x71Logo.png -- 2.38kb -> 2.33kb (1.81%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-76x76@1x.png -- 2.52kb -> 2.47kb (1.79%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-20x20@3x.png -- 2.01kb -> 1.98kb (1.46%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-29x29@3x.png -- 2.84kb -> 2.81kb (1.1%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -- 1.91kb -> 1.89kb (0.82%)
/packages/editor-app/src-tauri/icons/ios/AppIcon-29x29@2x.png -- 1.91kb -> 1.89kb (0.82%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: YHH <359807859@qq.com>
2025-12-01 23:34:47 +08:00
YHH
460cdb5af4 Feature/docs improvement (#254)
* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能

* feat(docs): 重构文档站主题样式

* chore(ci): 升级所有 workflow 的 pnpm 版本从 v8 到 v10
2025-12-01 23:33:04 +08:00
github-actions[bot]
290bd9858e chore(core): release v2.2.18 (#253)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-12-01 22:34:05 +08:00
YHH
b42a7b4e43 Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
2025-12-01 22:28:51 +08:00
github-actions[bot]
189714c727 chore(editor): bump version to 1.0.12 (#250)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-30 01:07:19 +08:00
YHH
987051acd4 Feature/advanced profiler (#249)
* feat(profiler): 实现高级性能分析器

* test(core): 添加 ProfilerSDK 和 AdvancedProfilerCollector 测试覆盖

* test(core): 添加 ProfilerSDK 和 AdvancedProfilerCollector 测试覆盖

* test(core): 添加 ProfilerSDK 和 AdvancedProfilerCollector 测试覆盖
2025-11-30 00:53:01 +08:00
YHH
374e08a79e feat(profiler): 实现高级性能分析器 (#248)
* feat(profiler): 实现高级性能分析器

* test(core): 添加 ProfilerSDK 和 AdvancedProfilerCollector 测试覆盖

* test(core): 添加 ProfilerSDK 和 AdvancedProfilerCollector 测试覆盖
2025-11-30 00:22:47 +08:00
YHH
359886c72f Feature/physics and tilemap enhancement (#247)
* feat(behavior-tree,tilemap): 修复编辑器连线缩放问题并增强插件系统

* feat(node-editor,blueprint): 新增通用节点编辑器和蓝图可视化脚本系统

* feat(editor,tilemap): 优化编辑器UI样式和Tilemap编辑器功能

* fix: 修复CodeQL安全警告和CI类型检查错误

* fix: 修复CodeQL安全警告和CI类型检查错误

* fix: 修复CodeQL安全警告和CI类型检查错误
2025-11-29 23:00:48 +08:00
yhh
f03b73b58e docs: 完善装饰器和 Matcher API 文档 2025-11-28 11:03:34 +08:00
github-actions[bot]
18d20df4da chore(core): release v2.2.17 (#246)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-28 11:03:11 +08:00
github-actions[bot]
c5642a8605 chore(editor): bump version to 1.0.11 (#245)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-28 11:01:04 +08:00
YHH
673f5e5855 feat(physics): 集成 Rapier2D 物理引擎并修复预览重置问题 (#244)
* feat(physics): 集成 Rapier2D 物理引擎并修复预览重置问题

* fix: 修复 CI 流程并清理代码
2025-11-28 10:32:28 +08:00
YHH
cabb625a17 Feature/UI input system fix (#243)
* feat(ui): 实现编辑器预览模式下的 UI 输入系统

* feat(platform-web): 为浏览器运行时添加 UI 输入系统绑定
2025-11-27 22:31:05 +08:00
github-actions[bot]
b8f05b79b0 chore(core): release v2.2.16 (#242)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-27 21:13:09 +08:00
github-actions[bot]
b22faaac86 chore(editor): bump version to 1.0.10 (#241)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-27 21:05:40 +08:00
YHH
107439d70c Feature/runtime cdn and plugin loader (#240)
* feat(ui): 完善 UI 布局系统和编辑器可视化工具

* refactor: 移除 ModuleRegistry,统一使用 PluginManager 插件系统

* fix: 修复 CodeQL 警告并提升测试覆盖率

* refactor: 分离运行时入口点,解决 runtime bundle 包含 React 的问题

* fix(ci): 添加 editor-core 和 editor-runtime 到 CI 依赖构建步骤

* docs: 完善 ServiceContainer 文档,新增 Symbol.for 模式和 @InjectProperty 说明

* fix(ci): 修复 type-check 失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): behavior-tree 构建添加 @tauri-apps 外部依赖

* fix(ci): behavior-tree 添加 @tauri-apps/plugin-fs 类型依赖

* fix(ci): platform-web 添加缺失的 behavior-tree 依赖

* fix(lint): 移除正则表达式中不必要的转义字符
2025-11-27 20:42:46 +08:00
github-actions[bot]
71869b1a58 chore(editor): bump version to 1.0.9 (#239)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-26 11:47:09 +08:00
yhh
9aed3134cf fix(ci): 修复 Windows 上 mkdir 命令报错 2025-11-26 11:28:14 +08:00
yhh
3ff57aff37 fix(ci): 修复 release-editor workflow 构建顺序 2025-11-26 11:22:43 +08:00
yhh
152c0541b8 fix(ci): 修复 release-editor workflow 构建顺序 2025-11-26 11:15:38 +08:00
YHH
7b14fa2da4 feat(editor): 添加 ECS UI 系统和编辑器更新优化 (#238) 2025-11-26 11:08:10 +08:00
YHH
3fb6f919f8 Feature/tilemap editor (#237)
* feat: 添加 Tilemap 编辑器插件和组件生命周期支持

* feat(editor-core): 添加声明式插件注册 API

* feat(editor-core): 改进tiledmap结构合并tileset进tiledmapeditor

* feat: 添加 editor-runtime SDK 和插件系统改进

* fix(ci): 修复SceneResourceManager里变量未使用问题
2025-11-25 22:23:19 +08:00
github-actions[bot]
551ca7805d chore(core): release v2.2.15 (#236)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-23 22:52:05 +08:00
yhh
8ab25fe293 fix(ci): 使用纯 bash+node 实现版本更新绕过 workspace 协议问题 2025-11-23 22:49:54 +08:00
yhh
eea7ed9e58 fix(ci): 将 npm version 改为 pnpm version 修复 workspace 协议问题 2025-11-23 22:46:21 +08:00
yhh
0279cf6d27 fix(ci): 使用 pnpm publish 修复 workspace:* 协议不支持的问题 2025-11-23 22:42:23 +08:00
yhh
0dff1ad2ad fix(ci): 修复 npm 发布时 workspace:* 协议不支持的问题 2025-11-23 22:36:54 +08:00
yhh
95fbcca66f chore: 移除渲染系统调试日志 2025-11-23 22:26:46 +08:00
github-actions[bot]
a61baa83a7 chore(editor): bump version to 1.0.8 (#235)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-23 22:21:20 +08:00
yhh
afebeecd68 fix(ci): 修复 Tauri 构建缺少 runtime 文件的问题 2025-11-23 22:08:38 +08:00
yhh
f4e9925319 fix(ci): 修复 Tauri 构建缺少 runtime 文件的问题 2025-11-23 21:57:10 +08:00
YHH
32460ac133 feat(editor): 优化编辑器UI和改进核心功能 (#234)
* feat(editor): 优化编辑器UI和改进核心功能

* feat(editor): 优化编辑器UI和改进核心功能
2025-11-23 21:45:10 +08:00
github-actions[bot]
4d95a7f044 chore(editor): bump version to 1.0.7 (#233)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-23 16:30:14 +08:00
yhh
57f919fbe0 fix(ci): 移除release-editor工作流中有问题的TypeScript缓存步骤 2025-11-23 16:12:42 +08:00
yhh
1cb9a0e58f fix(ci): 修复release-editor工作流hashFiles语法错误 2025-11-23 15:47:23 +08:00
yhh
1da43ee822 fix: 修复 release-editor workflow 构建问题 2025-11-23 15:26:17 +08:00
yhh
f4c7563763 chore: 移除 network 相关包并修复 CI 问题 2025-11-23 15:13:51 +08:00
YHH
a3f7cc38b1 Feature/render pipeline (#232)
* refactor(engine): 重构2D渲染管线坐标系统

* feat(engine): 完善2D渲染管线和编辑器视口功能

* feat(editor): 实现Viewport变换工具系统

* feat(editor): 优化Inspector渲染性能并修复Gizmo变换工具显示

* feat(editor): 实现Run on Device移动预览功能

* feat(editor): 添加组件属性控制和依赖关系系统

* feat(editor): 实现动画预览功能和优化SpriteAnimator编辑器

* feat(editor): 修复SpriteAnimator动画预览功能并迁移CI到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(ci): 迁移项目到pnpm并修复CI构建问题

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 移除 network 相关包

* chore: 移除 network 相关包
2025-11-23 14:49:37 +08:00
github-actions[bot]
b15cbab313 chore(editor): bump version to 1.0.6 (#231)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-21 12:42:30 +08:00
yhh
504b9ffb66 fix(ci): 添加编辑器工作流缺失的引擎构建步骤 2025-11-21 11:53:33 +08:00
github-actions[bot]
6226e3ff06 chore(core): release v2.2.14 (#230)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-21 11:40:22 +08:00
YHH
2621d7f659 refactor(core): 移除@Inject参数装饰器,统一使用@InjectProperty (#229)
* refactor(core): 移除@Inject参数装饰器,统一使用@InjectProperty

* refactor(core): 移除@Inject参数装饰器,统一使用@InjectProperty
2025-11-21 11:37:55 +08:00
YHH
a768b890fd feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 (#228)
* feat: 集成Rust WASM渲染引擎与TypeScript ECS框架

* feat: 增强编辑器UI功能与跨平台支持

* fix: 修复CI测试和类型检查问题

* fix: 修复CI问题并提高测试覆盖率

* fix: 修复CI问题并提高测试覆盖率
2025-11-21 10:03:18 +08:00
yhh
8b9616837d style(editor-app): 移除log信息 2025-11-20 09:51:29 +08:00
yhh
0d2948e60c feat(tools): rust工具初始化工具 2025-11-19 16:27:11 +08:00
YHH
ecfef727c8 feat: 实现可扩展的字段编辑器系统与专业资产选择器 (#227) 2025-11-19 14:54:03 +08:00
YHH
caed5428d5 refactor(editor-app): 改进架构和类型安全 (#226)
* refactor(editor-app): 改进架构和类型安全

* refactor(editor-app): 开始拆分 Inspector.tsx - 创建基础架构

* refactor(editor-app): 完成 Inspector.tsx 拆分

* refactor(editor-app): 优化 Inspector 类型定义,消除所有 any 使用

* refactor(editor): 实现可扩展的属性渲染器系统

* Potential fix for code scanning alert no. 231: Unused variable, import, function or class

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix(ci): 防止 Codecov 服务故障阻塞 CI 流程

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-11-18 22:28:13 +08:00
YHH
bce3a6e253 refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 (#216)
* refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构

* feat(editor): 添加插件市场功能

* feat(editor): 重构插件市场以支持版本管理和ZIP打包

* feat(editor): 重构插件发布流程并修复React渲染警告

* fix(plugin): 修复插件发布和市场的路径不一致问题

* feat: 重构插件发布流程并添加插件删除功能

* fix(editor): 完善插件删除功能并修复多个关键问题

* fix(auth): 修复自动登录与手动登录的竞态条件问题

* feat(editor): 重构插件管理流程

* feat(editor): 支持 ZIP 文件直接发布插件

- 新增 PluginSourceParser 解析插件源
- 重构发布流程支持文件夹和 ZIP 两种方式
- 优化发布向导 UI

* feat(editor): 插件市场支持多版本安装

- 插件解压到项目 plugins 目录
- 新增 Tauri 后端安装/卸载命令
- 支持选择任意版本安装
- 修复打包逻辑,保留完整 dist 目录结构

* feat(editor): 个人中心支持多版本管理

- 合并同一插件的不同版本
- 添加版本历史展开/折叠功能
- 禁止有待审核 PR 时更新插件

* fix(editor): 修复 InspectorRegistry 服务注册

- InspectorRegistry 实现 IService 接口
- 注册到 Core.services 供插件使用

* feat(behavior-tree-editor): 完善插件注册和文件操作

- 添加文件创建模板和操作处理器
- 实现右键菜单创建行为树功能
- 修复文件读取权限问题(使用 Tauri 命令)
- 添加 BehaviorTreeEditorPanel 组件
- 修复 rollup 配置支持动态导入

* feat(plugin): 完善插件构建和发布流程

* fix(behavior-tree-editor): 完整恢复编辑器并修复 Toast 集成

* fix(behavior-tree-editor): 修复节点选中、连线跟随和文件加载问题并优化性能

* fix(behavior-tree-editor): 修复端口连接失败问题并优化连线样式

* refactor(behavior-tree-editor): 移除调试面板功能简化代码结构

* refactor(behavior-tree-editor): 清理冗余代码合并重复逻辑

* feat(behavior-tree-editor): 完善编辑器核心功能增强扩展性

* fix(lint): 修复ESLint错误确保CI通过

* refactor(behavior-tree-editor): 优化编辑器工具栏和编译器功能

* refactor(behavior-tree-editor): 清理技术债务,优化代码质量

* fix(editor-app): 修复字符串替换安全问题
2025-11-18 14:46:51 +08:00
YHH
eac660b1a0 refactor(core): 统一参数命名 - worldId/sceneId 改为 worldName/sceneName (#225)
* refactor(core): 统一参数命名 - worldId/sceneId 改为 worldName/sceneName

* test(core): 更新测试用例以匹配新的错误消息

* refactor(core): 提高代码覆盖率 - 添加参数验证和测试
2025-11-15 00:20:17 +08:00
github-actions[bot]
af49870084 chore(core): release v2.2.13 (#224)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-14 12:13:47 +08:00
YHH
e2b316b3cc Fix/entity system dispose ondestroy (#223)
* fix(core): 修复 EntitySystem dispose 未调用 onDestroy 导致资源泄漏

* fix(core): 修复 Scene.end() 中 unload 调用时机导致用户无法清理资源
2025-11-14 12:10:59 +08:00
YHH
3a0544629d feat(core): 为 World 添加独立的服务容器 (#222)
* feat(core): 为 World 添加独立的服务容器

* test(core): 为 World 服务容器添加完整测试覆盖
2025-11-14 09:55:31 +08:00
LINGYE
609baace73 fix(logger): 移除自定义 factory 的缓存, 由使用方管理 (#221)
* fix(logger): 移除自定义 factory 的缓存, 由使用方管理

* test
2025-11-13 16:53:07 +08:00
LINGYE
b12cfba353 refactor(core): 移除 _activeWorlds 并优化 WorldManager 清理机制 (#220)
* refactor(core): 将 WorldManager 清理机制从定时器改为帧驱动

* refactor(core): 移除 WorldManager _activeWorlds 优化,简化状态管理

* test(core): 补充 WorldManager 测试用例

* docs(core): 更新 WorldManager cleanupFrameInterval 配置说明
2025-11-09 17:35:07 +08:00
LINGYE
6242c6daf3 fix(core): 修复 PerformanceMonitor 未遵循 Core debug 参数的问题 (#219)
- Core 传递 debug 配置到 WorldManager
- WorldManager 传递 debug 配置到 World
- World 在 debug=true 时为 Scene 注册并启用 PerformanceMonitor
- new Scene 的情况默认未开启,但暴露了 `performanceMonitor` 由使用者处理
2025-11-09 11:32:04 +08:00
github-actions[bot]
b5337de278 chore(core): release v2.2.12 (#218)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-11-07 12:12:44 +08:00
YHH
3512199ff4 fix(core): 移除fflate依赖,修复TextEncoder兼容性问题 (#217)
* fix(core): 移除fflate依赖,修复TextEncoder兼容性问题

* fix(core): 移除fflate依赖,修复TextEncoder兼容性问题
2025-11-07 12:10:52 +08:00
YHH
e03b106652 refactor(editor): 优化布局管理和行为树文件处理 2025-11-04 23:53:26 +08:00
YHH
f9afa22406 refactor(editor): 重构编辑器架构并增强行为树执行可视化 2025-11-04 18:29:28 +08:00
YHH
adfc7e91b3 Refactor/clean architecture phase1 (#215)
* refactor(editor): 建立Clean Architecture领域模型层

* refactor(editor): 实现应用层架构 - 命令模式、用例和状态管理

* refactor(editor): 实现展示层核心Hooks

* refactor(editor): 实现基础设施层和展示层组件

* refactor(editor): 迁移画布和连接渲染到 Clean Architecture 组件

* feat(editor): 集成应用层架构和命令模式,实现撤销/重做功能

* refactor(editor): UI组件拆分

* refactor(editor): 提取快速创建菜单逻辑

* refactor(editor): 重构BehaviorTreeEditor,提取组件和Hook

* refactor(editor): 提取端口连接和键盘事件Hook

* refactor(editor): 提取拖放处理Hook

* refactor(editor): 提取画布交互Hook和工具函数

* refactor(editor): 完成核心重构

* fix(editor): 修复节点无法创建和连接

* refactor(behavior-tree,editor): 重构节点子节点约束系统,实现元数据驱动的架构
2025-11-03 21:22:16 +08:00
YHH
40cde9c050 fix(editor): 修复行为树删除连接时children数组未同步清理的bug (#214) 2025-11-03 09:57:18 +08:00
YHH
ddc7a7750e Chore/lint fixes (#212)
* fix(eslint): 修复装饰器缩进配置

* fix(eslint): 修复装饰器缩进配置

* chore: 删除未使用的导入

* chore(lint): 移除未使用的导入和变量

* chore(lint): 修复editor-app中未使用的函数参数

* chore(lint): 修复未使用的赋值变量

* chore(eslint): 将所有错误级别改为警告以通过CI

* fix(codeql): 修复GitHub Advanced Security检测到的问题
2025-11-02 23:50:41 +08:00
YHH
50a01d9dd3 chore: 统一并强化ESLint配置规则 2025-11-02 12:45:47 +08:00
YHH
793aad0a5e chore: 移除旧版ESLint配置并更新子模块 2025-11-02 12:22:27 +08:00
YHH
9c1bf8dbed refactor(core): 移除全局EventBus,实现场景级事件隔离 (#211) 2025-11-01 18:19:23 +08:00
YHH
620f3eecc7 style(core): ESLint自动修复代码格式问题 (#210) 2025-11-01 17:41:50 +08:00
YHH
4355538d8d refactor(core): 重构Scene和Entity的代码质量,消除所有Lint警告 (#209) 2025-11-01 17:18:12 +08:00
YHH
3ad5dc9ca3 refactor(core): 改进事件系统类型安全并消除 ESLint 警告 (#208) 2025-11-01 16:12:18 +08:00
YHH
57c7e7be3f feat(core):统一 Core 库的命名规范和代码风格 (#207) 2025-11-01 10:23:46 +08:00
github-actions[bot]
6778ccace4 chore(editor): bump version to 1.0.5 (#201)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-10-31 17:42:41 +08:00
github-actions[bot]
1264232533 chore(behavior-tree): release v1.0.1 (#200)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-10-31 17:30:12 +08:00
YHH
61813e67b6 refactor(behavior-tree)!: 迁移到 Runtime 执行器架构 (#196)
* refactor(behavior-tree)!: 迁移到 Runtime 执行器架构

* fix(behavior-tree): 修复LogAction中的ReDoS安全漏洞

* feat(behavior-tree): 完善行为树核心功能并修复类型错误
2025-10-31 17:27:38 +08:00
YHH
c58e3411fd feat(core): 启用 TypeScript 最严格的类型检查 (#199)
* feat(core):  启用 TypeScript 最严格的类型检查

* ci: 配置 Codecov 以适应类型安全改进

* fix(core): 修复 CodeQL 安全警告

* fix(core): eslint.config.mjs
2025-10-31 16:14:23 +08:00
LINGYE
011d795361 perf(core): 优化 Scene.systems getter 避免每帧重复排序 (#197) 2025-10-30 23:27:37 +08:00
YHH
3f40a04370 Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-10-28 17:19:38 +08:00
YHH
fc042bb7d9 feat(editor): 添加插件多语言支持 2025-10-28 17:19:28 +08:00
github-actions[bot]
d051e52131 chore(core): release v2.2.11 (#195)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-10-28 14:12:56 +08:00
YHH
fb4316aeb9 Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-10-28 14:08:47 +08:00
YHH
683203919f refactor(core): 使用fflate替换msgpack以兼容小游戏环境 2025-10-28 14:08:34 +08:00
github-actions[bot]
a0cddbcae6 chore(core): release v2.2.10 (#194)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-10-28 11:58:05 +08:00
YHH
4e81fc7eba fix(docs): 修复行为树文档中的死链接 2025-10-28 11:54:39 +08:00
YHH
b410e2de47 fix(core): 移除TextEncoder依赖以兼容小游戏环境 2025-10-28 11:51:57 +08:00
YHH
9868c746e1 Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-10-28 11:45:48 +08:00
YHH
f0b4453a5f fix(behavior-tree): 修复插件节点执行问题并完善文档 2025-10-28 11:45:35 +08:00
github-actions[bot]
6b49471734 chore(editor-core): release v1.0.0 (#193)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-10-27 15:10:06 +08:00
YHH
fe791e83a8 fix(ci): 添加--access public参数以支持发布公开scoped包 2025-10-27 15:08:00 +08:00
YHH
edbc9eb27f ci(editor-core): 添加npm发布流程支持 2025-10-27 15:04:31 +08:00
github-actions[bot]
2f63034d9a chore(editor): bump version to 1.0.4 (#192)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
Co-authored-by: YHH <359807859@qq.com>
2025-10-27 10:25:27 +08:00
YHH
dee0e0284a chore(behavior-tree): 移除CI测试脚本 2025-10-27 10:12:35 +08:00
YHH
890e591f2a fix(behavior-tree): 修复发布时缺少publishConfig导致的402错误 2025-10-27 10:07:00 +08:00
YHH
cb6561e27b fix(ci): 修复首次发布时版本号未改变的错误 2025-10-27 10:03:14 +08:00
YHH
7ef70d7f9a Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-10-27 10:00:11 +08:00
YHH
86405c1dcd chore(ci): 禁用发布流程中的测试步骤 2025-10-27 10:00:02 +08:00
github-actions[bot]
60fa259285 chore(core): release v2.2.9 (#191)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-10-27 09:56:54 +08:00
YHH
27f86eece2 chore(ci): 将semantic-release改为手动发布并支持patch/minor/major版本选择 2025-10-27 09:51:44 +08:00
YHH
4cee396ea9 chore(ci): 重构发布流程为手动触发方式并添加behavior-tree包发布支持 2025-10-27 09:47:59 +08:00
YHH
d2ad295b48 chore: 移除Dependabot自动依赖更新配置 2025-10-27 09:36:03 +08:00
YHH
009f8af4e1 Feature/ecs behavior tree (#188)
* feat(behavior-tree): 完全 ECS 化的行为树系统

* feat(editor-app): 添加行为树可视化编辑器

* chore: 移除 Cocos Creator 扩展目录

* feat(editor-app): 行为树编辑器功能增强

* fix(editor-app): 修复 TypeScript 类型错误

* feat(editor-app): 使用 FlexLayout 重构面板系统并优化资产浏览器

* feat(editor-app): 改进编辑器UI样式并修复行为树执行顺序

* feat(behavior-tree,editor-app): 添加装饰器系统并优化编辑器性能

* feat(behavior-tree,editor-app): 添加属性绑定系统

* feat(editor-app,behavior-tree): 优化编辑器UI并改进行为树功能

* feat(editor-app,behavior-tree): 添加全局黑板系统并增强资产浏览器功能

* feat(behavior-tree,editor-app): 添加运行时资产导出系统

* feat(behavior-tree,editor-app): 添加SubTree系统和资产选择器

* feat(behavior-tree,editor-app): 优化系统架构并改进编辑器文件管理

* fix(behavior-tree,editor-app): 修复SubTree节点错误显示空节点警告

* fix(editor-app): 修复局部黑板类型定义文件扩展名错误
2025-10-27 09:29:11 +08:00
LINGYE
0cd99209c4 支持集成第三方日志库 (#190)
* 更新 ILogger 签名

改为纯可变参数兼容主流日志库

* 拆分日志类型与实现

* 新增 setLoggerFactory 方法

* tweak

* getLoggerName 返回类名,默认情况下子类无需重写

* 更新日志说明文档

* 增加测试

* 使用 getSystemInstanceTypeName,避免压缩导致获取类名不一致
2025-10-26 11:53:46 +08:00
YHH
3ea55303dc Merge pull request #187 from esengine/feature/complete-iscene-interface
feat(core): 完善 IScene 接口定义
2025-10-20 17:44:14 +08:00
YHH
c458a5e036 chore(ci): 移除 pr-size-labeler workflow 2025-10-20 17:36:46 +08:00
YHH
c511725d1f chore(ci): 移除 pr-agent 和 mergify 配置 2025-10-20 17:33:30 +08:00
YHH
3876d9b92b feat(core): 完善 IScene 接口定义 2025-10-20 17:24:56 +08:00
YHH
f863c48ab0 feat(ci): 自动告知用户可使用 AI 助手 2025-10-19 10:13:03 +08:00
YHH
10096795a1 ci(deps): 优化 Dependabot 自动化流程减少维护负担 2025-10-19 10:03:35 +08:00
YHH
8b146c8d5f Merge pull request #161 from esengine/dependabot/npm_and_yarn/ws-8.18.3
chore(deps): bump ws from 8.18.2 to 8.18.3
2025-10-19 09:53:24 +08:00
YHH
1208c4ffeb Merge branch 'master' into dependabot/npm_and_yarn/ws-8.18.3 2025-10-19 09:48:30 +08:00
YHH
ec5de97973 fix(ci): 换用 gpt-4o 模型解决 token 超限问题 2025-10-19 01:08:11 +08:00
YHH
0daa92cfb7 fix(ci): 优化 AI Issue Helper 避免 token 超限 2025-10-19 01:03:55 +08:00
YHH
130f466026 fix(ci): 修复 AI Issue Helper 的 MCP 配置和响应传递问题 2025-10-19 01:01:18 +08:00
YHH
f93de87940 fix(ci): 修复 AI 响应包含代码时的 JavaScript 语法错误 2025-10-19 00:59:04 +08:00
YHH
367d97e9bb fix(ci): 修复 AI Issue Helper 的 prompt 文件读取问题 2025-10-19 00:55:47 +08:00
YHH
77701f214c fix(ci): AI Issue Helper 忽略机器人评论 2025-10-19 00:51:23 +08:00
YHH
b5b64f8c41 feat(ci): 为 AI Issue Helper 添加代码检索功能 2025-10-19 00:43:40 +08:00
YHH
ab04ad30f1 fix(ci): 修复 AI Issue Helper 的数据解析问题 2025-10-19 00:39:10 +08:00
YHH
330d9a6fdb fix(ci): 为 AI workflow 添加 models 权限 2025-10-19 00:33:12 +08:00
YHH
e762343142 fix(ci): 修复 AI 工具 workflow 的 YAML 语法错误 2025-10-19 00:26:07 +08:00
YHH
fce9e3d4d6 Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-10-19 00:17:46 +08:00
YHH
0e5855ee4e ci: 添加 AI Issue 智能分析和批量处理工具 2025-10-19 00:16:05 +08:00
dependabot[bot]
ba61737bc7 chore(deps): bump ws from 8.18.2 to 8.18.3
Bumps [ws](https://github.com/websockets/ws) from 8.18.2 to 8.18.3.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.2...8.18.3)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.18.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-18 16:16:01 +00:00
YHH
bd7ea1f713 Merge pull request #173 from esengine/dependabot/npm_and_yarn/protobufjs-7.5.4
chore(deps): bump protobufjs from 7.5.3 to 7.5.4
2025-10-19 00:14:35 +08:00
YHH
1abd20edf5 Merge branch 'master' into dependabot/npm_and_yarn/protobufjs-7.5.4 2025-10-19 00:06:07 +08:00
YHH
496513c641 ci: 添加 AI 代码审查工具配置 2025-10-19 00:04:36 +08:00
YHH
848b637f45 docs: 增强 README可视化和统计展示 2025-10-18 23:45:34 +08:00
dependabot[bot]
39049601d4 chore(deps): bump protobufjs from 7.5.3 to 7.5.4
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.5.3 to 7.5.4.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v7.5.3...protobufjs-v7.5.4)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-version: 7.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-18 15:30:56 +00:00
YHH
e31cdd17d3 fix(ci): 修复 Mergify 配置语法错误 2025-10-18 23:13:14 +08:00
YHH
2a3f2d49b8 fix(ci): 放宽 commitlint scope 限制 2025-10-18 23:06:05 +08:00
YHH
ca452889d7 chore(deps): 更新 package-lock.json 2025-10-18 22:44:55 +08:00
YHH
2df501ec07 ci: 添加自动化工具提升项目质量 2025-10-18 22:32:39 +08:00
YHH
6b1e6c6fdc fix(ci): 修复发布流程改为手动触发并包含 package-lock 2025-10-18 22:18:47 +08:00
YHH
570e970e1c chore: modernize project infrastructure 2025-10-18 22:06:27 +08:00
YHH
f4e3505d52 规范项目标准,更改为MIT协议 2025-10-18 21:48:44 +08:00
YHH
9af2b9859a 新增deepwiki徽章 2025-10-18 21:16:17 +08:00
YHH
d99d314621 Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-10-18 21:06:43 +08:00
YHH
c5b8b18e33 显示自上个版本发布依赖的更新内容再pr里 2025-10-18 21:06:04 +08:00
YHH
7280265a64 core发布流程里的中文pr 2025-10-18 21:03:00 +08:00
YHH
35fa0ef884 Merge pull request #153 from esengine/release/core-v2.2.8
chore(core): Release v2.2.8
2025-10-18 21:00:39 +08:00
github-actions[bot]
9c778cb71b chore(core): bump version to 2.2.8 2025-10-18 12:59:36 +00:00
YHH
4a401744c1 更新core发布流程 2025-10-18 20:56:59 +08:00
YHH
5f6b2d4d40 更新core流程 2025-10-18 20:45:45 +08:00
YHH
bf25218af2 新增发布core npm自动流程 2025-10-18 20:36:10 +08:00
YHH
1f7f9d9f84 修复端口没有跟随设置更改的问题 2025-10-18 20:21:43 +08:00
YHH
2be53a04e4 Merge pull request #152 from foxling/feature/world-manager-config
feat(core): 支持通过 Core.create() 配置 WorldManager 参数
2025-10-18 13:16:52 +08:00
LING YE
7f56ebc786 feat(core): 支持通过 Core.create() 配置 WorldManager 参数 2025-10-18 13:10:09 +08:00
YHH
a801e4f50e Merge pull request #150 from esengine/issue-149-编辑器支持设置字体大小
支持字体设置大小
2025-10-17 23:47:42 +08:00
YHH
a9f9ad9b94 支持字体设置大小 2025-10-17 23:47:04 +08:00
YHH
3cf1dab5b9 Merge pull request #148 from esengine/issue-147-Scene的构造函数不应该由用户传入性能分析器
performancemonitor由内部框架维护
2025-10-17 22:19:53 +08:00
YHH
63165bbbfc performancemonitor由内部框架维护 2025-10-17 22:13:32 +08:00
YHH
61caad2bef Merge pull request #146 from esengine/issue-132-场景序列化系统
更新图标及场景序列化系统
2025-10-17 18:17:56 +08:00
YHH
b826bbc4c7 更新图标及场景序列化系统 2025-10-17 18:13:31 +08:00
YHH
2ce7dad8d8 Merge pull request #131 from esengine/release/editor-v1.0.3
chore(editor): Release v1.0.3
2025-10-16 23:53:37 +08:00
esengine
dff400bf22 chore(editor): bump version to 1.0.3 2025-10-16 15:52:45 +00:00
YHH
27ce902344 配置createUpdaterArtifacts生成sig 2025-10-16 23:38:11 +08:00
YHH
33ee0a04c6 升级tauri-action 2025-10-16 23:20:55 +08:00
YHH
d68f6922f8 Merge pull request #129 from esengine/release/editor-v1.0.1
chore(editor): Release v1.0.1
2025-10-16 23:09:26 +08:00
esengine
f8539d7958 chore(editor): bump version to 1.0.1 2025-10-16 15:08:22 +00:00
YHH
14dc911e0a Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-10-16 22:55:09 +08:00
YHH
deccb6bf84 修复editor再ci上版本冲突问题 2025-10-16 22:54:58 +08:00
YHH
dacbfcae95 Merge pull request #127 from esengine/imgbot
[ImgBot] Optimize images
2025-10-16 22:49:51 +08:00
ImgBotApp
1b69ed17b7 [ImgBot] Optimize images
*Total -- 496.42kb -> 376.40kb (24.18%)

/screenshots/main_screetshot.png -- 179.18kb -> 121.90kb (31.97%)
/screenshots/performance_profiler.png -- 59.99kb -> 41.74kb (30.43%)
/screenshots/port_manager.png -- 22.70kb -> 16.86kb (25.72%)
/screenshots/about.png -- 38.75kb -> 29.00kb (25.17%)
/screenshots/plugin_manager.png -- 38.49kb -> 29.16kb (24.25%)
/packages/editor-app/src-tauri/icons/icon.svg -- 3.52kb -> 2.76kb (21.6%)
/screenshots/settings.png -- 53.78kb -> 44.90kb (16.51%)
/packages/editor-app/src-tauri/icons/icon.png -- 33.45kb -> 29.78kb (10.98%)
/packages/editor-app/src-tauri/icons/512x512.png -- 33.45kb -> 29.78kb (10.98%)
/packages/editor-app/src-tauri/icons/256x256.png -- 13.97kb -> 12.76kb (8.68%)
/packages/editor-app/src-tauri/icons/128x128@2x.png -- 13.97kb -> 12.76kb (8.68%)
/packages/editor-app/src-tauri/icons/128x128.png -- 5.17kb -> 5.03kb (2.89%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-10-16 14:45:32 +00:00
YHH
241acc9050 更新文档 2025-10-16 22:45:08 +08:00
YHH
8fa921930c Merge pull request #126 from esengine/issue-125-编辑器热更新
热更新配置
2025-10-16 22:31:26 +08:00
YHH
011e43811a 热更新配置 2025-10-16 22:26:50 +08:00
YHH
9f16debd75 新增rust编译缓存和ts构建缓存 2025-10-16 20:54:33 +08:00
YHH
92c56c439b 移除过时的工作流 2025-10-16 20:50:32 +08:00
YHH
7de6a5af0f v2.2.5 2025-10-16 20:37:16 +08:00
YHH
173a063781 Merge pull request #124 from esengine/issue-122-格式化的标准配置
所有源代码文件使用 LF (Unix 风格)
2025-10-16 20:29:21 +08:00
YHH
e04ac7c909 所有源代码文件使用 LF (Unix 风格)
prettier格式化
eslint代码质量检查
2025-10-16 20:24:45 +08:00
YHH
a6e49e1d47 修复publish-release中release_id错误问题 2025-10-16 20:07:56 +08:00
YHH
f0046c7dc2 新增icons作为编辑器图标 2025-10-16 19:52:17 +08:00
YHH
2a17c47c25 修复ts警告 2025-10-16 18:20:31 +08:00
YHH
8d741bf1b9 Merge pull request #123 from esengine/issue-119-插件化编辑器
Issue 119 插件化编辑器
2025-10-16 17:50:35 +08:00
YHH
c676006632 构建配置 2025-10-16 17:44:57 +08:00
YHH
5bcfd597b9 Merge remote-tracking branch 'remotes/origin/master' into issue-119-插件化编辑器 2025-10-16 17:37:24 +08:00
YHH
3cda3c2238 显示客户端链接的ip:port 2025-10-16 17:33:43 +08:00
YHH
43bdd7e43b 远程读取日志 2025-10-16 17:10:22 +08:00
YHH
1ec7892338 设置界面 2025-10-16 13:07:19 +08:00
YHH
6bcfd48a2f 清理调试日志 2025-10-16 12:21:18 +08:00
YHH
345ef70972 支持color类型 2025-10-16 12:00:17 +08:00
YHH
c876edca0c 调试实体和组件属性 2025-10-16 11:55:41 +08:00
YHH
fcf3def284 收集远端数据再profiler dockpanel上 2025-10-15 23:24:13 +08:00
YHH
6f1a2896dd 性能分析器及端口管理器 2025-10-15 22:30:49 +08:00
YHH
62381f4160 记录上一次操作的面板的size大小持久化 2025-10-15 20:26:40 +08:00
YHH
171805debf 禁用默认右键 2025-10-15 20:23:55 +08:00
YHH
619abcbfbc 插件管理器 2025-10-15 20:10:52 +08:00
YHH
03909924c2 app loading 2025-10-15 18:29:48 +08:00
YHH
f4ea077114 菜单栏 2025-10-15 18:24:13 +08:00
YHH
956ccf9195 2D/3D视口 2025-10-15 18:08:55 +08:00
YHH
e880925e3f 视口视图 2025-10-15 17:28:45 +08:00
YHH
0a860920ad 日志面板 2025-10-15 17:21:59 +08:00
YHH
fb7a1b1282 可动态识别属性 2025-10-15 17:15:05 +08:00
YHH
59970ef7c3 Merge pull request #121 from foxling/fix/parent-child-deserialization-bug
fix: 修复场景反序列化时子实体丢失的问题
2025-10-15 15:55:26 +08:00
LING YE
a7750c2894 fix: 修复场景反序列化时子实体丢失的问题
在场景反序列化过程中,子实体虽然保持了父子引用关系,
但未被添加到 Scene 的实体集合和查询系统中,导致查询时子实体"丢失"。
2025-10-15 15:48:54 +08:00
YHH
b69b81f63a 支持树形资源管理器 2025-10-15 10:08:15 +08:00
YHH
00fc6dfd67 Dock系统,支持Tab和拖放 2025-10-15 09:58:45 +08:00
YHH
82451e9fd3 可拖动调整大小的面板 2025-10-15 09:43:48 +08:00
YHH
d0fcc0e447 项目启动流程和资产浏览器功能 2025-10-15 09:34:44 +08:00
YHH
285279629e 优化加载脚本逻辑 2025-10-15 09:19:30 +08:00
YHH
cbfe09b5e9 组件发现和动态加载系统 2025-10-15 00:40:27 +08:00
YHH
b757c1d06c 项目打开功能 2025-10-15 00:23:19 +08:00
YHH
4550a6146a 组件属性编辑器 2025-10-15 00:15:12 +08:00
YHH
3224bb9696 国际化系统 2025-10-14 23:56:54 +08:00
YHH
3a5e73266e 组件注册与添加 2025-10-14 23:42:06 +08:00
YHH
1cf5641c4c 实体存储和管理服务 2025-10-14 23:31:09 +08:00
YHH
85dad41e60 Tauri 窗口,显示编辑器界面 2025-10-14 23:15:07 +08:00
YHH
bd839cf431 Tauri 编辑器应用框架 2025-10-14 22:53:26 +08:00
YHH
b20b2ae4ce 编辑器核心框架 2025-10-14 22:33:55 +08:00
YHH
cac6aedf78 调试插件 DebugPlugin 2025-10-14 22:12:35 +08:00
YHH
a572c80967 v2.2.4 2025-10-14 21:22:07 +08:00
YHH
a09e8261db Merge pull request #118 from esengine/issue-117-统一组件注册系统,移除ComponentTypeManager重复实现
移除了功能重复的ComponentTypeManager
2025-10-14 18:24:17 +08:00
YHH
62e8ebe926 移除了功能重复的ComponentTypeManager 2025-10-14 18:19:08 +08:00
YHH
96e0a9126f Merge pull request #116 from esengine/issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query
使用ComponentRegistry来确保与Entity.componentMask使用相同的bitIndex
2025-10-14 17:56:26 +08:00
YHH
4afb195814 Merge remote-tracking branch 'origin/master' into issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query 2025-10-14 17:51:59 +08:00
YHH
7da5366bca 修复querysystem循环依赖的问题 2025-10-14 17:50:32 +08:00
YHH
d979c38615 使用ComponentRegistry来确保与Entity.componentMask使用相同的bitIndex 2025-10-14 17:34:15 +08:00
YHH
7def06126b Merge pull request #115 from esengine/issue-114-reactive-query_文档
新增实体查询系统文档
2025-10-14 13:39:23 +08:00
YHH
9f600f88b0 新增实体查询系统文档 2025-10-14 13:33:12 +08:00
YHH
e3c4d5f0c0 Merge pull request #105 from esengine/issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query
响应式查询
2025-10-14 12:29:54 +08:00
YHH
b97f3a8431 移除了 EntitySystem.update() 中的冗余 invalidate() 调用 2025-10-14 12:08:08 +08:00
YHH
3b917a06af 修复响应式查询缓存失效和测试隔离问题 2025-10-14 11:48:04 +08:00
YHH
360106fb92 优化ReactiveQuery: 添加公共API、修复内存泄漏、提升通知性能 2025-10-13 23:55:43 +08:00
YHH
507ed5005f Merge remote-tracking branch 'origin/master' into issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query 2025-10-13 23:36:12 +08:00
YHH
8bea5d5e68 Merge remote-tracking branch 'origin/master' into issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query 2025-10-13 23:29:56 +08:00
YHH
99076adb72 Merge pull request #113 from esengine/issue-112-优化_DebugManager_使用_DI_避免依赖问题
使用DI避免依赖问题
2025-10-13 23:21:10 +08:00
YHH
d1a6230b23 新增调试配置服务用于DI 2025-10-13 23:17:10 +08:00
YHH
cfe3916934 使用DI避免依赖问题 2025-10-13 22:56:49 +08:00
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
942043f0b0 报告响应式查询的数量而不是传统缓存 2025-10-11 18:44:55 +08:00
YHH
23d81bca35 响应式查询 2025-10-11 18:31: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
YHH
168e028098 更新支持es5环境 2025-09-04 16:26:29 +08:00
YHH
042ded37d2 新增自定义log颜色ci测试 2025-09-04 16:25:40 +08:00
YHH
4137eb2bce 网络层完善消息队列 2025-09-04 16:25:18 +08:00
YHH
20a3f03e12 支持用户自定义log颜色 2025-09-04 16:24:38 +08:00
YHH
7792710694 避免splice开销问题,改为SwapPop + typeId → denseIndex 2025-09-03 10:56:33 +08:00
YHH
dbddbbdfb8 更新ci测试用例 2025-09-03 10:39:29 +08:00
YHH
4869f5741e bits多态改为POD+原地操作 2025-09-03 10:29:43 +08:00
YHH
bda547dd2e 把掩码从BigInt-like多态都改成Mask64 2025-09-03 00:39:00 +08:00
YHH
ef80b03a44 更改为固定64位掩码,没必要为任意精度付出性能代价 2025-09-03 00:12:59 +08:00
YHH
6e511ae949 改成 SparseSet+SwapRemove 的致密存储 2025-09-02 22:29:11 +08:00
YHH
94541d0abb 实体中的线性数组换为按组件类型ID直址的稀疏数组 2025-09-02 21:59:59 +08:00
YHH
586a0e5d14 降低ci测试覆盖率 2025-09-02 21:16:36 +08:00
YHH
814842dbaf 降低测试覆盖率导致的ci错误 2025-09-02 21:11:15 +08:00
YHH
70a993573f 修复ci报错 2025-09-02 18:04:55 +08:00
YHH
21659cbb13 交/并/差运算全部改用ID集合单次扫描 2025-09-02 17:17:07 +08:00
YHH
a44251cc55 新增world概念(多world管理多scene概念)现在支持多个world多个scene同时更新 2025-08-20 17:48:31 +08:00
YHH
69616bbddc 实现ServerRpc和ClientRpc装饰器 2025-08-20 10:32:56 +08:00
YHH
0a1d7ac083 实现SyncVar装饰器和组件同步 2025-08-20 10:16:54 +08:00
YHH
364bc4cdab update demo 2025-08-20 09:17:43 +08:00
YHH
2504eb24e1 经测试转换为wasm没有明显的效率提升,因为js和rust的交互反而有所下降
等待以后有密集计算再考虑使用,先移除了
2025-08-20 09:15:06 +08:00
YHH
bdbef0bd0d core核心rust实现 2025-08-18 20:46:47 +08:00
YHH
e4e38ee4e6 更新api文档 2025-08-15 13:47:11 +08:00
YHH
021e892e33 ci流程需要core库先编译后再测试network-shared 2025-08-15 13:32:36 +08:00
YHH
c27d5022fd 优化内部组件索引机制(更改为SparseSet索引)减少用户切换索引成本
修复内部系统初始化逻辑 - 不应该再onInitialize中初始内部entities,移动到initialize中
ci跳过cocos项目避免ci失败
soa开放更多安全类型接口
2025-08-15 12:58:55 +08:00
YHH
6730a5d625 传输层实现(客户端/服务端,链接管理和心跳机制,重连机制)
消息序列化(json序列化,消息压缩,消息ID和时间戳)
网络服务器核心(networkserver/基础room/链接状态同步)
网络客户端核心(networkclient/消息队列)
2025-08-14 23:59:00 +08:00
YHH
32092f992d 更新文档 2025-08-14 18:45:24 +08:00
YHH
a5f0c8f6b5 更新文档 2025-08-14 18:44:04 +08:00
YHH
85cd93e51a 文档更新 2025-08-14 18:38:09 +08:00
YHH
0b7e623748 新增组件/系统装饰器避免混淆
更改Set兼容web/小游戏
2025-08-14 18:35:03 +08:00
YHH
62f250b43c 重构network库(mvp版本)搭建基础设施和核心接口
定义ITransport/ISerializer/INetworkMessage接口
NetworkIdentity组件
基础事件定义
2025-08-13 13:07:40 +08:00
YHH
25136349ff 修复动态require导致的跨平台错误
新增emitter的dispose方法用于清理事件
启用composite增量编译
2025-08-13 12:18:40 +08:00
YHH
baeb047e27 支持可以任意参数 2025-08-12 11:47:18 +08:00
YHH
56dd18b983 废弃core.scene更改为setscene方法 2025-08-12 11:08:27 +08:00
YHH
86cb70a94f Merge branch 'master' of https://github.com/esengine/ecs-framework
# Conflicts:
#	packages/network-shared/package.json
2025-08-12 09:43:29 +08:00
YHH
9f76d37a82 更新network库及core库优化 2025-08-12 09:39:07 +08:00
YHH
a026ed9428 降级ws版本 2025-08-11 12:33:42 +08:00
YHH
c178e2fbcc 移除ci性能测试,github下不应该测试这些文件 2025-08-11 11:34:38 +08:00
YHH
b88bb1dc87 修复ci中的大小写问题
update gitsubmodule
2025-08-11 11:11:56 +08:00
YHH
3069e28224 add submodule electric-world 2025-08-11 11:06:28 +08:00
YHH
d69b3af99b 更新math版本 2025-08-11 10:39:14 +08:00
YHH
7398b7c6d0 修复math库tsconfig引用问题 2025-08-11 10:38:11 +08:00
YHH
5d57904d22 更新使用rollup打包
解决大小写冲突问题
2025-08-11 10:34:13 +08:00
YHH
7daf352a25 更新库rollup配置 2025-08-11 10:25:28 +08:00
YHH
6a49f6a534 npm包发布配置更改 2025-08-11 10:03:19 +08:00
YHH
5bce08683a update 2.1.30 2025-08-11 09:31:44 +08:00
YHH
edc60fc3d8 添加发布core命令 2025-08-11 09:15:14 +08:00
YHH
1361fd8a90 导出soa装饰器 2025-08-11 09:01:01 +08:00
YHH
d539bb3dd9 更新文档 2025-08-11 08:18:18 +08:00
YHH
3b9ae4f384 新增math库 2025-08-10 16:00:02 +08:00
YHH
2783448de5 重新整理网络架构,tsrpc/syncvar并行 2025-08-10 12:35:39 +08:00
YHH
6e21ff08d5 集成tsrpc代替protobuf 2025-08-09 18:56:19 +08:00
YHH
e56278e4a6 移除路径映射,只让Jest处理路径解析 2025-08-08 15:57:56 +08:00
YHH
fc9bf816dd 修复build失败问题 2025-08-08 15:47:48 +08:00
YHH
854fd7df3a 修复ci测试 2025-08-08 15:41:37 +08:00
YHH
87dd564a12 项目统一改用Logger控制管理
拆分pool类和FluentAPI
2025-08-08 11:16:00 +08:00
YHH
2d389308ea 新增syncvar高级特性,使用protobuf定义 2025-08-07 20:23:49 +08:00
YHH
ea8523be35 使用Lerna 和 monorepo管理项目结构 2025-08-07 13:29:12 +08:00
YHH
4479f0fab0 避免throw导致的中止运行,增加fallback回退json的序列化 2025-08-07 10:16:36 +08:00
YHH
7a000318a6 整合组件类型至统一的componentregistry中 2025-08-07 09:43:34 +08:00
YHH
9a08ae74b6 移除json序列化只保留protobuf 2025-08-06 17:42:12 +08:00
YHH
f3d2950df3 修复ci失败 2025-08-06 17:12:39 +08:00
YHH
8cfba4a166 新增protobuf依赖(为网络和序列化做准备)
更新readme
2025-08-06 17:04:02 +08:00
YHH
51e6bba2a7 2.1.29 2025-08-06 09:56:55 +08:00
YHH
ccbfa78070 修复了QuerySystem在销毁实体时的内存泄漏问题
实现了完整的onAdded/onRemoved回调系统
修复了override修饰符和类型兼容性问题
2025-08-06 09:39:08 +08:00
YHH
69655f1936 测试用例更新 2025-07-31 15:37:40 +08:00
YHH
6ea366cfed 优化matcher内部实现改为querysystem
完善type类型
更新文档
2025-07-31 11:56:04 +08:00
YHH
b7d17fb16d soa添加float32 2025-07-30 18:10:14 +08:00
YHH
f3dc8c6344 BigIntFactory 缓存优化
- 为 zero() 和 one() 方法添加缓存,避免重复创建对象
ComponentIndexManager 优化
  - 添加了空实体检查,跳过不必要的索引操作
  - 实现了 Set 对象池,重用 Set 实例以减少内存分配
  - 优化了自动优化检查频率,从每次操作变为每100次操作检查一次
EntityManager 优化
  - 对空实体跳过不必要的组件索引、原型系统和脏标记操作
  - 批量创建时同样应用空实体优化
2025-07-30 17:10:58 +08:00
YHH
69ec545854 优化createEntity的性能/新增批量创建实体api 2025-07-30 16:05:16 +08:00
YHH
65386ff731 优化EntitySystem初始化逻辑/防止多次初始化
增加matcher和entitysystem的测试
2025-07-30 15:42:19 +08:00
YHH
01fa33e122 新增soastorage存储器 2025-07-30 14:14:04 +08:00
YHH
0411aa9aef 2.1.28 2025-07-30 11:14:26 +08:00
YHH
4a5c890121 对bigint进行兼容处理(不支持的环境回退到兼容模式) 2025-07-30 11:11:46 +08:00
1822 changed files with 352980 additions and 42124 deletions

62
.all-contributorsrc Normal file
View File

@@ -0,0 +1,62 @@
{
"projectName": "ecs-framework",
"projectOwner": "esengine",
"repoType": "github",
"repoHost": "https://github.com",
"files": ["README.md"],
"imageSize": 100,
"commit": true,
"commitConvention": "angular",
"contributors": [
{
"login": "yhh",
"name": "Frank Huang",
"avatar_url": "https://avatars.githubusercontent.com/u/145575?v=4",
"profile": "https://github.com/yhh",
"contributions": ["code"]
}
],
"contributorsPerLine": 7,
"contributorsSortAlphabetically": false,
"badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square)](#contributors)",
"contributorTemplate": "<a href=\"<%= contributor.profile %>\"><img src=\"<%= contributor.avatar_url %>\" width=\"<%= options.imageSize %>px;\" alt=\"<%= contributor.name %>\"/><br /><sub><b><%= contributor.name %></b></sub></a>",
"types": {
"code": {
"symbol": "💻",
"description": "Code",
"link": "[<%= symbol %>](<%= url %> \"Code\")"
},
"doc": {
"symbol": "📖",
"description": "Documentation",
"link": "[<%= symbol %>](<%= url %> \"Documentation\")"
},
"test": {
"symbol": "⚠️",
"description": "Tests",
"link": "[<%= symbol %>](<%= url %> \"Tests\")"
},
"bug": {
"symbol": "🐛",
"description": "Bug reports",
"link": "[<%= symbol %>](<%= url %> \"Bug reports\")"
},
"example": {
"symbol": "💡",
"description": "Examples",
"link": "[<%= symbol %>](<%= url %> \"Examples\")"
},
"design": {
"symbol": "🎨",
"description": "Design",
"link": "[<%= symbol %>](<%= url %> \"Design\")"
},
"ideas": {
"symbol": "🤔",
"description": "Ideas & Planning",
"link": "[<%= symbol %>](<%= url %> \"Ideas & Planning\")"
}
},
"skipCi": true
}

36
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,36 @@
# CodeRabbit 配置文件
# https://docs.coderabbit.ai/configuration
language: "zh-CN" # 使用中文评论
reviews:
# 审查级别
profile: "chill" # "chill" 或 "strict" 或 "assertive"
# 自动审查设置
auto_review:
enabled: true
drafts: false # 草稿 PR 不自动审查
base_branches:
- master
- main
# 审查内容
request_changes_workflow: false # 不阻止 PR 合并
high_level_summary: true # 生成高层次摘要
poem: false # 不生成诗歌(可以改为 true 增加趣味)
review_status: true # 显示审查状态
# 忽略的文件
path_filters:
- "!**/*.md" # 不审查 markdown
- "!**/package-lock.json" # 不审查 lock 文件
- "!**/dist/**" # 不审查构建输出
- "!**/*.min.js" # 不审查压缩文件
# 聊天设置
chat:
auto_reply: true # 自动回复问题
# 提交建议
suggestions:
enabled: true # 启用代码建议

29
.commitlintrc.json Normal file
View File

@@ -0,0 +1,29 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"build",
"ci",
"chore",
"revert"
]
],
"scope-enum": [
0
],
"scope-empty": [0],
"subject-empty": [2, "never"],
"subject-case": [0],
"header-max-length": [2, "always", 100]
}
}

35
.editorconfig Normal file
View File

@@ -0,0 +1,35 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# 所有文件的默认设置
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# TypeScript/JavaScript 文件
[*.{ts,tsx,js,jsx,mjs,cjs}]
indent_style = space
indent_size = 4
# JSON 文件
[*.json]
indent_style = space
indent_size = 2
# YAML 文件
[*.{yml,yaml}]
indent_style = space
indent_size = 2
# Markdown 文件
[*.md]
trim_trailing_whitespace = false
indent_size = 2
# 包管理文件
[{package.json,package-lock.json,tsconfig.json}]
indent_size = 2

44
.gitattributes vendored Normal file
View File

@@ -0,0 +1,44 @@
# 自动检测文本文件并规范化换行符
* text=auto
# 源代码文件强制使用 LF
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.mjs text eol=lf
*.cjs text eol=lf
*.json text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
# 配置文件强制使用 LF
.gitignore text eol=lf
.gitattributes text eol=lf
.editorconfig text eol=lf
.prettierrc text eol=lf
.prettierignore text eol=lf
.eslintrc.json text eol=lf
tsconfig.json text eol=lf
# Shell 脚本强制使用 LF
*.sh text eol=lf
# Windows 批处理文件使用 CRLF
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# 二进制文件不转换
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.svg binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
*.otf binary

130
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,130 @@
name: 🐛 Bug Report / 错误报告
description: Report a bug or issue / 报告一个错误或问题
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
感谢你提交 Bug 报告!请填写以下信息帮助我们更快定位问题。
Thanks for reporting a bug! Please fill in the information below to help us locate the issue faster.
- type: textarea
id: description
attributes:
label: 问题描述 / Bug Description
description: 清晰简洁地描述遇到的问题 / A clear and concise description of the bug
placeholder: 例如当我创建超过1000个实体时游戏卡顿严重...
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: 复现步骤 / Steps to Reproduce
description: 如何复现这个问题?/ How can we reproduce this issue?
placeholder: |
1. 创建场景
2. 添加 1000 个实体
3. 运行游戏
4. 观察卡顿
value: |
1.
2.
3.
validations:
required: true
- type: textarea
id: expected
attributes:
label: 期望行为 / Expected Behavior
description: 你期望发生什么?/ What did you expect to happen?
placeholder: 游戏应该流畅运行FPS 保持在 60...
validations:
required: true
- type: textarea
id: actual
attributes:
label: 实际行为 / Actual Behavior
description: 实际发生了什么?/ What actually happened?
placeholder: FPS 降到 20游戏严重卡顿...
validations:
required: true
- type: input
id: version
attributes:
label: 版本 / Version
description: 使用的 @esengine/ecs-framework 版本 / Version of @esengine/ecs-framework
placeholder: 例如 / e.g., 2.2.8
validations:
required: true
- type: dropdown
id: platform
attributes:
label: 平台 / Platform
description: 在哪个平台遇到问题?/ Which platform did you encounter the issue?
multiple: true
options:
- Web / 浏览器
- Cocos Creator
- Laya Engine
- WeChat Mini Game / 微信小游戏
- Other / 其他
validations:
required: true
- type: textarea
id: environment
attributes:
label: 环境信息 / Environment
description: |
相关环境信息 / Relevant environment information
例如操作系统、浏览器版本、Node.js 版本等
placeholder: |
- OS: Windows 11
- Browser: Chrome 120
- Node.js: 20.10.0
value: |
- OS:
- Browser:
- Node.js:
validations:
required: false
- type: textarea
id: code
attributes:
label: 代码示例 / Code Sample
description: 如果可能,提供最小可复现代码 / If possible, provide minimal reproducible code
render: typescript
placeholder: |
import { Core, Scene, Entity } from '@esengine/ecs-framework';
// 你的代码 / Your code here
validations:
required: false
- type: textarea
id: logs
attributes:
label: 错误日志 / Error Logs
description: 相关的错误日志或截图 / Relevant error logs or screenshots
render: shell
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: 检查清单 / Checklist
options:
- label: 我已经搜索过类似的 issue / I have searched for similar issues
required: true
- label: 我使用的是最新版本 / I am using the latest version
required: false
- label: 我愿意提交 PR 修复此问题 / I am willing to submit a PR to fix this issue
required: false

17
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
blank_issues_enabled: true
contact_links:
- name: 📚 文档 / Documentation
url: https://esengine.github.io/ecs-framework/
about: 查看完整文档和教程 / View full documentation and tutorials
- name: 🤖 AI 文档助手 / AI Documentation Assistant
url: https://deepwiki.com/esengine/ecs-framework
about: 使用 AI 助手快速找到答案 / Use AI assistant to quickly find answers
- name: 💬 QQ 交流群 / QQ Group
url: https://jq.qq.com/?_wv=1027&k=29w1Nud6
about: 加入社区交流群 / Join the community group
- name: 🌟 GitHub Discussions
url: https://github.com/esengine/ecs-framework/discussions
about: 参与社区讨论 / Join community discussions

View File

@@ -0,0 +1,90 @@
name: ✨ Feature Request / 功能建议
description: Suggest a new feature or enhancement / 建议新功能或改进
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
感谢你的功能建议!请详细描述你的想法。
Thanks for your feature suggestion! Please describe your idea in detail.
- type: textarea
id: problem
attributes:
label: 问题描述 / Problem Description
description: 这个功能解决什么问题?/ What problem does this feature solve?
placeholder: 当我需要...的时候,现在很不方便,因为... / When I need to..., it's inconvenient because...
validations:
required: true
- type: textarea
id: solution
attributes:
label: 建议的解决方案 / Proposed Solution
description: 你希望如何实现这个功能?/ How would you like this feature to work?
placeholder: 可以添加一个新的 API例如... / Could add a new API, for example...
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 其他方案 / Alternatives
description: 你考虑过哪些替代方案?/ What alternatives have you considered?
placeholder: 也可以通过...来实现,但是... / Could also achieve this by..., but...
validations:
required: false
- type: textarea
id: examples
attributes:
label: 使用示例 / Usage Example
description: 展示这个功能如何使用 / Show how this feature would be used
render: typescript
placeholder: |
// 理想的 API 设计 / Ideal API design
const pool = new ComponentPool(MyComponent, { size: 100 });
const component = pool.acquire();
validations:
required: false
- type: dropdown
id: scope
attributes:
label: 影响范围 / Scope
description: 这个功能主要影响哪个部分?/ Which part does this feature mainly affect?
options:
- Core / 核心框架
- Performance / 性能
- API Design / API 设计
- Developer Experience / 开发体验
- Documentation / 文档
- Editor / 编辑器
- Other / 其他
validations:
required: true
- type: dropdown
id: priority
attributes:
label: 优先级 / Priority
description: 你认为这个功能有多重要?/ How important do you think this feature is?
options:
- High / 高 - 非常需要这个功能
- Medium / 中 - 有会更好
- Low / 低 - 可有可无
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: 检查清单 / Checklist
options:
- label: 我已经搜索过类似的功能请求 / I have searched for similar feature requests
required: true
- label: 这个功能不会破坏现有 API / This feature won't break existing APIs
required: false
- label: 我愿意提交 PR 实现此功能 / I am willing to submit a PR to implement this feature
required: false

64
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: ❓ Question / 问题咨询
description: Ask a question about using the framework / 询问框架使用问题
title: "[Question]: "
labels: ["question"]
body:
- type: markdown
attributes:
value: |
💡 提示:如果是简单问题,可以先查看:
- [📚 文档](https://esengine.github.io/ecs-framework/)
- [📖 AI 文档助手](https://deepwiki.com/esengine/ecs-framework)
- [💬 QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
💡 Tip: For simple questions, please check first:
- [📚 Documentation](https://esengine.github.io/ecs-framework/)
- [📖 AI Documentation](https://deepwiki.com/esengine/ecs-framework)
- type: textarea
id: question
attributes:
label: 你的问题 / Your Question
description: 清晰描述你的问题 / Describe your question clearly
placeholder: 如何在 Cocos Creator 中使用 ECS Framework
validations:
required: true
- type: textarea
id: context
attributes:
label: 背景信息 / Context
description: 提供更多上下文帮助理解问题 / Provide more context to help understand
placeholder: |
我正在开发一个多人在线游戏...
我尝试过...但是...
validations:
required: false
- type: textarea
id: code
attributes:
label: 相关代码 / Related Code
description: 如果适用,提供相关代码片段 / If applicable, provide relevant code snippet
render: typescript
validations:
required: false
- type: input
id: version
attributes:
label: 版本 / Version
description: 使用的框架版本 / Framework version you're using
placeholder: 例如 / e.g., 2.2.8
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: 检查清单 / Checklist
options:
- label: 我已经查看过文档 / I have checked the documentation
required: true
- label: 我已经搜索过类似问题 / I have searched for similar questions
required: true

8
.github/codeql/codeql-config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
name: "CodeQL Config"
# Paths to exclude from analysis
paths-ignore:
- thirdparty
- "**/node_modules"
- "**/dist"
- "**/bin"

32
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
# 自动标签配置
# 根据 issue/PR 内容自动打标签
'bug':
- '/(bug|错误|崩溃|crash|error|exception|问题)/i'
'enhancement':
- '/(feature|功能|enhancement|improve|优化|建议)/i'
'documentation':
- '/(doc|文档|readme|guide|tutorial|教程)/i'
'question':
- '/(question|疑问|how to|如何|怎么)/i'
'performance':
- '/(performance|性能|slow|慢|lag|卡顿|optimize)/i'
'core':
- '/(@esengine\/ecs-framework|packages\/core|core package)/i'
'editor':
- '/(editor|编辑器|tauri)/i'
'network':
- '/(network|网络|multiplayer|多人)/i'
'help wanted':
- '/(help wanted|需要帮助|求助)/i'
'good first issue':
- '/(good first issue|新手友好|beginner)/i'

95
.github/labels.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
# GitHub Labels 配置
# 可以使用 https://github.com/Financial-Times/github-label-sync 来同步标签
# Size Labels (PR 大小)
- name: 'size/XS'
color: '00ff00'
description: '极小的改动 (< 10 行)'
- name: 'size/S'
color: '00ff00'
description: '小改动 (10-100 行)'
- name: 'size/M'
color: 'ffff00'
description: '中等改动 (100-500 行)'
- name: 'size/L'
color: 'ff9900'
description: '大改动 (500-1000 行)'
- name: 'size/XL'
color: 'ff0000'
description: '超大改动 (> 1000 行)'
# Type Labels
- name: 'bug'
color: 'd73a4a'
description: '错误或问题'
- name: 'enhancement'
color: 'a2eeef'
description: '新功能或改进'
- name: 'documentation'
color: '0075ca'
description: '文档相关'
- name: 'question'
color: 'd876e3'
description: '问题咨询'
- name: 'performance'
color: 'ff6b6b'
description: '性能相关'
# Scope Labels
- name: 'core'
color: '5319e7'
description: '核心包相关'
- name: 'editor'
color: '5319e7'
description: '编辑器相关'
- name: 'network'
color: '5319e7'
description: '网络相关'
# Status Labels
- name: 'stale'
color: 'ededed'
description: '长时间无活动'
- name: 'wip'
color: 'fbca04'
description: '进行中'
- name: 'help wanted'
color: '008672'
description: '需要帮助'
- name: 'good first issue'
color: '7057ff'
description: '适合新手'
- name: 'quick-review'
color: '00ff00'
description: '小改动,快速 Review'
- name: 'automerge'
color: 'bfdadc'
description: '自动合并'
- name: 'pinned'
color: 'c2e0c6'
description: '置顶,不会被标记为 stale'
- name: 'security'
color: 'ee0701'
description: '安全相关,高优先级'
# Dependencies
- name: 'dependencies'
color: '0366d6'
description: '依赖更新'

View File

@@ -0,0 +1,73 @@
name: AI Batch Analyze Issues
on:
workflow_dispatch:
inputs:
mode:
description: '分析模式'
required: true
type: choice
options:
- 'recent' # 最近 10 个 issue
- 'open' # 所有打开的 issue
- 'all' # 所有 issue慎用
default: 'recent'
permissions:
issues: write
contents: read
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install GitHub CLI
run: |
gh --version || (curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh)
- name: Batch Analyze Issues
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
MODE="${{ github.event.inputs.mode }}"
# 获取 issue 列表
if [ "$MODE" = "recent" ]; then
echo "📊 分析最近 10 个 issue..."
ISSUES=$(gh issue list --limit 10 --json number --jq '.[].number')
elif [ "$MODE" = "open" ]; then
echo "📊 分析所有打开的 issue..."
ISSUES=$(gh issue list --state open --json number --jq '.[].number')
else
echo "📊 分析所有 issue这可能需要很长时间..."
ISSUES=$(gh issue list --state all --limit 100 --json number --jq '.[].number')
fi
# 为每个 issue 添加 AI 分析评论
for issue_num in $ISSUES; do
echo "🤖 分析 Issue #$issue_num..."
# 获取 issue 内容
ISSUE_BODY=$(gh issue view $issue_num --json body --jq '.body')
ISSUE_TITLE=$(gh issue view $issue_num --json title --jq '.title')
# 添加触发评论
gh issue comment $issue_num --body "@ai-helper 请帮我分析这个 issue" || true
# 避免 API 限制
sleep 2
done
echo "✅ 批量分析完成!"
echo "查看结果https://github.com/${{ github.repository }}/issues"

61
.github/workflows/ai-helper-tip.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: AI Helper Tip
# 对所有新创建的 issue 自动回复 AI 助手使用说明(新老用户都适用)
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
tip:
runs-on: ubuntu-latest
steps:
- name: Post AI Helper Usage Tip
uses: actions/github-script@v7
with:
script: |
const message = [
"## 🤖 AI 助手可用 | AI Helper Available",
"",
"**中文说明:**",
"",
"本项目配备了 AI 智能助手,可以帮助你快速获得解答!",
"",
"**使用方法:** 在评论中提及 `@ai-helper`AI 会自动搜索项目代码并提供解决方案。",
"",
"**示例:**",
"```",
"@ai-helper 如何创建一个新的 System",
"@ai-helper 这个报错是什么原因?",
"```",
"",
"---",
"",
"**English:**",
"",
"This project has an AI assistant to help you get answers quickly!",
"",
"**How to use:** Mention `@ai-helper` in a comment, and AI will automatically search the codebase and provide solutions.",
"",
"**Examples:**",
"```",
"@ai-helper How do I create a new System?",
"@ai-helper What causes this error?",
"```",
"",
"---",
"",
"💡 *AI 助手基于代码库提供建议,复杂问题建议等待维护者回复*",
"💡 *AI suggestions are based on the codebase. For complex issues, please wait for maintainer responses*"
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message
});
console.log('✅ AI helper tip posted successfully');

85
.github/workflows/ai-issue-helper.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: AI Issue Helper
on:
issue_comment:
types: [created]
permissions:
issues: write
contents: read
models: read
jobs:
ai-helper:
runs-on: ubuntu-latest
# 只在真实用户提到 @ai-helper 时触发,忽略机器人评论
if: |
contains(github.event.comment.body, '@ai-helper') &&
github.event.comment.user.type != 'Bot'
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Get Issue Details
id: issue
uses: actions/github-script@v7
with:
script: |
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
// 限制长度,避免超过 token 限制
const maxLength = 1000;
const truncate = (str, max) => {
if (!str) return '';
return str.length > max ? str.substring(0, max) + '...[内容过长已截断]' : str;
};
core.exportVariable('ISSUE_TITLE', truncate(issue.data.title || '', 200));
core.exportVariable('ISSUE_BODY', truncate(issue.data.body || '', maxLength));
core.exportVariable('COMMENT_BODY', truncate(context.payload.comment.body || '', 500));
core.exportVariable('ISSUE_NUMBER', context.issue.number);
- name: Create Prompt
id: prompt
run: |
cat > prompt.txt << 'PROMPT_EOF'
Issue #${{ env.ISSUE_NUMBER }}
标题: ${{ env.ISSUE_TITLE }}
内容: ${{ env.ISSUE_BODY }}
评论: ${{ env.COMMENT_BODY }}
请搜索项目代码并提供解决方案。
PROMPT_EOF
- name: AI Analysis
uses: actions/ai-inference@v1
id: ai
with:
model: 'gpt-4o'
enable-github-mcp: true
max-tokens: 1500
system-prompt: |
你是 ECS Framework (TypeScript ECS 框架) 的 AI 助手。
主要代码在 packages/core/src。
搜索相关代码后,用中文简洁回答问题,包含问题分析、解决方案和代码引用。
prompt-file: prompt.txt
- name: Post AI Response
env:
AI_RESPONSE: ${{ steps.ai.outputs.response }}
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: process.env.AI_RESPONSE
});

View File

@@ -0,0 +1,56 @@
name: AI Issue Moderator
on:
issues:
types: [opened]
issue_comment:
types: [created]
permissions:
issues: write
contents: read
models: read
jobs:
moderate:
runs-on: ubuntu-latest
steps:
- name: Check Content
uses: actions/ai-inference@v1
id: check
with:
model: 'gpt-4o-mini'
system-prompt: |
你是一个内容审查助手。
检查内容是否包含:
1. 垃圾信息或广告
2. 恶意或攻击性内容
3. 与项目完全无关的内容
只返回 "SPAM" 或 "OK",不要其他内容。
prompt: |
标题:${{ github.event.issue.title || github.event.comment.body }}
内容:
${{ github.event.issue.body || github.event.comment.body }}
- name: Mark as Spam
if: contains(steps.check.outputs.response, 'SPAM')
uses: actions/github-script@v7
with:
script: |
// 添加 spam 标签
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['spam']
});
// 添加评论
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: '🤖 这个内容被 AI 检测为可能的垃圾内容。如果这是误判,请联系维护者。\n\n🤖 This content was detected as potential spam by AI. If this is a false positive, please contact the maintainers.'
});

160
.github/workflows/batch-label-issues.yml vendored Normal file
View File

@@ -0,0 +1,160 @@
name: Batch Label Issues
on:
workflow_dispatch:
inputs:
mode:
description: '标签模式'
required: true
type: choice
options:
- 'recent' # 最近 20 个 issue
- 'open' # 所有打开的 issue
- 'unlabeled' # 只处理没有标签的 issue
- 'all' # 所有 issue慎用
default: 'recent'
skip_labeled:
description: '跳过已有标签的 issue'
required: false
type: boolean
default: true
permissions:
issues: write
contents: read
jobs:
batch-label:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Batch Label Issues
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
MODE="${{ github.event.inputs.mode }}"
SKIP_LABELED="${{ github.event.inputs.skip_labeled }}"
echo "📊 开始批量打标签..."
echo "模式: $MODE"
echo "跳过已标签: $SKIP_LABELED"
# 获取 issue 列表
if [ "$MODE" = "recent" ]; then
echo "📋 获取最近 20 个 issue..."
ISSUES=$(gh issue list --limit 20 --json number,labels,title,body --jq '.[] | {number, labels: [.labels[].name], title, body}')
elif [ "$MODE" = "open" ]; then
echo "📋 获取所有打开的 issue..."
ISSUES=$(gh issue list --state open --json number,labels,title,body --jq '.[] | {number, labels: [.labels[].name], title, body}')
elif [ "$MODE" = "unlabeled" ]; then
echo "📋 获取没有标签的 issue..."
ISSUES=$(gh issue list --state all --json number,labels,title,body --jq '.[] | select(.labels | length == 0) | {number, labels: [.labels[].name], title, body}')
else
echo "📋 获取所有 issue限制 100 个)..."
ISSUES=$(gh issue list --state all --limit 100 --json number,labels,title,body --jq '.[] | {number, labels: [.labels[].name], title, body}')
fi
# 临时文件
echo "$ISSUES" > /tmp/issues.json
# 处理每个 issue
cat /tmp/issues.json | jq -c '.' | while read -r issue; do
ISSUE_NUM=$(echo "$issue" | jq -r '.number')
EXISTING_LABELS=$(echo "$issue" | jq -r '.labels | join(",")')
TITLE=$(echo "$issue" | jq -r '.title')
BODY=$(echo "$issue" | jq -r '.body')
echo ""
echo "🔍 处理 Issue #$ISSUE_NUM: $TITLE"
echo " 现有标签: $EXISTING_LABELS"
# 跳过已有标签的 issue
if [ "$SKIP_LABELED" = "true" ] && [ ! -z "$EXISTING_LABELS" ]; then
echo " ⏭️ 跳过(已有标签)"
continue
fi
# 分析内容并打标签
LABELS_TO_ADD=""
# 检测 bug
if echo "$TITLE $BODY" | grep -iE "(bug|错误|崩溃|crash|error|exception|问题|fix)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD bug"
echo " 🐛 检测到: bug"
fi
# 检测 feature request
if echo "$TITLE $BODY" | grep -iE "(feature|功能|enhancement|improve|优化|建议|新增|添加|add)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD enhancement"
echo " ✨ 检测到: enhancement"
fi
# 检测 question
if echo "$TITLE $BODY" | grep -iE "(question|疑问|how to|如何|怎么|为什么|why|咨询|\?|)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD question"
echo " ❓ 检测到: question"
fi
# 检测 documentation
if echo "$TITLE $BODY" | grep -iE "(doc|文档|readme|guide|tutorial|教程|说明)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD documentation"
echo " 📖 检测到: documentation"
fi
# 检测 performance
if echo "$TITLE $BODY" | grep -iE "(performance|性能|slow|慢|lag|卡顿|optimize|优化)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD performance"
echo " ⚡ 检测到: performance"
fi
# 检测 core
if echo "$TITLE $BODY" | grep -iE "(@esengine/ecs-framework|packages/core|core package|核心包)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD core"
echo " 🎯 检测到: core"
fi
# 检测 editor
if echo "$TITLE $BODY" | grep -iE "(editor|编辑器|tauri)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD editor"
echo " 🎨 检测到: editor"
fi
# 检测 network
if echo "$TITLE $BODY" | grep -iE "(network|网络|multiplayer|多人|同步)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD network"
echo " 🌐 检测到: network"
fi
# 检测 help wanted
if echo "$TITLE $BODY" | grep -iE "(help wanted|需要帮助|求助)" > /dev/null; then
LABELS_TO_ADD="$LABELS_TO_ADD help wanted"
echo " 🆘 检测到: help wanted"
fi
# 添加标签
if [ ! -z "$LABELS_TO_ADD" ]; then
echo " ✅ 添加标签: $LABELS_TO_ADD"
for label in $LABELS_TO_ADD; do
gh issue edit $ISSUE_NUM --add-label "$label" 2>&1 | grep -v "already exists" || true
done
echo " 💬 添加说明评论..."
gh issue comment $ISSUE_NUM --body $'🤖 自动标签系统检测到此 issue 并添加了相关标签。如有误判,请告知维护者。\n\n🤖 Auto-labeling system detected and labeled this issue. Please let maintainers know if this is incorrect.' || true
else
echo " 未检测到明确类型"
fi
# 避免 API 限制
sleep 1
done
echo ""
echo "✅ 批量标签完成!"
echo "查看结果: https://github.com/${{ github.repository }}/issues"

View File

@@ -3,65 +3,125 @@ name: CI
on:
push:
branches: [ master, main, develop ]
paths:
- 'packages/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'turbo.json'
- 'jest.config.*'
- '.github/workflows/ci.yml'
pull_request:
branches: [ master, main, develop ]
paths:
- 'packages/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'turbo.json'
- 'jest.config.*'
- '.github/workflows/ci.yml'
jobs:
test:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
cache: 'pnpm'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
# 缓存 Rust 编译结果
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: packages/engine
cache-on-failure: true
# 缓存 wasm-pack
- name: Cache wasm-pack
uses: actions/cache@v4
with:
path: ~/.cargo/bin/wasm-pack
key: wasm-pack-${{ runner.os }}
- name: Install wasm-pack
run: |
if ! command -v wasm-pack &> /dev/null; then
cargo install wasm-pack
fi
- name: Install dependencies
run: npm ci
run: pnpm install --no-frozen-lockfile
# 缓存 Turbo
- name: Cache Turbo
uses: actions/cache@v4
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
turbo-${{ runner.os }}-
# 构建所有包
- name: Build all packages
run: pnpm run build
- name: Copy WASM files to ecs-engine-bindgen
run: |
mkdir -p packages/ecs-engine-bindgen/src/wasm
cp packages/engine/pkg/es_engine.js packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine.d.ts packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
# 类型检查
- name: Type check
run: pnpm run type-check
# Lint 检查
- name: Lint check
run: pnpm run lint
# 测试
- name: Run tests with coverage
run: npm run test:ci
run: pnpm run test:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
continue-on-error: true
with:
file: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
build:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Build npm package
run: npm run build:npm
# 构建 npm 包
- name: Build npm packages
run: pnpm run build:npm
# 上传构建产物
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: |
bin/
dist/
retention-days: 7
packages/*/dist/
packages/*/bin/
retention-days: 7

146
.github/workflows/cleanup-dependabot.yml vendored Normal file
View File

@@ -0,0 +1,146 @@
name: Cleanup Old Dependabot PRs
# 手动触发的 workflow用于清理堆积的 Dependabot PR
on:
workflow_dispatch:
inputs:
days_old:
description: '关闭多少天前创建的 PR默认 7 天)'
required: false
default: '7'
dry_run:
description: '试运行模式true=仅显示,不关闭)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: List and Close Old Dependabot PRs
uses: actions/github-script@v7
with:
script: |
const daysOld = parseInt('${{ github.event.inputs.days_old }}') || 7;
const dryRun = '${{ github.event.inputs.dry_run }}' === 'true';
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
console.log(`🔍 查找超过 ${daysOld} 天的 Dependabot PR...`);
console.log(`📅 截止日期: ${cutoffDate.toISOString()}`);
console.log(`🏃 模式: ${dryRun ? '试运行(不会实际关闭)' : '实际执行'}`);
console.log('---');
// 获取所有 Dependabot PR
const { data: pulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
});
const dependabotPRs = pulls.filter(pr =>
pr.user.login === 'dependabot[bot]' &&
new Date(pr.created_at) < cutoffDate
);
console.log(`📊 找到 ${dependabotPRs.length} 个符合条件的 Dependabot PR`);
console.log('');
if (dependabotPRs.length === 0) {
console.log('✅ 没有需要清理的 PR');
return;
}
// 按类型分组
const byType = {
dev: [],
prod: [],
actions: [],
other: []
};
for (const pr of dependabotPRs) {
const title = pr.title.toLowerCase();
const labels = pr.labels.map(l => l.name);
let type = 'other';
if (title.includes('dev-dependencies') || title.includes('development')) {
type = 'dev';
} else if (title.includes('production-dependencies')) {
type = 'prod';
} else if (labels.includes('github-actions')) {
type = 'actions';
}
byType[type].push(pr);
}
console.log('📋 PR 分类统计:');
console.log(` 🔧 开发依赖: ${byType.dev.length} 个`);
console.log(` 📦 生产依赖: ${byType.prod.length} 个`);
console.log(` ⚙️ GitHub Actions: ${byType.actions.length} 个`);
console.log(` ❓ 其他: ${byType.other.length} 个`);
console.log('');
// 处理每个 PR
for (const pr of dependabotPRs) {
const age = Math.floor((Date.now() - new Date(pr.created_at)) / (1000 * 60 * 60 * 24));
console.log(`${dryRun ? '🔍' : '🗑️ '} #${pr.number}: ${pr.title}`);
console.log(` 创建时间: ${pr.created_at} (${age} 天前)`);
console.log(` 链接: ${pr.html_url}`);
if (!dryRun) {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
state: 'closed'
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `🤖 **自动关闭旧的 Dependabot PR**
此 PR 已超过 ${daysOld} 天未合并,已被自动关闭以清理积压。
📌 **下一步:**
- Dependabot 已配置为月度运行,届时会创建新的分组更新
- 新的 Mergify 规则会智能处理不同类型的依赖更新
- 开发依赖和 GitHub Actions 会自动合并(即使 CI 失败)
- 生产依赖需要 CI 通过才会自动合并
如果需要立即应用此更新,请手动更新依赖。
---
*此操作由仓库维护者手动触发的清理工作流执行*`
});
console.log(' ✅ 已关闭并添加说明');
} else {
console.log(' 试运行模式 - 未执行操作');
}
console.log('');
}
console.log('---');
if (dryRun) {
console.log(`✨ 试运行完成!共发现 ${dependabotPRs.length} 个待清理的 PR`);
console.log('💡 要实际执行清理,请将 dry_run 参数设为 false 重新运行');
} else {
console.log(`✅ 清理完成!已关闭 ${dependabotPRs.length} 个 Dependabot PR`);
}

51
.github/workflows/codecov.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Code Coverage
on:
push:
branches: [ master, main ]
pull_request:
branches: [ master, main ]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run tests with coverage
run: |
cd packages/core
pnpm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
continue-on-error: true
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/core/coverage/coverage-final.json
flags: core
name: core-coverage
fail_ci_if_error: false
verbose: true
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: packages/core/coverage/

42
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: "CodeQL"
on:
push:
branches: [ "master", "main" ]
pull_request:
branches: [ "master", "main" ]
schedule:
- cron: '0 0 * * 1' # 每周一运行
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-and-quality
config-file: ./.github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

36
.github/workflows/commitlint.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Commit Lint
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: read
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Install commitlint
run: |
pnpm add -D @commitlint/config-conventional @commitlint/cli
- name: Validate PR commits
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose

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

@@ -0,0 +1,72 @@
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: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Install dependencies
run: pnpm install
- name: Build core package
run: pnpm run build:core
- name: Generate API documentation
run: pnpm run docs:api
- name: Build documentation
run: pnpm 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

23
.github/workflows/issue-labeler.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Issue Labeler
on:
issues:
types: [opened, edited]
permissions:
issues: write
contents: read
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Label Issues
uses: github/issue-labeler@v3.4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler.yml
enable-versioned-regex: 1

28
.github/workflows/issue-translator.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Issue Translator
on:
issue_comment:
types: [created]
issues:
types: [opened]
permissions:
issues: write
jobs:
translate:
runs-on: ubuntu-latest
steps:
- name: Translate Issues
uses: tomsun28/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: false
# 设置为 true 会修改标题false 只在评论中添加翻译
CUSTOM_BOT_NOTE: |
<details>
<summary>🌏 Translation / 翻译</summary>
Bot detected the issue body's language is not English, translate it automatically.
机器人检测到 issue 内容非英文,自动翻译。
</details>

159
.github/workflows/release-editor.yml vendored Normal file
View File

@@ -0,0 +1,159 @@
name: Release Editor App
on:
push:
tags:
- 'editor-v*'
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., 1.0.0)'
required: true
default: '1.0.0'
jobs:
build-tauri:
strategy:
fail-fast: false
matrix:
include:
- platform: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- platform: macos-latest
target: x86_64-apple-darwin
arch: x64
- platform: macos-latest
target: aarch64-apple-darwin
arch: arm64
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: packages/editor-app/src-tauri
cache-on-failure: true
- name: Install dependencies (Ubuntu)
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install frontend dependencies
run: pnpm install
- name: Update version in config files (for manual trigger)
if: github.event_name == 'workflow_dispatch'
run: |
cd packages/editor-app
node -e "const pkg=require('./package.json'); pkg.version='${{ github.event.inputs.version }}'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2)+'\n')"
node scripts/sync-version.js
- name: Install wasm-pack
run: cargo install wasm-pack
# 使用 Turborepo 自动按依赖顺序构建所有包
# 这会自动处理core -> asset-system -> editor-core -> ui -> 等等
- name: Build all packages with Turborepo
run: pnpm run build
- name: Copy WASM files to ecs-engine-bindgen
shell: bash
run: |
mkdir -p packages/ecs-engine-bindgen/src/wasm
cp packages/engine/pkg/es_engine.js packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine.d.ts packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
- name: Bundle runtime files for Tauri
run: |
cd packages/editor-app
node scripts/bundle-runtime.mjs
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
projectPath: packages/editor-app
tagName: ${{ github.event_name == 'workflow_dispatch' && format('editor-v{0}', github.event.inputs.version) || github.ref_name }}
releaseName: 'ECS Editor v${{ github.event.inputs.version || github.ref_name }}'
releaseBody: 'See the assets to download this version and install.'
releaseDraft: false
prerelease: false
includeUpdaterJson: true
updaterJsonKeepUniversal: false
args: ${{ matrix.platform == 'macos-latest' && format('--target {0}', matrix.target) || '' }}
# 构建成功后,创建 PR 更新版本号
update-version-pr:
needs: build-tauri
if: github.event_name == 'workflow_dispatch' && success()
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Update version files
run: |
cd packages/editor-app
node -e "const pkg=require('./package.json'); pkg.version='${{ github.event.inputs.version }}'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2)+'\n')"
node scripts/sync-version.js
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore(editor): bump version to ${{ github.event.inputs.version }}"
branch: release/editor-v${{ github.event.inputs.version }}
delete-branch: true
title: "chore(editor): Release v${{ github.event.inputs.version }}"
body: |
## Release v${{ github.event.inputs.version }}
This PR updates the editor version after successful release build.
### Changes
- Updated `packages/editor-app/package.json` → `${{ github.event.inputs.version }}`
- Updated `packages/editor-app/src-tauri/tauri.conf.json` → `${{ github.event.inputs.version }}`
### Release
- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/editor-v${{ github.event.inputs.version }})
---
*This PR was automatically created by the release workflow.*
labels: |
release
editor
automated pr

135
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,135 @@
name: Release NPM Packages
on:
workflow_dispatch:
inputs:
package:
description: '选择要发布的包'
required: true
type: choice
options:
- core
- behavior-tree
- editor-core
- node-editor
- blueprint
- tilemap
- physics-rapier2d
version_type:
description: '版本更新类型'
required: true
type: choice
options:
- patch
- minor
- major
- custom
custom_version:
description: '自定义版本号 (仅当选择 custom 时使用,例如: 2.2.9)'
required: false
type: string
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release-package:
name: Release ${{ github.event.inputs.package }} Package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build core package (if needed)
if: ${{ github.event.inputs.package != 'core' && github.event.inputs.package != 'node-editor' }}
run: |
cd packages/core
pnpm run build
- name: Build node-editor package (if needed for blueprint)
if: ${{ github.event.inputs.package == 'blueprint' }}
run: |
cd packages/node-editor
pnpm run build
# - name: Run tests
# run: |
# cd packages/${{ github.event.inputs.package }}
# npm run test:ci
- name: Update version
id: version
run: |
cd packages/${{ github.event.inputs.package }}
if [ "${{ github.event.inputs.version_type }}" = "custom" ]; then
NEW_VERSION=${{ github.event.inputs.custom_version }}
else
# Get current version and bump it
CURRENT=$(node -p "require('./package.json').version")
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
case "${{ github.event.inputs.version_type }}" in
major) NEW_VERSION="$((MAJOR+1)).0.0" ;;
minor) NEW_VERSION="$MAJOR.$((MINOR+1)).0" ;;
patch) NEW_VERSION="$MAJOR.$MINOR.$((PATCH+1))" ;;
esac
fi
# Update package.json using node
node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json')); pkg.version='$NEW_VERSION'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2)+'\n')"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "发布版本: $NEW_VERSION"
- name: Build package
run: |
cd packages/${{ github.event.inputs.package }}
pnpm run build:npm
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
cd packages/${{ github.event.inputs.package }}/dist
pnpm publish --access public --no-git-checks
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore(${{ github.event.inputs.package }}): release v${{ steps.version.outputs.new_version }}"
branch: release/${{ github.event.inputs.package }}-v${{ steps.version.outputs.new_version }}
delete-branch: true
title: "chore(${{ github.event.inputs.package }}): Release v${{ steps.version.outputs.new_version }}"
body: |
## 🚀 Release v${{ steps.version.outputs.new_version }}
此 PR 更新 `@esengine/${{ github.event.inputs.package }}` 包的版本号
### 变更
- ✅ 已发布到 npm: [@esengine/${{ github.event.inputs.package }}@${{ steps.version.outputs.new_version }}](https://www.npmjs.com/package/@esengine/${{ github.event.inputs.package }}/v/${{ steps.version.outputs.new_version }})
- ✅ 更新 `packages/${{ github.event.inputs.package }}/package.json` → `${{ steps.version.outputs.new_version }}`
---
*此 PR 由发布工作流自动创建*
labels: |
release
${{ github.event.inputs.package }}
automated pr

48
.github/workflows/size-limit.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Size Limit
on:
pull_request:
branches:
- master
- main
paths:
- 'packages/core/src/**'
- 'packages/core/package.json'
- '.size-limit.json'
permissions:
contents: read
pull-requests: write
issues: write
jobs:
size:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build core package
run: |
cd packages/core
pnpm run build:npm
- name: Check bundle size
uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
skip_step: install

60
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: Stale Issues and PRs
on:
schedule:
- cron: '0 0 * * *' # 每天运行一次
workflow_dispatch: # 允许手动触发
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- name: Stale Bot
uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Issue 配置
stale-issue-message: |
这个 issue 已经 60 天没有活动了,将在 14 天后自动关闭。
如果这个问题仍然存在,请留言说明情况。
This issue has been inactive for 60 days and will be closed in 14 days.
If this issue is still relevant, please leave a comment.
close-issue-message: |
由于长时间无活动,这个 issue 已被自动关闭。
如需重新打开,请留言说明。
This issue has been automatically closed due to inactivity.
Please comment if you'd like to reopen it.
days-before-issue-stale: 60
days-before-issue-close: 14
stale-issue-label: 'stale'
exempt-issue-labels: 'pinned,security,enhancement,help wanted'
# PR 配置
stale-pr-message: |
这个 PR 已经 30 天没有活动了,将在 7 天后自动关闭。
如果你还在处理这个 PR请更新一下。
This PR has been inactive for 30 days and will be closed in 7 days.
If you're still working on it, please update it.
close-pr-message: |
由于长时间无活动,这个 PR 已被自动关闭。
如需继续,请重新打开或创建新的 PR。
This PR has been automatically closed due to inactivity.
Please reopen or create a new PR to continue.
days-before-pr-stale: 30
days-before-pr-close: 7
stale-pr-label: 'stale'
exempt-pr-labels: 'pinned,security,wip'
# 其他配置
operations-per-run: 100
remove-stale-when-updated: true
ascending: true

58
.github/workflows/welcome.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Welcome
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
permissions:
issues: write
pull-requests: write
jobs:
welcome:
runs-on: ubuntu-latest
steps:
- name: Welcome new contributors
uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: |
👋 你好!感谢你提交第一个 issue
我们会尽快查看并回复。同时,建议你:
- 📚 查看[文档](https://esengine.github.io/ecs-framework/)
- 🤖 使用 [AI 文档助手](https://deepwiki.com/esengine/ecs-framework)
- 💬 加入 [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
---
👋 Hello! Thanks for opening your first issue!
We'll review it as soon as possible. Meanwhile, you might want to:
- 📚 Check the [documentation](https://esengine.github.io/ecs-framework/)
- 🤖 Use [AI documentation assistant](https://deepwiki.com/esengine/ecs-framework)
pr-message: |
👋 你好!感谢你提交第一个 Pull Request
在我们 Review 之前,请确保:
- ✅ 代码遵循项目规范
- ✅ 通过所有测试
- ✅ 更新了相关文档
- ✅ Commit 遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范
查看完整的[贡献指南](https://github.com/esengine/ecs-framework/blob/master/CONTRIBUTING.md)。
---
👋 Hello! Thanks for your first Pull Request!
Before we review, please ensure:
- ✅ Code follows project conventions
- ✅ All tests pass
- ✅ Documentation is updated
- ✅ Commits follow [Conventional Commits](https://www.conventionalcommits.org/)
See the full [Contributing Guide](https://github.com/esengine/ecs-framework/blob/master/CONTRIBUTING.md).

19
.gitignore vendored
View File

@@ -16,6 +16,10 @@ dist/
*.tmp
*.temp
.cache/
.build-cache/
# Turborepo
.turbo/
# IDE 配置
.idea/
@@ -48,13 +52,15 @@ logs/
coverage/
*.lcov
# 包管理器锁文件保留npm的,忽略其他的
# 包管理器锁文件(忽略yarn保留pnpm
yarn.lock
pnpm-lock.yaml
package-lock.json
# 文档生成
docs/api/
docs/build/
docs/.vitepress/cache/
docs/.vitepress/dist/
# 备份文件
*.bak
@@ -67,3 +73,12 @@ docs/build/
/demo/.idea/
/demo/.vscode/
/demo_wxgame/
# Tauri 构建产物
**/src-tauri/target/
**/src-tauri/WixTools/
**/src-tauri/gen/
# Tauri 捆绑输出
**/src-tauri/target/release/bundle/
**/src-tauri/target/debug/bundle/

24
.gitmodules vendored
View File

@@ -4,27 +4,15 @@
[submodule "thirdparty/admin-backend"]
path = thirdparty/admin-backend
url = https://github.com/esengine/admin-backend.git
[submodule "extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension"]
path = extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension
url = https://github.com/esengine/cocos-ecs-extension.git
[submodule "extensions/cocos/cocos-ecs/extensions/behaviour-tree"]
path = extensions/cocos/cocos-ecs/extensions/behaviour-tree
url = https://github.com/esengine/behaviour-tree.git
[submodule "extensions/cocos/cocos-ecs/extensions/cocos-terrain-gen"]
path = extensions/cocos/cocos-ecs/extensions/cocos-terrain-gen
url = https://github.com/esengine/cocos-terrain-gen.git
[submodule "examples/lawn-mower-demo"]
path = examples/lawn-mower-demo
url = https://github.com/esengine/lawn-mower-demo.git
[submodule "extensions/cocos/cocos-ecs/extensions/mvvm-designer"]
path = extensions/cocos/cocos-ecs/extensions/mvvm-designer
url = https://github.com/esengine/mvvm-designer.git
[submodule "thirdparty/mvvm-ui-framework"]
path = thirdparty/mvvm-ui-framework
url = https://github.com/esengine/mvvm-ui-framework.git
[submodule "thirdparty/cocos-nexus"]
path = thirdparty/cocos-nexus
url = https://github.com/esengine/cocos-nexus.git
[submodule "extensions/cocos/cocos-ecs/extensions/utilityai_designer"]
path = extensions/cocos/cocos-ecs/extensions/utilityai_designer
url = https://github.com/esengine/utilityai_designer.git
[submodule "thirdparty/ecs-astar"]
path = thirdparty/ecs-astar
url = https://github.com/esengine/ecs-astar.git
[submodule "examples/lawn-mower-demo"]
path = examples/lawn-mower-demo
url = https://github.com/esengine/lawn-mower-demo.git

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
link-workspace-packages=true
prefer-workspace-packages=true

2
.nxignore Normal file
View File

@@ -0,0 +1,2 @@
examples/*/settings/**
extensions/*/settings/**

49
.prettierignore Normal file
View File

@@ -0,0 +1,49 @@
# 依赖和构建输出
node_modules/
dist/
bin/
build/
coverage/
*.min.js
*.min.css
# 编译输出
**/*.d.ts
tsconfig.tsbuildinfo
# 日志
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# 第三方库
thirdparty/
examples/lawn-mower-demo/
extensions/
# 文档生成
docs/.vitepress/cache/
docs/.vitepress/dist/
docs/api/
# 临时文件
*.tmp
*.bak
*.swp
*~
# 系统文件
.DS_Store
Thumbs.db
# 编辑器
.vscode/
.idea/
# 其他
*.backup
CHANGELOG.md
LICENSE
README.md

14
.prettierrc Normal file
View File

@@ -0,0 +1,14 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"useTabs": false,
"trailingComma": "none",
"printWidth": 120,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSpacing": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"proseWrap": "preserve"
}

25
.size-limit.json Normal file
View File

@@ -0,0 +1,25 @@
[
{
"name": "@esengine/ecs-framework (ESM)",
"path": "packages/core/dist/esm/index.js",
"import": "*",
"limit": "50 KB",
"webpack": false,
"gzip": true
},
{
"name": "@esengine/ecs-framework (UMD)",
"path": "packages/core/dist/umd/ecs-framework.js",
"limit": "60 KB",
"webpack": false,
"gzip": true
},
{
"name": "Core Runtime (Tree-shaking)",
"path": "packages/core/dist/esm/index.js",
"import": "{ Core, Scene, Entity, Component }",
"limit": "30 KB",
"webpack": false,
"gzip": true
}
]

130
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,130 @@
# 贡献指南 / Contributing Guide
感谢你对 ECS Framework 的关注!
Thank you for your interest in contributing to ECS Framework!
## Commit 规范 / Commit Convention
本项目使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范。
This project follows the [Conventional Commits](https://www.conventionalcommits.org/) specification.
### 格式 / Format
```
<type>(<scope>): <subject>
<body>
<footer>
```
### 类型 / Types
- **feat**: 新功能 / New feature
- **fix**: 错误修复 / Bug fix
- **docs**: 文档变更 / Documentation changes
- **style**: 代码格式(不影响代码运行) / Code style changes
- **refactor**: 重构(既不是新功能也不是修复) / Code refactoring
- **perf**: 性能优化 / Performance improvements
- **test**: 测试相关 / Test changes
- **build**: 构建系统或依赖变更 / Build system changes
- **ci**: CI 配置变更 / CI configuration changes
- **chore**: 其他变更 / Other changes
### 范围 / Scope
- **core**: 核心包 @esengine/ecs-framework
- **math**: 数学库包
- **editor**: 编辑器
- **docs**: 文档
### 示例 / Examples
```bash
# 新功能
feat(core): add component pooling system
# 错误修复
fix(core): fix entity deletion memory leak
# 破坏性变更
feat(core): redesign system lifecycle
BREAKING CHANGE: System.initialize() now requires Scene parameter
```
## 自动发布 / Automatic Release
本项目使用 Semantic Release 自动发布。
This project uses Semantic Release for automatic publishing.
### 版本规则 / Versioning Rules
根据你的 commit 类型,版本号会自动更新:
Based on your commit type, the version will be automatically updated:
- `feat`: 增加 **minor** 版本 (0.x.0)
- `fix`, `perf`, `refactor`: 增加 **patch** 版本 (0.0.x)
- `BREAKING CHANGE`: 增加 **major** 版本 (x.0.0)
### 发布流程 / Release Process
1. 提交代码到 `master` 分支 / Push commits to `master` branch
2. GitHub Actions 自动运行测试 / GitHub Actions runs tests automatically
3. Semantic Release 分析 commits / Semantic Release analyzes commits
4. 自动更新版本号 / Version is automatically updated
5. 自动生成 CHANGELOG.md / CHANGELOG.md is automatically generated
6. 自动发布到 npm / Package is automatically published to npm
7. 自动创建 GitHub Release / GitHub Release is automatically created
## 开发流程 / Development Workflow
1. Fork 本仓库 / Fork this repository
2. 创建特性分支 / Create a feature branch
```bash
git checkout -b feat/my-feature
```
3. 提交你的变更 / Commit your changes
```bash
git commit -m "feat(core): add new feature"
```
4. 推送到你的 Fork / Push to your fork
```bash
git push origin feat/my-feature
```
5. 创建 Pull Request / Create a Pull Request
## 本地测试 / Local Testing
```bash
# 安装依赖
npm install
# 运行测试
npm test
# 构建
npm run build
# 代码检查
npm run lint
# 代码格式化
npm run format
```
## 问题反馈 / Issue Reporting
如果你发现了 bug 或有新功能建议,请[创建 Issue](https://github.com/esengine/ecs-framework/issues/new)。
If you find a bug or have a feature request, please [create an issue](https://github.com/esengine/ecs-framework/issues/new).
## 许可证 / License
通过贡献代码,你同意你的贡献将遵循 MIT 许可证。
By contributing, you agree that your contributions will be licensed under the MIT License.

214
LICENSE
View File

@@ -1,201 +1,21 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
MIT License
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Copyright (c) 2025 ECS Framework
1. Definitions.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

553
README.md
View File

@@ -1,438 +1,245 @@
# ECS Framework
# ESEngine
[![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)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
**English** | [中文](./README_CN.md)
TypeScript ECS (Entity-Component-System) 框架,专为游戏开发设计。
**[Documentation](https://esengine.github.io/ecs-framework/) | [API Reference](https://esengine.github.io/ecs-framework/api/) | [Examples](./examples/)**
> 🤔 **什么是 ECS** 不熟悉 ECS 架构?建议先阅读 [ECS 架构基础](docs/concepts-explained.md#ecs-架构基础) 了解核心概念
ESEngine is a cross-platform 2D game engine for creating games from a unified interface. It provides a comprehensive set of common tools so that developers can focus on making games without having to reinvent the wheel.
## 特性
Games can be exported to multiple platforms including Web browsers, WeChat Mini Games, and other mini-game platforms.
- 🔧 **完整的 TypeScript 支持** - 强类型检查和代码提示
- 📡 **[类型安全事件系统](docs/concepts-explained.md#事件系统)** - 事件装饰器和异步事件处理
- 🔍 **[查询系统](docs/concepts-explained.md#实体管理)** - 流式 API 和智能缓存
-**[性能优化](docs/concepts-explained.md#性能优化技术)** - 组件索引、Archetype 系统、脏标记
- 🎯 **[实体管理器](docs/concepts-explained.md#实体管理)** - 统一的实体生命周期管理
- 🧰 **调试工具** - 内置性能监控和调试信息
## Free and Open Source
> 📖 **不熟悉这些概念?** 查看我们的 [技术概念详解](docs/concepts-explained.md) 了解它们的作用和应用场景
ESEngine is completely free and open source under the MIT license. No strings attached, no royalties. Your games are yours.
## 安装
## Features
- **Data-Driven Architecture**: Built on Entity-Component-System (ECS) pattern for flexible and performant game logic
- **High-Performance Rendering**: Rust/WebAssembly 2D renderer with sprite batching and WebGL 2.0 backend
- **Visual Editor**: Cross-platform desktop editor with scene management, asset browser, and visual tools
- **Modular Design**: Use only what you need. Each feature is a separate module that can be included independently
- **Multi-Platform**: Deploy to Web, WeChat Mini Games, and more from a single codebase
## Getting the Engine
### Using npm
```bash
npm install @esengine/ecs-framework
```
## 快速开始
### Building from Source
### 基础设置
See [Building from Source](#building-from-source) for detailed instructions.
### Editor Download
Pre-built editor binaries are available on the [Releases](https://github.com/esengine/ecs-framework/releases) page for Windows and macOS.
## Quick Start
```typescript
import { Core, Scene, Entity, Component, EntitySystem } from '@esengine/ecs-framework';
import {
Core, Scene, Entity, Component, EntitySystem,
Matcher, Time, ECSComponent, ECSSystem
} from '@esengine/ecs-framework';
// 创建核心实例 - 使用配置对象(推荐)
const core = Core.create({
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 core2 = Core.create(true); // 等同于 { debug: true, enableEntitySystems: true }
// 创建场景
const scene = new Scene();
Core.scene = scene;
```
### 定义组件
```typescript
class PositionComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
@ECSComponent('Position')
class Position extends Component {
x = 0;
y = 0;
}
class VelocityComponent extends Component {
constructor(public dx: number = 0, public dy: number = 0) {
super();
}
@ECSComponent('Velocity')
class Velocity extends Component {
dx = 0;
dy = 0;
}
class HealthComponent extends Component {
constructor(
public maxHealth: number = 100,
public currentHealth: number = 100
) {
super();
}
}
```
### 创建实体
```typescript
// 基础实体创建
const player = scene.createEntity("Player");
player.addComponent(new PositionComponent(100, 100));
player.addComponent(new VelocityComponent(5, 0));
player.addComponent(new HealthComponent(100, 100));
// 批量创建实体
const enemies = scene.createEntities(50, "Enemy");
```
### 创建系统
```typescript
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
super();
super(Matcher.all(Position, Velocity));
}
public process(entities: Entity[]) {
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const position = entity.getComponent(PositionComponent);
const velocity = entity.getComponent(VelocityComponent);
if (position && velocity) {
position.x += velocity.dx;
position.y += velocity.dy;
}
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
pos.x += vel.dx * Time.deltaTime;
pos.y += vel.dy * Time.deltaTime;
}
}
}
// 添加系统到场景
scene.addEntityProcessor(new MovementSystem());
```
Core.create();
const scene = new Scene();
scene.addSystem(new MovementSystem());
### 游戏循环
const player = scene.createEntity('Player');
player.addComponent(new Position());
player.addComponent(new Velocity());
ECS框架需要在游戏引擎的更新循环中调用
Core.setScene(scene);
```typescript
// 统一的API传入deltaTime
Core.update(deltaTime);
```
**不同平台的集成示例:**
```typescript
// Laya引擎
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000; // 转换为秒
Core.update(deltaTime);
});
// Cocos Creator
update(deltaTime: number) {
Core.update(deltaTime);
}
// 原生浏览器环境
// Game loop
let lastTime = 0;
function gameLoop(currentTime: number) {
const deltaTime = lastTime > 0 ? (currentTime - lastTime) / 1000 : 0.016;
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
Core.update(deltaTime);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
## 实体管理器
## Modules
EntityManager 提供了统一的实体管理接口:
ESEngine is organized into modular packages. Each feature has a runtime module and an optional editor extension.
```typescript
import { EntityManager } from '@esengine/ecs-framework';
### Core
const entityManager = new EntityManager();
| Package | Description |
|---------|-------------|
| `@esengine/ecs-framework` | Core ECS framework with entity management, component system, and queries |
| `@esengine/math` | Vector, matrix, and mathematical utilities |
| `@esengine/engine` | Rust/WASM 2D renderer |
| `@esengine/engine-core` | Engine module system and lifecycle management |
// 流式查询 API
const results = entityManager
.query()
.withAll(PositionComponent, VelocityComponent)
.withNone(HealthComponent)
.withTag(1)
.execute();
### Runtime Modules
// 批量操作使用Scene的方法
const bullets = scene.createEntities(100, "bullet");
| Package | Description |
|---------|-------------|
| `@esengine/sprite` | 2D sprite rendering and animation |
| `@esengine/tilemap` | Tile-based map rendering with animation support |
| `@esengine/physics-rapier2d` | 2D physics simulation powered by Rapier |
| `@esengine/behavior-tree` | Behavior tree AI system |
| `@esengine/blueprint` | Visual scripting runtime |
| `@esengine/camera` | Camera control and management |
| `@esengine/audio` | Audio playback |
| `@esengine/ui` | UI components |
| `@esengine/material-system` | Material and shader system |
| `@esengine/asset-system` | Asset loading and management |
// 按标签查询
const enemies = entityManager.getEntitiesByTag(2);
### Editor Extensions
| Package | Description |
|---------|-------------|
| `@esengine/sprite-editor` | Sprite inspector and tools |
| `@esengine/tilemap-editor` | Visual tilemap editor with brush tools |
| `@esengine/physics-rapier2d-editor` | Physics collider visualization and editing |
| `@esengine/behavior-tree-editor` | Visual behavior tree editor |
| `@esengine/blueprint-editor` | Visual scripting editor |
| `@esengine/material-editor` | Material and shader editor |
| `@esengine/shader-editor` | Shader code editor |
### Platform
| Package | Description |
|---------|-------------|
| `@esengine/platform-common` | Platform abstraction interfaces |
| `@esengine/platform-web` | Web browser runtime |
| `@esengine/platform-wechat` | WeChat Mini Game runtime |
## Editor
ESEngine Editor is a cross-platform desktop application built with Tauri and React.
### Features
- Scene hierarchy and entity management
- Component inspector with custom editors
- Asset browser with drag-and-drop support
- Tilemap editor with paint, fill, and selection tools
- Behavior tree visual editor
- Blueprint visual scripting
- Material and shader editing
- Built-in performance profiler
- Localization support (English, Chinese)
### Screenshot
![ESEngine Editor](screenshots/main_screetshot.png)
## Supported Platforms
| Platform | Runtime | Editor |
|----------|---------|--------|
| Web Browser | Yes | - |
| Windows | - | Yes |
| macOS | - | Yes |
| WeChat Mini Game | In Progress | - |
| Playable Ads | Planned | - |
| Android | Planned | - |
| iOS | Planned | - |
| Windows Native | Planned | - |
| Other Platforms | Planned | - |
## Building from Source
### Prerequisites
- Node.js 18 or later
- pnpm 10 or later
- Rust toolchain (for WASM renderer)
- wasm-pack
### Setup
```bash
# Clone repository
git clone https://github.com/esengine/ecs-framework.git
cd ecs-framework
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Build WASM renderer (optional)
pnpm build:wasm
```
## 事件系统
### Running the Editor
### [基础事件](docs/concepts-explained.md#类型安全事件)
类型安全的事件系统,编译时检查事件名和数据类型。
```typescript
import { EventBus, ECSEventType } from '@esengine/ecs-framework';
const eventBus = entityManager.eventBus;
// 监听预定义事件
eventBus.onEntityCreated((data) => {
console.log(`实体创建: ${data.entityName}`);
});
eventBus.onComponentAdded((data) => {
console.log(`组件添加: ${data.componentType}`);
});
// 自定义事件
eventBus.emit('player:death', { playerId: 123, reason: 'fall' });
```bash
cd packages/editor-app
pnpm tauri:dev
```
### [事件装饰器](docs/concepts-explained.md#事件装饰器)
使用装饰器语法自动注册事件监听器,减少样板代码。
```typescript
import { EventHandler, ECSEventType } from '@esengine/ecs-framework';
class GameSystem {
@EventHandler(ECSEventType.ENTITY_DESTROYED)
onEntityDestroyed(data: EntityDestroyedEventData) {
console.log('实体销毁:', data.entityName);
}
@EventHandler('player:levelup')
onPlayerLevelUp(data: { playerId: number; newLevel: number }) {
console.log(`玩家 ${data.playerId} 升级到 ${data.newLevel}`);
}
}
```
## 性能优化
### [组件索引](docs/concepts-explained.md#组件索引系统)
通过建立索引避免线性搜索,将查询复杂度从 O(n) 降低到 O(1)。
```typescript
// 使用Scene的查询系统进行组件索引
const querySystem = scene.querySystem;
// 查询具有特定组件的实体
const entitiesWithPosition = querySystem.queryAll(PositionComponent).entities;
const entitiesWithVelocity = querySystem.queryAll(VelocityComponent).entities;
// 性能统计
const stats = querySystem.getStats();
console.log('查询效率:', stats.hitRate);
```
**索引类型选择:**
- **哈希索引** - 适合稳定的、大量的组件(如位置、生命值)
- **位图索引** - 适合频繁变化的组件如Buff、状态
> 📋 详细选择指南参见 [索引类型选择指南](docs/concepts-explained.md#索引类型选择指南)
### [Archetype 系统](docs/concepts-explained.md#archetype-系统)
将具有相同组件组合的实体分组,减少查询时的组件检查开销。
```typescript
// 使用查询系统的Archetype功能
const querySystem = scene.querySystem;
// 查询统计
const stats = querySystem.getStats();
console.log('缓存命中率:', stats.hitRate);
```
### [脏标记系统](docs/concepts-explained.md#脏标记系统)
追踪数据变化,只处理发生改变的实体,避免不必要的计算。
```typescript
// 脏标记通过组件系统自动管理
// 组件变化时会自动标记为脏数据
// 查询系统会自动处理脏标记优化
const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
```
> 💡 **不确定何时使用这些优化?** 查看 [性能优化建议](docs/concepts-explained.md#性能建议) 了解适用场景
## API 参考
### 核心类
| 类 | 描述 |
|---|---|
| `Core` | 框架核心管理类 |
| `Scene` | 场景容器,管理实体和系统 |
| `Entity` | 实体对象,包含组件集合 |
| `Component` | 组件基类 |
| `EntitySystem` | 系统基类 |
| `EntityManager` | 实体管理器 |
### 查询 API
```typescript
entityManager
.query()
.withAll(...components) // 包含所有指定组件
.withAny(...components) // 包含任意指定组件
.withNone(...components) // 不包含指定组件
.withTag(tag) // 包含指定标签
.withoutTag(tag) // 不包含指定标签
.execute() // 执行查询
```
### 事件类型
```typescript
enum ECSEventType {
ENTITY_CREATED = 'entity:created',
ENTITY_DESTROYED = 'entity:destroyed',
COMPONENT_ADDED = 'component:added',
COMPONENT_REMOVED = 'component:removed',
SYSTEM_ADDED = 'system:added',
SYSTEM_REMOVED = 'system:removed'
}
```
## 与其他框架对比
| 特性 | @esengine/ecs-framework | bitECS | Miniplex |
|------|-------------------------|--------|----------|
| TypeScript 支持 | ✅ 原生支持 | ✅ 完整支持 | ✅ 原生支持 |
| 事件系统 | ✅ 内置+装饰器 | ❌ 需自己实现 | ✅ 响应式 |
| 查询系统 | ✅ 流式 API | ✅ 函数式 | ✅ 响应式 |
| 实体管理器 | ✅ 统一接口 | ❌ 低级 API | ✅ 高级接口 |
| 性能优化 | ✅ 多重优化 | ✅ 极致性能 | ✅ React 优化 |
| JavaScript引擎集成 | ✅ 专为JS引擎设计 | ✅ 通用设计 | ⚠️ 主要 React |
| 可视化调试工具 | ✅ [Cocos插件](https://store.cocos.com/app/detail/7823) | ❌ 无官方工具 | ✅ React DevTools |
**选择指南:**
- 选择本框架:需要完整的游戏开发工具链和中文社区支持
- 选择 bitECS需要极致性能和最小化设计
- 选择 Miniplex主要用于 React 应用开发
## 项目结构
### Project Structure
```
ecs-framework/
├── src/
│ ├── ECS/ # ECS 核心系统
│ │ ├── Core/ # 核心管理器
│ │ ├── Systems/ # 系统类型
│ │ └── Utils/ # ECS 工具
│ ├── Types/ # TypeScript接口定义
│ └── Utils/ # 通用工具
├── docs/ # 文档
└── scripts/ # 构建脚本
├── packages/ Engine packages (runtime, editor, platform)
├── docs/ Documentation source
├── examples/ Example projects
├── scripts/ Build utilities
└── thirdparty/ Third-party dependencies
```
## 文档
## Documentation
### 🎯 新手入门
- **[📖 新手教程完整指南](docs/beginner-tutorials.md)** - 完整学习路径,从零开始 ⭐ **强烈推荐**
- **[🚀 快速入门](docs/getting-started.md)** - 详细的入门教程包含Laya/Cocos/Node.js集成指南 ⭐ **平台集成必读**
- 💡 **Cocos Creator用户特别提示**:我们提供[专用调试插件](https://store.cocos.com/app/detail/7823)支持可视化ECS调试
- [🧠 技术概念详解](docs/concepts-explained.md) - 通俗易懂的技术概念解释 ⭐ **推荐新手阅读**
- [🎯 位掩码使用指南](docs/bitmask-guide.md) - 位掩码概念、原理和高级使用技巧
- [💡 使用场景示例](docs/use-cases.md) - 不同类型游戏的具体应用案例
- [🔧 框架类型系统](docs/concepts-explained.md#框架类型系统) - TypeScript接口设计和使用指南
- [Getting Started](https://esengine.github.io/ecs-framework/guide/getting-started.html)
- [Architecture Guide](https://esengine.github.io/ecs-framework/guide/)
- [API Reference](https://esengine.github.io/ecs-framework/api/)
### 📚 核心功能
- [🎭 实体管理指南](docs/entity-guide.md) - 实体的创建和使用方法
- [🧩 组件设计指南](docs/component-design-guide.md) - 如何设计高质量组件 ⭐ **设计必读**
- [⚙️ 系统详解指南](docs/system-guide.md) - 四种系统类型的详细使用
- [🎬 场景管理指南](docs/scene-management-guide.md) - 场景切换和数据管理
- [⏰ 定时器系统指南](docs/timer-guide.md) - 定时器的完整使用方法
## Community
### API 参考
- [核心 API 参考](docs/core-concepts.md) - 完整的 API 使用说明
- [实体基础指南](docs/entity-guide.md) - 实体的基本概念和操作
- [EntityManager 指南](docs/entity-manager-example.md) - 高性能查询和批量操作
- [事件系统指南](docs/event-system-example.md) - 事件系统完整用法
- [查询系统指南](docs/query-system-usage.md) - 查询系统使用方法
- [GitHub Issues](https://github.com/esengine/ecs-framework/issues) - Bug reports and feature requests
- [GitHub Discussions](https://github.com/esengine/ecs-framework/discussions) - Questions and ideas
### 性能相关
- [性能优化指南](docs/performance-optimization.md) - 性能优化技术和策略
## Contributing
## 构建
Contributions are welcome. Please read the contributing guidelines before submitting a pull request.
```bash
# 安装依赖
npm install
1. Fork the repository
2. Create a feature branch
3. Make changes with tests
4. Submit a pull request
# 构建项目
npm run build
## License
# 监听模式
npm run build:watch
# 清理构建文件
npm run clean
# 重新构建
npm run rebuild
```
## 性能监控
框架提供内置性能统计:
```typescript
// 场景统计
const sceneStats = scene.getStats();
console.log('性能统计:', {
实体数量: sceneStats.entityCount,
系统数量: sceneStats.processorCount
});
// 查询系统统计
const queryStats = scene.querySystem.getStats();
console.log('查询统计:', {
缓存命中率: queryStats.hitRate + '%',
查询次数: queryStats.queryCount
});
```
## 扩展库
- [路径寻找库](https://github.com/esengine/ecs-astar) - A*、BFS、Dijkstra 算法
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI
## 社区
- QQ 群:[ecs游戏框架交流](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
- GitHub[提交 Issue](https://github.com/esengine/ecs-framework/issues)
## 贡献
欢迎提交 Pull Request 和 Issue
### 开发要求
- Node.js >= 14.0.0
- TypeScript >= 4.0.0
## 许可证
[MIT](LICENSE)
ESEngine is licensed under the [MIT License](LICENSE).

246
README_CN.md Normal file
View File

@@ -0,0 +1,246 @@
# ESEngine
[English](./README.md) | **中文**
**[文档](https://esengine.github.io/ecs-framework/) | [API 参考](https://esengine.github.io/ecs-framework/api/) | [示例](./examples/)**
ESEngine 是一个跨平台 2D 游戏引擎,提供统一的开发界面。它包含完整的常用工具集,让开发者专注于游戏创作本身。
游戏可以导出到多个平台,包括 Web 浏览器、微信小游戏等小游戏平台。
## 免费开源
ESEngine 基于 MIT 协议完全免费开源。无附加条件,无版税。你的游戏完全属于你。
## 特性
- **数据驱动架构**:基于 ECS实体-组件-系统)模式构建,提供灵活高效的游戏逻辑
- **高性能渲染**Rust/WebAssembly 2D 渲染器,支持精灵批处理和 WebGL 2.0
- **可视化编辑器**:跨平台桌面编辑器,包含场景管理、资源浏览器和可视化工具
- **模块化设计**:按需使用,每个功能都是独立模块,可单独引入
- **多平台支持**:一套代码部署到 Web、微信小游戏等多个平台
## 获取引擎
### 通过 npm 安装
```bash
npm install @esengine/ecs-framework
```
### 从源码构建
详见 [从源码构建](#从源码构建) 章节。
### 编辑器下载
预编译的编辑器可在 [Releases](https://github.com/esengine/ecs-framework/releases) 页面下载,支持 Windows 和 macOS。
## 快速开始
```typescript
import {
Core, Scene, Entity, Component, EntitySystem,
Matcher, Time, ECSComponent, ECSSystem
} from '@esengine/ecs-framework';
@ECSComponent('Position')
class Position extends Component {
x = 0;
y = 0;
}
@ECSComponent('Velocity')
class Velocity extends Component {
dx = 0;
dy = 0;
}
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
pos.x += vel.dx * Time.deltaTime;
pos.y += vel.dy * Time.deltaTime;
}
}
}
Core.create();
const scene = new Scene();
scene.addSystem(new MovementSystem());
const player = scene.createEntity('Player');
player.addComponent(new Position());
player.addComponent(new Velocity());
Core.setScene(scene);
// 游戏循环
let lastTime = 0;
function gameLoop(currentTime: number) {
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
Core.update(deltaTime);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
## 模块
ESEngine 采用模块化组织。每个功能都有运行时模块和可选的编辑器扩展。
### 核心
| 包名 | 描述 |
|------|------|
| `@esengine/ecs-framework` | ECS 框架核心,包含实体管理、组件系统和查询 |
| `@esengine/math` | 向量、矩阵和数学工具 |
| `@esengine/engine` | Rust/WASM 2D 渲染器 |
| `@esengine/engine-core` | 引擎模块系统和生命周期管理 |
### 运行时模块
| 包名 | 描述 |
|------|------|
| `@esengine/sprite` | 2D 精灵渲染和动画 |
| `@esengine/tilemap` | Tilemap 渲染,支持动画 |
| `@esengine/physics-rapier2d` | 基于 Rapier 的 2D 物理模拟 |
| `@esengine/behavior-tree` | 行为树 AI 系统 |
| `@esengine/blueprint` | 可视化脚本运行时 |
| `@esengine/camera` | 相机控制和管理 |
| `@esengine/audio` | 音频播放 |
| `@esengine/ui` | UI 组件 |
| `@esengine/material-system` | 材质和着色器系统 |
| `@esengine/asset-system` | 资源加载和管理 |
### 编辑器扩展
| 包名 | 描述 |
|------|------|
| `@esengine/sprite-editor` | 精灵检视器和工具 |
| `@esengine/tilemap-editor` | 可视化 Tilemap 编辑器,支持笔刷工具 |
| `@esengine/physics-rapier2d-editor` | 物理碰撞体可视化和编辑 |
| `@esengine/behavior-tree-editor` | 可视化行为树编辑器 |
| `@esengine/blueprint-editor` | 可视化脚本编辑器 |
| `@esengine/material-editor` | 材质和着色器编辑器 |
| `@esengine/shader-editor` | 着色器代码编辑器 |
### 平台
| 包名 | 描述 |
|------|------|
| `@esengine/platform-common` | 平台抽象接口 |
| `@esengine/platform-web` | Web 浏览器运行时 |
| `@esengine/platform-wechat` | 微信小游戏运行时 |
## 编辑器
ESEngine 编辑器是基于 Tauri 和 React 构建的跨平台桌面应用。
### 功能
- 场景层级和实体管理
- 组件检视器,支持自定义编辑器
- 资源浏览器,支持拖放
- Tilemap 编辑器,支持绘制、填充、选择工具
- 行为树可视化编辑器
- 蓝图可视化脚本
- 材质和着色器编辑
- 内置性能分析器
- 多语言支持(英文、中文)
### 截图
![ESEngine Editor](screenshots/main_screetshot.png)
## 支持的平台
| 平台 | 运行时 | 编辑器 |
|------|--------|--------|
| Web 浏览器 | 支持 | - |
| Windows | - | 支持 |
| macOS | - | 支持 |
| 微信小游戏 | 开发中 | - |
| Playable 可玩广告 | 计划中 | - |
| Android | 计划中 | - |
| iOS | 计划中 | - |
| Windows 原生 | 计划中 | - |
| 其他平台 | 计划中 | - |
## 从源码构建
### 前置要求
- Node.js 18 或更高版本
- pnpm 10 或更高版本
- Rust 工具链(用于 WASM 渲染器)
- wasm-pack
### 安装
```bash
# 克隆仓库
git clone https://github.com/esengine/ecs-framework.git
cd ecs-framework
# 安装依赖
pnpm install
# 构建所有包
pnpm build
# 构建 WASM 渲染器(可选)
pnpm build:wasm
```
### 运行编辑器
```bash
cd packages/editor-app
pnpm tauri:dev
```
### 项目结构
```
ecs-framework/
├── packages/ 引擎包(运行时、编辑器、平台)
├── docs/ 文档源码
├── examples/ 示例项目
├── scripts/ 构建工具
└── thirdparty/ 第三方依赖
```
## 文档
- [快速入门](https://esengine.github.io/ecs-framework/guide/getting-started.html)
- [架构指南](https://esengine.github.io/ecs-framework/guide/)
- [API 参考](https://esengine.github.io/ecs-framework/api/)
## 社区
- [GitHub Issues](https://github.com/esengine/ecs-framework/issues) - Bug 反馈和功能建议
- [GitHub Discussions](https://github.com/esengine/ecs-framework/discussions) - 问题和想法
- [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - 中文社区
## 贡献
欢迎贡献代码。提交 PR 前请阅读贡献指南。
1. Fork 仓库
2. 创建功能分支
3. 修改代码并测试
4. 提交 PR
## 许可证
ESEngine 基于 [MIT 协议](LICENSE) 开源。

53
codecov.yml Normal file
View File

@@ -0,0 +1,53 @@
# Codecov 配置文件
# https://docs.codecov.com/docs/codecov-yaml
coverage:
status:
# 项目整体覆盖率要求
project:
default:
target: auto
threshold: 1%
base: auto
# 补丁覆盖率要求(针对 PR 中的新代码)
patch:
default:
target: 50% # 降低补丁覆盖率要求到 50%
threshold: 5%
base: auto
# 精确度设置
precision: 2
round: down
range: "70...100"
# 注释设置
comment:
layout: "reach,diff,flags,tree,files"
behavior: default
require_changes: false
require_base: false
require_head: true
# 忽略的文件/目录
ignore:
- "tests/**/*"
- "**/*.test.ts"
- "**/*.spec.ts"
- "**/test/**/*"
- "**/tests/**/*"
- "bin/**/*"
- "dist/**/*"
- "node_modules/**/*"
# 标志组
flags:
core:
paths:
- packages/core/src/
carryforward: true
# GitHub Checks 配置
github_checks:
annotations: true

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

@@ -0,0 +1,282 @@
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')
)
// Import i18n messages
import en from './i18n/en.json' with { type: 'json' }
import zh from './i18n/zh.json' with { type: 'json' }
// 创建侧边栏配置 | Create sidebar config
// prefix: 路径前缀,如 '' 或 '/en' | Path prefix like '' or '/en'
function createSidebar(t, prefix = '') {
return {
[`${prefix}/guide/`]: [
{
text: t.sidebar.gettingStarted,
items: [
{ text: t.sidebar.quickStart, link: `${prefix}/guide/getting-started` },
{ text: t.sidebar.guideOverview, link: `${prefix}/guide/` }
]
},
{
text: t.sidebar.coreConcepts,
collapsed: false,
items: [
{ text: t.sidebar.entity, link: `${prefix}/guide/entity` },
{ text: t.sidebar.hierarchy, link: `${prefix}/guide/hierarchy` },
{ text: t.sidebar.component, link: `${prefix}/guide/component` },
{ text: t.sidebar.entityQuery, link: `${prefix}/guide/entity-query` },
{
text: t.sidebar.system,
link: `${prefix}/guide/system`,
items: [
{ text: t.sidebar.workerSystem, link: `${prefix}/guide/worker-system` }
]
},
{
text: t.sidebar.scene,
link: `${prefix}/guide/scene`,
items: [
{ text: t.sidebar.sceneManager, link: `${prefix}/guide/scene-manager` },
{ text: t.sidebar.worldManager, link: `${prefix}/guide/world-manager` }
]
},
{
text: t.sidebar.behaviorTree,
link: `${prefix}/guide/behavior-tree/`,
items: [
{ text: t.sidebar.btGettingStarted, link: `${prefix}/guide/behavior-tree/getting-started` },
{ text: t.sidebar.btCoreConcepts, link: `${prefix}/guide/behavior-tree/core-concepts` },
{ text: t.sidebar.btEditorGuide, link: `${prefix}/guide/behavior-tree/editor-guide` },
{ text: t.sidebar.btEditorWorkflow, link: `${prefix}/guide/behavior-tree/editor-workflow` },
{ text: t.sidebar.btCustomActions, link: `${prefix}/guide/behavior-tree/custom-actions` },
{ text: t.sidebar.btCocosIntegration, link: `${prefix}/guide/behavior-tree/cocos-integration` },
{ text: t.sidebar.btLayaIntegration, link: `${prefix}/guide/behavior-tree/laya-integration` },
{ text: t.sidebar.btAdvancedUsage, link: `${prefix}/guide/behavior-tree/advanced-usage` },
{ text: t.sidebar.btBestPractices, link: `${prefix}/guide/behavior-tree/best-practices` }
]
},
{ text: t.sidebar.serialization, link: `${prefix}/guide/serialization` },
{ text: t.sidebar.eventSystem, link: `${prefix}/guide/event-system` },
{ text: t.sidebar.timeAndTimers, link: `${prefix}/guide/time-and-timers` },
{ text: t.sidebar.logging, link: `${prefix}/guide/logging` }
]
},
{
text: t.sidebar.advancedFeatures,
collapsed: false,
items: [
{ text: t.sidebar.serviceContainer, link: `${prefix}/guide/service-container` },
{ text: t.sidebar.pluginSystem, link: `${prefix}/guide/plugin-system` }
]
},
{
text: t.sidebar.platformAdapters,
link: `${prefix}/guide/platform-adapter`,
collapsed: false,
items: [
{ text: t.sidebar.browserAdapter, link: `${prefix}/guide/platform-adapter/browser` },
{ text: t.sidebar.wechatAdapter, link: `${prefix}/guide/platform-adapter/wechat-minigame` },
{ text: t.sidebar.nodejsAdapter, link: `${prefix}/guide/platform-adapter/nodejs` }
]
}
],
[`${prefix}/examples/`]: [
{
text: t.sidebar.examples,
items: [
{ text: t.sidebar.examplesOverview, link: `${prefix}/examples/` },
{ text: t.nav.workerDemo, link: `${prefix}/examples/worker-system-demo` }
]
}
],
[`${prefix}/api/`]: [
{
text: t.sidebar.apiReference,
items: [
{ text: t.sidebar.overview, link: `${prefix}/api/README` },
{
text: t.sidebar.coreClasses,
collapsed: false,
items: [
{ text: 'Core', link: `${prefix}/api/classes/Core` },
{ text: 'Scene', link: `${prefix}/api/classes/Scene` },
{ text: 'World', link: `${prefix}/api/classes/World` },
{ text: 'Entity', link: `${prefix}/api/classes/Entity` },
{ text: 'Component', link: `${prefix}/api/classes/Component` },
{ text: 'EntitySystem', link: `${prefix}/api/classes/EntitySystem` }
]
},
{
text: t.sidebar.systemClasses,
collapsed: true,
items: [
{ text: 'PassiveSystem', link: `${prefix}/api/classes/PassiveSystem` },
{ text: 'ProcessingSystem', link: `${prefix}/api/classes/ProcessingSystem` },
{ text: 'IntervalSystem', link: `${prefix}/api/classes/IntervalSystem` }
]
},
{
text: t.sidebar.utilities,
collapsed: true,
items: [
{ text: 'Matcher', link: `${prefix}/api/classes/Matcher` },
{ text: 'Time', link: `${prefix}/api/classes/Time` },
{ text: 'PerformanceMonitor', link: `${prefix}/api/classes/PerformanceMonitor` },
{ text: 'DebugManager', link: `${prefix}/api/classes/DebugManager` }
]
},
{
text: t.sidebar.interfaces,
collapsed: true,
items: [
{ text: 'IScene', link: `${prefix}/api/interfaces/IScene` },
{ text: 'IComponent', link: `${prefix}/api/interfaces/IComponent` },
{ text: 'ISystemBase', link: `${prefix}/api/interfaces/ISystemBase` },
{ text: 'ICoreConfig', link: `${prefix}/api/interfaces/ICoreConfig` }
]
},
{
text: t.sidebar.decorators,
collapsed: true,
items: [
{ text: '@ECSComponent', link: `${prefix}/api/functions/ECSComponent` },
{ text: '@ECSSystem', link: `${prefix}/api/functions/ECSSystem` }
]
},
{
text: t.sidebar.enums,
collapsed: true,
items: [
{ text: 'ECSEventType', link: `${prefix}/api/enumerations/ECSEventType` },
{ text: 'LogLevel', link: `${prefix}/api/enumerations/LogLevel` }
]
}
]
}
]
}
}
// 创建导航配置 | Create nav config
// prefix: 路径前缀,如 '' 或 '/en' | Path prefix like '' or '/en'
function createNav(t, prefix = '') {
return [
{ text: t.nav.home, link: `${prefix}/` },
{ text: t.nav.quickStart, link: `${prefix}/guide/getting-started` },
{ text: t.nav.guide, link: `${prefix}/guide/` },
{ text: t.nav.api, link: `${prefix}/api/README` },
{
text: t.nav.examples,
items: [
{ text: t.nav.workerDemo, link: `${prefix}/examples/worker-system-demo` },
{ text: t.nav.lawnMowerDemo, link: 'https://github.com/esengine/lawn-mower-demo' }
]
},
{
text: `v${corePackageJson.version}`,
link: 'https://github.com/esengine/ecs-framework/releases'
}
]
}
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: 'ESEngine',
appearance: 'force-dark',
locales: {
root: {
label: '简体中文',
lang: 'zh-CN',
description: '高性能 TypeScript ECS 框架 - 为游戏开发而生',
themeConfig: {
nav: createNav(zh, ''),
sidebar: createSidebar(zh, ''),
editLink: {
pattern: 'https://github.com/esengine/ecs-framework/edit/master/docs/:path',
text: zh.common.editOnGithub
},
outline: {
level: [2, 3],
label: zh.common.onThisPage
}
}
},
en: {
label: 'English',
lang: 'en',
link: '/en/',
description: 'High-performance TypeScript ECS Framework for Game Development',
themeConfig: {
nav: createNav(en, '/en'),
sidebar: createSidebar(en, '/en'),
editLink: {
pattern: 'https://github.com/esengine/ecs-framework/edit/master/docs/:path',
text: en.common.editOnGithub
},
outline: {
level: [2, 3],
label: en.common.onThisPage
}
}
}
},
themeConfig: {
siteTitle: 'ESEngine',
socialLinks: [
{ icon: 'github', link: 'https://github.com/esengine/ecs-framework' }
],
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2025 ECS Framework'
},
search: {
provider: 'local'
}
},
head: [
['meta', { name: 'theme-color', content: '#646cff' }],
['link', { rel: 'icon', href: '/favicon.ico' }]
],
base: '/',
cleanUrls: true,
markdown: {
lineNumbers: true,
theme: {
light: 'github-light',
dark: 'github-dark'
}
}
})

View File

@@ -0,0 +1,85 @@
{
"nav": {
"home": "Home",
"quickStart": "Quick Start",
"guide": "Guide",
"api": "API",
"examples": "Examples",
"workerDemo": "Worker System Demo",
"lawnMowerDemo": "Lawn Mower Demo"
},
"sidebar": {
"gettingStarted": "Getting Started",
"quickStart": "Quick Start",
"guideOverview": "Guide Overview",
"coreConcepts": "Core Concepts",
"entity": "Entity",
"hierarchy": "Hierarchy",
"component": "Component",
"entityQuery": "Entity Query",
"system": "System",
"workerSystem": "Worker System (Multithreading)",
"scene": "Scene",
"sceneManager": "SceneManager",
"worldManager": "WorldManager",
"behaviorTree": "Behavior Tree",
"btGettingStarted": "Getting Started",
"btCoreConcepts": "Core Concepts",
"btEditorGuide": "Editor Guide",
"btEditorWorkflow": "Editor Workflow",
"btCustomActions": "Custom Actions",
"btCocosIntegration": "Cocos Creator Integration",
"btLayaIntegration": "Laya Engine Integration",
"btAdvancedUsage": "Advanced Usage",
"btBestPractices": "Best Practices",
"serialization": "Serialization",
"eventSystem": "Event System",
"timeAndTimers": "Time and Timers",
"logging": "Logging",
"advancedFeatures": "Advanced Features",
"serviceContainer": "Service Container",
"pluginSystem": "Plugin System",
"platformAdapters": "Platform Adapters",
"browserAdapter": "Browser Adapter",
"wechatAdapter": "WeChat Mini Game Adapter",
"nodejsAdapter": "Node.js Adapter",
"examples": "Examples",
"examplesOverview": "Examples Overview",
"apiReference": "API Reference",
"overview": "Overview",
"coreClasses": "Core Classes",
"systemClasses": "System Classes",
"utilities": "Utilities",
"interfaces": "Interfaces",
"decorators": "Decorators",
"enums": "Enums"
},
"home": {
"title": "ESEngine - High-performance TypeScript ECS Framework",
"quickLinks": "Quick Links",
"viewDocs": "View Docs",
"getStarted": "Get Started",
"getStartedDesc": "From installation to your first ECS app, learn the core concepts in 5 minutes.",
"aiSystem": "AI System",
"behaviorTreeEditor": "Visual Behavior Tree Editor",
"behaviorTreeDesc": "Built-in AI behavior tree system with visual editing and real-time debugging.",
"coreFeatures": "Core Features",
"ecsArchitecture": "High-performance ECS Architecture",
"ecsArchitectureDesc": "Data-driven entity component system for large-scale entity processing with cache-friendly memory layout.",
"typeSupport": "Full Type Support",
"typeSupportDesc": "100% TypeScript with complete type definitions and compile-time checking for the best development experience.",
"visualBehaviorTree": "Visual Behavior Tree",
"visualBehaviorTreeDesc": "Built-in AI behavior tree system with visual editor, custom nodes, and real-time debugging.",
"multiPlatform": "Multi-Platform Support",
"multiPlatformDesc": "Support for browsers, Node.js, WeChat Mini Games, and seamless integration with major game engines.",
"modularDesign": "Modular Design",
"modularDesignDesc": "Core features packaged independently, import only what you need. Support for custom plugin extensions.",
"devTools": "Developer Tools",
"devToolsDesc": "Built-in performance monitoring, debugging tools, serialization system, and complete development toolchain.",
"learnMore": "Learn more →"
},
"common": {
"editOnGithub": "Edit this page on GitHub",
"onThisPage": "On this page"
}
}

View File

@@ -0,0 +1,21 @@
import en from './en.json'
import zh from './zh.json'
export const messages = { en, zh }
export type Locale = 'en' | 'zh'
export function getLocaleMessages(locale: Locale) {
return messages[locale] || messages.en
}
// Helper to get nested key value
export function t(messages: typeof en, key: string): string {
const keys = key.split('.')
let result: any = messages
for (const k of keys) {
result = result?.[k]
if (result === undefined) return key
}
return result
}

View File

@@ -0,0 +1,85 @@
{
"nav": {
"home": "首页",
"quickStart": "快速开始",
"guide": "指南",
"api": "API",
"examples": "示例",
"workerDemo": "Worker系统演示",
"lawnMowerDemo": "割草机演示"
},
"sidebar": {
"gettingStarted": "开始使用",
"quickStart": "快速开始",
"guideOverview": "指南概览",
"coreConcepts": "核心概念",
"entity": "实体类 (Entity)",
"hierarchy": "层级系统 (Hierarchy)",
"component": "组件系统 (Component)",
"entityQuery": "实体查询系统",
"system": "系统架构 (System)",
"workerSystem": "Worker系统 (多线程)",
"scene": "场景管理 (Scene)",
"sceneManager": "SceneManager",
"worldManager": "WorldManager",
"behaviorTree": "行为树系统 (Behavior Tree)",
"btGettingStarted": "快速开始",
"btCoreConcepts": "核心概念",
"btEditorGuide": "编辑器指南",
"btEditorWorkflow": "编辑器工作流",
"btCustomActions": "自定义动作组件",
"btCocosIntegration": "Cocos Creator集成",
"btLayaIntegration": "Laya引擎集成",
"btAdvancedUsage": "高级用法",
"btBestPractices": "最佳实践",
"serialization": "序列化系统 (Serialization)",
"eventSystem": "事件系统 (Event)",
"timeAndTimers": "时间和定时器 (Time)",
"logging": "日志系统 (Logger)",
"advancedFeatures": "高级特性",
"serviceContainer": "服务容器 (Service Container)",
"pluginSystem": "插件系统 (Plugin System)",
"platformAdapters": "平台适配器",
"browserAdapter": "浏览器适配器",
"wechatAdapter": "微信小游戏适配器",
"nodejsAdapter": "Node.js适配器",
"examples": "示例",
"examplesOverview": "示例概览",
"apiReference": "API 参考",
"overview": "概述",
"coreClasses": "核心类",
"systemClasses": "系统类",
"utilities": "工具类",
"interfaces": "接口",
"decorators": "装饰器",
"enums": "枚举"
},
"home": {
"title": "ESEngine - 高性能 TypeScript ECS 框架",
"quickLinks": "快速入口",
"viewDocs": "查看文档",
"getStarted": "快速开始",
"getStartedDesc": "从安装到创建第一个 ECS 应用,快速了解核心概念。",
"aiSystem": "AI 系统",
"behaviorTreeEditor": "行为树可视化编辑器",
"behaviorTreeDesc": "内置 AI 行为树系统,支持可视化编辑和实时调试。",
"coreFeatures": "核心特性",
"ecsArchitecture": "高性能 ECS 架构",
"ecsArchitectureDesc": "基于数据驱动的实体组件系统,支持大规模实体处理,缓存友好的内存布局。",
"typeSupport": "完整类型支持",
"typeSupportDesc": "100% TypeScript 编写,完整的类型定义和编译时检查,提供最佳的开发体验。",
"visualBehaviorTree": "可视化行为树",
"visualBehaviorTreeDesc": "内置 AI 行为树系统,提供可视化编辑器,支持自定义节点和实时调试。",
"multiPlatform": "多平台支持",
"multiPlatformDesc": "支持浏览器、Node.js、微信小游戏等多平台可与主流游戏引擎无缝集成。",
"modularDesign": "模块化设计",
"modularDesignDesc": "核心功能独立打包,按需引入。支持自定义插件扩展,灵活适配不同项目。",
"devTools": "开发者工具",
"devToolsDesc": "内置性能监控、调试工具、序列化系统等,提供完整的开发工具链。",
"learnMore": "了解更多 →"
},
"common": {
"editOnGithub": "在 GitHub 上编辑此页",
"onThisPage": "在这个页面上"
}
}

View File

@@ -0,0 +1,93 @@
<script setup>
defineProps({
title: String,
description: String,
icon: String,
link: String,
image: String
})
</script>
<template>
<a :href="link" class="feature-card">
<div class="card-image" v-if="image">
<img :src="image" :alt="title" />
</div>
<div class="card-body">
<div class="card-icon" v-if="icon && !image">{{ icon }}</div>
<h3 class="card-title">{{ title }}</h3>
<p class="card-description">{{ description }}</p>
</div>
</a>
</template>
<style scoped>
.feature-card {
display: flex;
flex-direction: column;
background: var(--es-bg-elevated, #252526);
border: 1px solid var(--es-border-default, #3e3e42);
border-radius: 4px;
overflow: hidden;
text-decoration: none;
transition: all 0.15s ease;
}
.feature-card:hover {
border-color: var(--es-primary, #007acc);
background: var(--es-bg-overlay, #2d2d2d);
}
.card-image {
width: 100%;
height: 160px;
overflow: hidden;
background: linear-gradient(135deg, #1e3a5f 0%, #1e1e1e 100%);
}
.card-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.feature-card:hover .card-image img {
transform: scale(1.05);
}
.card-body {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.card-icon {
font-size: 1.5rem;
margin-bottom: 12px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--es-bg-input, #3c3c3c);
border-radius: 4px;
}
.card-title {
font-size: 14px;
font-weight: 600;
color: var(--es-text-inverse, #ffffff);
margin: 0 0 8px 0;
line-height: 1.3;
}
.card-description {
font-size: 12px;
color: var(--es-text-secondary, #9d9d9d);
margin: 0;
line-height: 1.6;
flex: 1;
}
</style>

View File

@@ -0,0 +1,422 @@
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
const canvasRef = ref(null)
let animationId = null
let particles = []
let animationStartTime = null
let glowStartTime = null
// ESEngine 粒子颜色 - VS Code 风格配色(与编辑器统一)
const colors = ['#569CD6', '#4EC9B0', '#9CDCFE', '#C586C0', '#DCDCAA']
class Particle {
constructor(x, y, targetX, targetY) {
this.x = x
this.y = y
this.targetX = targetX
this.targetY = targetY
this.size = Math.random() * 2 + 1.5
this.alpha = Math.random() * 0.5 + 0.5
this.color = colors[Math.floor(Math.random() * colors.length)]
}
}
function createParticles(canvas, text, fontSize) {
const tempCanvas = document.createElement('canvas')
const tempCtx = tempCanvas.getContext('2d')
if (!tempCtx) return []
tempCtx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
const textMetrics = tempCtx.measureText(text)
const textWidth = textMetrics.width
const textHeight = fontSize
tempCanvas.width = textWidth + 40
tempCanvas.height = textHeight + 40
tempCtx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
tempCtx.textAlign = 'center'
tempCtx.textBaseline = 'middle'
tempCtx.fillStyle = '#ffffff'
tempCtx.fillText(text, tempCanvas.width / 2, tempCanvas.height / 2)
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height)
const pixels = imageData.data
const newParticles = []
const gap = 3
const width = canvas.width / (window.devicePixelRatio || 1)
const height = canvas.height / (window.devicePixelRatio || 1)
const offsetX = (width - tempCanvas.width) / 2
const offsetY = (height - tempCanvas.height) / 2
for (let y = 0; y < tempCanvas.height; y += gap) {
for (let x = 0; x < tempCanvas.width; x += gap) {
const index = (y * tempCanvas.width + x) * 4
const alpha = pixels[index + 3] || 0
if (alpha > 128) {
const angle = Math.random() * Math.PI * 2
const distance = Math.random() * Math.max(width, height)
newParticles.push(new Particle(
width / 2 + Math.cos(angle) * distance,
height / 2 + Math.sin(angle) * distance,
offsetX + x,
offsetY + y
))
}
}
}
return newParticles
}
function easeOutQuart(t) {
return 1 - Math.pow(1 - t, 4)
}
function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3)
}
function animate(canvas, ctx) {
const dpr = window.devicePixelRatio || 1
const width = canvas.width / dpr
const height = canvas.height / dpr
const currentTime = performance.now()
const duration = 2500
const glowDuration = 600
const elapsed = currentTime - animationStartTime
const progress = Math.min(elapsed / duration, 1)
const easedProgress = easeOutQuart(progress)
// 透明背景
ctx.clearRect(0, 0, width, height)
// 计算发光进度
let glowProgress = 0
if (progress >= 1) {
if (glowStartTime === null) {
glowStartTime = currentTime
}
glowProgress = Math.min((currentTime - glowStartTime) / glowDuration, 1)
glowProgress = easeOutCubic(glowProgress)
}
const text = 'ESEngine'
const fontSize = Math.min(width / 4, height / 3, 80)
const textY = height / 2
for (const particle of particles) {
const moveProgress = Math.min(easedProgress * 1.2, 1)
const currentX = particle.x + (particle.targetX - particle.x) * moveProgress
const currentY = particle.y + (particle.targetY - particle.y) * moveProgress
ctx.beginPath()
ctx.arc(currentX, currentY, particle.size, 0, Math.PI * 2)
ctx.fillStyle = particle.color
ctx.globalAlpha = particle.alpha * (1 - glowProgress * 0.3)
ctx.fill()
}
ctx.globalAlpha = 1
if (glowProgress > 0) {
ctx.save()
ctx.shadowColor = '#3b9eff'
ctx.shadowBlur = 30 * glowProgress
ctx.fillStyle = `rgba(255, 255, 255, ${glowProgress})`
ctx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, width / 2, textY)
ctx.restore()
}
if (glowProgress >= 1) {
const breathe = 0.8 + Math.sin(currentTime / 1000) * 0.2
ctx.save()
ctx.shadowColor = '#3b9eff'
ctx.shadowBlur = 20 * breathe
ctx.fillStyle = '#ffffff'
ctx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, width / 2, textY)
ctx.restore()
}
animationId = requestAnimationFrame(() => animate(canvas, ctx))
}
function initCanvas() {
const canvas = canvasRef.value
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const dpr = window.devicePixelRatio || 1
const container = canvas.parentElement
const width = container.offsetWidth
const height = container.offsetHeight
canvas.width = width * dpr
canvas.height = height * dpr
canvas.style.width = `${width}px`
canvas.style.height = `${height}px`
ctx.scale(dpr, dpr)
const text = 'ESEngine'
const fontSize = Math.min(width / 4, height / 3, 80)
particles = createParticles(canvas, text, fontSize)
animationStartTime = performance.now()
glowStartTime = null
if (animationId) {
cancelAnimationFrame(animationId)
}
animate(canvas, ctx)
}
onMounted(() => {
initCanvas()
window.addEventListener('resize', initCanvas)
})
onUnmounted(() => {
if (animationId) {
cancelAnimationFrame(animationId)
}
window.removeEventListener('resize', initCanvas)
})
</script>
<template>
<section class="hero-section">
<div class="hero-container">
<!-- 左侧文字区域 -->
<div class="hero-text">
<div class="hero-logo">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="14" stroke="#9147ff" stroke-width="2"/>
<path d="M10 10h8v2h-6v3h5v2h-5v3h6v2h-8v-12z" fill="#9147ff"/>
</svg>
<span>ESENGINE</span>
</div>
<h1 class="hero-title">
我们构建框架<br/>
而你将创造游戏
</h1>
<p class="hero-description">
ESEngine 是一个高性能的 TypeScript ECS 框架为游戏开发者提供现代化的实体组件系统
无论是 2D 还是 3D 游戏都能帮助你快速构建可扩展的游戏架构
</p>
<div class="hero-actions">
<a href="/guide/getting-started" class="btn-primary">开始使用</a>
<a href="https://github.com/esengine/ecs-framework" class="btn-secondary" target="_blank">了解更多</a>
</div>
</div>
<!-- 右侧粒子动画区域 -->
<div class="hero-visual">
<div class="visual-container">
<canvas ref="canvasRef" class="particle-canvas"></canvas>
<div class="visual-label">
<span class="label-title">Entity Component System</span>
<span class="label-subtitle">High Performance Framework</span>
</div>
</div>
</div>
</div>
</section>
</template>
<style scoped>
.hero-section {
background: #0d0d0d;
padding: 80px 0;
min-height: calc(100vh - 64px);
display: flex;
align-items: center;
}
.hero-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 48px;
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 64px;
align-items: center;
}
/* 左侧文字 */
.hero-text {
display: flex;
flex-direction: column;
gap: 24px;
}
.hero-logo {
display: flex;
align-items: center;
gap: 12px;
color: #ffffff;
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.1em;
}
.hero-title {
font-size: 3rem;
font-weight: 700;
color: #ffffff;
line-height: 1.2;
margin: 0;
}
.hero-description {
font-size: 1.125rem;
color: #707070;
line-height: 1.7;
margin: 0;
max-width: 480px;
}
.hero-actions {
display: flex;
gap: 16px;
margin-top: 8px;
}
.btn-primary,
.btn-secondary {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 28px;
border-radius: 4px;
font-weight: 600;
font-size: 0.9375rem;
text-decoration: none;
transition: all 0.2s ease;
}
.btn-primary {
background: #3b9eff;
color: #ffffff;
border: 1px solid #3b9eff;
border-radius: 6px;
}
.btn-primary:hover {
background: #5aadff;
border-color: #5aadff;
}
.btn-secondary {
background: #1a1a1a;
color: #a0a0a0;
border: 1px solid #2a2a2a;
border-radius: 6px;
}
.btn-secondary:hover {
background: #252525;
color: #ffffff;
}
.hero-visual {
display: flex;
justify-content: center;
}
.visual-container {
position: relative;
width: 100%;
max-width: 600px;
aspect-ratio: 4 / 3;
background: linear-gradient(135deg, #1a2a3a 0%, #1a1a1a 50%, #0d0d0d 100%);
border-radius: 12px;
border: 1px solid #2a2a2a;
overflow: hidden;
box-shadow: 0 20px 60px rgba(59, 158, 255, 0.1);
}
.particle-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.visual-label {
position: absolute;
bottom: 24px;
left: 24px;
display: flex;
flex-direction: column;
gap: 4px;
}
.label-title {
font-size: 1.125rem;
font-weight: 600;
color: #ffffff;
}
.label-subtitle {
font-size: 0.875rem;
color: #737373;
}
/* 响应式 */
@media (max-width: 1024px) {
.hero-container {
grid-template-columns: 1fr;
gap: 48px;
padding: 0 24px;
}
.hero-section {
padding: 48px 0;
min-height: auto;
}
.hero-title {
font-size: 2.25rem;
}
.hero-description {
font-size: 1rem;
}
.visual-container {
max-width: 100%;
aspect-ratio: 16 / 9;
}
}
@media (max-width: 640px) {
.hero-title {
font-size: 1.75rem;
}
.hero-actions {
flex-direction: column;
}
.btn-primary,
.btn-secondary {
width: 100%;
justify-content: center;
}
}
</style>

View File

@@ -0,0 +1,422 @@
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
const canvasRef = ref(null)
let animationId = null
let particles = []
let animationStartTime = null
let glowStartTime = null
// ESEngine particle colors - VS Code style colors (unified with editor)
const colors = ['#569CD6', '#4EC9B0', '#9CDCFE', '#C586C0', '#DCDCAA']
class Particle {
constructor(x, y, targetX, targetY) {
this.x = x
this.y = y
this.targetX = targetX
this.targetY = targetY
this.size = Math.random() * 2 + 1.5
this.alpha = Math.random() * 0.5 + 0.5
this.color = colors[Math.floor(Math.random() * colors.length)]
}
}
function createParticles(canvas, text, fontSize) {
const tempCanvas = document.createElement('canvas')
const tempCtx = tempCanvas.getContext('2d')
if (!tempCtx) return []
tempCtx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
const textMetrics = tempCtx.measureText(text)
const textWidth = textMetrics.width
const textHeight = fontSize
tempCanvas.width = textWidth + 40
tempCanvas.height = textHeight + 40
tempCtx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
tempCtx.textAlign = 'center'
tempCtx.textBaseline = 'middle'
tempCtx.fillStyle = '#ffffff'
tempCtx.fillText(text, tempCanvas.width / 2, tempCanvas.height / 2)
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height)
const pixels = imageData.data
const newParticles = []
const gap = 3
const width = canvas.width / (window.devicePixelRatio || 1)
const height = canvas.height / (window.devicePixelRatio || 1)
const offsetX = (width - tempCanvas.width) / 2
const offsetY = (height - tempCanvas.height) / 2
for (let y = 0; y < tempCanvas.height; y += gap) {
for (let x = 0; x < tempCanvas.width; x += gap) {
const index = (y * tempCanvas.width + x) * 4
const alpha = pixels[index + 3] || 0
if (alpha > 128) {
const angle = Math.random() * Math.PI * 2
const distance = Math.random() * Math.max(width, height)
newParticles.push(new Particle(
width / 2 + Math.cos(angle) * distance,
height / 2 + Math.sin(angle) * distance,
offsetX + x,
offsetY + y
))
}
}
}
return newParticles
}
function easeOutQuart(t) {
return 1 - Math.pow(1 - t, 4)
}
function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3)
}
function animate(canvas, ctx) {
const dpr = window.devicePixelRatio || 1
const width = canvas.width / dpr
const height = canvas.height / dpr
const currentTime = performance.now()
const duration = 2500
const glowDuration = 600
const elapsed = currentTime - animationStartTime
const progress = Math.min(elapsed / duration, 1)
const easedProgress = easeOutQuart(progress)
// Transparent background
ctx.clearRect(0, 0, width, height)
// Calculate glow progress
let glowProgress = 0
if (progress >= 1) {
if (glowStartTime === null) {
glowStartTime = currentTime
}
glowProgress = Math.min((currentTime - glowStartTime) / glowDuration, 1)
glowProgress = easeOutCubic(glowProgress)
}
const text = 'ESEngine'
const fontSize = Math.min(width / 4, height / 3, 80)
const textY = height / 2
for (const particle of particles) {
const moveProgress = Math.min(easedProgress * 1.2, 1)
const currentX = particle.x + (particle.targetX - particle.x) * moveProgress
const currentY = particle.y + (particle.targetY - particle.y) * moveProgress
ctx.beginPath()
ctx.arc(currentX, currentY, particle.size, 0, Math.PI * 2)
ctx.fillStyle = particle.color
ctx.globalAlpha = particle.alpha * (1 - glowProgress * 0.3)
ctx.fill()
}
ctx.globalAlpha = 1
if (glowProgress > 0) {
ctx.save()
ctx.shadowColor = '#3b9eff'
ctx.shadowBlur = 30 * glowProgress
ctx.fillStyle = `rgba(255, 255, 255, ${glowProgress})`
ctx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, width / 2, textY)
ctx.restore()
}
if (glowProgress >= 1) {
const breathe = 0.8 + Math.sin(currentTime / 1000) * 0.2
ctx.save()
ctx.shadowColor = '#3b9eff'
ctx.shadowBlur = 20 * breathe
ctx.fillStyle = '#ffffff'
ctx.font = `bold ${fontSize}px "Segoe UI", Arial, sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, width / 2, textY)
ctx.restore()
}
animationId = requestAnimationFrame(() => animate(canvas, ctx))
}
function initCanvas() {
const canvas = canvasRef.value
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const dpr = window.devicePixelRatio || 1
const container = canvas.parentElement
const width = container.offsetWidth
const height = container.offsetHeight
canvas.width = width * dpr
canvas.height = height * dpr
canvas.style.width = `${width}px`
canvas.style.height = `${height}px`
ctx.scale(dpr, dpr)
const text = 'ESEngine'
const fontSize = Math.min(width / 4, height / 3, 80)
particles = createParticles(canvas, text, fontSize)
animationStartTime = performance.now()
glowStartTime = null
if (animationId) {
cancelAnimationFrame(animationId)
}
animate(canvas, ctx)
}
onMounted(() => {
initCanvas()
window.addEventListener('resize', initCanvas)
})
onUnmounted(() => {
if (animationId) {
cancelAnimationFrame(animationId)
}
window.removeEventListener('resize', initCanvas)
})
</script>
<template>
<section class="hero-section">
<div class="hero-container">
<!-- Left text area -->
<div class="hero-text">
<div class="hero-logo">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="14" stroke="#9147ff" stroke-width="2"/>
<path d="M10 10h8v2h-6v3h5v2h-5v3h6v2h-8v-12z" fill="#9147ff"/>
</svg>
<span>ESENGINE</span>
</div>
<h1 class="hero-title">
We build the framework.<br/>
You create the game.
</h1>
<p class="hero-description">
ESEngine is a high-performance TypeScript ECS framework for game developers.
Whether 2D or 3D games, it helps you build scalable game architecture quickly.
</p>
<div class="hero-actions">
<a href="/en/guide/getting-started" class="btn-primary">Get Started</a>
<a href="https://github.com/esengine/ecs-framework" class="btn-secondary" target="_blank">Learn More</a>
</div>
</div>
<!-- Right particle animation area -->
<div class="hero-visual">
<div class="visual-container">
<canvas ref="canvasRef" class="particle-canvas"></canvas>
<div class="visual-label">
<span class="label-title">Entity Component System</span>
<span class="label-subtitle">High Performance Framework</span>
</div>
</div>
</div>
</div>
</section>
</template>
<style scoped>
.hero-section {
background: #0d0d0d;
padding: 80px 0;
min-height: calc(100vh - 64px);
display: flex;
align-items: center;
}
.hero-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 48px;
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 64px;
align-items: center;
}
/* Left text */
.hero-text {
display: flex;
flex-direction: column;
gap: 24px;
}
.hero-logo {
display: flex;
align-items: center;
gap: 12px;
color: #ffffff;
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.1em;
}
.hero-title {
font-size: 3rem;
font-weight: 700;
color: #ffffff;
line-height: 1.2;
margin: 0;
}
.hero-description {
font-size: 1.125rem;
color: #707070;
line-height: 1.7;
margin: 0;
max-width: 480px;
}
.hero-actions {
display: flex;
gap: 16px;
margin-top: 8px;
}
.btn-primary,
.btn-secondary {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 28px;
border-radius: 4px;
font-weight: 600;
font-size: 0.9375rem;
text-decoration: none;
transition: all 0.2s ease;
}
.btn-primary {
background: #3b9eff;
color: #ffffff;
border: 1px solid #3b9eff;
border-radius: 6px;
}
.btn-primary:hover {
background: #5aadff;
border-color: #5aadff;
}
.btn-secondary {
background: #1a1a1a;
color: #a0a0a0;
border: 1px solid #2a2a2a;
border-radius: 6px;
}
.btn-secondary:hover {
background: #252525;
color: #ffffff;
}
.hero-visual {
display: flex;
justify-content: center;
}
.visual-container {
position: relative;
width: 100%;
max-width: 600px;
aspect-ratio: 4 / 3;
background: linear-gradient(135deg, #1a2a3a 0%, #1a1a1a 50%, #0d0d0d 100%);
border-radius: 12px;
border: 1px solid #2a2a2a;
overflow: hidden;
box-shadow: 0 20px 60px rgba(59, 158, 255, 0.1);
}
.particle-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.visual-label {
position: absolute;
bottom: 24px;
left: 24px;
display: flex;
flex-direction: column;
gap: 4px;
}
.label-title {
font-size: 1.125rem;
font-weight: 600;
color: #ffffff;
}
.label-subtitle {
font-size: 0.875rem;
color: #737373;
}
/* Responsive */
@media (max-width: 1024px) {
.hero-container {
grid-template-columns: 1fr;
gap: 48px;
padding: 0 24px;
}
.hero-section {
padding: 48px 0;
min-height: auto;
}
.hero-title {
font-size: 2.25rem;
}
.hero-description {
font-size: 1rem;
}
.visual-container {
max-width: 100%;
aspect-ratio: 16 / 9;
}
}
@media (max-width: 640px) {
.hero-title {
font-size: 1.75rem;
}
.hero-actions {
flex-direction: column;
}
.btn-primary,
.btn-secondary {
width: 100%;
justify-content: center;
}
}
</style>

View File

@@ -0,0 +1,594 @@
:root {
color-scheme: dark;
--vp-nav-height: 64px;
--es-bg-base: #1e1e1e;
--es-bg-elevated: #252526;
--es-bg-overlay: #2d2d2d;
--es-bg-input: #3c3c3c;
--es-bg-inset: #181818;
--es-bg-hover: #2a2d2e;
--es-bg-active: #37373d;
--es-bg-sidebar: #262626;
--es-bg-card: #2a2a2a;
--es-bg-header: #2d2d2d;
--es-text-primary: #cccccc;
--es-text-secondary: #9d9d9d;
--es-text-tertiary: #6a6a6a;
--es-text-inverse: #ffffff;
--es-text-muted: #aaaaaa;
--es-text-dim: #6a6a6a;
--es-font-xs: 11px;
--es-font-sm: 12px;
--es-font-base: 13px;
--es-font-md: 14px;
--es-font-lg: 16px;
--es-border-default: #3a3a3a;
--es-border-subtle: #1a1a1a;
--es-border-strong: #4a4a4a;
--es-primary: #3b82f6;
--es-primary-hover: #2563eb;
--es-success: #4ade80;
--es-warning: #f59e0b;
--es-error: #ef4444;
--es-info: #3b82f6;
--es-selected: #3d5a80;
--es-selected-hover: #4a6a90;
}
body {
background: var(--es-bg-base) !important;
}
html,
html.dark {
--vp-c-bg: var(--es-bg-base);
--vp-c-bg-soft: var(--es-bg-elevated);
--vp-c-bg-mute: var(--es-bg-overlay);
--vp-c-bg-alt: var(--es-bg-sidebar);
--vp-c-text-1: var(--es-text-primary);
--vp-c-text-2: var(--es-text-tertiary);
--vp-c-text-3: var(--es-text-muted);
--vp-c-divider: var(--es-border-default);
--vp-c-divider-light: var(--es-border-subtle);
}
html:not(.dark) {
--vp-c-bg: var(--es-bg-base) !important;
--vp-c-bg-soft: var(--es-bg-elevated) !important;
--vp-c-bg-mute: var(--es-bg-overlay) !important;
--vp-c-bg-alt: var(--es-bg-sidebar) !important;
--vp-c-text-1: var(--es-text-primary) !important;
--vp-c-text-2: var(--es-text-tertiary) !important;
--vp-c-text-3: var(--es-text-muted) !important;
}
.VPNav {
background: var(--es-bg-header) !important;
border-bottom: 1px solid var(--es-border-subtle) !important;
}
.VPNav .VPNavBar {
background: var(--es-bg-header) !important;
}
.VPNav .VPNavBar .wrapper {
background: var(--es-bg-header) !important;
}
.VPNav .VPNavBar::before,
.VPNav .VPNavBar::after {
display: none !important;
}
.VPNavBar {
background: var(--es-bg-header) !important;
}
.VPNavBar::before {
display: none !important;
}
.VPNavBarTitle .title {
color: var(--es-text-primary);
font-weight: 500;
font-size: var(--es-font-base);
}
.VPNavBarMenuLink {
color: var(--es-text-secondary) !important;
font-size: var(--es-font-sm) !important;
font-weight: 400 !important;
}
.VPNavBarMenuLink:hover {
color: var(--es-text-primary) !important;
}
.VPNavBarMenuLink.active {
color: var(--es-text-primary) !important;
}
.VPNavBarSearch .DocSearch-Button {
background: var(--es-bg-input) !important;
border: 1px solid var(--es-border-default) !important;
border-radius: 2px;
height: 26px;
}
.VPSidebar {
background: var(--es-bg-sidebar) !important;
border-right: 1px solid var(--es-border-subtle) !important;
}
.VPSidebarItem.level-0 > .item {
padding: 8px 0 4px 0;
}
.VPSidebarItem.level-0 > .item > .text {
font-weight: 600;
font-size: var(--es-font-xs);
color: var(--es-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.VPSidebarItem .link {
padding: 4px 8px;
margin: 1px 0;
border-radius: 2px;
color: var(--es-text-primary);
font-size: var(--es-font-sm);
transition: all 0.1s ease;
border-left: 2px solid transparent;
}
.VPSidebarItem .link:hover {
background: rgba(255, 255, 255, 0.03);
color: var(--es-text-inverse);
}
.VPSidebarItem.is-active > .item > .link {
background: var(--es-selected);
color: var(--es-text-inverse);
border-left: 2px solid var(--es-primary);
}
.VPSidebarItem.is-active > .item > .link:hover {
background: var(--es-selected-hover);
}
.VPSidebarItem.level-1 .link {
padding-left: 20px;
font-size: var(--es-font-sm);
}
.VPSidebarItem.level-2 .link {
padding-left: 32px;
font-size: var(--es-font-sm);
}
.VPSidebarItem .caret {
color: var(--es-text-secondary);
}
.VPSidebarItem .caret:hover {
color: var(--es-text-primary);
}
.VPContent {
background: var(--es-bg-card) !important;
padding-top: 0 !important;
}
.VPContent.has-sidebar {
background: var(--es-bg-card) !important;
}
/* 首页布局修复 | Home page layout fix */
.VPPage {
padding-top: 0 !important;
}
.Layout > .VPContent {
padding-top: var(--vp-nav-height) !important;
}
.VPDoc {
background: transparent !important;
}
.VPNavBar .content {
background: var(--es-bg-header) !important;
}
.VPNavBar .content-body {
background: var(--es-bg-header) !important;
}
.VPNavBar .divider {
display: none;
}
.VPLocalNav {
background: var(--es-bg-header) !important;
border-bottom: 1px solid var(--es-border-subtle) !important;
}
.VPNavScreenMenu {
background: var(--es-bg-base) !important;
}
.VPNavScreen {
background: var(--es-bg-base) !important;
}
.curtain {
display: none !important;
}
.VPNav .curtain,
.VPNavBar .curtain {
display: none !important;
}
[class*="curtain"] {
display: none !important;
}
.VPNav > div::before,
.VPNav > div::after {
display: none !important;
}
.vp-doc {
color: var(--es-text-primary);
}
.vp-doc h1 {
font-size: var(--es-font-lg);
font-weight: 600;
color: var(--es-text-inverse);
border-bottom: none;
padding-bottom: 0;
margin-bottom: 16px;
line-height: 1.3;
}
.vp-doc h2 {
font-size: var(--es-font-md);
font-weight: 600;
color: var(--es-text-inverse);
border-bottom: none;
padding-bottom: 0;
margin-top: 32px;
margin-bottom: 12px;
padding: 6px 12px;
background: var(--es-bg-header);
border-left: 3px solid var(--es-primary);
}
.vp-doc h3 {
font-size: var(--es-font-base);
font-weight: 600;
color: var(--es-text-primary);
margin-top: 20px;
margin-bottom: 8px;
}
.vp-doc p {
color: var(--es-text-primary);
line-height: 1.7;
font-size: var(--es-font-base);
margin: 12px 0;
}
.vp-doc ul,
.vp-doc ol {
padding-left: 20px;
margin: 12px 0;
}
.vp-doc li {
line-height: 1.7;
margin: 4px 0;
color: var(--es-text-primary);
font-size: var(--es-font-base);
}
.vp-doc li::marker {
color: var(--es-text-secondary);
}
.vp-doc strong {
color: var(--es-text-primary);
font-weight: 600;
}
.vp-doc a {
color: var(--es-primary);
text-decoration: none;
}
.vp-doc a:hover {
text-decoration: underline;
}
.VPDocAside {
padding-left: 16px;
border-left: 1px solid var(--es-border-subtle);
}
.VPDocAsideOutline {
padding: 0;
border: none !important;
}
.VPDocAsideOutline .content {
border: none !important;
padding-left: 0 !important;
}
.VPDocAsideOutline .outline-title {
font-size: var(--es-font-xs);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--es-text-secondary);
padding-bottom: 8px;
}
.VPDocAsideOutline .outline-link {
color: var(--es-text-secondary);
font-size: var(--es-font-xs);
padding: 4px 0;
line-height: 1.4;
display: block;
}
.VPDocAsideOutline .outline-link:hover {
color: var(--es-text-primary);
}
.VPDocAsideOutline .outline-link.active {
color: var(--es-primary);
}
.VPDocAsideOutline .outline-marker {
display: none;
}
div[class*='language-'] {
background: var(--es-bg-inset) !important;
border: 1px solid var(--es-border-default);
border-radius: 2px;
margin: 12px 0;
}
.vp-code-group .tabs {
background: var(--es-bg-header);
border-bottom: 1px solid var(--es-border-subtle);
}
.vp-doc :not(pre) > code {
background: var(--es-bg-input);
color: var(--es-primary);
padding: 2px 6px;
border-radius: 2px;
font-size: var(--es-font-xs);
}
.vp-doc table {
display: table;
width: 100%;
background: transparent;
border: none;
border-collapse: collapse;
margin: 16px 0;
font-size: var(--es-font-sm);
}
.vp-doc tr {
border-bottom: 1px solid var(--es-border-subtle);
background: transparent;
}
.vp-doc tr:last-child {
border-bottom: none;
}
.vp-doc tr:hover {
background: rgba(255, 255, 255, 0.02);
}
.vp-doc th {
background: var(--es-bg-header);
font-weight: 600;
font-size: var(--es-font-xs);
color: var(--es-text-secondary);
text-align: left;
padding: 8px 12px;
border-bottom: 1px solid var(--es-border-subtle);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.vp-doc td {
font-size: var(--es-font-sm);
color: var(--es-text-primary);
padding: 8px 12px;
vertical-align: top;
line-height: 1.5;
}
.vp-doc td:first-child {
font-weight: 500;
color: var(--es-text-primary);
min-width: 100px;
}
.vp-doc .warning,
.vp-doc .custom-block.warning {
background: rgba(245, 158, 11, 0.08);
border: none;
border-left: 3px solid var(--es-warning);
border-radius: 0 2px 2px 0;
padding: 10px 12px;
margin: 16px 0;
}
.vp-doc .warning .custom-block-title,
.vp-doc .custom-block.warning .custom-block-title {
color: var(--es-warning);
font-weight: 600;
font-size: var(--es-font-xs);
margin-bottom: 4px;
}
.vp-doc .warning p {
color: var(--es-text-primary);
margin: 0;
font-size: var(--es-font-xs);
}
.vp-doc .tip,
.vp-doc .custom-block.tip {
background: rgba(59, 130, 246, 0.08);
border: none;
border-left: 3px solid var(--es-primary);
border-radius: 0 2px 2px 0;
padding: 10px 12px;
margin: 16px 0;
}
.vp-doc .tip .custom-block-title,
.vp-doc .custom-block.tip .custom-block-title {
color: var(--es-primary);
font-weight: 600;
font-size: var(--es-font-xs);
margin-bottom: 4px;
}
.vp-doc .tip p {
color: var(--es-text-primary);
margin: 0;
font-size: var(--es-font-xs);
}
.vp-doc .info,
.vp-doc .custom-block.info {
background: rgba(74, 222, 128, 0.08);
border: none;
border-left: 3px solid var(--es-success);
border-radius: 0 2px 2px 0;
padding: 10px 12px;
margin: 16px 0;
}
.vp-doc .info .custom-block-title,
.vp-doc .custom-block.info .custom-block-title {
color: var(--es-success);
font-weight: 600;
font-size: var(--es-font-xs);
margin-bottom: 4px;
}
.vp-doc .danger,
.vp-doc .custom-block.danger {
background: rgba(239, 68, 68, 0.08);
border: none;
border-left: 3px solid var(--es-error);
border-radius: 0 2px 2px 0;
padding: 10px 12px;
margin: 16px 0;
}
.vp-doc .danger .custom-block-title,
.vp-doc .custom-block.danger .custom-block-title {
color: var(--es-error);
font-weight: 600;
font-size: var(--es-font-xs);
margin-bottom: 4px;
}
.vp-doc .card {
background: var(--es-bg-sidebar);
border: 1px solid var(--es-border-subtle);
border-radius: 4px;
padding: 12px;
margin: 16px 0;
}
.vp-doc .card-title {
font-size: var(--es-font-sm);
font-weight: 600;
color: var(--es-text-primary);
margin-bottom: 6px;
}
.vp-doc .card-description {
font-size: var(--es-font-xs);
color: var(--es-text-muted);
line-height: 1.5;
}
.vp-doc .tag {
display: inline-block;
padding: 2px 8px;
background: transparent;
border: 1px solid var(--es-border-default);
border-radius: 2px;
color: var(--es-text-secondary);
font-size: var(--es-font-xs);
margin-right: 4px;
margin-bottom: 4px;
}
.VPFooter {
background: var(--es-bg-sidebar) !important;
border-top: 1px solid var(--es-border-subtle) !important;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--es-bg-card);
}
::-webkit-scrollbar-thumb {
background: var(--es-border-strong);
border-radius: 4px;
border: 2px solid var(--es-bg-card);
}
::-webkit-scrollbar-thumb:hover {
background: #5a5a5a;
}
::-webkit-scrollbar-corner {
background: transparent;
}
.home-container {
max-width: 1000px;
margin: 0 auto;
padding: 0 16px;
}
.home-section {
padding: 32px 0;
}
@media (max-width: 960px) {
.VPDoc .content {
padding: 16px !important;
}
}

View File

@@ -0,0 +1,14 @@
import DefaultTheme from 'vitepress/theme'
import ParticleHero from './components/ParticleHero.vue'
import ParticleHeroEn from './components/ParticleHeroEn.vue'
import FeatureCard from './components/FeatureCard.vue'
import './custom.css'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('ParticleHero', ParticleHero)
app.component('ParticleHeroEn', ParticleHeroEn)
app.component('FeatureCard', FeatureCard)
}
}

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,431 +0,0 @@
# 位掩码使用指南
本文档详细解释ECS框架中位掩码的概念、原理和使用方法。
## 目录
1. [什么是位掩码](#什么是位掩码)
2. [位掩码的优势](#位掩码的优势)
3. [基础使用方法](#基础使用方法)
4. [高级位掩码操作](#高级位掩码操作)
5. [实际应用场景](#实际应用场景)
6. [性能优化技巧](#性能优化技巧)
## 什么是位掩码
### 基本概念
位掩码BitMask是一种使用二进制位来表示状态或属性的技术。在ECS框架中每个组件类型对应一个二进制位实体的组件组合可以用一个数字来表示。
### 简单例子
假设我们有以下组件:
- PositionComponent → 位置 0 (二进制: 001)
- VelocityComponent → 位置 1 (二进制: 010)
- HealthComponent → 位置 2 (二进制: 100)
那么一个同时拥有Position和Health组件的实体其位掩码就是
```
001 (Position) + 100 (Health) = 101 (二进制) = 5 (十进制)
```
### 可视化理解
```typescript
// 组件类型对应的位位置
PositionComponent 0 2^0 = 1 二进制: 001
VelocityComponent 1 2^1 = 2 二进制: 010
HealthComponent 2 2^2 = 4 二进制: 100
RenderComponent 3 2^3 = 8 二进制: 1000
// 实体的组件组合示例
实体A: Position + Velocity 001 + 010 = 011 () = 3 ()
实体B: Position + Health 001 + 100 = 101 () = 5 ()
实体C: Position + Velocity + Health 001 + 010 + 100 = 111 () = 7 ()
```
## 位掩码的优势
### 1. 极快的查询速度
```typescript
// 传统方式:需要遍历组件列表
function hasComponents(entity, componentTypes) {
for (const type of componentTypes) {
if (!entity.hasComponent(type)) {
return false;
}
}
return true;
}
// 位掩码方式:一次位运算即可
function hasComponentsMask(entityMask, requiredMask) {
return (entityMask & requiredMask) === requiredMask;
}
```
### 2. 内存效率
```typescript
// 一个bigint可以表示64个组件的组合状态
// 相比存储组件列表,内存使用量大大减少
const entity = scene.createEntity("Player");
entity.addComponent(new PositionComponent());
entity.addComponent(new HealthComponent());
// 获取位掩码(只是一个数字)
const mask = entity.componentMask; // bigint类型
console.log(`位掩码: ${mask}`); // 输出: 5 (二进制: 101)
```
### 3. 批量操作优化
```typescript
// 可以快速筛选大量实体
const entities = scene.getAllEntities();
const requiredMask = BigInt(0b101); // Position + Health
const filteredEntities = entities.filter(entity =>
(entity.componentMask & requiredMask) === requiredMask
);
```
## 基础使用方法
### 获取实体的位掩码
```typescript
import { Scene, Entity, Component } from '@esengine/ecs-framework';
// 创建组件
class PositionComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class HealthComponent extends Component {
constructor(public maxHealth: number = 100) {
super();
}
}
// 创建实体并添加组件
const scene = new Scene();
const entity = scene.createEntity("Player");
console.log(`初始位掩码: ${entity.componentMask}`); // 0
entity.addComponent(new PositionComponent(100, 200));
console.log(`添加Position后: ${entity.componentMask}`); // 可能是 1
entity.addComponent(new HealthComponent(100));
console.log(`添加Health后: ${entity.componentMask}`); // 可能是 5
// 查看二进制表示
console.log(`二进制表示: ${entity.componentMask.toString(2)}`);
```
### 手动检查位掩码
```typescript
// 检查实体是否拥有特定组件组合
function checkEntityComponents(entity: Entity) {
const mask = entity.componentMask;
// 将位掩码转换为二进制字符串查看
const binaryString = mask.toString(2).padStart(8, '0');
console.log(`实体组件状态: ${binaryString}`);
// 分析每一位
console.log(`位0 (Position): ${(mask & 1n) !== 0n ? '有' : '无'}`);
console.log(`位1 (Velocity): ${(mask & 2n) !== 0n ? '有' : '无'}`);
console.log(`位2 (Health): ${(mask & 4n) !== 0n ? '有' : '无'}`);
console.log(`位3 (Render): ${(mask & 8n) !== 0n ? '有' : '无'}`);
}
```
## 高级位掩码操作
### 使用BitMaskOptimizer
框架提供了BitMaskOptimizer类来简化位掩码操作
```typescript
import { BitMaskOptimizer } from '@esengine/ecs-framework';
// 获取优化器实例
const optimizer = BitMaskOptimizer.getInstance();
// 注册组件类型(建议在游戏初始化时进行)
optimizer.registerComponentType('PositionComponent');
optimizer.registerComponentType('VelocityComponent');
optimizer.registerComponentType('HealthComponent');
optimizer.registerComponentType('RenderComponent');
// 创建单个组件的掩码
const positionMask = optimizer.createSingleComponentMask('PositionComponent');
console.log(`Position掩码: ${positionMask} (二进制: ${positionMask.toString(2)})`);
// 创建组合掩码
const movementMask = optimizer.createCombinedMask(['PositionComponent', 'VelocityComponent']);
console.log(`Movement掩码: ${movementMask} (二进制: ${movementMask.toString(2)})`);
// 检查实体是否匹配掩码
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent());
entity.addComponent(new VelocityComponent());
const hasMovementComponents = optimizer.maskContainsAllComponents(
entity.componentMask,
['PositionComponent', 'VelocityComponent']
);
console.log(`实体拥有移动组件: ${hasMovementComponents}`);
```
### 位掩码分析工具
```typescript
// 分析位掩码的实用函数
class MaskAnalyzer {
private optimizer = BitMaskOptimizer.getInstance();
// 分析实体的组件组合
analyzeEntity(entity: Entity): void {
const mask = entity.componentMask;
const componentNames = this.optimizer.maskToComponentNames(mask);
const componentCount = this.optimizer.getComponentCount(mask);
console.log(`实体 ${entity.name} 分析:`);
console.log(`- 位掩码: ${mask} (二进制: ${mask.toString(2)})`);
console.log(`- 组件数量: ${componentCount}`);
console.log(`- 组件列表: ${componentNames.join(', ')}`);
}
// 比较两个实体的组件差异
compareEntities(entityA: Entity, entityB: Entity): void {
const maskA = entityA.componentMask;
const maskB = entityB.componentMask;
const commonMask = maskA & maskB;
const onlyInA = maskA & ~maskB;
const onlyInB = maskB & ~maskA;
console.log(`实体比较:`);
console.log(`- 共同组件: ${this.optimizer.maskToComponentNames(commonMask).join(', ')}`);
console.log(`- 仅在A中: ${this.optimizer.maskToComponentNames(onlyInA).join(', ')}`);
console.log(`- 仅在B中: ${this.optimizer.maskToComponentNames(onlyInB).join(', ')}`);
}
// 查找具有特定组件组合的实体
findEntitiesWithMask(entities: Entity[], requiredComponents: string[]): Entity[] {
const requiredMask = this.optimizer.createCombinedMask(requiredComponents);
return entities.filter(entity =>
(entity.componentMask & requiredMask) === requiredMask
);
}
}
// 使用示例
const analyzer = new MaskAnalyzer();
analyzer.analyzeEntity(entity);
```
## 实际应用场景
### 1. 高性能实体查询
```typescript
class GameSystem {
private optimizer = BitMaskOptimizer.getInstance();
private movementMask: bigint;
private combatMask: bigint;
constructor() {
// 预计算常用掩码
this.movementMask = this.optimizer.createCombinedMask([
'PositionComponent', 'VelocityComponent'
]);
this.combatMask = this.optimizer.createCombinedMask([
'PositionComponent', 'HealthComponent', 'WeaponComponent'
]);
}
// 快速查找移动实体
findMovingEntities(entities: Entity[]): Entity[] {
return entities.filter(entity =>
(entity.componentMask & this.movementMask) === this.movementMask
);
}
// 快速查找战斗单位
findCombatUnits(entities: Entity[]): Entity[] {
return entities.filter(entity =>
(entity.componentMask & this.combatMask) === this.combatMask
);
}
}
```
### 2. 实体分类和管理
```typescript
class EntityClassifier {
private optimizer = BitMaskOptimizer.getInstance();
// 定义实体类型掩码
private readonly ENTITY_TYPES = {
PLAYER: this.optimizer.createCombinedMask([
'PositionComponent', 'HealthComponent', 'InputComponent'
]),
ENEMY: this.optimizer.createCombinedMask([
'PositionComponent', 'HealthComponent', 'AIComponent'
]),
PROJECTILE: this.optimizer.createCombinedMask([
'PositionComponent', 'VelocityComponent', 'DamageComponent'
]),
PICKUP: this.optimizer.createCombinedMask([
'PositionComponent', 'PickupComponent'
])
};
// 根据组件组合判断实体类型
classifyEntity(entity: Entity): string {
const mask = entity.componentMask;
for (const [type, typeMask] of Object.entries(this.ENTITY_TYPES)) {
if ((mask & typeMask) === typeMask) {
return type;
}
}
return 'UNKNOWN';
}
// 批量分类实体
classifyEntities(entities: Entity[]): Map<string, Entity[]> {
const classified = new Map<string, Entity[]>();
for (const entity of entities) {
const type = this.classifyEntity(entity);
if (!classified.has(type)) {
classified.set(type, []);
}
classified.get(type)!.push(entity);
}
return classified;
}
}
```
## 性能优化技巧
### 1. 预计算常用掩码
```typescript
class MaskCache {
private optimizer = BitMaskOptimizer.getInstance();
// 预计算游戏中常用的组件组合
public readonly COMMON_MASKS = {
RENDERABLE: this.optimizer.createCombinedMask([
'PositionComponent', 'RenderComponent'
]),
MOVABLE: this.optimizer.createCombinedMask([
'PositionComponent', 'VelocityComponent'
]),
LIVING: this.optimizer.createCombinedMask([
'HealthComponent'
]),
INTERACTIVE: this.optimizer.createCombinedMask([
'PositionComponent', 'ColliderComponent'
])
};
constructor() {
// 预计算常用组合以提升性能
this.optimizer.precomputeCommonMasks([
['PositionComponent', 'RenderComponent'],
['PositionComponent', 'VelocityComponent'],
['PositionComponent', 'HealthComponent', 'WeaponComponent']
]);
}
}
```
### 2. 位掩码调试工具
```typescript
// 位掩码调试工具
class MaskDebugger {
private optimizer = BitMaskOptimizer.getInstance();
// 可视化位掩码
visualizeMask(mask: bigint, maxBits: number = 16): string {
const binary = mask.toString(2).padStart(maxBits, '0');
const components = this.optimizer.maskToComponentNames(mask);
let visualization = `位掩码: ${mask} (二进制: ${binary})\n`;
visualization += `组件: ${components.join(', ')}\n`;
visualization += `可视化: `;
for (let i = maxBits - 1; i >= 0; i--) {
const bit = (mask & (1n << BigInt(i))) !== 0n ? '1' : '0';
visualization += bit;
if (i % 4 === 0 && i > 0) visualization += ' ';
}
return visualization;
}
}
```
## 最佳实践
### 1. 组件注册
```typescript
// 在游戏初始化时注册所有组件类型
function initializeComponentTypes() {
const optimizer = BitMaskOptimizer.getInstance();
// 按使用频率注册(常用的组件分配较小的位位置)
optimizer.registerComponentType('PositionComponent'); // 位置0
optimizer.registerComponentType('VelocityComponent'); // 位置1
optimizer.registerComponentType('HealthComponent'); // 位置2
optimizer.registerComponentType('RenderComponent'); // 位置3
// ... 其他组件
}
```
### 2. 掩码缓存管理
```typescript
// 定期清理掩码缓存以避免内存泄漏
setInterval(() => {
const optimizer = BitMaskOptimizer.getInstance();
const stats = optimizer.getCacheStats();
// 如果缓存过大,清理一部分
if (stats.size > 1000) {
optimizer.clearCache();
console.log('位掩码缓存已清理');
}
}, 60000); // 每分钟检查一次
```
## 总结
位掩码是ECS框架中的核心优化技术它提供了
1. **极快的查询速度** - 位运算比遍历快数百倍
2. **内存效率** - 用一个数字表示复杂的组件组合
3. **批量操作优化** - 可以快速处理大量实体
4. **灵活的查询构建** - 支持复杂的组件组合查询
通过理解和正确使用位掩码,可以显著提升游戏的性能表现。记住要在游戏初始化时注册组件类型,预计算常用掩码,并合理管理缓存。

View File

@@ -1,692 +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 GameSystem {
updatePlayers() {
// 只处理玩家实体
const players = this.scene.findEntitiesWithComponent(PlayerComponent);
// ...
}
updateEnemies() {
// 只处理敌人实体
const enemies = this.scene.findEntitiesWithComponent(EnemyComponent);
// ...
}
}
```
### 3. 行为组件Behavior Components
包含简单行为逻辑的组件。
```typescript
class WeaponComponent extends Component {
public damage: number;
public fireRate: number;
public ammo: number;
public maxAmmo: number;
public lastFireTime: number = 0;
constructor(damage: number = 10, fireRate: number = 0.5) {
super();
this.damage = damage;
this.fireRate = fireRate;
this.maxAmmo = 30;
this.ammo = this.maxAmmo;
}
canFire(): boolean {
return this.ammo > 0 &&
Time.totalTime - this.lastFireTime >= this.fireRate;
}
fire(): boolean {
if (this.canFire()) {
this.ammo--;
this.lastFireTime = Time.totalTime;
return true;
}
return false;
}
reload() {
this.ammo = this.maxAmmo;
}
getAmmoPercentage(): number {
return this.ammo / this.maxAmmo;
}
}
class InventoryComponent extends Component {
private items: Map<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,665 +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 = createEntity()
.add(PositionComponent) // 位置数据
.add(HealthComponent) // 生命值数据
.add(PlayerInputComponent) // 玩家输入标记
const enemy = createEntity()
.add(PositionComponent) // 复用位置数据
.add(HealthComponent) // 复用生命值数据
.add(AIComponent) // AI标记
// 系统处理具有特定组件的实体
MovementSystem.process([PositionComponent, VelocityComponent]);
```
### ECS 的优势
1. **灵活组合** - 像搭积木一样组装功能
2. **代码复用** - 组件可以在不同实体间复用
3. **性能优化** - 数据连续存储,缓存友好
4. **并行处理** - 系统间相互独立,可以并行执行
5. **易于测试** - 组件和系统可以独立测试
### 实际应用场景
**游戏开发中的例子:**
- **RPG游戏**玩家、NPC、怪物都有位置和生命值但只有玩家有输入组件
- **射击游戏**:子弹、玩家、敌人都有位置和碰撞体,但行为完全不同
- **策略游戏**:建筑、单位、资源都是实体,通过不同组件组合实现功能
## 性能优化技术
### 组件索引系统
**问题:** 没有索引时,查找组件需要遍历所有实体
```typescript
// 慢的方式:线性搜索 O(n)
function findEntitiesWithHealth() {
const result = [];
for (const entity of allEntities) { // 遍历10万个实体
if (entity.hasComponent(HealthComponent)) {
result.push(entity);
}
}
return result;
}
```
**解决方案:** 建立索引,直接访问
```typescript
// 快的方式:索引查找 O(1)
const healthIndex = componentIndex.get(HealthComponent);
const entitiesWithHealth = healthIndex.getEntities(); // 直接获取
```
**应用场景:**
- 频繁查询特定组件的实体
- 大规模实体场景(数千到数万个实体)
- 实时游戏中的系统更新
### 索引类型选择指南
框架提供两种索引类型,选择合适的类型对性能至关重要:
#### 🔸 哈希索引 (Hash Index)
**适用场景:**
- 实体数量较多(> 1000个
- 组件数据变化不频繁
- 需要快速查找特定实体
**优势:**
- 查询速度极快 O(1)
- 内存使用相对较少
- 适合大量实体
**缺点:**
- 添加/删除组件时有额外开销
- 不适合频繁变化的组件
```typescript
// 适合哈希索引的组件
componentIndex.setIndexType(PositionComponent, 'hash'); // 位置变化不频繁
componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件稳定
componentIndex.setIndexType(PlayerComponent, 'hash'); // 玩家标记组件
```
#### 🔹 位图索引 (Bitmap Index)
**适用场景:**
- 组件频繁添加/删除
- 实体数量适中(< 10000个
- 需要批量操作
**优势:**
- 添加/删除组件极快
- 批量查询效率高
- 内存访问模式好
**缺点:**
- 随实体数量增长,内存占用增加
- 稀疏数据时效率降低
```typescript
// 适合位图索引的组件
componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff经常添加删除
componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件
componentIndex.setIndexType(StateComponent, 'bitmap'); // 状态组件变化频繁
```
#### 📊 选择决策表
| 考虑因素 | 哈希索引 (Hash) | 位图索引 (Bitmap) |
|---------|----------------|-------------------|
| **实体数量** | > 1,000 | < 10,000 |
| **组件变化频率** | 低频变化 | 高频变化 |
| **查询频率** | 高频查询 | 中等查询 |
| **内存使用** | 较少 | 随实体数增加 |
| **批量操作** | 一般 | 优秀 |
#### 🤔 快速决策流程
**第一步:判断组件变化频率**
- 组件经常添加/删除? → 选择 **位图索引**
- 组件相对稳定? → 继续第二步
**第二步:判断实体数量**
- 实体数量 > 1000 → 选择 **哈希索引**
- 实体数量 < 1000 → 选择 **位图索引**
**第三步:特殊情况**
- 需要频繁批量操作? → 选择 **位图索引**
- 内存使用很重要? → 选择 **哈希索引**
#### 🎮 实际游戏中的选择示例
**射击游戏:**
```typescript
// 稳定组件用哈希索引
componentIndex.setIndexType(PositionComponent, 'hash'); // 实体位置稳定存在
componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件持续存在
componentIndex.setIndexType(WeaponComponent, 'hash'); // 武器组件不常变化
// 变化组件用位图索引
componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff频繁添加删除
componentIndex.setIndexType(ReloadingComponent, 'bitmap'); // 装弹状态临时组件
```
**策略游戏:**
```typescript
// 大量单位,核心组件用哈希
componentIndex.setIndexType(UnitComponent, 'hash'); // 单位类型稳定
componentIndex.setIndexType(OwnerComponent, 'hash'); // 所属玩家稳定
// 状态组件用位图
componentIndex.setIndexType(SelectedComponent, 'bitmap'); // 选中状态常变化
componentIndex.setIndexType(MovingComponent, 'bitmap'); // 移动状态变化
componentIndex.setIndexType(AttackingComponent, 'bitmap'); // 攻击状态临时
```
**RPG游戏**
```typescript
// 角色核心属性用哈希
componentIndex.setIndexType(StatsComponent, 'hash'); // 属性组件稳定
componentIndex.setIndexType(InventoryComponent, 'hash'); // 背包组件稳定
componentIndex.setIndexType(LevelComponent, 'hash'); // 等级组件稳定
// 临时状态用位图
componentIndex.setIndexType(StatusEffectComponent, 'bitmap'); // 状态效果变化
componentIndex.setIndexType(QuestComponent, 'bitmap'); // 任务状态变化
componentIndex.setIndexType(CombatComponent, 'bitmap'); // 战斗状态临时
```
#### ❌ 常见选择错误
**错误示例1大量实体使用位图索引**
```typescript
// ❌ 错误10万个单位用位图索引内存爆炸
const entityCount = 100000;
componentIndex.setIndexType(UnitComponent, 'bitmap'); // 内存占用过大!
// ✅ 正确:大量实体用哈希索引
componentIndex.setIndexType(UnitComponent, 'hash');
```
**错误示例2频繁变化组件用哈希索引**
```typescript
// ❌ 错误Buff频繁添加删除哈希索引效率低
componentIndex.setIndexType(BuffComponent, 'hash'); // 添加删除慢!
// ✅ 正确:变化频繁的组件用位图索引
componentIndex.setIndexType(BuffComponent, 'bitmap');
```
**错误示例3不考虑实际使用场景**
```typescript
// ❌ 错误:所有组件都用同一种索引
componentIndex.setIndexType(PositionComponent, 'hash');
componentIndex.setIndexType(BuffComponent, 'hash'); // 应该用bitmap
componentIndex.setIndexType(TemporaryComponent, 'hash'); // 应该用bitmap
// ✅ 正确:根据组件特性选择
componentIndex.setIndexType(PositionComponent, 'hash'); // 稳定组件
componentIndex.setIndexType(BuffComponent, 'bitmap'); // 变化组件
componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件
```
### Archetype 系统
**什么是 Archetype**
Archetype原型是具有相同组件组合的实体分组。
**没有 Archetype 的问题:**
```typescript
// 每次都要检查每个实体的组件组合
for (const entity of allEntities) {
if (entity.has(Position) && entity.has(Velocity) && !entity.has(Frozen)) {
// 处理移动
}
}
```
**Archetype 的解决方案:**
```typescript
// 实体按组件组合自动分组
const movableArchetype = [Position, Velocity, !Frozen];
const movableEntities = archetypeSystem.getEntities(movableArchetype);
// 直接处理,无需逐个检查
```
**应用场景:**
- 大量实体的游戏RTS、MMO
- 频繁的实体查询操作
- 批量处理相同类型的实体
### 脏标记系统
**什么是脏标记?**
脏标记Dirty Tracking追踪哪些数据发生了变化避免处理未变化的数据。
**没有脏标记的问题:**
```typescript
// 每帧都重新计算所有实体,即使它们没有移动
function renderSystem() {
for (const entity of entities) {
updateRenderPosition(entity); // 浪费计算
updateRenderRotation(entity); // 浪费计算
updateRenderScale(entity); // 浪费计算
}
}
```
**脏标记的解决方案:**
```typescript
// 只处理发生变化的实体
function renderSystem() {
const dirtyEntities = dirtyTracking.getDirtyEntities();
for (const entity of dirtyEntities) {
if (dirtyTracking.isDirty(entity, PositionComponent)) {
updateRenderPosition(entity); // 只在需要时计算
}
if (dirtyTracking.isDirty(entity, RotationComponent)) {
updateRenderRotation(entity);
}
}
dirtyTracking.clearDirtyFlags();
}
```
**应用场景:**
- 渲染系统优化(只更新变化的物体)
- 物理系统优化(只计算移动的物体)
- UI更新优化只刷新变化的界面元素
- 网络同步优化(只发送变化的数据)
**实际例子:**
```typescript
// 游戏中的应用
class MovementSystem {
process() {
// 玩家移动时标记为脏
if (playerInput.moved) {
dirtyTracking.markDirty(player, PositionComponent);
}
// 静止的敌人不会被标记为脏,渲染系统会跳过它们
}
}
```
## 事件系统
### 类型安全事件
**传统事件的问题:**
```typescript
// 类型不安全,容易出错
eventEmitter.emit('player_died', playerData);
eventEmitter.on('player_dead', handler); // 事件名拼写错误!
```
**类型安全事件的解决方案:**
```typescript
// 编译时检查,避免错误
enum GameEvents {
PLAYER_DIED = 'player:died',
LEVEL_COMPLETED = 'level:completed'
}
eventBus.emit(GameEvents.PLAYER_DIED, { playerId: 123 });
eventBus.on(GameEvents.PLAYER_DIED, (data) => {
// data 类型自动推断
});
```
### 事件装饰器
**什么是装饰器?**
装饰器让你用简单的语法自动注册事件监听器。
**传统方式:**
```typescript
class GameManager {
constructor() {
// 手动注册事件
eventBus.on('entity:created', this.onEntityCreated.bind(this));
eventBus.on('entity:destroyed', this.onEntityDestroyed.bind(this));
eventBus.on('component:added', this.onComponentAdded.bind(this));
}
onEntityCreated(data) { ... }
onEntityDestroyed(data) { ... }
onComponentAdded(data) { ... }
}
```
**装饰器方式:**
```typescript
class GameManager {
@EventHandler('entity:created')
onEntityCreated(data) { ... } // 自动注册
@EventHandler('entity:destroyed')
onEntityDestroyed(data) { ... } // 自动注册
@EventHandler('component:added')
onComponentAdded(data) { ... } // 自动注册
}
```
**应用场景:**
- 游戏状态管理
- UI更新响应
- 音效播放触发
- 成就系统检查
## 实体管理
### 实体生命周期
**创建实体的不同方式:**
```typescript
// 单个创建 - 适用于重要实体
const player = scene.createEntity("Player");
// 批量创建 - 适用于大量相似实体
const bullets = scene.createEntities(100, "Bullet");
// 延迟创建 - 避免性能峰值
// 分批创建大量实体以避免单帧卡顿
for (let i = 0; i < 100; i++) {
setTimeout(() => {
const batch = scene.createEntities(10, "Enemy");
// 配置批次实体...
}, i * 16); // 每16ms创建一批
}
```
### 查询系统
**流式API的优势**
```typescript
// 传统方式:复杂的条件判断
const result = [];
for (const entity of entities) {
if (entity.has(Position) &&
entity.has(Velocity) &&
!entity.has(Frozen) &&
entity.tag === EntityTag.ENEMY) {
result.push(entity);
}
}
// 流式API清晰表达意图
const result = entityManager
.query()
.withAll(Position, Velocity)
.withNone(Frozen)
.withTag(EntityTag.ENEMY)
.execute();
```
### 批量操作
**为什么需要批量操作?**
```typescript
// 慢的方式:逐个处理
for (let i = 0; i < 1000; i++) {
const bullet = createEntity();
bullet.addComponent(new PositionComponent());
bullet.addComponent(new VelocityComponent());
}
// 快的方式:批量处理
const bullets = scene.createEntities(1000, "Bullet");
bullets.forEach(bullet => {
bullet.addComponent(new PositionComponent());
bullet.addComponent(new VelocityComponent());
});
```
**应用场景:**
- 生成大量子弹/粒子
- 加载关卡时创建大量实体
- 清理场景时删除大量实体
## 性能建议
### 什么时候使用这些优化?
| 实体数量 | 推荐配置 | 说明 |
|---------|---------|------|
| < 1,000 | 默认配置 | 简单场景,不需要特殊优化 |
| 1,000 - 10,000 | 启用组件索引 | 中等规模,索引提升查询速度 |
| 10,000 - 50,000 | 启用Archetype | 大规模场景,分组优化 |
| > 50,000 | 全部优化 | 超大规模,需要所有优化技术 |
### 常见使用误区
**错误:过度优化**
```typescript
// 不要在小项目中使用所有优化
const entityManager = new EntityManager();
entityManager.enableAllOptimizations(); // 小项目不需要
```
**正确:按需优化**
```typescript
// 根据实际需求选择优化
if (entityCount > 10000) {
entityManager.enableArchetypeSystem();
}
if (hasFrequentQueries) {
entityManager.enableComponentIndex();
}
```
## 总结
这些技术概念可能看起来复杂,但它们解决的都是实际开发中的具体问题:
1. **ECS架构** - 让代码更灵活、可维护
2. **组件索引** - 让查询更快速
3. **Archetype系统** - 让批量操作更高效
4. **脏标记系统** - 让更新更智能
5. **事件系统** - 让组件间通信更安全
6. **实体管理** - 让大规模场景成为可能
从简单的场景开始,随着项目复杂度增加,逐步引入这些优化技术。
## 框架类型系统
### TypeScript接口设计
ECS框架采用了精简的TypeScript接口设计提供类型安全保障的同时保持实现的灵活性。
#### 核心接口
**IComponent接口**
```typescript
interface IComponent {
readonly id: number;
enabled: boolean;
updateOrder: number;
onAddedToEntity(): void;
onRemovedFromEntity(): void;
onEnabled(): void;
onDisabled(): void;
update(): void;
}
```
- 定义所有组件的基本契约
- Component基类实现此接口
- 确保组件生命周期方法的一致性
**ISystemBase接口**
```typescript
interface ISystemBase {
readonly systemName: string;
readonly entities: readonly any[];
updateOrder: number;
enabled: boolean;
initialize(): void;
update(): void;
lateUpdate?(): void;
}
```
- 为EntitySystem类提供类型约束
- 定义系统的核心执行方法
- 支持可选的延迟更新
**IEventBus接口**
```typescript
interface IEventBus {
emit<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,656 +0,0 @@
# 核心 API 参考
本文档详细介绍 ECS Framework 的核心 API 和使用方法。
> 🤔 **不熟悉ECS概念** 建议先阅读 [技术概念详解](concepts-explained.md) 了解ECS架构基础和性能优化原理
## ECS 架构概述
ECS 架构将传统的面向对象设计分解为三个核心部分:
- **Entity实体** - 游戏世界中的对象,包含基本属性如位置、旋转、缩放
- **Component组件** - 包含数据和行为的功能模块
- **System系统** - 处理实体集合的逻辑处理单元
## Core核心
Core 是框架的核心管理类,负责游戏的生命周期管理。
### 创建和配置
```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(); // 默认调试模式
```
### 事件系统
```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.scene = 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);
```
## 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** 管理游戏生命周期和全局功能
- **Entity** 作为游戏对象的基础容器
- **Component** 实现具体的功能模块,支持对象池优化
- **System** 处理游戏逻辑
- **Scene** 管理游戏世界状态,支持批量操作
- **高级优化** 位掩码优化器、组件对象池、批量操作等
通过合理使用这些核心概念和优化功能,可以构建出高性能、结构清晰、易于维护的游戏代码。

View File

@@ -0,0 +1,412 @@
# Quick Start
This guide will help you get started with ECS Framework, from installation to creating your first ECS application.
## Installation
### NPM Installation
```bash
# Using npm
npm install @esengine/ecs-framework
```
## Initialize Core
### Basic Initialization
The core of ECS Framework is the `Core` class, a singleton that manages the entire framework lifecycle.
```typescript
import { Core } from '@esengine/ecs-framework'
// Method 1: Using config object (recommended)
const core = Core.create({
debug: true, // Enable debug mode for detailed logs and performance monitoring
debugConfig: { // Optional: Advanced debug configuration
enabled: false, // Whether to enable WebSocket debug server
websocketUrl: 'ws://localhost:8080',
debugFrameRate: 30, // Debug data send frame rate
channels: {
entities: true,
systems: true,
performance: true,
components: true,
scenes: true
}
}
});
// Method 2: Simplified creation (backward compatible)
const core = Core.create(true); // Equivalent to { debug: true }
// Method 3: Production environment configuration
const core = Core.create({
debug: false // Disable debug in production
});
```
### Core Configuration Details
```typescript
interface ICoreConfig {
/** Enable debug mode - affects log level and performance monitoring */
debug?: boolean;
/** Advanced debug configuration - for dev tools integration */
debugConfig?: {
enabled: boolean; // Enable debug server
websocketUrl: string; // WebSocket server URL
autoReconnect?: boolean; // Auto reconnect
debugFrameRate?: 60 | 30 | 15; // Debug data send frame rate
channels: { // Data channel configuration
entities: boolean; // Entity data
systems: boolean; // System data
performance: boolean; // Performance data
components: boolean; // Component data
scenes: boolean; // Scene data
};
};
}
```
### Core Instance Management
Core uses singleton pattern, accessible via static property after creation:
```typescript
// Create instance
const core = Core.create(true);
// Get created instance
const instance = Core.Instance; // Returns current instance, null if not created
```
### Game Loop Integration
**Important**: Before creating entities and systems, you need to understand how to integrate ECS Framework into your game engine.
`Core.update(deltaTime)` is the framework heartbeat, must be called every frame. It handles:
- Updating the built-in Time class
- Updating all global managers (timers, object pools, etc.)
- Updating all entity systems in all scenes
- Processing entity creation and destruction
- Collecting performance data (in debug mode)
See engine integration examples: [Game Engine Integration](#game-engine-integration)
## Create Your First ECS Application
### 1. Define Components
Components are pure data containers that store entity state:
```typescript
import { Component, ECSComponent } from '@esengine/ecs-framework'
// Position component
@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
}
}
// Velocity component
@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
}
}
// Sprite component
@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. Create Entity Systems
Systems contain game logic and process entities with specific components. ECS Framework provides Matcher-based entity filtering:
```typescript
import { EntitySystem, Matcher, Time, ECSSystem } from '@esengine/ecs-framework'
// Movement system - handles position and velocity
@ECSSystem('MovementSystem')
class MovementSystem extends EntitySystem {
constructor() {
// Use Matcher to define target entities: must have both Position and Velocity
super(Matcher.empty().all(Position, Velocity))
}
protected process(entities: readonly Entity[]): void {
// process method receives all matching entities
for (const entity of entities) {
const position = entity.getComponent(Position)!
const velocity = entity.getComponent(Velocity)!
// Update position (using framework's Time class)
position.x += velocity.dx * Time.deltaTime
position.y += velocity.dy * Time.deltaTime
// Boundary check example
if (position.x < 0) position.x = 0
if (position.y < 0) position.y = 0
}
}
}
// Render system - handles visible objects
@ECSSystem('RenderSystem')
class RenderSystem extends EntitySystem {
constructor() {
// Must have Position and Sprite, optional Velocity (for direction)
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) // May be null
// Flip sprite based on velocity direction (optional logic)
let flipX = false
if (velocity && velocity.dx < 0) {
flipX = true
}
// Render logic (pseudocode here)
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) {
// Actual render implementation depends on your game engine
const direction = flipX ? '<-' : '->'
console.log(`Render ${texture} at (${x.toFixed(1)}, ${y.toFixed(1)}) direction: ${direction}`)
}
}
```
### 3. Create Scene
Recommended to extend Scene class for custom scenes:
```typescript
import { Scene } from '@esengine/ecs-framework'
// Recommended: Extend Scene for custom scene
class GameScene extends Scene {
initialize(): void {
// Scene initialization logic
this.name = "MainScene";
// Add systems to scene
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
}
onStart(): void {
// Logic when scene starts running
console.log("Game scene started");
}
unload(): void {
// Cleanup logic when scene unloads
console.log("Game scene unloaded");
}
}
// Create and set scene
const gameScene = new GameScene();
Core.setScene(gameScene);
```
### 4. Create Entities
```typescript
// Create player entity
const player = gameScene.createEntity("Player");
player.addComponent(new Position(100, 100));
player.addComponent(new Velocity(50, 30)); // Move 50px/sec (x), 30px/sec (y)
player.addComponent(new Sprite("player.png", 64, 64));
```
## Scene Management
Core has built-in scene management, very simple to use:
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// Initialize Core
Core.create({ debug: true });
// Create and set scene
class GameScene extends Scene {
initialize(): void {
this.name = "GamePlay";
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
}
}
const gameScene = new GameScene();
Core.setScene(gameScene);
// Game loop (auto-updates scene)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Auto-updates global services and scene
}
// Switch scenes
Core.loadScene(new MenuScene()); // Delayed switch (next frame)
Core.setScene(new GameScene()); // Immediate switch
// Access current scene
const currentScene = Core.scene;
// Using fluent API
const player = Core.ecsAPI?.createEntity('Player')
.addComponent(Position, 100, 100)
.addComponent(Velocity, 50, 0);
```
### Advanced: Using WorldManager for Multi-World
Only for complex server-side applications (MMO game servers, game room systems, etc.):
```typescript
import { Core, WorldManager } from '@esengine/ecs-framework';
// Initialize Core
Core.create({ debug: true });
// Get WorldManager from service container (Core auto-creates and registers it)
const worldManager = Core.services.resolve(WorldManager);
// Create multiple independent game worlds
const room1 = worldManager.createWorld('room_001');
const room2 = worldManager.createWorld('room_002');
// Create scenes in each world
const gameScene1 = room1.createScene('game', new GameScene());
const gameScene2 = room2.createScene('game', new GameScene());
// Activate scenes
room1.setSceneActive('game', true);
room2.setSceneActive('game', true);
// Game loop (need to manually update worlds)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Update global services
worldManager.updateAll(); // Manually update all worlds
}
```
## Game Engine Integration
### Laya Engine Integration
```typescript
import { Stage } from "laya/display/Stage";
import { Laya } from "Laya";
import { Core } from '@esengine/ecs-framework';
// Initialize Laya
Laya.init(800, 600).then(() => {
// Initialize ECS
Core.create(true);
Core.setScene(new GameScene());
// Start game loop
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime); // Auto-updates global services and scene
});
});
```
### Cocos Creator Integration
```typescript
import { Component, _decorator } from 'cc';
import { Core } from '@esengine/ecs-framework';
const { ccclass } = _decorator;
@ccclass('ECSGameManager')
export class ECSGameManager extends Component {
onLoad() {
// Initialize ECS
Core.create(true);
Core.setScene(new GameScene());
}
update(deltaTime: number) {
// Auto-updates global services and scene
Core.update(deltaTime);
}
onDestroy() {
// Cleanup resources
Core.destroy();
}
}
```
## Next Steps
You've successfully created your first ECS application! Next you can:
- Check the complete [API Documentation](/api/README)
- Explore more [practical examples](/examples/)
## FAQ
### Why isn't my system executing?
Ensure:
1. System is added to scene: `this.addSystem(system)` (in Scene's initialize method)
2. Scene is set: `Core.setScene(scene)`
3. Game loop is calling: `Core.update(deltaTime)`
### How to debug ECS applications?
Enable debug mode:
```typescript
Core.create({ debug: true })
// Get debug data
const debugData = Core.getDebugData()
console.log(debugData)
```

43
docs/en/guide/index.md Normal file
View File

@@ -0,0 +1,43 @@
# Guide
Welcome to the ECS Framework Guide. This guide covers the core concepts and usage of the framework.
## Core Concepts
### [Entity](/guide/entity)
Learn the basics of ECS architecture - how to use entities, lifecycle management, and best practices.
### [Component](/guide/component)
Learn how to create and use components for modular game feature design.
### [System](/guide/system)
Master system development to implement game logic processing.
### [Entity Query & Matcher](/guide/entity-query)
Learn to use Matcher for entity filtering and queries with `all`, `any`, `none`, `nothing` conditions.
### [Scene](/guide/scene)
Understand scene lifecycle, system management, and entity container features.
### [Event System](/guide/event-system)
Master the type-safe event system for component communication and system coordination.
### [Serialization](/guide/serialization)
Master serialization for scenes, entities, and components. Supports full and incremental serialization for game saves, network sync, and more.
### [Time and Timers](/guide/time-and-timers)
Learn time management and timer systems for precise game logic timing control.
### [Logging](/guide/logging)
Master the leveled logging system for debugging, monitoring, and error tracking.
### [Platform Adapter](/guide/platform-adapter)
Learn how to implement and register platform adapters for browsers, mini-games, Node.js, and more.
## Advanced Features
### [Service Container](/guide/service-container)
Master dependency injection and service management for loosely-coupled architecture.
### [Plugin System](/guide/plugin-system)
Learn how to develop and use plugins to extend framework functionality.

317
docs/en/index.md Normal file
View File

@@ -0,0 +1,317 @@
---
layout: page
title: ESEngine - High-performance TypeScript ECS Framework
---
<ParticleHeroEn />
<section class="news-section">
<div class="news-container">
<div class="news-header">
<h2 class="news-title">Quick Links</h2>
<a href="/en/guide/" class="news-more">View Docs</a>
</div>
<div class="news-grid">
<a href="/en/guide/getting-started" class="news-card">
<div class="news-card-image" style="background: linear-gradient(135deg, #1e3a5f 0%, #1e1e1e 100%);">
<div class="news-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#4fc1ff" d="M12 3L1 9l4 2.18v6L12 21l7-3.82v-6l2-1.09V17h2V9zm6.82 6L12 12.72L5.18 9L12 5.28zM17 16l-5 2.72L7 16v-3.73L12 15l5-2.73z"/></svg>
</div>
<span class="news-badge">Quick Start</span>
</div>
<div class="news-card-content">
<h3>Get Started in 5 Minutes</h3>
<p>From installation to your first ECS app, learn the core concepts quickly.</p>
</div>
</a>
<a href="/en/guide/behavior-tree/" class="news-card">
<div class="news-card-image" style="background: linear-gradient(135deg, #1e3a5f 0%, #1e1e1e 100%);">
<div class="news-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#4ec9b0" d="M12 2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m3 20h-1v-7l-2-2l-2 2v7H9v-7.5l-2 2V22H6v-6l3-3l1-3.5c-.3.4-.6.7-1 1L6 9v1H4V8l5-3c.5-.3 1.1-.5 1.7-.5H11c.6 0 1.2.2 1.7.5l5 3v2h-2V9l-3 1.5c-.4-.3-.7-.6-1-1l1 3.5l3 3v6Z"/></svg>
</div>
<span class="news-badge">AI System</span>
</div>
<div class="news-card-content">
<h3>Visual Behavior Tree Editor</h3>
<p>Built-in AI behavior tree system with visual editing and real-time debugging.</p>
</div>
</a>
</div>
</div>
</section>
<section class="features-section">
<div class="features-container">
<h2 class="features-title">Core Features</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#4fc1ff" d="M13 2.05v2.02c3.95.49 7 3.85 7 7.93c0 1.45-.39 2.79-1.06 3.95l1.59 1.09A9.94 9.94 0 0 0 22 12c0-5.18-3.95-9.45-9-9.95M12 19c-3.87 0-7-3.13-7-7c0-3.53 2.61-6.43 6-6.92V2.05c-5.06.5-9 4.76-9 9.95c0 5.52 4.47 10 9.99 10c3.31 0 6.24-1.61 8.06-4.09l-1.6-1.1A7.93 7.93 0 0 1 12 19"/><path fill="#4fc1ff" d="M12 6a6 6 0 0 0-6 6c0 3.31 2.69 6 6 6a6 6 0 0 0 0-12m0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4s4 1.79 4 4s-1.79 4-4 4"/></svg>
</div>
<h3 class="feature-title">High-performance ECS Architecture</h3>
<p class="feature-desc">Data-driven entity component system for large-scale entity processing with cache-friendly memory layout.</p>
<a href="/en/guide/entity" class="feature-link">Learn more</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#569cd6" d="M3 3h18v18H3zm16.525 13.707c0-.795-.272-1.425-.816-1.89c-.544-.465-1.404-.804-2.58-1.016l-1.704-.296c-.616-.104-1.052-.26-1.308-.468c-.256-.21-.384-.468-.384-.776c0-.392.168-.7.504-.924c.336-.224.8-.336 1.392-.336c.56 0 1.008.124 1.344.372c.336.248.536.584.6 1.008h2.016c-.08-.96-.464-1.716-1.152-2.268c-.688-.552-1.6-.828-2.736-.828c-1.2 0-2.148.3-2.844.9c-.696.6-1.044 1.38-1.044 2.34c0 .76.252 1.368.756 1.824c.504.456 1.308.792 2.412.996l1.704.312c.624.12 1.068.28 1.332.48c.264.2.396.46.396.78c0 .424-.192.756-.576.996c-.384.24-.9.36-1.548.36c-.672 0-1.2-.14-1.584-.42c-.384-.28-.608-.668-.672-1.164H8.868c.048 1.016.46 1.808 1.236 2.376c.776.568 1.796.852 3.06.852c1.24 0 2.22-.292 2.94-.876c.72-.584 1.08-1.364 1.08-2.34z"/></svg>
</div>
<h3 class="feature-title">Full Type Support</h3>
<p class="feature-desc">100% TypeScript with complete type definitions and compile-time checking for the best development experience.</p>
<a href="/en/guide/component" class="feature-link">Learn more</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#4ec9b0" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10s10-4.5 10-10S17.5 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m-5-8l4-4v3h4v2h-4v3z"/></svg>
</div>
<h3 class="feature-title">Visual Behavior Tree</h3>
<p class="feature-desc">Built-in AI behavior tree system with visual editor, custom nodes, and real-time debugging.</p>
<a href="/en/guide/behavior-tree/" class="feature-link">Learn more</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#c586c0" d="M4 6h18V4H4c-1.1 0-2 .9-2 2v11H0v3h14v-3H4zm19 2h-6c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1m-1 9h-4v-7h4z"/></svg>
</div>
<h3 class="feature-title">Multi-Platform Support</h3>
<p class="feature-desc">Support for browsers, Node.js, WeChat Mini Games, and seamless integration with major game engines.</p>
<a href="/en/guide/platform-adapter" class="feature-link">Learn more</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#dcdcaa" d="M4 3h6v2H4v14h6v2H4c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2m9 0h6c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2h-6v-2h6V5h-6zm-1 7h4v2h-4z"/></svg>
</div>
<h3 class="feature-title">Modular Design</h3>
<p class="feature-desc">Core features packaged independently, import only what you need. Support for custom plugin extensions.</p>
<a href="/en/guide/plugin-system" class="feature-link">Learn more</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#9cdcfe" d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9c-2-2-5-2.4-7.4-1.3L9 6L6 9L1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4"/></svg>
</div>
<h3 class="feature-title">Developer Tools</h3>
<p class="feature-desc">Built-in performance monitoring, debugging tools, serialization system, and complete development toolchain.</p>
<a href="/en/guide/logging" class="feature-link">Learn more</a>
</div>
</div>
</div>
</section>
<style scoped>
/* Home page specific styles */
.news-section {
background: #0d0d0d;
padding: 64px 0;
border-top: 1px solid #2a2a2a;
}
.news-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 48px;
}
.news-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
}
.news-title {
font-size: 1.5rem;
font-weight: 700;
color: #ffffff;
margin: 0;
}
.news-more {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 6px;
color: #a0a0a0;
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.news-more:hover {
background: #252525;
color: #ffffff;
}
.news-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.news-card {
display: flex;
background: #1f1f1f;
border: 1px solid #2a2a2a;
border-radius: 12px;
overflow: hidden;
text-decoration: none;
transition: all 0.2s;
}
.news-card:hover {
border-color: #3b9eff;
}
.news-card-image {
width: 200px;
min-height: 140px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16px;
gap: 12px;
}
.news-icon {
opacity: 0.9;
}
.news-badge {
display: inline-block;
padding: 4px 12px;
background: transparent;
border: 1px solid #3a3a3a;
border-radius: 16px;
color: #a0a0a0;
font-size: 0.75rem;
font-weight: 500;
}
.news-card-content {
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.news-card-content h3 {
font-size: 1.125rem;
font-weight: 600;
color: #ffffff;
margin: 0 0 8px 0;
}
.news-card-content p {
font-size: 0.875rem;
color: #707070;
margin: 0;
line-height: 1.6;
}
.features-section {
background: #0d0d0d;
padding: 64px 0;
}
.features-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 48px;
}
.features-title {
font-size: 1.5rem;
font-weight: 700;
color: #ffffff;
margin: 0 0 32px 0;
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.feature-card {
background: #1f1f1f;
border: 1px solid #2a2a2a;
border-radius: 12px;
padding: 24px;
transition: all 0.15s ease;
}
.feature-card:hover {
border-color: #3b9eff;
background: #252525;
}
.feature-icon {
width: 48px;
height: 48px;
background: #0d0d0d;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
}
.feature-title {
font-size: 16px;
font-weight: 600;
color: #ffffff;
margin: 0 0 8px 0;
}
.feature-desc {
font-size: 14px;
color: #707070;
line-height: 1.7;
margin: 0 0 16px 0;
}
.feature-link {
font-size: 14px;
color: #3b9eff;
text-decoration: none;
font-weight: 500;
}
.feature-link:hover {
text-decoration: underline;
}
@media (max-width: 1024px) {
.news-container,
.features-container {
padding: 0 24px;
}
.news-grid {
grid-template-columns: 1fr;
}
.features-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.news-card {
flex-direction: column;
}
.news-card-image {
width: 100%;
min-height: 120px;
}
.features-grid {
grid-template-columns: 1fr;
}
}
</style>

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,663 +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框架提供了灵活的配置选项来满足不同项目需求
```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框架内置了强大的调试功能支持运行时监控和远程调试
#### 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.scene = 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.empty().all(PositionComponent, SpriteComponent));
this.layaScene = layaScene;
}
protected process(entities: Entity[]): void {
entities.forEach(entity => {
const pos = entity.getComponent(PositionComponent);
const sprite = entity.getComponent(SpriteComponent);
if (pos && sprite && 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 } 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.scene = 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.empty().all(PositionComponent, SpriteComponent));
this.rootNode = rootNode;
}
protected process(entities: Entity[]): void {
entities.forEach(entity => {
const pos = entity.getComponent(PositionComponent);
const sprite = entity.getComponent(SpriteComponent);
if (pos && sprite && 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 } 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: {...} })
this.scene = new Scene();
this.scene.name = "ServerScene";
Core.scene = 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();
```
### 原生浏览器
```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.scene = 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(x: number = 0, y: number = 0) {
super();
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(x: number = 0, y: number = 0) {
super();
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(maxHealth: number = 100) {
super();
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.empty().all(PositionComponent, VelocityComponent));
}
protected process(entities: Entity[]): void {
const movingEntities = this.scene.querySystem.queryAll(PositionComponent, VelocityComponent);
movingEntities.entities.forEach(entity => {
const position = entity.getComponent(PositionComponent);
const velocity = entity.getComponent(VelocityComponent);
if (position && velocity) {
position.x += velocity.x * Time.deltaTime;
position.y += velocity.y * Time.deltaTime;
}
});
}
}
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) {
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 enemiesByTag = 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) - 学习高性能查询系统的详细用法

View File

@@ -0,0 +1,392 @@
# 高级用法
本文介绍行为树系统的高级功能和使用技巧。
## 全局黑板
全局黑板在所有行为树实例之间共享数据。
### 使用全局黑板
```typescript
import { GlobalBlackboardService } from '@esengine/behavior-tree';
import { Core } from '@esengine/ecs-framework';
// 获取全局黑板服务
const globalBlackboard = Core.services.resolve(GlobalBlackboardService);
// 设置全局变量
globalBlackboard.setValue('gameState', 'playing');
globalBlackboard.setValue('playerCount', 4);
globalBlackboard.setValue('difficulty', 'hard');
// 读取全局变量
const gameState = globalBlackboard.getValue('gameState');
const playerCount = globalBlackboard.getValue<number>('playerCount');
```
### 在自定义执行器中访问全局黑板
```typescript
import { INodeExecutor, NodeExecutionContext, BindingHelper } from '@esengine/behavior-tree';
import { GlobalBlackboardService } from '@esengine/behavior-tree';
import { Core } from '@esengine/ecs-framework';
export class CheckGameState implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const globalBlackboard = Core.services.resolve(GlobalBlackboardService);
const gameState = globalBlackboard.getValue('gameState');
if (gameState === 'paused') {
return TaskStatus.Failure;
}
return TaskStatus.Success;
}
}
```
## 性能优化
### 1. 降低更新频率
对于不需要每帧更新的AI,可以使用冷却装饰器:
```typescript
// 每0.1秒执行一次
const ai = BehaviorTreeBuilder.create('ThrottledAI')
.cooldown(0.1, 'ThrottleRoot')
.selector('MainLogic')
// AI逻辑...
.end()
.end()
.build();
```
### 2. 条件缓存
在自定义执行器中缓存昂贵的条件检查结果:
```typescript
export class CachedCheck implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { state, runtime, totalTime } = context;
const cacheTime = state.lastCheckTime || 0;
// 如果缓存未过期(1秒内),直接使用缓存结果
if (totalTime - cacheTime < 1.0) {
return state.cachedResult || TaskStatus.Failure;
}
// 执行昂贵的检查
const result = performExpensiveCheck();
const status = result ? TaskStatus.Success : TaskStatus.Failure;
// 缓存结果
state.cachedResult = status;
state.lastCheckTime = totalTime;
return status;
}
reset(context: NodeExecutionContext): void {
context.state.cachedResult = undefined;
context.state.lastCheckTime = undefined;
}
}
```
### 3. 分帧执行
将大量计算分散到多帧:
```typescript
export class ProcessLargeDataset implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { state, runtime } = context;
const data = runtime.getBlackboardValue<any[]>('dataset') || [];
let processedIndex = state.processedIndex || 0;
const batchSize = 100; // 每帧处理100个
const endIndex = Math.min(processedIndex + batchSize, data.length);
for (let i = processedIndex; i < endIndex; i++) {
processItem(data[i]);
}
state.processedIndex = endIndex;
if (endIndex >= data.length) {
return TaskStatus.Success;
}
return TaskStatus.Running;
}
reset(context: NodeExecutionContext): void {
context.state.processedIndex = 0;
}
}
```
## 调试技巧
### 1. 使用日志节点
在关键位置添加日志:
```typescript
const tree = BehaviorTreeBuilder.create('Debug')
.log('开始战斗序列', 'StartCombat')
.sequence('Combat')
.log('检查生命值', 'CheckHealth')
.blackboardCompare('health', 0, 'greater')
.log('执行攻击', 'Attack')
.end()
.build();
```
### 2. 监控黑板状态
```typescript
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
// 输出所有黑板变量
console.log('黑板变量:', runtime?.getAllBlackboardVariables());
// 输出活动节点
console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));
```
### 3. 在自定义执行器中调试
```typescript
export class DebugAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { nodeData, runtime, state } = context;
console.log(`[${nodeData.name}] 开始执行`);
console.log('配置:', nodeData.config);
console.log('状态:', state);
console.log('黑板:', runtime.getAllBlackboardVariables());
// 执行逻辑...
return TaskStatus.Success;
}
}
```
### 4. 性能分析
测量节点执行时间:
```typescript
export class ProfiledAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const startTime = performance.now();
// 执行操作
doSomething();
const elapsed = performance.now() - startTime;
console.log(`[${context.nodeData.name}] 耗时: ${elapsed.toFixed(2)}ms`);
return TaskStatus.Success;
}
}
```
## 常见模式
### 1. 状态机模式
使用行为树实现状态机:
```typescript
const fsm = BehaviorTreeBuilder.create('StateMachine')
.defineBlackboardVariable('currentState', 'idle')
.selector('StateSwitch')
// Idle状态
.sequence('IdleState')
.blackboardCompare('currentState', 'idle', 'equals')
.log('执行Idle行为', 'IdleBehavior')
.end()
// Move状态
.sequence('MoveState')
.blackboardCompare('currentState', 'move', 'equals')
.log('执行Move行为', 'MoveBehavior')
.end()
// Attack状态
.sequence('AttackState')
.blackboardCompare('currentState', 'attack', 'equals')
.log('执行Attack行为', 'AttackBehavior')
.end()
.end()
.build();
```
状态转换通过修改黑板变量实现:
```typescript
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('currentState', 'move');
```
### 2. 优先级队列模式
按优先级执行任务:
```typescript
const tree = BehaviorTreeBuilder.create('PriorityQueue')
.selector('Priorities')
// 最高优先级:生存
.sequence('Survive')
.blackboardCompare('health', 20, 'less')
.log('治疗', 'Heal')
.end()
// 中优先级:战斗
.sequence('Combat')
.blackboardExists('nearbyEnemy')
.log('战斗', 'Fight')
.end()
// 低优先级:收集资源
.sequence('Gather')
.log('收集资源', 'CollectResources')
.end()
.end()
.build();
```
### 3. 并行任务模式
同时执行多个任务:
```typescript
const tree = BehaviorTreeBuilder.create('ParallelTasks')
.parallel('Effects', { successPolicy: 'all' })
.log('播放动画', 'PlayAnimation')
.log('播放音效', 'PlaySound')
.log('生成粒子', 'SpawnParticles')
.end()
.build();
```
### 4. 重试模式
失败时重试:
```typescript
// 使用自定义重试装饰器(参见custom-actions.md中的RetryDecorator示例)
// 或者使用UntilSuccess装饰器
const tree = BehaviorTreeBuilder.create('Retry')
.untilSuccess('RetryUntilSuccess')
.log('尝试操作', 'TryOperation')
.end()
.build();
```
### 5. 超时模式
限制任务执行时间:
```typescript
const tree = BehaviorTreeBuilder.create('Timeout')
.timeout(5.0, 'TimeLimit')
.log('长时间运行的任务', 'LongTask')
.end()
.build();
```
## 与游戏引擎集成
### Cocos Creator集成
参见[Cocos Creator集成指南](./cocos-integration.md)
### LayaAir集成
参见[LayaAir集成指南](./laya-integration.md)
## 最佳实践
### 1. 合理使用黑板
```typescript
// 好的做法:使用类型化的黑板访问
const health = runtime.getBlackboardValue<number>('health');
// 好的做法:定义所有黑板变量
const tree = BehaviorTreeBuilder.create('AI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.defineBlackboardVariable('state', 'idle')
// ...
```
### 2. 避免过深的树结构
```typescript
// 不好:嵌套过深
.selector()
.sequence()
.selector()
.sequence()
.selector()
// 太深了!
.end()
.end()
.end()
.end()
.end()
// 好:使用合理的深度
.selector()
.sequence()
.log('Action1')
.log('Action2')
.end()
.sequence()
.log('Action3')
.log('Action4')
.end()
.end()
```
### 3. 使用有意义的节点名称
```typescript
// 好的做法
.selector('CombatDecision')
.sequence('AttackEnemy')
.blackboardExists('target', 'HasTarget')
.log('执行攻击', 'Attack')
.end()
.end()
// 不好的做法
.selector('Node1')
.sequence('Node2')
.blackboardExists('target', 'Node3')
.log('Attack', 'Node4')
.end()
.end()
```
### 4. 模块化设计
将复杂逻辑分解为多个独立的行为树,在需要时组合使用。
### 5. 性能考虑
- 避免在每帧执行昂贵的操作
- 使用冷却装饰器控制执行频率
- 缓存计算结果
- 合理使用并行节点
## 下一步
- 查看[自定义节点执行器](./custom-actions.md)学习如何创建自定义节点
- 阅读[最佳实践](./best-practices.md)了解行为树设计技巧
- 参考[编辑器使用指南](./editor-guide.md)学习可视化编辑

View File

@@ -0,0 +1,506 @@
# 资产管理
本文介绍如何加载、管理和复用行为树资产。
## 为什么需要资产管理?
在实际游戏开发中,你可能会遇到以下场景:
1. **多个实体共享同一个行为树** - 100个敌人使用同一套AI逻辑
2. **动态加载行为树** - 从JSON文件加载行为树配置
3. **子树复用** - 将常用的行为片段(如"巡逻"、"追击")做成独立的子树
4. **运行时切换行为树** - 敌人在不同阶段使用不同的行为树
## BehaviorTreeAssetManager
框架提供了 `BehaviorTreeAssetManager` 服务来统一管理行为树资产。
### 核心概念
- **BehaviorTreeData行为树数据**:行为树的定义,可以被多个实体共享
- **BehaviorTreeRuntimeComponent运行时组件**:每个实体独立的运行时状态
- **AssetManager资产管理器**:统一管理所有 BehaviorTreeData
### 基本使用
```typescript
import { Core } from '@esengine/ecs-framework';
import {
BehaviorTreeAssetManager,
BehaviorTreeBuilder,
BehaviorTreeStarter
} from '@esengine/behavior-tree';
// 1. 获取资产管理器(插件已自动注册)
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
// 2. 创建并注册行为树资产
const enemyAI = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.selector('MainBehavior')
.log('攻击')
.end()
.build();
assetManager.loadAsset(enemyAI);
// 3. 为多个实体使用同一份资产
const enemy1 = scene.createEntity('Enemy1');
const enemy2 = scene.createEntity('Enemy2');
const enemy3 = scene.createEntity('Enemy3');
// 获取共享的资产
const sharedTree = assetManager.getAsset('EnemyAI');
if (sharedTree) {
BehaviorTreeStarter.start(enemy1, sharedTree);
BehaviorTreeStarter.start(enemy2, sharedTree);
BehaviorTreeStarter.start(enemy3, sharedTree);
}
```
### 资产管理器 API
```typescript
// 加载资产
assetManager.loadAsset(treeData);
// 获取资产
const tree = assetManager.getAsset('TreeID');
// 检查资产是否存在
if (assetManager.hasAsset('TreeID')) {
// ...
}
// 卸载资产
assetManager.unloadAsset('TreeID');
// 获取所有资产ID
const allIds = assetManager.getAllAssetIds();
// 清空所有资产
assetManager.clearAll();
```
## 从文件加载行为树
### JSON 格式
行为树可以导出为 JSON 格式:
```json
{
"version": "1.0.0",
"metadata": {
"name": "EnemyAI",
"description": "敌人AI行为树"
},
"rootNodeId": "root-1",
"nodes": [
{
"id": "root-1",
"name": "RootSelector",
"nodeType": "Composite",
"data": {
"compositeType": "Selector"
},
"children": ["combat-1", "patrol-1"]
},
{
"id": "combat-1",
"name": "Combat",
"nodeType": "Action",
"data": {
"actionType": "LogAction",
"message": "攻击敌人"
},
"children": []
}
],
"blackboard": [
{
"name": "health",
"type": "number",
"defaultValue": 100
}
]
}
```
### 加载 JSON 文件
```typescript
import {
BehaviorTreeAssetSerializer,
BehaviorTreeAssetManager
} from '@esengine/behavior-tree';
async function loadTreeFromFile(filePath: string) {
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
// 1. 读取文件内容
const jsonContent = await fetch(filePath).then(res => res.text());
// 2. 反序列化
const treeData = BehaviorTreeAssetSerializer.deserialize(jsonContent);
// 3. 加载到资产管理器
assetManager.loadAsset(treeData);
return treeData;
}
// 使用
const tree = await loadTreeFromFile('/assets/enemy-ai.btree.json');
BehaviorTreeStarter.start(entity, tree);
```
## 子树SubTree
子树允许你将常用的行为片段做成独立的树,然后在其他树中引用。
### 为什么使用子树?
1. **代码复用** - 避免重复定义相同的行为
2. **模块化** - 将复杂的行为树拆分成小的可管理单元
3. **团队协作** - 不同成员可以独立开发不同的子树
### 创建子树
```typescript
// 1. 创建巡逻子树
const patrolTree = BehaviorTreeBuilder.create('PatrolBehavior')
.sequence('Patrol')
.log('选择巡逻点', 'PickWaypoint')
.log('移动到巡逻点', 'MoveToWaypoint')
.wait(2.0, 'WaitAtWaypoint')
.end()
.build();
// 2. 创建追击子树
const chaseTree = BehaviorTreeBuilder.create('ChaseBehavior')
.sequence('Chase')
.log('锁定目标', 'LockTarget')
.log('追击目标', 'ChaseTarget')
.end()
.build();
// 3. 注册子树到资产管理器
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
assetManager.loadAsset(patrolTree);
assetManager.loadAsset(chaseTree);
```
### 使用子树
```typescript
// 在主行为树中使用子树
const mainTree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('hasTarget', false)
.selector('MainBehavior')
// 条件:发现目标时执行追击子树
.sequence('CombatBranch')
.blackboardExists('hasTarget')
.subTree('ChaseBehavior', { shareBlackboard: true })
.end()
// 默认:执行巡逻子树
.subTree('PatrolBehavior', { shareBlackboard: true })
.end()
.build();
assetManager.loadAsset(mainTree);
// 启动主行为树
const enemy = scene.createEntity('Enemy');
BehaviorTreeStarter.start(enemy, mainTree);
```
### SubTree 配置选项
```typescript
.subTree('SubTreeID', {
shareBlackboard: true, // 是否共享黑板默认true
})
```
- **shareBlackboard: true** - 子树和父树共享黑板变量
- **shareBlackboard: false** - 子树使用独立的黑板
## 资源预加载
在游戏启动时预加载所有行为树资产:
```typescript
class BehaviorTreePreloader {
private assetManager: BehaviorTreeAssetManager;
constructor() {
this.assetManager = Core.services.resolve(BehaviorTreeAssetManager);
}
async preloadAll() {
// 定义所有行为树文件
const treeFiles = [
'/assets/ai/enemy-ai.btree.json',
'/assets/ai/boss-ai.btree.json',
'/assets/ai/patrol.btree.json',
'/assets/ai/chase.btree.json'
];
// 并行加载所有文件
const loadPromises = treeFiles.map(file => this.loadTree(file));
await Promise.all(loadPromises);
console.log(`已加载 ${this.assetManager.getAssetCount()} 个行为树资产`);
}
private async loadTree(filePath: string) {
const jsonContent = await fetch(filePath).then(res => res.text());
const treeData = BehaviorTreeAssetSerializer.deserialize(jsonContent);
this.assetManager.loadAsset(treeData);
}
}
// 游戏启动时调用
const preloader = new BehaviorTreePreloader();
await preloader.preloadAll();
```
## 运行时切换行为树
敌人在不同阶段使用不同的行为树:
```typescript
class EnemyAI {
private entity: Entity;
private assetManager: BehaviorTreeAssetManager;
constructor(entity: Entity) {
this.entity = entity;
this.assetManager = Core.services.resolve(BehaviorTreeAssetManager);
}
// 切换到巡逻AI
switchToPatrol() {
const tree = this.assetManager.getAsset('PatrolAI');
if (tree) {
BehaviorTreeStarter.stop(this.entity);
BehaviorTreeStarter.start(this.entity, tree);
}
}
// 切换到战斗AI
switchToCombat() {
const tree = this.assetManager.getAsset('CombatAI');
if (tree) {
BehaviorTreeStarter.stop(this.entity);
BehaviorTreeStarter.start(this.entity, tree);
}
}
// 切换到狂暴模式
switchToBerserk() {
const tree = this.assetManager.getAsset('BerserkAI');
if (tree) {
BehaviorTreeStarter.stop(this.entity);
BehaviorTreeStarter.start(this.entity, tree);
}
}
}
// 使用
const enemyAI = new EnemyAI(enemyEntity);
// Boss血量低于30%时进入狂暴
const runtime = enemyEntity.getComponent(BehaviorTreeRuntimeComponent);
const health = runtime?.getBlackboardValue<number>('health');
if (health && health < 30) {
enemyAI.switchToBerserk();
}
```
## 内存优化
### 1. 共享行为树数据
```typescript
// 好的做法100个敌人共享1份BehaviorTreeData
const sharedTree = assetManager.getAsset('EnemyAI');
for (let i = 0; i < 100; i++) {
const enemy = scene.createEntity(`Enemy${i}`);
BehaviorTreeStarter.start(enemy, sharedTree!); // 共享数据
}
// 不好的做法每个敌人创建独立的BehaviorTreeData
for (let i = 0; i < 100; i++) {
const enemy = scene.createEntity(`Enemy${i}`);
const tree = BehaviorTreeBuilder.create('EnemyAI') // 重复创建
// ... 节点定义
.build();
BehaviorTreeStarter.start(enemy, tree);
}
```
### 2. 及时卸载不用的资产
```typescript
// 关卡结束时卸载该关卡的AI
function onLevelEnd() {
assetManager.unloadAsset('Level1BossAI');
assetManager.unloadAsset('Level1EnemyAI');
}
// 加载新关卡的AI
function onLevelStart() {
const boss2AI = await loadTreeFromFile('/assets/level2-boss.btree.json');
assetManager.loadAsset(boss2AI);
}
```
## 完整示例:多敌人类型的游戏
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreePlugin,
BehaviorTreeAssetManager,
BehaviorTreeBuilder,
BehaviorTreeStarter
} from '@esengine/behavior-tree';
async function setupGame() {
// 1. 初始化
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
// 2. 创建共享的子树
const patrolTree = BehaviorTreeBuilder.create('Patrol')
.sequence('PatrolLoop')
.log('巡逻')
.wait(1.0)
.end()
.build();
const combatTree = BehaviorTreeBuilder.create('Combat')
.sequence('CombatLoop')
.log('战斗')
.end()
.build();
assetManager.loadAsset(patrolTree);
assetManager.loadAsset(combatTree);
// 3. 创建不同类型敌人的AI
const meleeEnemyAI = BehaviorTreeBuilder.create('MeleeEnemyAI')
.selector('MeleeBehavior')
.sequence('Attack')
.blackboardExists('target')
.log('近战攻击')
.end()
.subTree('Patrol')
.end()
.build();
const rangedEnemyAI = BehaviorTreeBuilder.create('RangedEnemyAI')
.selector('RangedBehavior')
.sequence('Attack')
.blackboardExists('target')
.log('远程攻击')
.end()
.subTree('Patrol')
.end()
.build();
assetManager.loadAsset(meleeEnemyAI);
assetManager.loadAsset(rangedEnemyAI);
// 4. 创建多个敌人实体
// 10个近战敌人共享同一份AI
const meleeAI = assetManager.getAsset('MeleeEnemyAI')!;
for (let i = 0; i < 10; i++) {
const enemy = scene.createEntity(`MeleeEnemy${i}`);
BehaviorTreeStarter.start(enemy, meleeAI);
}
// 5个远程敌人共享同一份AI
const rangedAI = assetManager.getAsset('RangedEnemyAI')!;
for (let i = 0; i < 5; i++) {
const enemy = scene.createEntity(`RangedEnemy${i}`);
BehaviorTreeStarter.start(enemy, rangedAI);
}
console.log(`已创建 15 个敌人,使用 ${assetManager.getAssetCount()} 个行为树资产`);
// 5. 游戏循环
setInterval(() => {
Core.update(0.016);
}, 16);
}
setupGame();
```
## 常见问题
### 如何检查资产是否已加载?
```typescript
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
if (!assetManager.hasAsset('EnemyAI')) {
// 加载资产
const tree = await loadTreeFromFile('/assets/enemy-ai.btree.json');
assetManager.loadAsset(tree);
}
```
### 子树找不到怎么办?
确保子树已经加载到资产管理器中:
```typescript
// 1. 先加载子树
const subTree = BehaviorTreeBuilder.create('SubTreeID')
// ...
.build();
assetManager.loadAsset(subTree);
// 2. 再加载使用子树的主树
const mainTree = BehaviorTreeBuilder.create('MainTree')
.subTree('SubTreeID')
.build();
```
### 如何导出行为树为 JSON
```typescript
import { BehaviorTreeAssetSerializer } from '@esengine/behavior-tree';
const tree = BehaviorTreeBuilder.create('MyTree')
// ... 节点定义
.build();
// 序列化为JSON字符串
const json = BehaviorTreeAssetSerializer.serialize(tree);
// 保存到文件或发送到服务器
console.log(json);
```
## 下一步
- 学习[Cocos Creator 集成](./cocos-integration.md)了解如何在游戏引擎中加载资源
- 查看[自定义节点执行器](./custom-actions.md)创建自定义行为
- 阅读[最佳实践](./best-practices.md)优化你的行为树设计

View File

@@ -0,0 +1,468 @@
# 最佳实践
本文介绍行为树设计和使用的最佳实践,帮助你构建高效、可维护的AI系统。
## 行为树设计原则
### 1. 保持树的层次清晰
将复杂行为分解成清晰的层次结构:
```
Root Selector
├── Emergency (高优先级:紧急情况)
│ ├── FleeFromDanger
│ └── CallForHelp
├── Combat (中优先级:战斗)
│ ├── Attack
│ └── Defend
└── Idle (低优先级:空闲)
├── Patrol
└── Rest
```
### 2. 单一职责原则
每个节点应该只做一件事。要实现复杂动作,创建自定义执行器,参见[自定义节点执行器](./custom-actions.md)。
```typescript
// 好的设计 - 使用内置节点
.sequence('AttackSequence')
.blackboardExists('target', 'CheckTarget')
.log('瞄准', 'Aim')
.log('开火', 'Fire')
.end()
```
### 3. 使用描述性名称
节点名称应该清楚地表达其功能:
```typescript
// 好的命名
.blackboardCompare('health', 20, 'less', 'CheckHealthLow')
.log('寻找最近的医疗包', 'FindHealthPack')
.log('移动到医疗包', 'MoveToHealthPack')
// 不好的命名
.blackboardCompare('health', 20, 'less', 'C1')
.log('Do something', 'Action1')
.log('Move', 'A2')
```
## 黑板变量管理
### 1. 变量命名规范
使用清晰的命名约定:
```typescript
const tree = BehaviorTreeBuilder.create('AI')
// 状态变量
.defineBlackboardVariable('currentState', 'idle')
.defineBlackboardVariable('isMoving', false)
// 目标和引用
.defineBlackboardVariable('targetEnemy', null)
.defineBlackboardVariable('patrolPoints', [])
// 配置参数
.defineBlackboardVariable('attackRange', 5.0)
.defineBlackboardVariable('moveSpeed', 10.0)
// 临时数据
.defineBlackboardVariable('lastAttackTime', 0)
.defineBlackboardVariable('searchAttempts', 0)
// ...
.build();
```
### 2. 避免过度使用黑板
只在需要跨节点共享的数据才放入黑板。在自定义执行器中使用局部变量:
```typescript
// 好的做法 - 使用局部变量
export class CalculateAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
// 局部计算
const temp1 = 10;
const temp2 = 20;
const result = temp1 + temp2;
// 只保存需要共享的结果
context.runtime.setBlackboardValue('calculationResult', result);
return TaskStatus.Success;
}
}
```
### 3. 使用类型安全的访问
```typescript
export class TypeSafeAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { runtime } = context;
// 使用泛型进行类型安全访问
const health = runtime.getBlackboardValue<number>('health');
const target = runtime.getBlackboardValue<Entity | null>('target');
const state = runtime.getBlackboardValue<string>('currentState');
if (health !== undefined && health < 30) {
runtime.setBlackboardValue('currentState', 'flee');
}
return TaskStatus.Success;
}
}
```
## 执行器设计
### 1. 保持执行器无状态
状态必须存储在`context.state`中,而不是执行器实例:
```typescript
// 正确的做法
export class TimedAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
if (!context.state.startTime) {
context.state.startTime = context.totalTime;
}
const elapsed = context.totalTime - context.state.startTime;
if (elapsed >= 3.0) {
return TaskStatus.Success;
}
return TaskStatus.Running;
}
reset(context: NodeExecutionContext): void {
context.state.startTime = undefined;
}
}
```
### 2. 条件应该是无副作用的
条件检查不应该修改状态:
```typescript
// 好的做法 - 只读检查
@NodeExecutorMetadata({
implementationType: 'IsHealthLow',
nodeType: NodeType.Condition,
displayName: '检查生命值低',
category: '条件',
configSchema: {
threshold: {
type: 'number',
default: 30,
supportBinding: true
}
}
})
export class IsHealthLow implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const threshold = BindingHelper.getValue<number>(context, 'threshold', 30);
const health = context.runtime.getBlackboardValue<number>('health') || 0;
return health < threshold ? TaskStatus.Success : TaskStatus.Failure;
}
}
```
### 3. 错误处理
```typescript
export class SafeAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
try {
const resourceId = context.runtime.getBlackboardValue('resourceId');
if (!resourceId) {
console.error('[SafeAction] 资源ID未设置');
return TaskStatus.Failure;
}
// 执行操作...
return TaskStatus.Success;
} catch (error) {
console.error('[SafeAction] 执行失败:', error);
context.runtime.setBlackboardValue('lastError', error.message);
return TaskStatus.Failure;
}
}
}
```
## 性能优化技巧
### 1. 使用冷却装饰器
避免高频执行昂贵操作:
```typescript
const tree = BehaviorTreeBuilder.create('ThrottledAI')
.cooldown(1.0, 'ThrottleSearch') // 最多每秒执行一次
.log('昂贵的搜索操作', 'ExpensiveSearch')
.end()
.build();
```
### 2. 缓存计算结果
```typescript
export class CachedFindNearest implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { state, runtime, totalTime } = context;
// 检查缓存是否有效
const cacheTime = state.enemyCacheTime || 0;
if (totalTime - cacheTime < 0.5) { // 缓存0.5秒
const cached = runtime.getBlackboardValue('nearestEnemy');
return cached ? TaskStatus.Success : TaskStatus.Failure;
}
// 执行搜索
const nearest = findNearestEnemy();
runtime.setBlackboardValue('nearestEnemy', nearest);
state.enemyCacheTime = totalTime;
return nearest ? TaskStatus.Success : TaskStatus.Failure;
}
reset(context: NodeExecutionContext): void {
context.state.enemyCacheTime = undefined;
}
}
```
### 3. 使用早期退出
```typescript
const tree = BehaviorTreeBuilder.create('EarlyExit')
.selector('FindTarget')
// 先检查缓存的目标
.blackboardExists('cachedTarget', 'HasCachedTarget')
// 没有缓存才进行搜索(需要自定义执行器)
.log('执行昂贵的搜索', 'SearchNewTarget')
.end()
.build();
```
## 可维护性
### 1. 使用有意义的节点名称
```typescript
// 好的做法
const tree = BehaviorTreeBuilder.create('CombatAI')
.selector('CombatDecision')
.sequence('AttackEnemy')
.blackboardExists('target', 'HasTarget')
.log('执行攻击', 'Attack')
.end()
.end()
.build();
// 不好的做法
const tree = BehaviorTreeBuilder.create('AI')
.selector('Node1')
.sequence('Node2')
.blackboardExists('target', 'Node3')
.log('Attack', 'Node4')
.end()
.end()
.build();
```
### 2. 使用编辑器创建复杂树
对于复杂的AI,使用可视化编辑器:
- 更直观的结构
- 方便非程序员调整
- 易于版本控制
- 支持实时调试
### 3. 添加注释和文档
```typescript
// 为行为树添加清晰的注释
const bossAI = BehaviorTreeBuilder.create('BossAI')
.defineBlackboardVariable('phase', 1) // 1=正常, 2=狂暴, 3=濒死
.selector('MainBehavior')
// 阶段3: 生命值<20%,使用终极技能
.sequence('Phase3')
.blackboardCompare('phase', 3, 'equals')
.log('使用终极技能', 'UltimateAbility')
.end()
// 阶段2: 生命值<50%,进入狂暴
.sequence('Phase2')
.blackboardCompare('phase', 2, 'equals')
.log('进入狂暴模式', 'BerserkMode')
.end()
// 阶段1: 正常战斗
.sequence('Phase1')
.log('普通攻击', 'NormalAttack')
.end()
.end()
.build();
```
## 调试技巧
### 1. 使用日志节点
```typescript
const tree = BehaviorTreeBuilder.create('Debug')
.log('开始攻击序列', 'StartAttack')
.sequence('Attack')
.log('检查目标', 'CheckTarget')
.blackboardExists('target')
.log('执行攻击', 'DoAttack')
.end()
.build();
```
### 2. 在自定义执行器中调试
```typescript
export class DebugAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { nodeData, runtime, state } = context;
console.group(`[${nodeData.name}]`);
console.log('配置:', nodeData.config);
console.log('状态:', state);
console.log('黑板:', runtime.getAllBlackboardVariables());
console.log('活动节点:', Array.from(runtime.activeNodeIds));
console.groupEnd();
return TaskStatus.Success;
}
}
```
### 3. 状态可视化
```typescript
export class VisualizeState implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
if (process.env.NODE_ENV === 'development') {
console.group('AI State');
console.log('Entity:', context.entity.name);
console.log('Health:', context.runtime.getBlackboardValue('health'));
console.log('State:', context.runtime.getBlackboardValue('currentState'));
console.log('Target:', context.runtime.getBlackboardValue('target'));
console.groupEnd();
}
return TaskStatus.Success;
}
}
```
## 常见反模式
### 1. 过深的嵌套
```typescript
// 不好 - 太深的嵌套
.selector()
.sequence()
.sequence()
.sequence()
.log('太深了', 'DeepAction')
.end()
.end()
.end()
.end()
// 好 - 使用合理的深度
.selector()
.sequence()
.log('Action1')
.log('Action2')
.end()
.sequence()
.log('Action3')
.log('Action4')
.end()
.end()
```
### 2. 在执行器中存储状态
```typescript
// 错误 - 状态存储在执行器中
export class BadAction implements INodeExecutor {
private startTime = 0; // 错误!多个节点会共享这个值
execute(context: NodeExecutionContext): TaskStatus {
this.startTime = context.totalTime; // 错误!
return TaskStatus.Success;
}
}
// 正确 - 状态存储在context.state中
export class GoodAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
if (!context.state.startTime) {
context.state.startTime = context.totalTime; // 正确!
}
return TaskStatus.Success;
}
}
```
### 3. 频繁修改黑板
```typescript
// 不好 - 每帧都修改黑板
export class FrequentUpdate implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const pos = getCurrentPosition();
context.runtime.setBlackboardValue('position', pos); // 每帧都set
context.runtime.setBlackboardValue('velocity', getVelocity());
context.runtime.setBlackboardValue('rotation', getRotation());
return TaskStatus.Running;
}
}
// 好 - 只在需要时修改
export class SmartUpdate implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const oldPos = context.runtime.getBlackboardValue('position');
const newPos = getCurrentPosition();
// 只在位置变化时更新
if (!positionsEqual(oldPos, newPos)) {
context.runtime.setBlackboardValue('position', newPos);
}
return TaskStatus.Running;
}
}
```
## 下一步
- 学习[自定义节点执行器](./custom-actions.md)扩展行为树功能
- 探索[高级用法](./advanced-usage.md)了解更多技巧
- 参考[核心概念](./core-concepts.md)深入理解原理

View File

@@ -0,0 +1,683 @@
# Cocos Creator 集成
本教程将引导你在 Cocos Creator 项目中集成和使用行为树系统。
## 前置要求
- Cocos Creator 3.x 或更高版本
- 基本的 TypeScript 知识
- 已完成[快速开始](./getting-started.md)教程
## 安装
### 步骤1安装依赖
在你的 Cocos Creator 项目根目录下:
```bash
npm install @esengine/ecs-framework @esengine/behavior-tree
```
### 步骤2配置 tsconfig.json
确保 `tsconfig.json` 中包含以下配置:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node"
}
}
```
## 项目结构
建议的项目结构:
```
assets/
├── scripts/
│ ├── ai/
│ │ ├── EnemyAIComponent.ts # AI 组件
│ │ └── PlayerDetector.ts # 检测器
│ ├── systems/
│ │ └── BehaviorTreeSystem.ts # 行为树系统
│ └── Main.ts # 主入口
├── resources/
│ └── behaviors/
│ ├── enemy-ai.btree.json # 行为树资产
│ └── patrol.btree.json # 子树资产
└── types/
└── enemy-ai.ts # 类型定义
```
## 初始化 ECS 和行为树
### 创建主入口组件
创建 `assets/scripts/Main.ts`
```typescript
import { _decorator, Component } from 'cc';
import { Core, Scene } from '@esengine/ecs-framework';
import { BehaviorTreePlugin } from '@esengine/behavior-tree';
const { ccclass } = _decorator;
@ccclass('Main')
export class Main extends Component {
async onLoad() {
// 初始化 ECS Core
Core.create();
// 安装行为树插件
const behaviorTreePlugin = new BehaviorTreePlugin();
await Core.installPlugin(behaviorTreePlugin);
// 创建并设置场景
const scene = new Scene();
behaviorTreePlugin.setupScene(scene);
Core.setScene(scene);
console.log('ECS 和行为树系统初始化完成');
}
update(deltaTime: number) {
// 更新 ECS会自动更新场景
Core.update(deltaTime);
}
onDestroy() {
// 清理资源
Core.destroy();
}
}
```
### 添加组件到场景
1. 在场景中创建一个空节点(命名为 `GameManager`
2. 添加 `Main` 组件到该节点
## 创建 AI 组件
创建 `assets/scripts/ai/EnemyAIComponent.ts`
```typescript
import { _decorator, Component, Node } from 'cc';
import { Core, Entity } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
const { ccclass, property } = _decorator;
@ccclass('EnemyAIComponent')
export class EnemyAIComponent extends Component {
private aiEntity: Entity | null = null;
async start() {
// 创建行为树
await this.createBehaviorTree();
}
private async createBehaviorTree() {
try {
// 获取Core管理的场景
const scene = Core.scene;
if (!scene) {
console.error('场景未初始化');
return;
}
// 使用Builder API创建行为树
const tree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('cocosNode', this.node)
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('playerNode', null)
.defineBlackboardVariable('detectionRange', 10)
.defineBlackboardVariable('attackRange', 2)
.selector('MainBehavior')
.sequence('Combat')
.blackboardExists('playerNode')
.blackboardCompare('health', 30, 'greater')
.log('攻击玩家', 'AttackPlayer')
.end()
.sequence('Flee')
.blackboardCompare('health', 30, 'lessOrEqual')
.log('逃跑', 'RunAway')
.end()
.log('巡逻', 'Patrol')
.end()
.build();
// 创建AI实体并启动
this.aiEntity = scene.createEntity(`AI_${this.node.name}`);
BehaviorTreeStarter.start(this.aiEntity, tree);
console.log('敌人 AI 已启动');
} catch (error) {
console.error('初始化行为树失败:', error);
}
}
onDestroy() {
// 停止 AI
if (this.aiEntity) {
BehaviorTreeStarter.stop(this.aiEntity);
}
}
}
```
## 与 Cocos 节点交互
### 创建自定义执行器
要实现与Cocos节点的交互需要创建自定义执行器
```typescript
import {
INodeExecutor,
NodeExecutionContext,
NodeExecutorMetadata
} from '@esengine/behavior-tree';
import { TaskStatus, NodeType } from '@esengine/behavior-tree';
import { Animation } from 'cc';
@NodeExecutorMetadata({
implementationType: 'PlayAnimation',
nodeType: NodeType.Action,
displayName: '播放动画',
description: '播放Cocos节点上的动画',
category: 'Cocos',
configSchema: {
animationName: {
type: 'string',
default: 'attack'
}
}
})
export class PlayAnimationAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const cocosNode = context.runtime.getBlackboardValue('cocosNode');
const animationName = context.nodeData.config.animationName;
if (!cocosNode) {
return TaskStatus.Failure;
}
const animation = cocosNode.getComponent(Animation);
if (animation) {
animation.play(animationName);
return TaskStatus.Success;
}
return TaskStatus.Failure;
}
}
```
## 完整示例:敌人 AI
### 行为树设计
使用编辑器创建 `enemy-ai.btree.json`
```
RootSelector
├── CombatSequence
│ ├── CheckPlayerInRange (Condition)
│ ├── CheckHealthGood (Condition)
│ └── AttackPlayer (Action)
├── FleeSequence
│ ├── CheckHealthLow (Condition)
│ └── RunAway (Action)
└── PatrolSequence
├── PickWaypoint (Action)
├── MoveToWaypoint (Action)
└── Wait (Action)
```
### 黑板变量
定义以下黑板变量:
- `cocosNode`Node - Cocos 节点引用
- `health`Number - 生命值
- `playerNode`Object - 玩家节点引用
- `detectionRange`Number - 检测范围
- `attackRange`Number - 攻击范围
- `currentWaypoint`Number - 当前路点索引
### 实现检测系统
创建 `assets/scripts/ai/PlayerDetector.ts`
```typescript
import { _decorator, Component, Node, Vec3 } from 'cc';
import { BehaviorTreeRuntimeComponent } from '@esengine/behavior-tree';
const { ccclass, property } = _decorator;
@ccclass('PlayerDetector')
export class PlayerDetector extends Component {
@property(Node)
player: Node = null;
@property
detectionRange: number = 10;
private runtime: BehaviorTreeRuntimeComponent | null = null;
start() {
// 假设AI组件在同一节点上
const aiComponent = this.node.getComponent('EnemyAIComponent') as any;
if (aiComponent && aiComponent.aiEntity) {
this.runtime = aiComponent.aiEntity.getComponent(BehaviorTreeRuntimeComponent);
}
}
update(deltaTime: number) {
if (!this.runtime || !this.player) {
return;
}
// 计算距离
const distance = Vec3.distance(this.node.position, this.player.position);
// 更新黑板
this.runtime.setBlackboardValue('playerNode', this.player);
this.runtime.setBlackboardValue('playerInRange', distance <= this.detectionRange);
this.runtime.setBlackboardValue('distanceToPlayer', distance);
}
}
```
## 资源管理
### 使用 BehaviorTreeAssetManager
框架提供了 `BehaviorTreeAssetManager` 来统一管理行为树资产,避免重复创建:
```typescript
import { Core } from '@esengine/ecs-framework';
import {
BehaviorTreeAssetManager,
BehaviorTreeBuilder,
BehaviorTreeStarter
} from '@esengine/behavior-tree';
// 获取资产管理器(插件已自动注册)
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
// 创建并注册行为树(只创建一次)
const enemyAI = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.selector('MainBehavior')
.log('攻击')
.end()
.build();
assetManager.loadAsset(enemyAI);
// 为多个敌人实体使用同一份资产
for (let i = 0; i < 10; i++) {
const enemy = scene.createEntity(`Enemy${i}`);
const tree = assetManager.getAsset('EnemyAI')!;
BehaviorTreeStarter.start(enemy, tree); // 10个敌人共享1份数据
}
```
### 从 Cocos Creator 资源加载
#### 1. 将行为树 JSON 放入 resources 目录
```
assets/
└── resources/
└── behaviors/
├── enemy-ai.btree.json
└── boss-ai.btree.json
```
#### 2. 创建资源加载器
创建 `assets/scripts/BehaviorTreeLoader.ts`
```typescript
import { resources, JsonAsset } from 'cc';
import { Core } from '@esengine/ecs-framework';
import {
BehaviorTreeAssetManager,
BehaviorTreeAssetSerializer,
BehaviorTreeData
} from '@esengine/behavior-tree';
export class BehaviorTreeLoader {
private assetManager: BehaviorTreeAssetManager;
constructor() {
this.assetManager = Core.services.resolve(BehaviorTreeAssetManager);
}
/**
* 从 resources 目录加载行为树
* @param path 相对于 resources 的路径,不带扩展名
* @example await loader.load('behaviors/enemy-ai')
*/
async load(path: string): Promise<BehaviorTreeData | null> {
return new Promise((resolve, reject) => {
resources.load(path, JsonAsset, (err, jsonAsset) => {
if (err) {
console.error(`加载行为树失败: ${path}`, err);
reject(err);
return;
}
try {
// 反序列化 JSON 为 BehaviorTreeData
const jsonStr = JSON.stringify(jsonAsset.json);
const treeData = BehaviorTreeAssetSerializer.deserialize(jsonStr);
// 加载到资产管理器
this.assetManager.loadAsset(treeData);
console.log(`行为树已加载: ${treeData.name}`);
resolve(treeData);
} catch (error) {
console.error(`解析行为树失败: ${path}`, error);
reject(error);
}
});
});
}
/**
* 预加载所有行为树
*/
async preloadAll(paths: string[]): Promise<void> {
const promises = paths.map(path => this.load(path));
await Promise.all(promises);
console.log(`已预加载 ${paths.length} 个行为树`);
}
}
```
#### 3. 在游戏启动时预加载
修改 `Main.ts`
```typescript
import { _decorator, Component } from 'cc';
import { Core, Scene } from '@esengine/ecs-framework';
import { BehaviorTreePlugin } from '@esengine/behavior-tree';
import { BehaviorTreeLoader } from './BehaviorTreeLoader';
const { ccclass } = _decorator;
@ccclass('Main')
export class Main extends Component {
private loader: BehaviorTreeLoader | null = null;
async onLoad() {
// 初始化 ECS Core
Core.create();
// 安装行为树插件
const behaviorTreePlugin = new BehaviorTreePlugin();
await Core.installPlugin(behaviorTreePlugin);
// 创建场景
const scene = new Scene();
behaviorTreePlugin.setupScene(scene);
Core.setScene(scene);
// 创建加载器并预加载所有行为树
this.loader = new BehaviorTreeLoader();
await this.loader.preloadAll([
'behaviors/enemy-ai',
'behaviors/boss-ai',
'behaviors/patrol', // 子树
'behaviors/chase' // 子树
]);
console.log('游戏初始化完成');
}
update(deltaTime: number) {
Core.update(deltaTime);
}
onDestroy() {
Core.destroy();
}
}
```
#### 4. 在敌人组件中使用
```typescript
import { _decorator, Component } from 'cc';
import { Core, Entity } from '@esengine/ecs-framework';
import {
BehaviorTreeAssetManager,
BehaviorTreeStarter
} from '@esengine/behavior-tree';
const { ccclass, property } = _decorator;
@ccclass('EnemyAIComponent')
export class EnemyAIComponent extends Component {
@property
aiType: string = 'enemy-ai'; // 在编辑器中配置使用哪个AI
private aiEntity: Entity | null = null;
start() {
const scene = Core.scene;
if (!scene) return;
// 从资产管理器获取已加载的行为树
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
const tree = assetManager.getAsset(this.aiType);
if (tree) {
this.aiEntity = scene.createEntity(`AI_${this.node.name}`);
BehaviorTreeStarter.start(this.aiEntity, tree);
// 设置黑板变量
const runtime = this.aiEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('cocosNode', this.node);
} else {
console.error(`找不到行为树资产: ${this.aiType}`);
}
}
onDestroy() {
if (this.aiEntity) {
BehaviorTreeStarter.stop(this.aiEntity);
}
}
}
```
## 调试
### 可视化调试信息
创建调试组件显示 AI 状态:
```typescript
import { _decorator, Component, Label } from 'cc';
import { BehaviorTreeRuntimeComponent } from '@esengine/behavior-tree';
const { ccclass, property } = _decorator;
@ccclass('AIDebugger')
export class AIDebugger extends Component {
@property(Label)
debugLabel: Label = null;
private runtime: BehaviorTreeRuntimeComponent | null = null;
start() {
const aiComponent = this.node.getComponent('EnemyAIComponent') as any;
if (aiComponent && aiComponent.aiEntity) {
this.runtime = aiComponent.aiEntity.getComponent(BehaviorTreeRuntimeComponent);
}
}
update() {
if (!this.runtime || !this.debugLabel) {
return;
}
const health = this.runtime.getBlackboardValue('health');
const playerNode = this.runtime.getBlackboardValue('playerNode');
this.debugLabel.string = `Health: ${health}\nHas Target: ${playerNode ? 'Yes' : 'No'}`;
}
}
```
## 性能优化
### 1. 限制行为树数量
合理控制同时运行的行为树数量:
```typescript
class AIManager {
private activeAIs: Entity[] = [];
private maxAIs: number = 20;
addAI(entity: Entity, tree: BehaviorTreeData) {
if (this.activeAIs.length >= this.maxAIs) {
// 移除最远的AI
const furthest = this.findFurthestAI();
if (furthest) {
BehaviorTreeStarter.stop(furthest);
this.activeAIs = this.activeAIs.filter(e => e !== furthest);
}
}
BehaviorTreeStarter.start(entity, tree);
this.activeAIs.push(entity);
}
removeAI(entity: Entity) {
BehaviorTreeStarter.stop(entity);
this.activeAIs = this.activeAIs.filter(e => e !== entity);
}
private findFurthestAI(): Entity | null {
// 根据距离找到最远的AI
// 实现细节略
return this.activeAIs[0];
}
}
```
### 2. 使用冷却装饰器
对于不需要每帧更新的AI使用冷却装饰器
```typescript
const tree = BehaviorTreeBuilder.create('ThrottledAI')
.cooldown(0.2, 'ThrottleRoot') // 每0.2秒执行一次
.selector('MainBehavior')
// AI逻辑...
.end()
.end()
.build();
```
### 3. 缓存计算结果
在自定义执行器中缓存昂贵的计算:
```typescript
export class CachedFindTarget implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { state, runtime, totalTime } = context;
const cacheTime = state.lastFindTime || 0;
if (totalTime - cacheTime < 1.0) {
const cached = runtime.getBlackboardValue('target');
return cached ? TaskStatus.Success : TaskStatus.Failure;
}
const target = findNearestTarget();
runtime.setBlackboardValue('target', target);
state.lastFindTime = totalTime;
return target ? TaskStatus.Success : TaskStatus.Failure;
}
}
```
## 多平台注意事项
### 性能考虑
不同平台的性能差异:
- **Web平台**: 受浏览器性能限制建议减少同时运行的AI数量
- **原生平台**: 性能较好可以运行更多AI
- **小游戏平台**: 内存受限,注意控制行为树数量和复杂度
### 平台适配
```typescript
import { sys } from 'cc';
// 根据平台调整AI数量
const maxAIs = sys.isNative ? 50 : (sys.isBrowser ? 20 : 30);
// 根据平台调整更新频率
const updateInterval = sys.isNative ? 0.016 : 0.05;
```
## 常见问题
### 行为树无法加载?
检查:
1. 资源路径是否正确(相对于 `resources` 目录)
2. 文件是否已添加到项目中
3. 检查控制台错误信息
### AI 不执行?
确保:
1. `Main` 组件的 `update` 方法被调用
2. `Scene.update()` 在每帧被调用
3. 行为树已通过 `BehaviorTreeStarter.start()` 启动
### 黑板变量不更新?
检查:
1. 变量名拼写是否正确
2. 是否在正确的时机更新变量
3. 使用 `BehaviorTreeRuntimeComponent.getBlackboardValue()``setBlackboardValue()` 方法
## 下一步
- 查看[资产管理](./asset-management.md)了解如何加载和管理行为树资产、使用子树
- 学习[高级用法](./advanced-usage.md)了解性能优化和调试技巧
- 阅读[最佳实践](./best-practices.md)优化你的 AI
- 学习[自定义节点执行器](./custom-actions.md)创建自定义行为

View File

@@ -0,0 +1,491 @@
# 核心概念
本文介绍行为树系统的核心概念和工作原理。
## 什么是行为树?
行为树(Behavior Tree)是一种用于控制AI和自动化系统的决策结构。它通过树状层次结构组织任务,从根节点开始逐层执行,直到找到合适的行为。
### 与状态机的对比
传统状态机:
- 基于状态和转换
- 状态之间的转换复杂
- 难以扩展和维护
- 不便于复用
行为树:
- 基于任务和层次结构
- 模块化、易于复用
- 可视化编辑
- 灵活的决策逻辑
## 树结构
行为树由节点组成,形成树状结构:
```
Root (根节点)
├── Selector (选择器)
│ ├── Sequence (序列)
│ │ ├── Condition (条件)
│ │ └── Action (动作)
│ └── Action (动作)
└── Sequence (序列)
├── Action (动作)
└── Wait (等待)
```
每个节点都有:
- 父节点(除了根节点)
- 零个或多个子节点
- 执行状态
- 返回结果
## 节点类型
### 复合节点(Composite)
复合节点有多个子节点,按特定规则执行它们。
#### Selector(选择器)
按顺序尝试执行子节点,直到某个子节点成功。
```typescript
const tree = BehaviorTreeBuilder.create('FindFood')
.selector('FindFoodSelector')
.log('尝试吃附近的食物', 'EatNearby')
.log('搜索食物', 'SearchFood')
.log('放弃', 'GiveUp')
.end()
.build();
```
执行逻辑:
1. 尝试第一个子节点
2. 如果返回Success,选择器成功
3. 如果返回Failure,尝试下一个子节点
4. 如果返回Running,选择器返回Running
5. 所有子节点都失败时,选择器失败
#### Sequence(序列)
按顺序执行所有子节点,直到某个子节点失败。
```typescript
const tree = BehaviorTreeBuilder.create('Attack')
.sequence('AttackSequence')
.blackboardExists('target') // 检查是否有目标
.log('瞄准', 'Aim')
.log('开火', 'Fire')
.end()
.build();
```
执行逻辑:
1. 依次执行子节点
2. 如果子节点返回Failure,序列失败
3. 如果子节点返回Running,序列返回Running
4. 如果子节点返回Success,继续下一个子节点
5. 所有子节点都成功时,序列成功
#### Parallel(并行)
同时执行多个子节点。
```typescript
const tree = BehaviorTreeBuilder.create('PlayEffects')
.parallel('Effects', {
successPolicy: 'all', // 所有任务都要成功
failurePolicy: 'one' // 任一失败则失败
})
.log('播放动画', 'PlayAnimation')
.log('播放音效', 'PlaySound')
.log('生成粒子', 'SpawnEffect')
.end()
.build();
```
策略类型:
- `successPolicy: 'all'`: 所有子节点都成功才成功
- `successPolicy: 'one'`: 任意一个子节点成功就成功
- `failurePolicy: 'all'`: 所有子节点都失败才失败
- `failurePolicy: 'one'`: 任意一个子节点失败就失败
### 装饰器节点(Decorator)
装饰器节点只有一个子节点,用于修改子节点的行为或结果。
#### Inverter(反转)
反转子节点的结果:
```typescript
const tree = BehaviorTreeBuilder.create('CheckSafe')
.inverter('NotHasEnemy')
.blackboardExists('enemy')
.end()
.build();
```
#### Repeater(重复)
重复执行子节点:
```typescript
const tree = BehaviorTreeBuilder.create('Jump3Times')
.repeater(3, 'RepeatJump')
.log('跳跃', 'Jump')
.end()
.build();
```
#### Cooldown(冷却)
限制子节点的执行频率:
```typescript
const tree = BehaviorTreeBuilder.create('UseSkill')
.cooldown(5.0, 'SkillCooldown')
.log('使用特殊技能', 'UseSpecialAbility')
.end()
.build();
```
#### Timeout(超时)
限制子节点的执行时间:
```typescript
const tree = BehaviorTreeBuilder.create('TimedTask')
.timeout(10.0, 'TaskTimeout')
.log('长时间运行的任务', 'ComplexTask')
.end()
.build();
```
### 叶节点(Leaf)
叶节点没有子节点,执行具体的任务。
#### Action(动作)
执行具体操作。内置动作节点包括:
```typescript
const tree = BehaviorTreeBuilder.create('Actions')
.sequence()
.wait(2.0) // 等待2秒
.log('Hello', 'LogAction') // 输出日志
.setBlackboardValue('score', 100) // 设置黑板值
.modifyBlackboardValue('score', 'add', 10) // 修改黑板值
.end()
.build();
```
要实现自定义动作,需要创建自定义执行器,参见[自定义节点执行器](./custom-actions.md)。
#### Condition(条件)
检查条件。内置条件节点包括:
```typescript
const tree = BehaviorTreeBuilder.create('Conditions')
.selector()
.blackboardExists('player') // 检查变量是否存在
.blackboardCompare('health', 50, 'greater') // 比较变量值
.randomProbability(0.5) // 50%概率
.end()
.build();
```
#### Wait(等待)
等待指定时间:
```typescript
const tree = BehaviorTreeBuilder.create('WaitExample')
.wait(2.0, 'Wait2Seconds')
.build();
```
## 任务状态
每个节点执行后返回以下状态之一:
### Success(成功)
任务成功完成。
```typescript
// 内置节点会根据逻辑自动返回Success
.log('任务完成') // 总是返回Success
.blackboardCompare('score', 100, 'greater') // 条件满足时返回Success
```
### Failure(失败)
任务执行失败。
```typescript
.blackboardCompare('score', 100, 'greater') // 条件不满足返回Failure
.blackboardExists('nonExistent') // 变量不存在返回Failure
```
### Running(运行中)
任务需要多帧完成,仍在执行中。
```typescript
.wait(3.0) // 等待过程中返回Running,3秒后返回Success
```
### Invalid(无效)
节点未初始化或已重置。通常不需要手动处理此状态。
## 黑板系统
黑板(Blackboard)是行为树的数据存储系统,用于在节点之间共享数据。
### 本地黑板
每个行为树实例都有自己的本地黑板:
```typescript
const tree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.defineBlackboardVariable('state', 'idle')
// ...
.build();
```
### 支持的数据类型
黑板支持以下数据类型:
- String字符串
- Number数字
- Boolean布尔值
- Vector2二维向量
- Vector3三维向量
- Object对象引用
- Array数组
示例:
```typescript
const tree = BehaviorTreeBuilder.create('Variables')
.defineBlackboardVariable('name', 'Enemy') // 字符串
.defineBlackboardVariable('count', 0) // 数字
.defineBlackboardVariable('isActive', true) // 布尔值
.defineBlackboardVariable('position', { x: 0, y: 0 }) // 对象(也可用于Vector2)
.defineBlackboardVariable('velocity', { x: 0, y: 0, z: 0 }) // 对象(也可用于Vector3)
.defineBlackboardVariable('items', []) // 数组
.build();
```
### 读写变量
通过`BehaviorTreeRuntimeComponent`访问黑板:
```typescript
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
// 读取变量
const health = runtime?.getBlackboardValue('health');
const target = runtime?.getBlackboardValue('target');
// 写入变量
runtime?.setBlackboardValue('health', 50);
runtime?.setBlackboardValue('lastAttackTime', Date.now());
// 获取所有变量
const allVars = runtime?.getAllBlackboardVariables();
```
也可以使用内置节点操作黑板:
```typescript
const tree = BehaviorTreeBuilder.create('BlackboardOps')
.sequence()
.setBlackboardValue('score', 100) // 设置值
.modifyBlackboardValue('score', 'add', 10) // 增加10
.blackboardCompare('score', 110, 'equals') // 检查是否等于110
.end()
.build();
```
### 全局黑板
所有行为树实例共享的黑板,通过`GlobalBlackboardService`访问:
```typescript
import { GlobalBlackboardService } from '@esengine/behavior-tree';
import { Core } from '@esengine/ecs-framework';
const globalBlackboard = Core.services.resolve(GlobalBlackboardService);
// 设置全局变量
globalBlackboard.setValue('gameState', 'playing');
globalBlackboard.setValue('difficulty', 5);
// 读取全局变量
const gameState = globalBlackboard.getValue('gameState');
```
在自定义执行器中访问全局黑板:
```typescript
import { GlobalBlackboardService } from '@esengine/behavior-tree';
import { Core } from '@esengine/ecs-framework';
export class CheckGameState implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const globalBlackboard = Core.services.resolve(GlobalBlackboardService);
const gameState = globalBlackboard.getValue('gameState');
if (gameState === 'paused') {
return TaskStatus.Failure;
}
return TaskStatus.Success;
}
}
```
## 执行流程
### 初始化
```typescript
// 1. 初始化Core和插件
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
// 2. 创建场景
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 3. 构建行为树
const tree = BehaviorTreeBuilder.create('AI')
// ... 定义节点
.build();
// 4. 创建实体并启动
const entity = scene.createEntity('AIEntity');
BehaviorTreeStarter.start(entity, tree);
```
### 更新循环
```typescript
// 每帧更新
gameLoop(() => {
const deltaTime = getDeltaTime();
Core.update(deltaTime); // Core会自动更新场景和所有行为树
});
```
### 执行顺序
```
1. 从根节点开始
2. 根节点执行其逻辑(通常是Selector或Sequence)
3. 根节点的子节点按顺序执行
4. 每个子节点可能有自己的子节点
5. 叶节点执行具体操作并返回状态
6. 状态向上传播到父节点
7. 父节点根据策略决定如何处理子节点的状态
8. 最终根节点返回整体状态
```
### 执行示例
```typescript
const tree = BehaviorTreeBuilder.create('Example')
.selector('Root') // 1. 执行选择器
.sequence('Branch1') // 2. 尝试第一个分支
.blackboardCompare('ready', true, 'equals', 'CheckReady') // 3. 条件失败
.end() // 4. 序列失败,选择器继续下一个分支
.sequence('Branch2') // 5. 尝试第二个分支
.blackboardCompare('active', true, 'equals', 'CheckActive') // 6. 条件成功
.log('执行动作', 'DoAction') // 7. 动作成功
.end() // 8. 序列成功,选择器成功
.end() // 9. 整个树成功
.build();
```
执行流程图:
```
Root(Selector)
→ Branch1(Sequence)
→ CheckReady: Failure
→ Branch1 fails
→ Branch2(Sequence)
→ CheckActive: Success
→ DoAction: Success
→ Branch2 succeeds
→ Root succeeds
```
## Runtime架构
本框架的行为树采用Runtime执行器架构:
### 核心组件
- **BehaviorTreeData**: 纯数据结构,描述行为树的结构和配置
- **BehaviorTreeRuntimeComponent**: 运行时组件,管理执行状态和黑板
- **BehaviorTreeExecutionSystem**: 执行系统,驱动行为树运行
- **INodeExecutor**: 节点执行器接口,定义节点的执行逻辑
- **NodeExecutionContext**: 执行上下文,包含执行所需的所有信息
### 架构特点
1. **数据与逻辑分离**: BehaviorTreeData是纯数据,执行逻辑在执行器中
2. **无状态执行器**: 执行器实例可以在多个节点间共享,状态存储在Runtime中
3. **类型安全**: 通过TypeScript类型系统保证类型安全
4. **高性能**: 避免不必要的对象创建,优化内存使用
### 数据流
```
BehaviorTreeBuilder
↓ (构建)
BehaviorTreeData
↓ (加载到)
BehaviorTreeAssetManager
↓ (读取)
BehaviorTreeExecutionSystem
↓ (执行)
INodeExecutor.execute(context)
↓ (返回)
TaskStatus
↓ (更新)
NodeRuntimeState
```
## 下一步
现在你已经理解了行为树的核心概念,接下来可以:
- 查看[快速开始](./getting-started.md)创建第一个行为树
- 学习[自定义节点执行器](./custom-actions.md)创建自定义节点
- 探索[高级用法](./advanced-usage.md)了解更多功能
- 阅读[最佳实践](./best-practices.md)学习设计模式

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
# 行为树编辑器使用指南
行为树编辑器提供了可视化的方式来创建和编辑行为树。
## 启动编辑器
```bash
cd packages/editor-app
npm run tauri:dev
```
## 基本操作
### 打开行为树编辑器
通过以下方式打开行为树编辑器窗口:
1. 在资产浏览器中双击 `.btree` 文件
2. 菜单栏:`窗口` → 选择行为树编辑器相关插件
### 创建新行为树
在行为树编辑器窗口的工具栏中点击"新建"按钮(加号图标)
### 保存行为树
在行为树编辑器窗口的工具栏中点击"保存"按钮(磁盘图标)
### 添加节点
从左侧节点面板拖拽节点到画布:
- 复合节点Selector、Sequence、Parallel
- 装饰器Inverter、Repeater、UntilFail等
- 动作节点ExecuteAction、Wait等
- 条件节点Condition
### 连接节点
拖拽父节点底部的连接点到子节点顶部建立连接
### 删除节点
选中节点后按 `Delete``Backspace`
### 编辑属性
点击节点后在右侧属性面板中编辑节点参数
## 黑板变量
在黑板面板中管理共享数据:
1. 点击"添加变量"按钮
2. 输入变量名、选择类型并设置默认值
3. 在节点中通过变量名引用黑板变量
支持的变量类型:
- String字符串
- Number数字
- Boolean布尔值
- Vector2二维向量
- Vector3三维向量
- Object对象引用
- Array数组
## 导出运行时资产
### 导出步骤
1. 点击工具栏的"导出"按钮
2. 选择导出模式:
- 当前文件:仅导出当前打开的行为树
- 工作区导出:导出项目中所有行为树
3. 选择资产输出路径
4. 选择TypeScript类型定义输出路径
5. 为每个文件选择导出格式:
- 二进制:.btree.bin默认文件更小加载更快
- JSON.btree.json可读性好便于调试
6. 点击"导出"按钮
### 加载运行时资产
编辑器导出的文件是编辑器格式包含UI布局信息。当前版本中从编辑器导出的资产可以使用Builder API在代码中重新构建或者等待资产加载系统的完善。
推荐使用Builder API创建行为树
```typescript
import { BehaviorTreeBuilder, BehaviorTreeStarter } from '@esengine/behavior-tree';
import { Core, Scene } from '@esengine/ecs-framework';
// 使用Builder创建行为树
const tree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.selector('MainBehavior')
.sequence('AttackBranch')
.blackboardCompare('health', 50, 'greater')
.log('攻击玩家', 'Attack')
.end()
.log('逃离战斗', 'Flee')
.end()
.build();
// 启动行为树
const entity = scene.createEntity('Enemy');
BehaviorTreeStarter.start(entity, tree);
```
## 支持的操作
- `Delete` / `Backspace`:删除选中的节点或连线
- `Ctrl` + 点击:多选节点
- 框选:拖拽空白区域进行框选
- 拖拽画布:按住鼠标中键或空格键拖拽
## 下一步
- 查看[编辑器工作流](./editor-workflow.md)了解完整的开发流程
- 查看[自定义节点执行器](./custom-actions.md)学习如何扩展节点

View File

@@ -0,0 +1,253 @@
# 编辑器工作流
本教程介绍如何使用行为树编辑器创建AI并在游戏中加载使用。
## 完整流程
```
1. 启动编辑器
2. 创建行为树并定义黑板变量
3. 添加和配置节点
4. 导出JSON文件
5. 在游戏中加载并使用
```
## 使用编辑器创建
### 启动编辑器
```bash
cd packages/editor-app
npm run tauri:dev
```
### 基本操作
1. **创建行为树**`文件``新建项目` → 创建行为树文件
2. **定义黑板变量**:在黑板面板中添加共享变量
3. **添加节点**:从节点面板拖拽到画布
4. **连接节点**:拖拽连接点建立父子关系
5. **配置属性**:选中节点后在属性面板编辑
6. **导出**`文件``导出``JSON格式`
### 示例敌人AI的黑板变量
在编辑器黑板面板中定义:
```
health: Number = 100
target: Object = null
moveSpeed: Number = 5.0
attackRange: Number = 2.0
```
### 示例:行为树结构
```
Root: Selector
├── Combat Sequence
│ ├── CheckHasTarget (Condition)
│ ├── CheckInAttackRange (Condition)
│ └── ExecuteAttack (Action)
├── Patrol Sequence
│ ├── MoveToNextPatrolPoint (Action)
│ └── Wait 2s
└── Idle (Action)
```
## 在游戏中使用
### 使用Builder API创建
推荐使用Builder API在代码中创建行为树
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreePlugin,
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
// 初始化
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 使用Builder创建行为树
const tree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.defineBlackboardVariable('moveSpeed', 5.0)
.selector('MainBehavior')
.sequence('AttackBranch')
.blackboardExists('target')
.blackboardCompare('health', 30, 'greater')
.log('攻击目标', 'Attack')
.end()
.log('巡逻', 'Patrol')
.end()
.build();
// 创建实体并启动行为树
const entity = scene.createEntity('Enemy');
BehaviorTreeStarter.start(entity, tree);
// 访问和修改黑板
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('target', someTarget);
// 游戏循环
setInterval(() => {
Core.update(0.016); // 60 FPS
}, 16);
```
## 实现自定义执行器
要扩展行为树的功能,需要创建自定义执行器(详见[自定义节点执行器](./custom-actions.md)
```typescript
import {
INodeExecutor,
NodeExecutionContext,
BindingHelper,
NodeExecutorMetadata
} from '@esengine/behavior-tree';
import { TaskStatus, NodeType } from '@esengine/behavior-tree';
@NodeExecutorMetadata({
implementationType: 'AttackAction',
nodeType: NodeType.Action,
displayName: '攻击目标',
description: '对目标造成伤害',
category: '战斗',
configSchema: {
damage: {
type: 'number',
default: 10,
supportBinding: true
}
}
})
export class AttackAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const damage = BindingHelper.getValue<number>(context, 'damage', 10);
const target = context.runtime.getBlackboardValue('target');
if (!target) {
return TaskStatus.Failure;
}
// 执行攻击逻辑
performAttack(context.entity, target, damage);
return TaskStatus.Success;
}
reset(context: NodeExecutionContext): void {
// 清理状态
}
}
```
## 调试技巧
### 1. 使用日志节点
在行为树中添加Log节点输出调试信息
```typescript
const tree = BehaviorTreeBuilder.create('DebugAI')
.log('开始战斗序列', 'StartCombat')
.sequence('Combat')
.blackboardCompare('health', 0, 'greater')
.log('执行攻击', 'Attack')
.end()
.build();
```
### 2. 监控黑板状态
```typescript
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
console.log('黑板变量:', runtime?.getAllBlackboardVariables());
console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));
```
### 3. 在自定义执行器中调试
```typescript
export class DebugAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { nodeData, runtime, state } = context;
console.group(`[${nodeData.name}]`);
console.log('配置:', nodeData.config);
console.log('状态:', state);
console.log('黑板:', runtime.getAllBlackboardVariables());
console.groupEnd();
return TaskStatus.Success;
}
}
```
## 完整示例
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreePlugin,
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
// 初始化
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 使用Builder API构建行为树
const tree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('hasTarget', false)
.selector('Root')
.sequence('Combat')
.blackboardCompare('hasTarget', true, 'equals')
.log('攻击玩家', 'Attack')
.end()
.log('空闲', 'Idle')
.end()
.build();
// 创建实体并启动
const entity = scene.createEntity('Enemy');
BehaviorTreeStarter.start(entity, tree);
// 模拟发现目标
setTimeout(() => {
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('hasTarget', true);
}, 2000);
// 游戏循环
setInterval(() => {
Core.update(0.016);
}, 16);
```
## 下一步
- 查看[自定义节点执行器](./custom-actions.md)学习如何创建自定义节点
- 查看[高级用法](./advanced-usage.md)了解性能优化等高级特性
- 查看[最佳实践](./best-practices.md)优化你的AI设计

View File

@@ -0,0 +1,385 @@
# 快速开始
本教程将引导你在5分钟内创建第一个行为树。
## 安装
```bash
npm install @esengine/behavior-tree
```
## 第一个行为树
让我们创建一个简单的AI行为树,实现"巡逻-发现敌人-攻击"的逻辑。
### 步骤1: 导入依赖
```typescript
import { Core, Scene, Entity } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin
} from '@esengine/behavior-tree';
```
### 步骤2: 初始化Core并安装插件
```typescript
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
```
### 步骤3: 创建场景并设置行为树系统
```typescript
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
```
### 步骤4: 构建行为树数据
```typescript
const guardAITree = BehaviorTreeBuilder.create('GuardAI')
// 定义黑板变量
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('hasEnemy', false)
.defineBlackboardVariable('patrolPoint', 0)
// 根选择器
.selector('RootSelector')
// 分支1: 如果发现敌人且生命值高,则攻击
.selector('CombatBranch')
.blackboardExists('hasEnemy', 'CheckEnemy')
.blackboardCompare('health', 30, 'greater', 'CheckHealth')
.log('守卫正在攻击敌人', 'Attack')
.end()
// 分支2: 如果生命值低,则逃跑
.selector('FleeBranch')
.blackboardCompare('health', 30, 'lessOrEqual', 'CheckLowHealth')
.log('守卫生命值过低,正在逃跑', 'Flee')
.end()
// 分支3: 默认巡逻
.selector('PatrolBranch')
.modifyBlackboardValue('patrolPoint', 'add', 1, 'IncrementPatrol')
.log('守卫正在巡逻', 'Patrol')
.wait(2.0, 'WaitAtPoint')
.end()
.end()
.build();
```
### 步骤5: 创建实体并启动行为树
```typescript
// 创建守卫实体
const guardEntity = scene.createEntity('Guard');
// 启动行为树
BehaviorTreeStarter.start(guardEntity, guardAITree);
```
### 步骤6: 运行游戏循环
```typescript
// 模拟游戏循环
setInterval(() => {
Core.update(0.1); // 传入deltaTime(秒)
}, 100); // 每100ms更新一次
```
### 步骤7: 模拟游戏事件
```typescript
// 5秒后模拟发现敌人
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('hasEnemy', true);
console.log('发现敌人!');
}, 5000);
// 10秒后模拟受伤
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('health', 20);
console.log('守卫受伤!');
}, 10000);
```
## 完整代码
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
async function main() {
// 1. 创建核心并安装插件
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
// 2. 创建场景
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 3. 构建行为树数据
const guardAITree = BehaviorTreeBuilder.create('GuardAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('hasEnemy', false)
.defineBlackboardVariable('patrolPoint', 0)
.selector('RootSelector')
.selector('CombatBranch')
.blackboardExists('hasEnemy')
.blackboardCompare('health', 30, 'greater')
.log('守卫正在攻击敌人')
.end()
.selector('FleeBranch')
.blackboardCompare('health', 30, 'lessOrEqual')
.log('守卫生命值过低,正在逃跑')
.end()
.selector('PatrolBranch')
.modifyBlackboardValue('patrolPoint', 'add', 1)
.log('守卫正在巡逻')
.wait(2.0)
.end()
.end()
.build();
// 4. 创建守卫实体并启动行为树
const guardEntity = scene.createEntity('Guard');
BehaviorTreeStarter.start(guardEntity, guardAITree);
// 5. 运行游戏循环
setInterval(() => {
Core.update(0.1);
}, 100);
// 6. 模拟游戏事件
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('hasEnemy', true);
console.log('发现敌人!');
}, 5000);
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('health', 20);
console.log('守卫受伤!');
}, 10000);
}
main();
```
## 运行结果
运行程序后,你会看到类似的输出:
```
守卫正在巡逻
守卫正在巡逻
守卫正在巡逻
发现敌人!
守卫正在攻击敌人
守卫正在攻击敌人
守卫受伤!
守卫生命值过低,正在逃跑
```
## 理解代码
### 黑板变量
```typescript
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('hasEnemy', false)
.defineBlackboardVariable('patrolPoint', 0)
```
黑板用于在节点之间共享数据。这里定义了三个变量:
- `health`: 守卫的生命值
- `hasEnemy`: 是否发现敌人
- `patrolPoint`: 当前巡逻点编号
### 选择器节点
```typescript
.selector('RootSelector')
// 分支1
// 分支2
// 分支3
.end()
```
选择器按顺序尝试执行子节点,直到某个子节点返回成功。类似于编程中的 `if-else if-else`
### 条件节点
```typescript
.blackboardExists('hasEnemy') // 检查变量是否存在
.blackboardCompare('health', 30, 'greater') // 比较变量值
```
条件节点用于检查黑板变量的值。
### 动作节点
```typescript
.log('守卫正在攻击敌人') // 输出日志
.wait(2.0) // 等待2秒
.modifyBlackboardValue('patrolPoint', 'add', 1) // 修改黑板值
```
动作节点执行具体的操作。
### Runtime组件
```typescript
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('hasEnemy', true);
runtime?.getBlackboardValue('health');
```
通过`BehaviorTreeRuntimeComponent`访问和修改黑板变量。
## 常见任务状态
行为树的每个节点返回以下状态之一:
- **Success**: 任务成功完成
- **Failure**: 任务执行失败
- **Running**: 任务正在执行,需要在后续帧继续
- **Invalid**: 无效状态(未初始化或已重置)
## 内置节点
### 复合节点
- `sequence()` - 序列节点,按顺序执行所有子节点
- `selector()` - 选择器节点,按顺序尝试子节点直到成功
- `parallel()` - 并行节点,同时执行多个子节点
- `parallelSelector()` - 并行选择器
- `randomSequence()` - 随机序列
- `randomSelector()` - 随机选择器
### 装饰器节点
- `inverter()` - 反转子节点结果
- `repeater(count)` - 重复执行子节点
- `alwaysSucceed()` - 总是返回成功
- `alwaysFail()` - 总是返回失败
- `untilSuccess()` - 重复直到成功
- `untilFail()` - 重复直到失败
- `conditional(key, value, operator)` - 条件装饰器
- `cooldown(time)` - 冷却装饰器
- `timeout(time)` - 超时装饰器
### 动作节点
- `wait(duration)` - 等待指定时间
- `log(message)` - 输出日志
- `setBlackboardValue(key, value)` - 设置黑板值
- `modifyBlackboardValue(key, operation, value)` - 修改黑板值
- `executeAction(actionName)` - 执行自定义动作
### 条件节点
- `blackboardExists(key)` - 检查变量是否存在
- `blackboardCompare(key, value, operator)` - 比较黑板值
- `randomProbability(probability)` - 随机概率
- `executeCondition(conditionName)` - 执行自定义条件
## 控制行为树
### 启动
```typescript
BehaviorTreeStarter.start(entity, treeData);
```
### 停止
```typescript
BehaviorTreeStarter.stop(entity);
```
### 暂停和恢复
```typescript
BehaviorTreeStarter.pause(entity);
// ... 一段时间后
BehaviorTreeStarter.resume(entity);
```
### 重启
```typescript
BehaviorTreeStarter.restart(entity);
```
## 下一步
现在你已经创建了第一个行为树,接下来可以:
1. 学习[核心概念](./core-concepts.md)深入理解行为树原理
2. 学习[资产管理](./asset-management.md)了解如何加载和复用行为树、使用子树
3. 查看[自定义节点执行器](./custom-actions.md)学习如何创建自定义节点
4. 根据你的场景查看集成教程:[Cocos Creator](./cocos-integration.md) 或 [Node.js](./nodejs-usage.md)
5. 查看[高级用法](./advanced-usage.md)了解更多功能
## 常见问题
### 为什么行为树不执行?
确保:
1. 已经安装了 `BehaviorTreePlugin`
2. 调用了 `plugin.setupScene(scene)`
3. 调用了 `BehaviorTreeStarter.start(entity, treeData)`
4. 在游戏循环中调用了 `Core.update(deltaTime)`
### 如何访问黑板变量?
```typescript
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
// 读取
const health = runtime?.getBlackboardValue('health');
// 写入
runtime?.setBlackboardValue('health', 50);
// 获取所有变量
const allVars = runtime?.getAllBlackboardVariables();
```
### 如何调试行为树?
使用日志节点:
```typescript
.log('到达这个节点', 'DebugLog')
```
或者在代码中监控黑板:
```typescript
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
console.log('黑板变量:', runtime?.getAllBlackboardVariables());
console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));
```
### 如何使用自定义逻辑?
内置的`executeAction``executeCondition`节点只是占位符。要实现真正的自定义逻辑,你需要创建自定义执行器:
参见[自定义节点执行器](./custom-actions.md)学习如何创建。

View File

@@ -0,0 +1,197 @@
# 行为树系统
行为树(Behavior Tree)是一种用于游戏AI和自动化控制的强大工具。本框架提供了基于Runtime执行器架构的行为树系统,具有高性能、类型安全、易于扩展的特点。
## 什么是行为树?
行为树是一种层次化的任务执行结构,由多个节点组成,每个节点负责特定的任务。行为树特别适合于:
- 游戏AI(敌人、NPC行为)
- 状态机的替代方案
- 复杂的决策逻辑
- 可视化的行为设计
## 核心特性
### Runtime执行器架构
- 数据与逻辑分离
- 无状态执行器设计
- 高性能执行
- 类型安全
### 可视化编辑器
- 图形化节点编辑
- 实时预览和调试
- 拖拽式节点创建
- 属性连接和绑定
### 灵活的黑板系统
- 本地黑板(单个行为树)
- 全局黑板(所有行为树共享)
- 类型安全的变量访问
- 支持属性绑定
### 插件系统
- 自动注册机制
- 装饰器声明元数据
- 支持多语言
- 易于扩展
## 文档导航
### 入门教程
- **[快速开始](./getting-started.md)** - 5分钟上手行为树
- **[核心概念](./core-concepts.md)** - 理解行为树的基本原理
### 编辑器使用
- **[编辑器使用指南](./editor-guide.md)** - 可视化创建行为树
- **[编辑器工作流](./editor-workflow.md)** - 完整的开发流程
### 资源管理
- **[资产管理](./asset-management.md)** - 加载、管理和复用行为树资产、使用子树
### 引擎集成
- **[Cocos Creator 集成](./cocos-integration.md)** - 在 Cocos Creator 中使用行为树
- **[Laya 引擎集成](./laya-integration.md)** - 在 Laya 中使用行为树
- **[Node.js 服务端使用](./nodejs-usage.md)** - 在服务器、聊天机器人等场景中使用行为树
### 高级主题
- **[高级用法](./advanced-usage.md)** - 性能优化、调试技巧
- **[自定义节点执行器](./custom-actions.md)** - 创建自定义行为节点
- **[最佳实践](./best-practices.md)** - 行为树设计模式和技巧
## 快速示例
### 使用Builder创建
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin
} from '@esengine/behavior-tree';
// 初始化
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 创建行为树
const enemyAI = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.selector('MainBehavior')
// 如果生命值高,则攻击
.sequence('AttackBranch')
.blackboardCompare('health', 50, 'greater')
.log('攻击玩家', 'Attack')
.end()
// 否则逃跑
.log('逃离战斗', 'Flee')
.end()
.build();
// 启动AI
const entity = scene.createEntity('Enemy');
BehaviorTreeStarter.start(entity, enemyAI);
```
### 使用编辑器创建
1. 打开行为树编辑器
2. 创建新的行为树资产
3. 拖拽节点到画布
4. 配置节点属性和连接
5. 保存并在代码中使用
## 架构说明
### Runtime执行器架构
本框架采用Runtime执行器架构,将节点定义和执行逻辑分离:
**核心组件:**
- `BehaviorTreeData`: 纯数据结构,描述行为树
- `BehaviorTreeRuntimeComponent`: 运行时组件,管理状态和黑板
- `BehaviorTreeExecutionSystem`: 执行系统,驱动行为树运行
- `INodeExecutor`: 节点执行器接口
- `NodeExecutionContext`: 执行上下文
**优势:**
- 数据与逻辑分离,易于序列化
- 执行器无状态,可复用
- 类型安全,编译时检查
- 高性能执行
### 自定义执行器
创建自定义节点非常简单:
```typescript
import {
INodeExecutor,
NodeExecutionContext,
BindingHelper,
NodeExecutorMetadata
} from '@esengine/behavior-tree';
import { TaskStatus, NodeType } from '@esengine/behavior-tree';
@NodeExecutorMetadata({
implementationType: 'AttackAction',
nodeType: NodeType.Action,
displayName: '攻击',
description: '攻击目标',
category: '战斗',
configSchema: {
damage: {
type: 'number',
default: 10,
supportBinding: true
}
}
})
export class AttackAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const damage = BindingHelper.getValue<number>(context, 'damage', 10);
const target = context.runtime.getBlackboardValue('target');
if (!target) {
return TaskStatus.Failure;
}
console.log(`造成 ${damage} 点伤害`);
return TaskStatus.Success;
}
}
```
详细说明请参见[自定义节点执行器](./custom-actions.md)。
## 下一步
建议按照以下顺序学习:
1. 阅读[快速开始](./getting-started.md)了解基础用法
2. 学习[核心概念](./core-concepts.md)理解行为树原理
3. 学习[资产管理](./asset-management.md)了解如何加载和复用行为树、使用子树
4. 根据你的场景查看集成教程:
- 客户端游戏:[Cocos Creator](./cocos-integration.md) 或 [Laya](./laya-integration.md)
- 服务端应用:[Node.js 服务端使用](./nodejs-usage.md)
5. 尝试[编辑器使用指南](./editor-guide.md)可视化创建行为树
6. 探索[高级用法](./advanced-usage.md)和[自定义节点执行器](./custom-actions.md)提升技能
## 获取帮助
- 提交 [Issue](https://github.com/esengine/ecs-framework/issues)
- 加入社区讨论
- 参考文档中的完整代码示例

View File

@@ -0,0 +1,313 @@
# Laya 引擎集成
本教程将引导你在 Laya 引擎项目中集成和使用行为树系统。
## 前置要求
- LayaAir 3.x 或更高版本
- 基本的 TypeScript 知识
- 已完成[快速开始](./getting-started.md)教程
## 安装
在你的 Laya 项目根目录下:
```bash
npm install @esengine/ecs-framework @esengine/behavior-tree
```
## 项目结构
建议的项目结构:
```
src/
├── ai/
│ ├── EnemyAI.ts
│ └── BossAI.ts
├── systems/
│ └── AISystem.ts
└── Main.ts
resources/
└── behaviors/
├── enemy.btree.json
└── boss.btree.json
```
## 初始化
### 在Main.ts中初始化
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { BehaviorTreePlugin } from '@esengine/behavior-tree';
export class Main {
constructor() {
Laya.init(1280, 720).then(() => {
this.initECS();
this.startGame();
});
}
private async initECS() {
// 初始化 ECS
Core.create();
// 安装行为树插件
const btPlugin = new BehaviorTreePlugin();
await Core.installPlugin(btPlugin);
// 创建并设置场景
const scene = new Scene();
btPlugin.setupScene(scene);
Core.setScene(scene);
// 启动更新循环
Laya.timer.frameLoop(1, this, this.update);
}
private update() {
// Core.update会自动更新场景
Core.update(Laya.timer.delta / 1000);
}
private startGame() {
// 加载场景
}
}
new Main();
```
## 创建AI组件
```typescript
import { Core, Entity } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
export class EnemyAI extends Laya.Script {
private aiEntity: Entity;
onEnable() {
this.createBehaviorTree();
}
private createBehaviorTree() {
// 获取Core管理的场景
const scene = Core.scene;
if (!scene) {
console.error('场景未初始化');
return;
}
const sprite = this.owner as Laya.Sprite;
// 使用Builder API创建行为树
const tree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('layaSprite', sprite)
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('position', { x: sprite.x, y: sprite.y })
.selector('MainBehavior')
.sequence('Combat')
.blackboardCompare('health', 30, 'greater')
.log('攻击', 'Attack')
.end()
.log('巡逻', 'Patrol')
.end()
.build();
// 创建AI实体并启动
this.aiEntity = scene.createEntity(`AI_${sprite.name}`);
BehaviorTreeStarter.start(this.aiEntity, tree);
}
onDisable() {
// 停止AI
if (this.aiEntity) {
BehaviorTreeStarter.stop(this.aiEntity);
}
}
}
```
## 与Laya节点交互
要实现与Laya节点的交互需要创建自定义执行器。下面展示一个完整示例。
## 完整示例
创建一个使用自定义执行器的敌人AI系统
```typescript
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
INodeExecutor,
NodeExecutionContext,
NodeExecutorMetadata,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
import { TaskStatus, NodeType } from '@esengine/behavior-tree';
import { Core, Entity } from '@esengine/ecs-framework';
// 自定义移动执行器
@NodeExecutorMetadata({
implementationType: 'MoveToTarget',
nodeType: NodeType.Action,
displayName: '移动到目标',
category: 'Laya',
configSchema: {
speed: {
type: 'number',
default: 50,
supportBinding: true
}
}
})
export class MoveToTargetAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const sprite = context.runtime.getBlackboardValue('layaSprite');
const targetPos = context.runtime.getBlackboardValue('targetPosition');
const speed = context.nodeData.config.speed;
if (!sprite || !targetPos) {
return TaskStatus.Failure;
}
const dx = targetPos.x - sprite.x;
const dy = targetPos.y - sprite.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
return TaskStatus.Success;
}
sprite.x += (dx / distance) * speed * context.deltaTime;
sprite.y += (dy / distance) * speed * context.deltaTime;
return TaskStatus.Running;
}
}
export class SimpleEnemyAI extends Laya.Script {
public player: Laya.Sprite;
private aiEntity: Entity;
onEnable() {
this.buildAI();
}
private buildAI() {
const scene = Core.scene;
if (!scene) {
console.error('场景未初始化');
return;
}
const sprite = this.owner as Laya.Sprite;
const tree = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('layaSprite', sprite)
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('player', this.player)
.defineBlackboardVariable('targetPosition', { x: 0, y: 0 })
.selector('MainBehavior')
.sequence('Attack')
.blackboardExists('player')
.log('攻击玩家', 'DoAttack')
.end()
.log('巡逻', 'Patrol')
.end()
.build();
this.aiEntity = scene.createEntity(`AI_${sprite.name}`);
BehaviorTreeStarter.start(this.aiEntity, tree);
// 可以在帧更新中修改黑板
Laya.timer.frameLoop(1, this, () => {
const runtime = this.aiEntity?.getComponent(BehaviorTreeRuntimeComponent);
if (runtime && this.player) {
runtime.setBlackboardValue('targetPosition', {
x: this.player.x,
y: this.player.y
});
}
});
}
onDisable() {
if (this.aiEntity) {
BehaviorTreeStarter.stop(this.aiEntity);
}
Laya.timer.clearAll(this);
}
}
```
## 性能优化
### 使用冷却装饰器
对于不需要每帧更新的AI使用冷却装饰器
```typescript
const tree = BehaviorTreeBuilder.create('ThrottledAI')
.cooldown(0.2, 'ThrottleRoot') // 每0.2秒执行一次
.selector('MainBehavior')
// AI逻辑...
.end()
.end()
.build();
```
### 限制同时运行的AI数量
```typescript
class AIManager {
private activeAIs: Entity[] = [];
private maxAIs: number = 20;
addAI(entity: Entity, tree: BehaviorTreeData) {
if (this.activeAIs.length >= this.maxAIs) {
const furthest = this.activeAIs.shift();
if (furthest) {
BehaviorTreeStarter.stop(furthest);
}
}
BehaviorTreeStarter.start(entity, tree);
this.activeAIs.push(entity);
}
}
```
## 常见问题
### 资源加载失败?
确保:
1. 资源路径正确
2. 资源已添加到项目中
3. 使用 `Laya.loader.load()` 加载
### AI不执行
检查:
1. `onUpdate()` 是否被调用
2. `Scene.update()` 是否执行
3. 行为树是否已启动
## 下一步
- 查看[高级用法](./advanced-usage.md)
- 学习[最佳实践](./best-practices.md)

View File

@@ -0,0 +1,580 @@
# Node.js 服务端使用
本文介绍如何在 Node.js 服务端环境(如游戏服务器、机器人、自动化工具)中使用行为树系统。
## 使用场景
行为树不仅适用于游戏客户端AI在服务端也有广泛应用
1. **游戏服务器** - NPC AI逻辑、副本关卡脚本
2. **聊天机器人** - 对话流程控制、智能回复
3. **自动化测试** - 测试用例执行流程
4. **工作流引擎** - 业务流程自动化
5. **爬虫系统** - 数据采集流程控制
## 基础设置
### 安装
```bash
npm install @esengine/ecs-framework @esengine/behavior-tree
```
### TypeScript 配置
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
```
## 快速开始
### 简单的游戏服务器 NPC
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreePlugin,
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
async function startServer() {
// 1. 初始化 ECS Core
Core.create();
// 2. 安装行为树插件
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
// 3. 创建场景
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 4. 创建 NPC 行为树
const npcAI = BehaviorTreeBuilder.create('MerchantNPC')
.defineBlackboardVariable('mood', 'friendly')
.defineBlackboardVariable('goldAmount', 1000)
.selector('NPCBehavior')
// 如果玩家触发对话
.sequence('Dialogue')
.blackboardExists('playerRequest')
.log('NPC: 欢迎光临!')
.end()
// 默认行为:闲置
.sequence('Idle')
.log('NPC: 正在整理商品...')
.wait(5.0)
.end()
.end()
.build();
// 5. 创建 NPC 实体
const npc = scene.createEntity('Merchant');
BehaviorTreeStarter.start(npc, npcAI);
// 6. 启动游戏循环20 TPS
setInterval(() => {
Core.update(0.05); // 50ms = 1/20秒
}, 50);
// 7. 模拟玩家交互
setTimeout(() => {
const runtime = npc.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('playerRequest', 'buy_sword');
console.log('玩家发起交易请求');
}, 3000);
console.log('游戏服务器已启动');
}
startServer();
```
## 实战示例:聊天机器人
创建一个基于行为树的智能聊天机器人:
```typescript
import { Core, Scene, Entity } from '@esengine/ecs-framework';
import {
BehaviorTreePlugin,
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreeRuntimeComponent,
INodeExecutor,
NodeExecutionContext,
TaskStatus,
NodeType,
NodeExecutorMetadata
} from '@esengine/behavior-tree';
// 1. 创建自定义节点:回复消息
@NodeExecutorMetadata({
implementationType: 'SendMessage',
nodeType: NodeType.Action,
displayName: '发送消息',
configSchema: {
message: { type: 'string', default: '' }
}
})
class SendMessageAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const message = context.nodeData.config['message'] as string;
const userMessage = context.runtime.getBlackboardValue<string>('userMessage');
console.log(`[机器人回复]: ${message}`);
console.log(` 回复给: ${userMessage}`);
return TaskStatus.Success;
}
}
// 2. 创建自定义节点:匹配关键词
@NodeExecutorMetadata({
implementationType: 'MatchKeyword',
nodeType: NodeType.Condition,
displayName: '匹配关键词',
configSchema: {
keyword: { type: 'string', default: '' }
}
})
class MatchKeywordCondition implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const keyword = context.nodeData.config['keyword'] as string;
const userMessage = context.runtime.getBlackboardValue<string>('userMessage') || '';
return userMessage.includes(keyword) ? TaskStatus.Success : TaskStatus.Failure;
}
}
// 3. 创建聊天机器人类
class ChatBot {
private botEntity: Entity;
private runtime: BehaviorTreeRuntimeComponent | null = null;
constructor(scene: Scene) {
// 创建机器人行为树
const botBehavior = BehaviorTreeBuilder.create('ChatBotAI')
.defineBlackboardVariable('userMessage', '')
.defineBlackboardVariable('userName', 'Guest')
.selector('ResponseSelector')
// 问候语
.sequence('Greeting')
.executeCondition('MatchKeyword', { keyword: '你好' })
.executeAction('SendMessage', { message: '你好!我是智能助手,有什么可以帮你的吗?' })
.end()
// 帮助请求
.sequence('Help')
.executeCondition('MatchKeyword', { keyword: '帮助' })
.executeAction('SendMessage', { message: '我可以帮你回答问题、查询信息。试试问我一些问题吧!' })
.end()
// 查询天气
.sequence('Weather')
.executeCondition('MatchKeyword', { keyword: '天气' })
.executeAction('SendMessage', { message: '今天天气不错,晴天,温度适宜。' })
.end()
// 查询时间
.sequence('Time')
.executeCondition('MatchKeyword', { keyword: '时间' })
.executeAction('SendMessage', { message: `现在时间是 ${new Date().toLocaleString()}` })
.end()
// 默认回复
.executeAction('SendMessage', { message: '抱歉,我还不太理解你的意思。可以换个方式问我吗?' })
.end()
.build();
// 创建实体并启动
this.botEntity = scene.createEntity('ChatBot');
BehaviorTreeStarter.start(this.botEntity, botBehavior);
this.runtime = this.botEntity.getComponent(BehaviorTreeRuntimeComponent);
}
// 处理用户消息
async handleMessage(userName: string, message: string) {
if (this.runtime) {
this.runtime.setBlackboardValue('userName', userName);
this.runtime.setBlackboardValue('userMessage', message);
}
// 等待一帧让行为树执行
await new Promise(resolve => setTimeout(resolve, 100));
}
}
// 4. 主程序
async function main() {
// 初始化
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 注册自定义节点
const system = scene.getSystem(BehaviorTreeExecutionSystem);
if (system) {
const registry = system.getExecutorRegistry();
registry.register('SendMessage', new SendMessageAction());
registry.register('MatchKeyword', new MatchKeywordCondition());
}
// 创建聊天机器人
const bot = new ChatBot(scene);
// 启动更新循环
setInterval(() => {
Core.update(0.1);
}, 100);
// 模拟用户对话
console.log('\n=== 聊天机器人测试 ===\n');
await bot.handleMessage('Alice', '你好');
await new Promise(resolve => setTimeout(resolve, 200));
await bot.handleMessage('Bob', '现在几点了?');
await new Promise(resolve => setTimeout(resolve, 200));
await bot.handleMessage('Charlie', '今天天气怎么样');
await new Promise(resolve => setTimeout(resolve, 200));
await bot.handleMessage('David', '你能帮我做什么');
await new Promise(resolve => setTimeout(resolve, 200));
await bot.handleMessage('Eve', '你好吗?');
}
main();
```
## 实战示例:多人游戏服务器
### 房间管理系统
```typescript
import { Core, Scene, Entity } from '@esengine/ecs-framework';
import {
BehaviorTreePlugin,
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreeAssetManager
} from '@esengine/behavior-tree';
// 游戏房间
class GameRoom {
private scene: Scene;
private assetManager: BehaviorTreeAssetManager;
private monsters: Entity[] = [];
constructor(roomId: string) {
// 创建房间场景
this.scene = new Scene();
const plugin = new BehaviorTreePlugin();
plugin.setupScene(this.scene);
this.assetManager = Core.services.resolve(BehaviorTreeAssetManager);
// 初始化房间
this.spawnMonsters();
console.log(`房间 ${roomId} 已创建,怪物数量: ${this.monsters.length}`);
}
private spawnMonsters() {
// 从资产管理器获取怪物AI所有房间共享
const monsterAI = this.assetManager.getAsset('MonsterAI');
if (!monsterAI) return;
// 生成10个怪物
for (let i = 0; i < 10; i++) {
const monster = this.scene.createEntity(`Monster_${i}`);
BehaviorTreeStarter.start(monster, monsterAI);
this.monsters.push(monster);
}
}
update(deltaTime: number) {
this.scene.update(deltaTime);
}
destroy() {
this.monsters.forEach(m => m.destroy());
this.monsters = [];
}
}
// 房间管理器
class RoomManager {
private rooms: Map<string, GameRoom> = new Map();
createRoom(roomId: string): GameRoom {
const room = new GameRoom(roomId);
this.rooms.set(roomId, room);
return room;
}
getRoom(roomId: string): GameRoom | undefined {
return this.rooms.get(roomId);
}
destroyRoom(roomId: string) {
const room = this.rooms.get(roomId);
if (room) {
room.destroy();
this.rooms.delete(roomId);
}
}
update(deltaTime: number) {
this.rooms.forEach(room => room.update(deltaTime));
}
}
// 主程序
async function startGameServer() {
// 初始化
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
// 预加载怪物AI所有房间共享
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
const monsterAI = BehaviorTreeBuilder.create('MonsterAI')
.defineBlackboardVariable('health', 100)
.selector('Behavior')
.log('攻击玩家')
.end()
.build();
assetManager.loadAsset(monsterAI);
// 创建房间管理器
const roomManager = new RoomManager();
// 模拟房间创建
roomManager.createRoom('room_1');
roomManager.createRoom('room_2');
// 服务器主循环60 TPS
setInterval(() => {
roomManager.update(1/60);
}, 1000 / 60);
console.log('游戏服务器已启动');
}
startGameServer();
```
## 性能优化
### 1. 控制更新频率
```typescript
// 不同类型的AI使用不同的更新频率
class AIManager {
private importantAIs: Entity[] = []; // Boss等重要AI60 TPS
private normalAIs: Entity[] = []; // 普通敌人20 TPS
private backgroundAIs: Entity[] = []; // 背景NPC5 TPS
update() {
// 重要AI每帧更新
this.updateAIs(this.importantAIs, 1/60);
// 普通AI每3帧更新一次
if (frameCount % 3 === 0) {
this.updateAIs(this.normalAIs, 3/60);
}
// 背景AI每12帧更新一次
if (frameCount % 12 === 0) {
this.updateAIs(this.backgroundAIs, 12/60);
}
}
}
```
### 2. 资源管理
```typescript
// 使用资产管理器避免重复创建
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
// 预加载所有AI
const enemyAI = BehaviorTreeBuilder.create('EnemyAI').build();
const bossAI = BehaviorTreeBuilder.create('BossAI').build();
assetManager.loadAsset(enemyAI);
assetManager.loadAsset(bossAI);
// 创建1000个敌人但只使用1份BehaviorTreeData
for (let i = 0; i < 1000; i++) {
const enemy = scene.createEntity(`Enemy${i}`);
const ai = assetManager.getAsset('EnemyAI')!;
BehaviorTreeStarter.start(enemy, ai);
}
```
### 3. 使用对象池
```typescript
class EntityPool {
private pool: Entity[] = [];
private active: Entity[] = [];
spawn(scene: Scene, treeId: string): Entity {
let entity = this.pool.pop();
if (!entity) {
entity = scene.createEntity();
const tree = assetManager.getAsset(treeId)!;
BehaviorTreeStarter.start(entity, tree);
} else {
BehaviorTreeStarter.restart(entity);
}
this.active.push(entity);
return entity;
}
recycle(entity: Entity) {
BehaviorTreeStarter.pause(entity);
const index = this.active.indexOf(entity);
if (index >= 0) {
this.active.splice(index, 1);
this.pool.push(entity);
}
}
}
```
## 最佳实践
### 1. 使用环境变量控制调试
```typescript
const DEBUG = process.env.NODE_ENV === 'development';
const aiTree = BehaviorTreeBuilder.create('AI')
.selector('Main')
.when(DEBUG, builder =>
builder.log('调试信息开始AI逻辑')
)
// AI 逻辑...
.end()
.build();
```
### 2. 错误处理
```typescript
try {
const tree = BehaviorTreeBuilder.create('AI')
// ... 构建逻辑
.build();
assetManager.loadAsset(tree);
BehaviorTreeStarter.start(entity, tree);
} catch (error) {
console.error('启动AI失败:', error);
// 使用默认AI或进行降级处理
}
```
### 3. 监控和日志
```typescript
// 定期输出AI状态
setInterval(() => {
const assetManager = Core.services.resolve(BehaviorTreeAssetManager);
const count = assetManager.getAssetCount();
const entities = scene.getEntitiesFor(Matcher.empty().all(BehaviorTreeRuntimeComponent));
console.log(`[AI监控] 行为树资产: ${count}, 活跃实体: ${entities.length}`);
}, 10000);
```
## 常见问题
### 如何与 Express/Koa 等框架集成?
```typescript
import express from 'express';
import { Core, Scene } from '@esengine/ecs-framework';
const app = express();
const scene = new Scene();
// 在单独的循环中更新ECS
setInterval(() => {
Core.update(0.016);
}, 16);
app.post('/npc/:id/interact', (req, res) => {
const npcId = req.params.id;
const npc = scene.findEntity(npcId);
if (npc) {
const runtime = npc.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('playerRequest', req.body);
res.json({ success: true });
} else {
res.status(404).json({ error: 'NPC not found' });
}
});
app.listen(3000);
```
### 如何持久化行为树状态?
```typescript
// 保存状态
function saveAIState(entity: Entity) {
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
if (runtime) {
return {
treeId: runtime.treeId,
blackboard: runtime.getAllBlackboardVariables(),
activeNodes: Array.from(runtime.activeNodeIds)
};
}
}
// 恢复状态
function loadAIState(entity: Entity, savedState: any) {
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
if (runtime) {
// 恢复黑板变量
Object.entries(savedState.blackboard).forEach(([key, value]) => {
runtime.setBlackboardValue(key, value);
});
}
}
```
## 下一步
- 查看[资产管理](./asset-management.md)了解资源加载和子树
- 学习[自定义节点执行器](./custom-actions.md)创建自定义行为
- 阅读[最佳实践](./best-practices.md)优化你的服务端AI

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

@@ -0,0 +1,725 @@
# 组件系统
在 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 装饰器
`@ECSComponent` 是组件类必须使用的装饰器,它为组件提供了类型标识和元数据管理。
#### 为什么必须使用
| 功能 | 说明 |
|------|------|
| **类型识别** | 提供稳定的类型名称,代码混淆后仍能正确识别 |
| **序列化支持** | 序列化/反序列化时使用该名称作为类型标识 |
| **组件注册** | 自动注册到 ComponentRegistry分配唯一的位掩码 |
| **调试支持** | 在调试工具和日志中显示可读的组件名称 |
#### 基本语法
```typescript
@ECSComponent(typeName: string)
```
- `typeName`: 组件的类型名称,建议使用与类名相同或相近的名称
#### 使用示例
```typescript
// ✅ 正确的用法
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// ✅ 推荐:类型名与类名保持一致
@ECSComponent('PlayerController')
class PlayerController extends Component {
speed: number = 5;
}
// ❌ 错误的用法 - 没有装饰器
class BadComponent extends Component {
// 这样定义的组件可能在生产环境出现问题:
// 1. 代码压缩后类名变化,无法正确序列化
// 2. 组件未注册到框架,查询和匹配可能失效
}
```
#### 与 @Serializable 配合使用
当组件需要支持序列化时,`@ECSComponent``@Serializable` 需要一起使用:
```typescript
import { Component, ECSComponent, Serializable, Serialize } from '@esengine/ecs-framework';
@ECSComponent('Player')
@Serializable({ version: 1 })
class PlayerComponent extends Component {
@Serialize()
name: string = '';
@Serialize()
level: number = 1;
// 不使用 @Serialize() 的字段不会被序列化
private _cachedData: any = null;
}
```
> **注意**`@ECSComponent` 的 `typeName` 和 `@Serializable` 的 `typeId` 可以不同。如果 `@Serializable` 没有指定 `typeId`,则默认使用 `@ECSComponent` 的 `typeName`。
#### 组件类型名的唯一性
每个组件的类型名应该是唯一的:
```typescript
// ❌ 错误:两个组件使用相同的类型名
@ECSComponent('Health')
class HealthComponent extends Component { }
@ECSComponent('Health') // 冲突!
class EnemyHealthComponent extends Component { }
// ✅ 正确:使用不同的类型名
@ECSComponent('PlayerHealth')
class PlayerHealthComponent extends Component { }
@ECSComponent('EnemyHealth')
class EnemyHealthComponent 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 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。

View File

619
docs/guide/entity-query.md Normal file
View File

@@ -0,0 +1,619 @@
# 实体查询系统
实体查询是 ECS 架构的核心功能之一。本指南将介绍如何使用 Matcher 和 QuerySystem 来查询和筛选实体。
## 核心概念
### Matcher - 查询条件描述符
Matcher 是一个链式 API,用于描述实体查询条件。它本身不执行查询,而是作为条件传递给 EntitySystem 或 QuerySystem。
### QuerySystem - 查询执行引擎
QuerySystem 负责实际执行查询,内部使用响应式查询机制自动优化性能。
## 在 EntitySystem 中使用 Matcher
这是最常见的使用方式。EntitySystem 通过 Matcher 自动筛选和处理符合条件的实体。
### 基础用法
```typescript
import { EntitySystem, Matcher, Entity, Component } from '@esengine/ecs-framework';
class PositionComponent extends Component {
public x: number = 0;
public y: number = 0;
}
class VelocityComponent extends Component {
public vx: number = 0;
public vy: number = 0;
}
class MovementSystem extends EntitySystem {
constructor() {
// 方式1: 使用 Matcher.empty().all()
super(Matcher.empty().all(PositionComponent, VelocityComponent));
// 方式2: 直接使用 Matcher.all() (等价)
// super(Matcher.all(PositionComponent, VelocityComponent));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const pos = entity.getComponent(PositionComponent)!;
const vel = entity.getComponent(VelocityComponent)!;
pos.x += vel.vx;
pos.y += vel.vy;
}
}
}
// 添加到场景
scene.addEntityProcessor(new MovementSystem());
```
### Matcher 链式 API
#### all() - 必须包含所有组件
```typescript
class HealthSystem extends EntitySystem {
constructor() {
// 实体必须同时拥有 Health 和 Position 组件
super(Matcher.empty().all(HealthComponent, PositionComponent));
}
protected process(entities: readonly Entity[]): void {
// 只处理同时拥有两个组件的实体
}
}
```
#### any() - 至少包含一个组件
```typescript
class DamageableSystem extends EntitySystem {
constructor() {
// 实体至少拥有 Health 或 Shield 其中之一
super(Matcher.any(HealthComponent, ShieldComponent));
}
protected process(entities: readonly Entity[]): void {
// 处理拥有生命值或护盾的实体
}
}
```
#### none() - 不能包含指定组件
```typescript
class AliveEntitySystem extends EntitySystem {
constructor() {
// 实体不能拥有 DeadTag 组件
super(Matcher.all(HealthComponent).none(DeadTag));
}
protected process(entities: readonly Entity[]): void {
// 只处理活着的实体
}
}
```
#### 组合条件
```typescript
class CombatSystem extends EntitySystem {
constructor() {
super(
Matcher.empty()
.all(PositionComponent, HealthComponent) // 必须有位置和生命
.any(WeaponComponent, MagicComponent) // 至少有武器或魔法
.none(DeadTag, FrozenTag) // 不能是死亡或冰冻状态
);
}
protected process(entities: readonly Entity[]): void {
// 处理可以战斗的活着的实体
}
}
```
#### nothing() - 不匹配任何实体
用于创建只需要生命周期方法(`onBegin``onEnd`)但不需要处理实体的系统。
```typescript
class FrameTimerSystem extends EntitySystem {
constructor() {
// 不匹配任何实体
super(Matcher.nothing());
}
protected onBegin(): void {
// 每帧开始时执行
Performance.markFrameStart();
}
protected process(entities: readonly Entity[]): void {
// 永远不会被调用,因为没有匹配的实体
}
protected onEnd(): void {
// 每帧结束时执行
Performance.markFrameEnd();
}
}
```
#### empty() vs nothing() 的区别
| 方法 | 行为 | 使用场景 |
|------|------|----------|
| `Matcher.empty()` | 匹配**所有**实体 | 需要处理场景中所有实体 |
| `Matcher.nothing()` | 不匹配**任何**实体 | 只需要生命周期回调,不处理实体 |
```typescript
// empty() - 返回场景中的所有实体
class AllEntitiesSystem extends EntitySystem {
constructor() {
super(Matcher.empty());
}
protected process(entities: readonly Entity[]): void {
// entities 包含场景中的所有实体
console.log(`场景中共有 ${entities.length} 个实体`);
}
}
// nothing() - 不返回任何实体
class NoEntitiesSystem extends EntitySystem {
constructor() {
super(Matcher.nothing());
}
protected process(entities: readonly Entity[]): void {
// entities 永远是空数组,此方法不会被调用
}
}
```
### 按标签查询
```typescript
class PlayerSystem extends EntitySystem {
constructor() {
// 查询特定标签的实体
super(Matcher.empty().withTag(Tags.PLAYER));
}
protected process(entities: readonly Entity[]): void {
// 只处理玩家实体
}
}
```
### 按名称查询
```typescript
class BossSystem extends EntitySystem {
constructor() {
// 查询特定名称的实体
super(Matcher.empty().withName('Boss'));
}
protected process(entities: readonly Entity[]): void {
// 只处理名为 'Boss' 的实体
}
}
```
## 直接使用 QuerySystem
如果不需要创建系统,可以直接使用 Scene 的 querySystem 进行查询。
### 基础查询方法
```typescript
// 获取场景的查询系统
const querySystem = scene.querySystem;
// 查询拥有所有指定组件的实体
const result1 = querySystem.queryAll(PositionComponent, VelocityComponent);
console.log(`找到 ${result1.count} 个移动实体`);
console.log(`查询耗时: ${result1.executionTime.toFixed(2)}ms`);
// 查询拥有任意指定组件的实体
const result2 = querySystem.queryAny(WeaponComponent, MagicComponent);
console.log(`找到 ${result2.count} 个战斗单位`);
// 查询不包含指定组件的实体
const result3 = querySystem.queryNone(DeadTag);
console.log(`找到 ${result3.count} 个活着的实体`);
```
### 按标签查询
```typescript
const playerResult = querySystem.queryByTag(Tags.PLAYER);
for (const player of playerResult.entities) {
console.log('玩家:', player.name);
}
```
### 按名称查询
```typescript
const bossResult = querySystem.queryByName('Boss');
if (bossResult.count > 0) {
const boss = bossResult.entities[0];
console.log('找到Boss:', boss);
}
```
### 按单个组件查询
```typescript
const healthResult = querySystem.queryByComponent(HealthComponent);
console.log(`${healthResult.count} 个实体拥有生命值`);
```
## 性能优化
### 自动缓存
QuerySystem 内部使用响应式查询自动缓存结果,相同的查询条件会直接使用缓存:
```typescript
// 第一次查询,执行实际查询
const result1 = querySystem.queryAll(PositionComponent);
console.log('fromCache:', result1.fromCache); // false
// 第二次相同查询,使用缓存
const result2 = querySystem.queryAll(PositionComponent);
console.log('fromCache:', result2.fromCache); // true
```
### 实体变化自动更新
当实体添加/移除组件时,查询缓存会自动更新:
```typescript
// 查询拥有武器的实体
const before = querySystem.queryAll(WeaponComponent);
console.log('之前:', before.count); // 假设为 5
// 给实体添加武器
const enemy = scene.createEntity('Enemy');
enemy.addComponent(new WeaponComponent());
// 再次查询,自动包含新实体
const after = querySystem.queryAll(WeaponComponent);
console.log('之后:', after.count); // 现在是 6
```
### 查询性能统计
```typescript
const stats = querySystem.getStats();
console.log('总查询次数:', stats.queryStats.totalQueries);
console.log('缓存命中率:', stats.queryStats.cacheHitRate);
console.log('缓存大小:', stats.cacheStats.size);
```
## 实际应用场景
### 场景1: 物理系统
```typescript
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(TransformComponent, RigidbodyComponent));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(TransformComponent)!;
const rigidbody = entity.getComponent(RigidbodyComponent)!;
// 应用重力
rigidbody.velocity.y -= 9.8 * Time.deltaTime;
// 更新位置
transform.position.x += rigidbody.velocity.x * Time.deltaTime;
transform.position.y += rigidbody.velocity.y * Time.deltaTime;
}
}
}
```
### 场景2: 渲染系统
```typescript
class RenderSystem extends EntitySystem {
constructor() {
super(
Matcher.empty()
.all(TransformComponent, SpriteComponent)
.none(InvisibleTag) // 排除不可见实体
);
}
protected process(entities: readonly Entity[]): void {
// 按 z-order 排序
const sorted = entities.slice().sort((a, b) => {
const zA = a.getComponent(TransformComponent)!.z;
const zB = b.getComponent(TransformComponent)!.z;
return zA - zB;
});
// 渲染实体
for (const entity of sorted) {
const transform = entity.getComponent(TransformComponent)!;
const sprite = entity.getComponent(SpriteComponent)!;
renderer.drawSprite(sprite.texture, transform.position);
}
}
}
```
### 场景3: 碰撞检测
```typescript
class CollisionSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(TransformComponent, ColliderComponent));
}
protected process(entities: readonly Entity[]): void {
// 简单的 O(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(a: Entity, b: Entity): void {
const transA = a.getComponent(TransformComponent)!;
const transB = b.getComponent(TransformComponent)!;
const colliderA = a.getComponent(ColliderComponent)!;
const colliderB = b.getComponent(ColliderComponent)!;
if (this.isOverlapping(transA, colliderA, transB, colliderB)) {
// 触发碰撞事件
scene.eventSystem.emit('collision', { entityA: a, entityB: b });
}
}
private isOverlapping(...args: any[]): boolean {
// 碰撞检测逻辑
return false;
}
}
```
### 场景4: 一次性查询
```typescript
// 在系统外部执行一次性查询
class GameManager {
private scene: Scene;
public countEnemies(): number {
const result = this.scene.querySystem.queryByTag(Tags.ENEMY);
return result.count;
}
public findNearestEnemy(playerPos: Vector2): Entity | null {
const enemies = this.scene.querySystem.queryByTag(Tags.ENEMY);
let nearest: Entity | null = null;
let minDistance = Infinity;
for (const enemy of enemies.entities) {
const transform = enemy.getComponent(TransformComponent);
if (!transform) continue;
const distance = Vector2.distance(playerPos, transform.position);
if (distance < minDistance) {
minDistance = distance;
nearest = enemy;
}
}
return nearest;
}
}
```
## 最佳实践
### 1. 优先使用 EntitySystem
```typescript
// 推荐: 使用 EntitySystem
class GoodSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(HealthComponent));
}
protected process(entities: readonly Entity[]): void {
// 自动获得符合条件的实体,每帧自动更新
}
}
// 不推荐: 在 update 中手动查询
class BadSystem extends EntitySystem {
constructor() {
super(Matcher.empty());
}
protected process(entities: readonly Entity[]): void {
// 每帧手动查询,浪费性能
const result = this.scene!.querySystem.queryAll(HealthComponent);
for (const entity of result.entities) {
// ...
}
}
}
```
### 2. 合理使用 none() 排除条件
```typescript
// 排除已死亡的敌人
class EnemyAISystem extends EntitySystem {
constructor() {
super(
Matcher.empty()
.all(EnemyTag, AIComponent)
.none(DeadTag) // 不处理死亡的敌人
);
}
}
```
### 3. 使用标签优化查询
```typescript
// 不好: 查询所有实体再过滤
const allEntities = scene.querySystem.getAllEntities();
const players = allEntities.filter(e => e.hasComponent(PlayerTag));
// 好: 直接按标签查询
const players = scene.querySystem.queryByTag(Tags.PLAYER).entities;
```
### 4. 避免过于复杂的查询条件
```typescript
// 不推荐: 过于复杂
super(
Matcher.empty()
.all(A, B, C, D)
.any(E, F, G)
.none(H, I, J)
);
// 推荐: 拆分成多个简单系统
class SystemAB extends EntitySystem {
constructor() {
super(Matcher.empty().all(A, B));
}
}
class SystemCD extends EntitySystem {
constructor() {
super(Matcher.empty().all(C, D));
}
}
```
## 注意事项
### 1. 查询结果是只读的
```typescript
const result = querySystem.queryAll(PositionComponent);
// 不要修改返回的数组
result.entities.push(someEntity); // 错误!
// 如果需要修改,先复制
const mutableArray = [...result.entities];
mutableArray.push(someEntity); // 正确
```
### 2. 组件添加/移除后的查询时机
```typescript
// 创建实体并添加组件
const entity = scene.createEntity('Player');
entity.addComponent(new PositionComponent());
// 立即查询可能获取到新实体
const result = scene.querySystem.queryAll(PositionComponent);
// result.entities 包含新创建的实体
```
### 3. Matcher 是不可变的
```typescript
const matcher = Matcher.empty().all(PositionComponent);
// 链式调用返回新的 Matcher 实例
const matcher2 = matcher.any(VelocityComponent);
// matcher 本身不变
console.log(matcher === matcher2); // false
```
## Matcher API 快速参考
### 静态创建方法
| 方法 | 说明 | 示例 |
|------|------|------|
| `Matcher.all(...types)` | 必须包含所有指定组件 | `Matcher.all(Position, Velocity)` |
| `Matcher.any(...types)` | 至少包含一个指定组件 | `Matcher.any(Health, Shield)` |
| `Matcher.none(...types)` | 不能包含任何指定组件 | `Matcher.none(Dead)` |
| `Matcher.byTag(tag)` | 按标签查询 | `Matcher.byTag(1)` |
| `Matcher.byName(name)` | 按名称查询 | `Matcher.byName("Player")` |
| `Matcher.byComponent(type)` | 按单个组件查询 | `Matcher.byComponent(Health)` |
| `Matcher.empty()` | 创建空匹配器(匹配所有实体) | `Matcher.empty()` |
| `Matcher.nothing()` | 不匹配任何实体 | `Matcher.nothing()` |
| `Matcher.complex()` | 创建复杂查询构建器 | `Matcher.complex()` |
### 链式方法
| 方法 | 说明 | 示例 |
|------|------|------|
| `.all(...types)` | 添加必须包含的组件 | `.all(Position)` |
| `.any(...types)` | 添加可选组件(至少一个) | `.any(Weapon, Magic)` |
| `.none(...types)` | 添加排除的组件 | `.none(Dead)` |
| `.exclude(...types)` | `.none()` 的别名 | `.exclude(Disabled)` |
| `.one(...types)` | `.any()` 的别名 | `.one(Player, Enemy)` |
| `.withTag(tag)` | 添加标签条件 | `.withTag(1)` |
| `.withName(name)` | 添加名称条件 | `.withName("Boss")` |
| `.withComponent(type)` | 添加单组件条件 | `.withComponent(Health)` |
### 实用方法
| 方法 | 说明 |
|------|------|
| `.getCondition()` | 获取查询条件(只读) |
| `.isEmpty()` | 检查是否为空条件 |
| `.isNothing()` | 检查是否为 nothing 匹配器 |
| `.clone()` | 克隆匹配器 |
| `.reset()` | 重置所有条件 |
| `.toString()` | 获取字符串表示 |
### 常用组合示例
```typescript
// 基础移动系统
Matcher.all(Position, Velocity)
// 可攻击的活着的实体
Matcher.all(Position, Health)
.any(Weapon, Magic)
.none(Dead, Disabled)
// 所有带标签的敌人
Matcher.byTag(Tags.ENEMY)
.all(AIComponent)
// 只需要生命周期的系统
Matcher.nothing()
```
## 相关 API
- [Matcher](../api/classes/Matcher.md) - 查询条件描述符 API 参考
- [QuerySystem](../api/classes/QuerySystem.md) - 查询系统 API 参考
- [EntitySystem](../api/classes/EntitySystem.md) - 实体系统 API 参考
- [Entity](../api/classes/Entity.md) - 实体 API 参考

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

@@ -0,0 +1,300 @@
# 实体类
在 ECS 架构中实体Entity是游戏世界中的基本对象。实体本身不包含游戏逻辑或数据它只是一个容器用来组合不同的组件来实现各种功能。
## 基本概念
实体是一个轻量级的对象,主要用于:
- 作为组件的容器
- 提供唯一标识ID
- 管理组件的生命周期
::: tip 关于父子层级关系
实体间的父子层级关系通过 `HierarchyComponent``HierarchySystem` 管理,而非 Entity 内置属性。这种设计遵循 ECS 组合原则 —— 只有需要层级关系的实体才添加此组件。
详见 [层级系统](./hierarchy.md) 文档。
:::
## 创建实体
**重要提示:实体必须通过场景创建,不支持手动创建!**
实体必须通过场景的 `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 架构的核心概念之一,理解如何正确使用实体将帮助你构建高效、可维护的游戏代码。
## 下一步
- 了解 [层级系统](./hierarchy.md) 建立实体间的父子关系
- 了解 [组件系统](./component.md) 为实体添加功能
- 了解 [场景管理](./scene.md) 组织和管理实体

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,412 @@
# 快速开始
本指南将帮助你快速上手 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, // 启用调试模式,提供详细的日志和性能监控
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 }
// 方式3生产环境配置
const core = Core.create({
debug: false // 生产环境关闭调试
});
```
### Core 配置详解
```typescript
interface ICoreConfig {
/** 是否启用调试模式 - 影响日志级别和性能监控 */
debug?: 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)
```

437
docs/guide/hierarchy.md Normal file
View File

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

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

@@ -0,0 +1,43 @@
# 指南
欢迎使用 ECS Framework 指南。这里将详细介绍框架的各个核心概念和使用方法。
## 核心概念
### [实体类 (Entity)](./entity.md)
了解 ECS 架构的基础 - 实体类的使用方法、生命周期管理和最佳实践。
### [组件系统 (Component)](./component.md)
学习如何创建和使用组件,实现游戏功能的模块化设计。
### [系统架构 (System)](./system.md)
掌握系统的编写方法,实现游戏逻辑的处理。
### [实体查询与 Matcher](./entity-query.md)
学习使用 Matcher 进行实体筛选和查询,掌握 `all``any``none``nothing` 等匹配条件。
### [场景管理 (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)
学习如何开发和使用插件,扩展框架功能,实现功能模块化。

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

@@ -0,0 +1,594 @@
# 日志系统
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('渲染系统启动');
}
}
```
### 集成第三方日志库
通过 `setLoggerFactory` 可以将业务代码中的日志器替换为第三方日志库(如 winston、pino、nestjs Logger 等)。
**说明**: 目前框架内部日志仍使用 ConsoleLogger自定义日志器仅影响业务代码如 EntitySystem
#### 基本用法
```typescript
import { setLoggerFactory } from '@esengine/ecs-framework';
setLoggerFactory((name?: string) => {
// 返回实现 ILogger 接口的日志器实例
return yourLogger;
});
```
#### 使用示例
```typescript
// 集成 Winston
setLoggerFactory((name?: string) => winston.createLogger({ /* ... */ }));
// 集成 Pino
setLoggerFactory((name?: string) => pino({ name }));
// 集成 NestJS Logger
setLoggerFactory((name?: string) => new Logger(name));
```
#### EntitySystem 中的使用
EntitySystem 会自动使用类名创建日志器:
```typescript
class PlayerMovementSystem extends EntitySystem {
// this.logger 自动使用 'PlayerMovementSystem' 作为名称
protected process(entities: readonly Entity[]): void {
this.logger.info(`处理 ${entities.length} 个玩家实体`);
}
}
```
### 自定义输出
```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 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。

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

@@ -0,0 +1,923 @@
# 序列化系统
序列化系统提供了完整的场景、实体和组件数据持久化方案,支持全量序列化和增量序列化两种模式,适用于游戏存档、网络同步、场景编辑器、时间回溯等场景。
## 基本概念
序列化系统分为两个层次:
- **全量序列化**:序列化完整的场景状态,包括所有实体、组件和场景数据
- **增量序列化**:只序列化相对于基础快照的变更部分,大幅减少数据量
### 支持的数据格式
- **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
// 基类组件
@ECSComponent('Collider2DBase')
@Serializable({ version: 1, typeId: 'Collider2DBase' })
abstract class Collider2DBase extends Component {
@Serialize()
public friction: number = 0.5;
@Serialize()
public restitution: number = 0.0;
@Serialize()
public isTrigger: boolean = false;
}
// 子类组件 - 自动继承父类的序列化字段
@ECSComponent('BoxCollider2D')
@Serializable({ version: 1, typeId: 'BoxCollider2D' })
class BoxCollider2DComponent extends Collider2DBase {
@Serialize()
public width: number = 1.0;
@Serialize()
public height: number = 1.0;
}
// 另一个子类组件
@ECSComponent('CircleCollider2D')
@Serializable({ version: 1, typeId: 'CircleCollider2D' })
class CircleCollider2DComponent extends Collider2DBase {
@Serialize()
public radius: number = 0.5;
}
```
#### 继承规则
1. **字段继承**:子类自动继承父类所有被 `@Serialize()` 标记的字段
2. **独立元数据**:每个子类维护独立的序列化元数据,修改子类不会影响父类或其他子类
3. **typeId 区分**:使用 `typeId` 选项为每个类指定唯一标识,确保反序列化时能正确识别组件类型
#### 使用 typeId 的重要性
当使用组件继承时,**强烈建议**为每个类设置唯一的 `typeId`
```typescript
// ✅ 推荐:明确指定 typeId
@Serializable({ version: 1, typeId: 'BoxCollider2D' })
class BoxCollider2DComponent extends Collider2DBase { }
@Serializable({ version: 1, typeId: 'CircleCollider2D' })
class CircleCollider2DComponent extends Collider2DBase { }
// ⚠️ 不推荐:依赖类名作为 typeId
// 在代码压缩后类名可能变化,导致反序列化失败
@Serializable({ version: 1 })
class BoxCollider2DComponent extends Collider2DBase { }
```
#### 子类覆盖父类字段
子类可以重新声明父类的字段以修改其序列化选项:
```typescript
@ECSComponent('SpecialCollider')
@Serializable({ version: 1, typeId: 'SpecialCollider' })
class SpecialColliderComponent extends Collider2DBase {
// 覆盖父类字段,使用不同的别名
@Serialize({ alias: 'fric' })
public override friction: number = 0.8;
@Serialize()
public specialProperty: string = '';
}
```
#### 忽略继承的字段
使用 `@IgnoreSerialization()` 可以在子类中忽略从父类继承的字段:
```typescript
@ECSComponent('TriggerOnly')
@Serializable({ version: 1, typeId: 'TriggerOnly' })
class TriggerOnlyCollider extends Collider2DBase {
// 忽略父类的 friction 和 restitution 字段
// 因为 Trigger 不需要物理材质属性
@IgnoreSerialization()
public override friction: number = 0;
@IgnoreSerialization()
public override restitution: number = 0;
}
```
### 场景自定义数据
除了实体和组件,还可以序列化场景级别的配置数据:
```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,828 @@
# 服务容器
服务容器ServiceContainer是 ECS Framework 的依赖注入容器,负责管理框架中所有服务的注册、解析和生命周期。通过服务容器,你可以实现松耦合的架构设计,提高代码的可测试性和可维护性。
## 概述
### 什么是服务容器
服务容器是一个轻量级的依赖注入DI容器它提供了
- **服务注册**: 将服务类型注册到容器中
- **服务解析**: 从容器中获取服务实例
- **生命周期管理**: 自动管理服务实例的创建和销毁
- **依赖注入**: 自动解析服务之间的依赖关系
### 核心概念
#### 服务Service
服务是实现了 `IService` 接口的类,必须提供 `dispose()` 方法用于资源清理:
```typescript
import { IService } from '@esengine/ecs-framework';
class MyService implements IService {
constructor() {
// 初始化逻辑
}
dispose(): void {
// 清理资源
}
}
```
#### 服务标识符ServiceIdentifier
服务标识符用于在容器中唯一标识一个服务,支持两种类型:
- **类构造函数**: 直接使用服务类作为标识符,适用于具体实现类
- **Symbol**: 使用 Symbol 作为标识符,适用于接口抽象(推荐用于插件和跨包场景)
```typescript
// 方式1: 使用类作为标识符
Core.services.registerSingleton(DataService);
const data = Core.services.resolve(DataService);
// 方式2: 使用 Symbol 作为标识符(推荐用于接口)
const IFileSystem = Symbol.for('IFileSystem');
Core.services.registerInstance(IFileSystem, new TauriFileSystem());
const fs = Core.services.resolve<IFileSystem>(IFileSystem);
```
> **提示**: 使用 `Symbol.for()` 而非 `Symbol()` 可确保跨包/跨模块共享同一个标识符。详见[高级用法 - 接口与 Symbol 标识符模式](#接口与-symbol-标识符模式)。
#### 生命周期
服务容器支持两种生命周期:
- **Singleton单例**: 整个应用程序生命周期内只有一个实例,所有解析请求返回同一个实例
- **Transient瞬时**: 每次解析都创建新的实例
## 基础使用
### 访问服务容器
ECS Framework 提供了三级服务容器:
> **版本说明**World 服务容器功能在 v2.2.13+ 版本中可用
#### Core 级别服务容器
应用程序全局服务容器,可以通过 `Core.services` 访问:
```typescript
import { Core } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 访问全局服务容器
const container = Core.services;
```
#### World 级别服务容器
每个 World 拥有独立的服务容器,用于管理 World 范围内的服务:
```typescript
import { World } from '@esengine/ecs-framework';
// 创建 World
const world = new World({ name: 'GameWorld' });
// 访问 World 级别的服务容器
const worldContainer = world.services;
// 注册 World 级别的服务
world.services.registerSingleton(RoomManager);
```
#### Scene 级别服务容器
每个 Scene 拥有独立的服务容器,用于管理 Scene 范围内的服务:
```typescript
// 访问 Scene 级别的服务容器
const sceneContainer = scene.services;
// 注册 Scene 级别的服务
scene.services.registerSingleton(PhysicsSystem);
```
#### 服务容器层级
```
Core.services (应用程序全局)
└─ World.services (World 级别)
└─ Scene.services (Scene 级别)
```
不同级别的服务容器是独立的,服务不会自动向上或向下查找。选择合适的容器级别:
- **Core.services**: 应用程序级别的全局服务(配置、插件管理器等)
- **World.services**: World 级别的服务(房间管理器、多人游戏状态等)
- **Scene.services**: Scene 级别的服务ECS 系统、场景特定逻辑等)
### 注册服务
#### 注册单例服务
单例服务在首次解析时创建,之后所有解析请求都返回同一个实例:
```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');
}
}
```
### @InjectProperty 装饰器
通过属性装饰器注入依赖。注入时机是在构造函数执行后、`onInitialize()` 调用前完成:
```typescript
import { Injectable, InjectProperty, IService } from '@esengine/ecs-framework';
@Injectable()
class PlayerService implements IService {
@InjectProperty(DataService)
private data!: DataService;
@InjectProperty(GameService)
private game!: GameService;
dispose(): void {
// 清理资源
}
}
```
在 EntitySystem 中使用属性注入:
```typescript
@Injectable()
class CombatSystem extends EntitySystem {
@InjectProperty(TimeService)
private timeService!: TimeService;
@InjectProperty(AudioService)
private audio!: AudioService;
constructor() {
super(Matcher.all(Health, Attack));
}
onInitialize(): void {
// 此时属性已注入完成,可以安全使用
console.log('Delta time:', this.timeService.getDeltaTime());
}
processEntity(entity: Entity): void {
// 使用注入的服务
this.audio.playSound('attack');
}
}
```
> **注意**: 属性声明时使用 `!` 断言(如 `private data!: DataService`),表示该属性会在使用前被注入。
### 注册可注入服务
使用 `registerInjectable` 自动处理依赖注入:
```typescript
import { registerInjectable } from '@esengine/ecs-framework';
// 注册服务(会自动解析 @InjectProperty 依赖)
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);
```
## 高级用法
### 接口与 Symbol 标识符模式
在大型项目或需要跨平台适配的游戏中,推荐使用"接口 + Symbol.for 标识符"模式。这种模式实现了真正的依赖倒置,让代码依赖于抽象而非具体实现。
#### 为什么使用 Symbol.for
- **跨包共享**: `Symbol.for('key')` 在全局 Symbol 注册表中创建/获取 Symbol确保不同包中使用相同的标识符
- **接口解耦**: 消费者只依赖接口定义,不依赖具体实现类
- **可替换实现**: 可以在运行时注入不同的实现(如测试 Mock、不同平台适配
#### 定义接口和标识符
以音频服务为例游戏需要在不同平台Web、微信小游戏、原生App使用不同的音频实现
```typescript
// IAudioService.ts - 定义接口和标识符
export interface IAudioService {
dispose(): void;
playSound(id: string): void;
playMusic(id: string, loop?: boolean): void;
stopMusic(): void;
setVolume(volume: number): void;
preload(id: string, url: string): Promise<void>;
}
// 使用 Symbol.for 确保跨包共享同一个 Symbol
export const IAudioService = Symbol.for('IAudioService');
```
#### 实现接口
```typescript
// WebAudioService.ts - Web 平台实现
import { IAudioService } from './IAudioService';
export class WebAudioService implements IAudioService {
private audioContext: AudioContext;
private sounds: Map<string, AudioBuffer> = new Map();
constructor() {
this.audioContext = new AudioContext();
}
playSound(id: string): void {
const buffer = this.sounds.get(id);
if (buffer) {
const source = this.audioContext.createBufferSource();
source.buffer = buffer;
source.connect(this.audioContext.destination);
source.start();
}
}
async preload(id: string, url: string): Promise<void> {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
this.sounds.set(id, audioBuffer);
}
// ... 其他方法实现
dispose(): void {
this.audioContext.close();
this.sounds.clear();
}
}
```
```typescript
// WechatAudioService.ts - 微信小游戏平台实现
export class WechatAudioService implements IAudioService {
private innerAudioContexts: Map<string, WechatMinigame.InnerAudioContext> = new Map();
playSound(id: string): void {
const ctx = this.innerAudioContexts.get(id);
if (ctx) {
ctx.play();
}
}
async preload(id: string, url: string): Promise<void> {
const ctx = wx.createInnerAudioContext();
ctx.src = url;
this.innerAudioContexts.set(id, ctx);
}
// ... 其他方法实现
dispose(): void {
for (const ctx of this.innerAudioContexts.values()) {
ctx.destroy();
}
this.innerAudioContexts.clear();
}
}
```
#### 注册和使用
```typescript
import { IAudioService } from './IAudioService';
import { WebAudioService } from './WebAudioService';
import { WechatAudioService } from './WechatAudioService';
// 根据平台注册不同实现
if (typeof wx !== 'undefined') {
Core.services.registerInstance(IAudioService, new WechatAudioService());
} else {
Core.services.registerInstance(IAudioService, new WebAudioService());
}
// 业务代码中使用 - 不关心具体实现
const audio = Core.services.resolve<IAudioService>(IAudioService);
await audio.preload('explosion', '/sounds/explosion.mp3');
audio.playSound('explosion');
```
#### 跨模块使用
```typescript
// 在游戏系统中使用
import { IAudioService } from '@mygame/core';
class CombatSystem extends EntitySystem {
private audio: IAudioService;
initialize(): void {
// 获取音频服务,不需要知道具体实现
this.audio = this.scene.services.resolve<IAudioService>(IAudioService);
}
onEntityDeath(entity: Entity): void {
this.audio.playSound('death');
}
}
```
#### Symbol vs Symbol.for
```typescript
// Symbol() - 每次创建唯一的 Symbol
const sym1 = Symbol('test');
const sym2 = Symbol('test');
console.log(sym1 === sym2); // false - 不同的 Symbol
// Symbol.for() - 在全局注册表中共享
const sym3 = Symbol.for('test');
const sym4 = Symbol.for('test');
console.log(sym3 === sym4); // true - 同一个 Symbol
// 跨包场景
// package-a/index.ts
export const IMyService = Symbol.for('IMyService');
// package-b/index.ts (不同的包)
const IMyService = Symbol.for('IMyService');
// 与 package-a 中的是同一个 Symbol
```
### 循环依赖检测
服务容器会自动检测循环依赖:
```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) - 在系统中使用服务

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

@@ -0,0 +1,735 @@
# 系统架构
在 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 组件的实体
// 不匹配任何实体
const nothingMatcher = Matcher.nothing(); // 用于只需要生命周期回调的系统
```
### 空匹配器 vs Nothing 匹配器
```typescript
// empty() - 空条件,匹配所有实体
const emptyMatcher = Matcher.empty();
// nothing() - 不匹配任何实体,用于只需要生命周期方法的系统
const nothingMatcher = Matcher.nothing();
// 使用场景:只需要 onBegin/onEnd 生命周期的系统
@ECSSystem('FrameTimer')
class FrameTimerSystem extends EntitySystem {
constructor() {
super(Matcher.nothing()); // 不处理任何实体
}
protected onBegin(): void {
// 每帧开始时执行,例如:记录帧开始时间
console.log('帧开始');
}
protected process(entities: readonly Entity[]): void {
// 永远不会被调用,因为没有匹配的实体
}
protected onEnd(): void {
// 每帧结束时执行
console.log('帧结束');
}
}
```
> 💡 **提示**:更多关于 Matcher 和实体查询的详细用法,请参考 [实体查询系统](/guide/entity-query) 文档。
## 系统生命周期
系统提供了完整的生命周期回调:
```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 装饰器
`@ECSSystem` 是系统类必须使用的装饰器,它为系统提供类型标识和元数据管理。
#### 为什么必须使用
| 功能 | 说明 |
|------|------|
| **类型识别** | 提供稳定的系统名称,代码混淆后仍能正确识别 |
| **调试支持** | 在性能监控、日志和调试工具中显示可读的系统名称 |
| **系统管理** | 通过名称查找和管理系统 |
| **序列化支持** | 场景序列化时可以记录系统配置 |
#### 基本语法
```typescript
@ECSSystem(systemName: string)
```
- `systemName`: 系统的名称,建议使用描述性的名称
#### 使用示例
```typescript
// ✅ 正确的用法
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
// 系统实现
}
// ✅ 推荐:使用描述性的名称
@ECSSystem('PlayerMovement')
class PlayerMovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Player, Position, Velocity));
}
}
// ❌ 错误的用法 - 没有装饰器
class BadSystem extends EntitySystem {
// 这样定义的系统可能在生产环境出现问题:
// 1. 代码压缩后类名变化,无法正确识别
// 2. 性能监控和调试工具显示不正确的名称
}
```
#### 系统名称的作用
```typescript
@ECSSystem('Combat')
class CombatSystem extends EntitySystem {
protected onInitialize(): void {
// 使用 systemName 属性访问系统名称
console.log(`系统 ${this.systemName} 已初始化`); // 输出: 系统 Combat 已初始化
}
}
// 通过名称查找系统
const combat = scene.getSystemByName('Combat');
// 性能监控中会显示系统名称
const perfData = combatSystem.getPerformanceData();
console.log(`${combatSystem.systemName} 执行时间: ${perfData?.executionTime}ms`);
```
### 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,
// cleanupFrameInterval: 1800 // 间隔多少帧清理闲置 World
// }
```
### 手动清理
```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)。

317
docs/index.md Normal file
View File

@@ -0,0 +1,317 @@
---
layout: page
title: ESEngine - 高性能 TypeScript ECS 框架
---
<ParticleHero />
<section class="news-section">
<div class="news-container">
<div class="news-header">
<h2 class="news-title">快速入口</h2>
<a href="/guide/" class="news-more">查看文档</a>
</div>
<div class="news-grid">
<a href="/guide/getting-started" class="news-card">
<div class="news-card-image" style="background: linear-gradient(135deg, #1e3a5f 0%, #1e1e1e 100%);">
<div class="news-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#4fc1ff" d="M12 3L1 9l4 2.18v6L12 21l7-3.82v-6l2-1.09V17h2V9zm6.82 6L12 12.72L5.18 9L12 5.28zM17 16l-5 2.72L7 16v-3.73L12 15l5-2.73z"/></svg>
</div>
<span class="news-badge">快速开始</span>
</div>
<div class="news-card-content">
<h3>5 分钟上手 ESEngine</h3>
<p>从安装到创建第一个 ECS 应用,快速了解核心概念。</p>
</div>
</a>
<a href="/guide/behavior-tree/" class="news-card">
<div class="news-card-image" style="background: linear-gradient(135deg, #1e3a5f 0%, #1e1e1e 100%);">
<div class="news-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#4ec9b0" d="M12 2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m3 20h-1v-7l-2-2l-2 2v7H9v-7.5l-2 2V22H6v-6l3-3l1-3.5c-.3.4-.6.7-1 1L6 9v1H4V8l5-3c.5-.3 1.1-.5 1.7-.5H11c.6 0 1.2.2 1.7.5l5 3v2h-2V9l-3 1.5c-.4-.3-.7-.6-1-1l1 3.5l3 3v6Z"/></svg>
</div>
<span class="news-badge">AI 系统</span>
</div>
<div class="news-card-content">
<h3>行为树可视化编辑器</h3>
<p>内置 AI 行为树系统,支持可视化编辑和实时调试。</p>
</div>
</a>
</div>
</div>
</section>
<section class="features-section">
<div class="features-container">
<h2 class="features-title">核心特性</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#4fc1ff" d="M13 2.05v2.02c3.95.49 7 3.85 7 7.93c0 1.45-.39 2.79-1.06 3.95l1.59 1.09A9.94 9.94 0 0 0 22 12c0-5.18-3.95-9.45-9-9.95M12 19c-3.87 0-7-3.13-7-7c0-3.53 2.61-6.43 6-6.92V2.05c-5.06.5-9 4.76-9 9.95c0 5.52 4.47 10 9.99 10c3.31 0 6.24-1.61 8.06-4.09l-1.6-1.1A7.93 7.93 0 0 1 12 19"/><path fill="#4fc1ff" d="M12 6a6 6 0 0 0-6 6c0 3.31 2.69 6 6 6a6 6 0 0 0 0-12m0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4s4 1.79 4 4s-1.79 4-4 4"/></svg>
</div>
<h3 class="feature-title">高性能 ECS 架构</h3>
<p class="feature-desc">基于数据驱动的实体组件系统,支持大规模实体处理,缓存友好的内存布局。</p>
<a href="/guide/entity" class="feature-link">了解更多 →</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#569cd6" d="M3 3h18v18H3zm16.525 13.707c0-.795-.272-1.425-.816-1.89c-.544-.465-1.404-.804-2.58-1.016l-1.704-.296c-.616-.104-1.052-.26-1.308-.468c-.256-.21-.384-.468-.384-.776c0-.392.168-.7.504-.924c.336-.224.8-.336 1.392-.336c.56 0 1.008.124 1.344.372c.336.248.536.584.6 1.008h2.016c-.08-.96-.464-1.716-1.152-2.268c-.688-.552-1.6-.828-2.736-.828c-1.2 0-2.148.3-2.844.9c-.696.6-1.044 1.38-1.044 2.34c0 .76.252 1.368.756 1.824c.504.456 1.308.792 2.412.996l1.704.312c.624.12 1.068.28 1.332.48c.264.2.396.46.396.78c0 .424-.192.756-.576.996c-.384.24-.9.36-1.548.36c-.672 0-1.2-.14-1.584-.42c-.384-.28-.608-.668-.672-1.164H8.868c.048 1.016.46 1.808 1.236 2.376c.776.568 1.796.852 3.06.852c1.24 0 2.22-.292 2.94-.876c.72-.584 1.08-1.364 1.08-2.34z"/></svg>
</div>
<h3 class="feature-title">完整类型支持</h3>
<p class="feature-desc">100% TypeScript 编写,完整的类型定义和编译时检查,提供最佳的开发体验。</p>
<a href="/guide/component" class="feature-link">了解更多 →</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#4ec9b0" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10s10-4.5 10-10S17.5 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m-5-8l4-4v3h4v2h-4v3z"/></svg>
</div>
<h3 class="feature-title">可视化行为树</h3>
<p class="feature-desc">内置 AI 行为树系统,提供可视化编辑器,支持自定义节点和实时调试。</p>
<a href="/guide/behavior-tree/" class="feature-link">了解更多 →</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#c586c0" d="M4 6h18V4H4c-1.1 0-2 .9-2 2v11H0v3h14v-3H4zm19 2h-6c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1m-1 9h-4v-7h4z"/></svg>
</div>
<h3 class="feature-title">多平台支持</h3>
<p class="feature-desc">支持浏览器、Node.js、微信小游戏等多平台可与主流游戏引擎无缝集成。</p>
<a href="/guide/platform-adapter" class="feature-link">了解更多 →</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#dcdcaa" d="M4 3h6v2H4v14h6v2H4c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2m9 0h6c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2h-6v-2h6V5h-6zm-1 7h4v2h-4z"/></svg>
</div>
<h3 class="feature-title">模块化设计</h3>
<p class="feature-desc">核心功能独立打包,按需引入。支持自定义插件扩展,灵活适配不同项目。</p>
<a href="/guide/plugin-system" class="feature-link">了解更多 →</a>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#9cdcfe" d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9c-2-2-5-2.4-7.4-1.3L9 6L6 9L1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4"/></svg>
</div>
<h3 class="feature-title">开发者工具</h3>
<p class="feature-desc">内置性能监控、调试工具、序列化系统等,提供完整的开发工具链。</p>
<a href="/guide/logging" class="feature-link">了解更多 →</a>
</div>
</div>
</div>
</section>
<style scoped>
/* 首页专用样式 | Home page specific styles */
.news-section {
background: #0d0d0d;
padding: 64px 0;
border-top: 1px solid #2a2a2a;
}
.news-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 48px;
}
.news-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
}
.news-title {
font-size: 1.5rem;
font-weight: 700;
color: #ffffff;
margin: 0;
}
.news-more {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 6px;
color: #a0a0a0;
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.news-more:hover {
background: #252525;
color: #ffffff;
}
.news-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.news-card {
display: flex;
background: #1f1f1f;
border: 1px solid #2a2a2a;
border-radius: 12px;
overflow: hidden;
text-decoration: none;
transition: all 0.2s;
}
.news-card:hover {
border-color: #3b9eff;
}
.news-card-image {
width: 200px;
min-height: 140px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16px;
gap: 12px;
}
.news-icon {
opacity: 0.9;
}
.news-badge {
display: inline-block;
padding: 4px 12px;
background: transparent;
border: 1px solid #3a3a3a;
border-radius: 16px;
color: #a0a0a0;
font-size: 0.75rem;
font-weight: 500;
}
.news-card-content {
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.news-card-content h3 {
font-size: 1.125rem;
font-weight: 600;
color: #ffffff;
margin: 0 0 8px 0;
}
.news-card-content p {
font-size: 0.875rem;
color: #707070;
margin: 0;
line-height: 1.6;
}
.features-section {
background: #0d0d0d;
padding: 64px 0;
}
.features-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 48px;
}
.features-title {
font-size: 1.5rem;
font-weight: 700;
color: #ffffff;
margin: 0 0 32px 0;
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.feature-card {
background: #1f1f1f;
border: 1px solid #2a2a2a;
border-radius: 12px;
padding: 24px;
transition: all 0.15s ease;
}
.feature-card:hover {
border-color: #3b9eff;
background: #252525;
}
.feature-icon {
width: 48px;
height: 48px;
background: #0d0d0d;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
}
.feature-title {
font-size: 16px;
font-weight: 600;
color: #ffffff;
margin: 0 0 8px 0;
}
.feature-desc {
font-size: 14px;
color: #707070;
line-height: 1.7;
margin: 0 0 16px 0;
}
.feature-link {
font-size: 14px;
color: #3b9eff;
text-decoration: none;
font-weight: 500;
}
.feature-link:hover {
text-decoration: underline;
}
@media (max-width: 1024px) {
.news-container,
.features-container {
padding: 0 24px;
}
.news-grid {
grid-template-columns: 1fr;
}
.features-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.news-card {
flex-direction: column;
}
.news-card-image {
width: 100%;
min-height: 120px;
}
.features-grid {
grid-template-columns: 1fr;
}
}
</style>

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