Compare commits

...

221 Commits

Author SHA1 Message Date
github-actions[bot]
ddc7d1f726 chore: release packages (#397)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-30 16:59:49 +08:00
YHH
04b08f3f07 fix(ecs): add entity field to COMPONENT_ADDED event (#396)
Fix missing entity field in COMPONENT_ADDED event payload that caused
ECSRoom's @NetworkEntity auto-broadcast to fail with 'Cannot read
properties of undefined'
2025-12-30 16:57:11 +08:00
yhh
d9969d0b08 Merge branch 'master' of https://github.com/esengine/esengine 2025-12-30 16:23:54 +08:00
YHH
bdbbf8a80a feat(ecs): 添加 @NetworkEntity 装饰器,支持自动广播实体生成/销毁 (#395)
* docs: add editor-app README with setup instructions

* docs: add separate EN/CN editor setup guides

* feat(ecs): add @NetworkEntity decorator for auto spawn/despawn broadcasting

- Add @NetworkEntity decorator to mark components for automatic network broadcasting
- ECSRoom now auto-broadcasts spawn on component:added event
- ECSRoom now auto-broadcasts despawn on entity:destroyed event
- Entity.destroy() emits entity:destroyed event via ECSEventType
- Entity active state changes emit ENTITY_ENABLED/ENTITY_DISABLED events
- Add enableAutoNetworkEntity config option to ECSRoom (default true)
- Update documentation for both Chinese and English
2025-12-30 16:19:01 +08:00
yhh
1368473c71 Merge remote master 2025-12-30 12:29:24 +08:00
YHH
b28169b186 fix(editor): fix build errors and refactor behavior-tree architecture (#394)
* docs: add editor-app README with setup instructions

* docs: add separate EN/CN editor setup guides

* fix(editor): fix build errors and refactor behavior-tree architecture

- Fix fairygui-editor tsconfig extends path and add missing tsconfig.build.json
- Refactor behavior-tree-editor to not depend on asset-system in runtime
  - Create local BehaviorTreeRuntimeModule for pure runtime logic
  - Move asset loader registration to editor module install()
  - Add BehaviorTreeLoader for asset system integration
- Fix rapier2d WASM loader to not pass arguments to init()
- Add WASM base64 loader config to rapier2d tsup.config
- Update README documentation and simplify setup steps
2025-12-30 11:13:26 +08:00
yhh
e2598b2292 docs: add separate EN/CN editor setup guides 2025-12-30 10:02:53 +08:00
yhh
2e3889abed docs: add editor-app README with setup instructions 2025-12-30 09:54:41 +08:00
github-actions[bot]
d21caa974e chore: release packages (#393)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-30 09:41:17 +08:00
YHH
a08a84b7db fix(sync): use GlobalComponentRegistry for network sync decoding (#392)
- Decoder.ts now uses GlobalComponentRegistry.getComponentType() instead of local registry
- @sync decorator uses getComponentTypeName() to get @ECSComponent decorator name
- @ECSComponent decorator updates SYNC_METADATA.typeId when defined
- Removed deprecated registerSyncComponent/autoRegisterSyncComponent functions
- Updated ComponentSync.ts in network package to use GlobalComponentRegistry
- Updated tests to use correct @ECSComponent type names

This ensures that components decorated with @ECSComponent are automatically
available for network sync decoding without any manual registration.
2025-12-30 09:39:17 +08:00
github-actions[bot]
449bd420a6 chore: release packages (#391)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-29 21:10:36 +08:00
YHH
1f297ac769 feat(ecs): ECS 网络状态同步系统 | add ECS network state synchronization (#390)
## @esengine/ecs-framework

新增 @sync 装饰器和二进制编解码器,支持基于 Component 的网络状态同步:

- `sync` 装饰器标记需要同步的字段
- `ChangeTracker` 组件变更追踪
- 二进制编解码器 (BinaryWriter/BinaryReader)
- `encodeSnapshot`/`decodeSnapshot` 批量编解码
- `encodeSpawn`/`decodeSpawn` 实体生成编解码
- `encodeDespawn`/`processDespawn` 实体销毁编解码

将以下方法标记为 @internal,用户应通过 Core.update() 驱动更新:
- Scene.update()
- SceneManager.update()
- WorldManager.updateAll()

## @esengine/network

- 新增 ComponentSyncSystem 基于 @sync 自动同步组件状态
- 将 ecs-framework 从 devDependencies 移到 peerDependencies

## @esengine/server

新增 ECSRoom,带有 ECS World 支持的房间基类:

- 每个 ECSRoom 在 Core.worldManager 中创建独立的 World
- Core.update() 统一更新 Time 和所有 World
- onTick() 只处理状态同步逻辑
- 自动创建/销毁玩家实体
- 增量状态广播
2025-12-29 21:08:34 +08:00
github-actions[bot]
4cf868a769 chore: release packages (#389)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-29 17:14:53 +08:00
YHH
afdeb00b4d feat(server): 添加可插拔速率限制系统 | add pluggable rate limiting system (#388)
* feat(server): 添加可插拔速率限制系统 | add pluggable rate limiting system

- 新增令牌桶策略 (TokenBucketStrategy) - 推荐用于一般场景
- 新增滑动窗口策略 (SlidingWindowStrategy) - 精确跟踪
- 新增固定窗口策略 (FixedWindowStrategy) - 简单高效
- 新增房间速率限制 mixin (withRateLimit)
- 新增速率限制装饰器 (@rateLimit, @noRateLimit)
- 新增按消息类型限流装饰器 (@rateLimitMessage, @noRateLimitMessage)
- 支持与认证系统组合使用
- 添加中英文文档
- 导出路径: @esengine/server/ratelimit

* docs: 更新 README 添加新模块 | update README with new modules

- 添加程序化生成 (procgen) 模块
- 添加 RPC 框架模块
- 添加游戏服务器 (server) 模块
- 添加事务系统 (transaction) 模块
- 添加世界流送 (world-streaming) 模块
- 更新网络模块描述
- 更新项目结构目录
2025-12-29 17:12:54 +08:00
github-actions[bot]
764ce67742 chore: release packages (#387)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-29 16:12:03 +08:00
YHH
61a13baca2 feat(server): 添加可插拔认证系统 | add pluggable authentication system (#386)
* feat(server): 添加可插拔认证系统 | add pluggable authentication system

- 新增 JWT 认证提供者 (createJwtAuthProvider)
- 新增 Session 认证提供者 (createSessionAuthProvider)
- 新增服务器认证 mixin (withAuth)
- 新增房间认证 mixin (withRoomAuth)
- 新增认证装饰器 (@requireAuth, @requireRole)
- 新增测试工具 (MockAuthProvider)
- 新增中英文文档
- 导出路径: @esengine/server/auth, @esengine/server/auth/testing

* fix(server): 使用加密安全的随机数生成 session ID | use crypto-secure random for session ID
2025-12-29 16:10:09 +08:00
github-actions[bot]
1cfa64aa0f chore: release packages (#385)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-29 15:04:07 +08:00
YHH
3b978384c7 feat(framework): server testing utils, transaction storage simplify, pathfinding tests (#384)
## Server Testing Utils
- Add TestServer, TestClient, MockRoom for unit testing
- Export testing utilities from @esengine/server/testing

## Transaction Storage (BREAKING)
- Simplify RedisStorage/MongoStorage to factory pattern only
- Remove DI client injection option
- Add lazy connection and Symbol.asyncDispose support
- Add 161 unit tests with full coverage

## Pathfinding Tests
- Add 150 unit tests covering all components
- BinaryHeap, Heuristics, AStarPathfinder, GridMap, NavMesh, PathSmoother

## Docs
- Update storage.md for new factory pattern API
2025-12-29 15:02:13 +08:00
YHH
10c3891abd docs: 添加缺失的侧边栏导航项 | add missing sidebar items (#383)
- RPC: 添加 server, client, codec 页面
- Network: 添加 prediction, aoi, delta 页面
- Transaction: 添加完整模块导航
- Changelog: 添加 transaction, rpc 链接
2025-12-29 11:29:42 +08:00
github-actions[bot]
18af48a0fc chore: release packages (#382)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-29 11:08:46 +08:00
YHH
d4cef828e1 feat(transaction): 添加游戏事务系统 | add game transaction system (#381)
- TransactionManager/TransactionContext 事务管理
- MemoryStorage/RedisStorage/MongoStorage 存储实现
- CurrencyOperation/InventoryOperation/TradeOperation 内置操作
- SagaOrchestrator 分布式 Saga 编排
- withTransactions() Room 集成
- 完整中英文文档
2025-12-29 10:54:00 +08:00
github-actions[bot]
2d46ccf896 chore: release packages (#380)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-29 10:44:48 +08:00
YHH
fb8bde6485 feat(network): 网络模块增强 - 预测、AOI、增量压缩 (#379)
- 添加 NetworkPredictionSystem 客户端预测系统
- 添加 NetworkAOISystem 兴趣区域管理
- 添加 StateDeltaCompressor 状态增量压缩
- 添加断线重连和状态恢复
- 增强协议支持时间戳、序列号、速度
- 添加中英文文档
2025-12-29 10:42:48 +08:00
yhh
30437dc5d5 docs: add world-streaming to sidebar navigation 2025-12-28 19:50:44 +08:00
YHH
9f84c2f870 chore: bump pathfinding and world-streaming to 1.1.0 (#378) 2025-12-28 19:36:34 +08:00
github-actions[bot]
e9ea52d9b3 chore: release packages (#377)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-28 19:26:41 +08:00
YHH
0662b07445 chore: update pathfinding, add rpc/world-streaming docs, refactor world-streaming location (#376) 2025-12-28 19:18:28 +08:00
github-actions[bot]
838cda91aa chore: release packages (#375)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-28 14:11:27 +08:00
YHH
a000cc07d7 feat(rpc): export RpcClient from main entry point (#374) 2025-12-28 14:09:16 +08:00
github-actions[bot]
1316d7de49 chore: release packages (#373)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-28 13:49:34 +08:00
YHH
9c41181875 fix(server): expose id property on ServerConnection type (#372) 2025-12-28 13:47:27 +08:00
github-actions[bot]
9f3f9a547a chore: release packages (#371)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-28 13:32:44 +08:00
YHH
18df9d1cda fix(server): allow define() to be called before start() (#370) 2025-12-28 13:29:17 +08:00
github-actions[bot]
9a4b3388e0 chore: release packages (#369)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-28 12:51:20 +08:00
YHH
66d5dc27f7 fix(server): 修复发布缺少 dist | fix missing dist (#368)
* fix(server): 修复发布缺少 dist | fix missing dist in publish

* ci: 添加 server 和 create-esengine-server 构建 | add server packages to build

* fix: 添加 create-esengine-server 到 changeset
2025-12-28 12:46:50 +08:00
github-actions[bot]
8a3e54cb45 chore: release packages (#367)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-28 12:25:51 +08:00
YHH
b6f1235239 feat(server): 添加游戏服务器框架 | add game server framework (#366)
**@esengine/server** - 游戏服务器框架 | Game server framework
- 文件路由系统 | File-based routing
- Room 生命周期 (onCreate, onJoin, onLeave, onTick, onDispose)
- @onMessage 装饰器 | Message handler decorator
- 玩家管理与断线处理 | Player management with auto-disconnect
- 内置 JoinRoom/LeaveRoom API | Built-in room APIs
- defineApi/defineMsg 类型安全辅助函数 | Type-safe helpers

**create-esengine-server** - CLI 脚手架工具 | CLI scaffolding
- 生成 shared/server/client 项目结构 | Project structure
- 类型安全的协议定义 | Type-safe protocol definitions
- 包含 GameRoom 示例 | Example implementation

**其他 | Other**
- 删除旧的 network-server 包 | Remove old network-server
- 更新服务器文档 | Update server documentation
2025-12-28 12:23:55 +08:00
github-actions[bot]
41529f6fbb chore: release packages (#365)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-28 11:00:37 +08:00
yhh
7dadacc8f9 fix(ci): add @esengine/rpc to release workflow build 2025-12-28 10:58:15 +08:00
YHH
7940f581a6 feat(rpc,network): 新增 RPC 库并迁移网络模块 (#364)
* feat(rpc,network): 新增 RPC 库并迁移网络模块

## @esengine/rpc (新增)
- 新增类型安全的 RPC 库,支持 WebSocket 通信
- 新增 RpcClient 类:connect/disconnect, call/send/on/off/once 方法
- 新增 RpcServer 类:Node.js WebSocket 服务端
- 新增编解码系统:支持 JSON 和 MessagePack
- 新增 TextEncoder/TextDecoder polyfill,兼容微信小游戏平台
- 新增 WebSocketAdapter 接口,支持跨平台 WebSocket 抽象

## @esengine/network (重构)
- 重构 NetworkService:拆分为 RpcService 基类和 GameNetworkService
- 新增 gameProtocol:类型安全的 API 和消息定义
- 新增类型安全便捷方法:sendInput(), onSync(), onSpawn(), onDespawn()
- 更新 NetworkPlugin 使用新的服务架构
- 移除 TSRPC 依赖,改用 @esengine/rpc

## 文档
- 新增 RPC 模块文档(中英文)
- 更新 Network 模块文档(中英文)
- 更新侧边栏导航

* fix(network,cli): 修复 CI 构建和更新 CLI 适配器

## 修复
- 在 tsconfig.build.json 添加 rpc 引用,修复类型声明生成

## CLI 更新
- 更新 nodejs 适配器使用新的 @esengine/rpc
- 生成的服务器代码使用 RpcServer 替代旧的 GameServer
- 添加 ws 和 @types/ws 依赖
- 更新 README 模板中的客户端连接示例

* chore: 添加 CLI changeset

* fix(ci): add @esengine/rpc to build and check scripts

- Add rpc package to CI build step (must build before network)
- Add rpc to type-check:framework, lint:framework, test:ci:framework

* fix(rpc,network): fix tsconfig for declaration generation

- Remove composite mode from rpc (not needed, causes CI issues)
- Remove rpc from network project references (resolves via node_modules)
- Remove unused references from network tsconfig.build.json
2025-12-28 10:54:51 +08:00
YHH
8605888f11 docs: restructure documentation with modular sub-pages (#363)
* docs: split Entity docs into sub-modules and fix Starlight CI

- Split monolithic entity.md into 4 focused sub-documents:
  - guide/entity/index.md - Overview and basic concepts
  - guide/entity/component-operations.md - Component API operations
  - guide/entity/entity-handle.md - EntityHandle system for safe references
  - guide/entity/lifecycle.md - Lifecycle and persistence management

- Created bilingual versions (Chinese and English)

- Updated sidebar configuration in astro.config.mjs

- Fixed CI workflow for Starlight migration:
  - Updated docs.yml to upload from docs/dist instead of .vitepress/dist
  - Updated package.json scripts to use pnpm filter for docs
  - Added docs directory to pnpm-workspace.yaml
  - Renamed docs package to @esengine/docs

- Documented missing Entity APIs:
  - createComponent() method
  - addComponents() batch method
  - getComponentByType() with inheritance support
  - markDirty() for change detection

* docs: split Network docs and fix API errors

- Split network module into focused sub-documents:
  - modules/network/index.md - Overview and quick start
  - modules/network/client.md - Client-side usage
  - modules/network/server.md - Server-side GameServer/Room
  - modules/network/sync.md - Interpolation and prediction
  - modules/network/api.md - Complete API reference

- Fixed incorrect API documentation:
  - localClientId → clientId
  - ENetworkState enum values (strings → numbers)
  - connect() method signature
  - Removed non-existent localPlayerId property
  - Fixed onConnected callback signature

- Created bilingual versions (Chinese and English)
- Updated sidebar configuration
- Updated pnpm-lock.yaml for docs workspace

* docs(worker-system): split into focused sub-modules

Split 773-line worker-system.md into 5 focused documents:
- index.md: Core features and quick start
- configuration.md: IWorkerSystemConfig and processing modes
- examples.md: Complete particle physics implementation
- wechat.md: WeChat Mini Game limitations and solutions
- best-practices.md: Performance optimization tips

Updated sidebar config to reflect new structure.
Created both Chinese and English versions.

* docs(scene): split into focused sub-modules

Split 666-line scene.md into 7 focused documents:
- index.md: Overview and quick start
- lifecycle.md: Scene lifecycle methods
- entity-management.md: Entity creation, find, destroy
- system-management.md: System add, remove, control
- events.md: Event system usage
- debugging.md: Stats, performance monitoring
- best-practices.md: Design patterns and examples

Updated sidebar config to reflect new structure.
Created both Chinese and English versions.

* docs(plugin-system): split into focused sub-modules

Split 645-line plugin-system.md into 7 focused documents:
- index.md: Overview and quick start
- development.md: IPlugin interface and lifecycle
- services-systems.md: Register services and add systems
- dependencies.md: Dependency management
- management.md: Plugin management via Core/PluginManager
- examples.md: Complete plugin examples
- best-practices.md: Design guidelines and FAQ

Updated sidebar config to reflect new structure.
Created both Chinese and English versions.

* docs(behavior-tree): add English docs and expand sidebar navigation

- Add 12 English behavior-tree documentation pages
- Update sidebar config to show behavior-tree sub-navigation
- Include: overview, getting-started, core-concepts, custom-actions,
  editor-guide, editor-workflow, asset-management, advanced-usage,
  best-practices, cocos-integration, laya-integration, nodejs-usage

* docs(modules): split spatial and timer module docs

Spatial module (602 lines -> 5 files):
- index.md: Overview and quick start
- spatial-index.md: Grid index, range queries, raycasting API
- aoi.md: Area of Interest management
- examples.md: Combat, MMO sync, AI perception examples
- utilities.md: Geometry detection, performance tips

Timer module (481 lines -> 4 files):
- index.md: Overview and core concepts
- api.md: Complete timer and cooldown API
- examples.md: Skill cooldowns, DOT, buff systems
- best-practices.md: Usage tips, ECS integration

Also includes English versions and sidebar navigation updates.

* docs: split FSM, pathfinding, blueprint, procgen module docs

- FSM: Split into index, api, examples (3 files)
- Pathfinding: Split into index, grid-map, navmesh, smoothing, examples (5 files)
- Blueprint: Split into index, vm, custom-nodes, nodes, composition, examples (6 files)
- Procgen: Split into index, noise, random, sampling, examples (5 files)
- Added English versions for all split modules
- Updated sidebar navigation with sub-menus for all modules
2025-12-27 20:35:54 +08:00
YHH
d57a007a42 docs: split Entity docs into sub-modules and fix Starlight CI (#362)
* docs: split Entity docs into sub-modules and fix Starlight CI

- Split monolithic entity.md into 4 focused sub-documents:
  - guide/entity/index.md - Overview and basic concepts
  - guide/entity/component-operations.md - Component API operations
  - guide/entity/entity-handle.md - EntityHandle system for safe references
  - guide/entity/lifecycle.md - Lifecycle and persistence management

- Created bilingual versions (Chinese and English)

- Updated sidebar configuration in astro.config.mjs

- Fixed CI workflow for Starlight migration:
  - Updated docs.yml to upload from docs/dist instead of .vitepress/dist
  - Updated package.json scripts to use pnpm filter for docs
  - Added docs directory to pnpm-workspace.yaml
  - Renamed docs package to @esengine/docs

- Documented missing Entity APIs:
  - createComponent() method
  - addComponents() batch method
  - getComponentByType() with inheritance support
  - markDirty() for change detection

* docs: split Network docs and fix API errors

- Split network module into focused sub-documents:
  - modules/network/index.md - Overview and quick start
  - modules/network/client.md - Client-side usage
  - modules/network/server.md - Server-side GameServer/Room
  - modules/network/sync.md - Interpolation and prediction
  - modules/network/api.md - Complete API reference

- Fixed incorrect API documentation:
  - localClientId → clientId
  - ENetworkState enum values (strings → numbers)
  - connect() method signature
  - Removed non-existent localPlayerId property
  - Fixed onConnected callback signature

- Created bilingual versions (Chinese and English)
- Updated sidebar configuration
- Updated pnpm-lock.yaml for docs workspace
2025-12-27 13:28:49 +08:00
YHH
89cdfe396b docs: migrate documentation from VitePress to Starlight (#361)
- Replace VitePress with Astro Starlight for documentation
- Reorganize document structure with better hierarchy
- Split large documents into sub-modules for better readability
- Add new sections: component, entity-query, serialization, system
- Support both Chinese (default) and English documentation
- Add responsive design and improved navigation
2025-12-27 12:54:53 +08:00
github-actions[bot]
fac4bc19c5 chore: release packages (#360)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 10:57:16 +08:00
YHH
aed91dbe45 feat(cli): add update command for ESEngine packages (#359)
* feat(cli): add update command for ESEngine packages

- Add 'update' command to check and update @esengine/* packages
- Support --check flag to only show available updates without installing
- Support --yes flag to skip confirmation prompt
- Display package update status with current vs latest version comparison
- Preserve version prefix (^ or ~) when updating
- Bump version to 1.4.0

* chore: add changeset for CLI update command

* fix(cli): handle 'latest' tag in update command

- Treat 'latest' and '*' version tags as needing update
- Pin to specific version (^x.x.x) when updating from 'latest'
- Show '(pin version)' hint in update status output

* fix(cli): minimize file system race condition in update command

Re-read package.json immediately before writing to reduce the window
for potential race conditions between reading and writing.

* fix(cli): use atomic file write to avoid race condition

Write to temp file first, then rename for atomic update.
2025-12-27 10:54:04 +08:00
github-actions[bot]
c7f8208b6f chore: release packages (#358)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 10:31:43 +08:00
yhh
5131ec3c52 chore: update pnpm-lock.yaml 2025-12-27 10:28:35 +08:00
yhh
7d74623710 fix(core): 配置 publishConfig.directory 确保从 dist 目录发布 2025-12-27 10:27:00 +08:00
github-actions[bot]
044463dd5f chore: release packages (#357)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 09:55:30 +08:00
YHH
ce2db4e48a fix(core): 修复 World cleanup 在打包环境下的兼容性问题 (#356)
- 使用 forEach 替代 spread + for...of 解构模式,避免某些打包工具转换后的兼容性问题
- 重构 World 和 WorldManager 类,提升代码质量
- 提取默认配置为常量
- 统一双语注释格式
2025-12-27 09:51:04 +08:00
github-actions[bot]
0a88c6f2fc chore: release packages (#355)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 00:20:04 +08:00
yhh
b0b95c60b4 fix: 从 ignore 列表移除 network-server 以支持版本发布 2025-12-27 00:17:44 +08:00
yhh
683ac7a7d4 Merge branch 'master' of https://github.com/esengine/esengine 2025-12-27 00:16:12 +08:00
YHH
1e240e86f2 feat(cli): 增强 Node.js 服务端适配器 (#354)
* docs(network): 添加网络模块文档和 CLI 支持

- 添加中英文网络模块文档
- 将 network、network-protocols、network-server 加入 CLI 模块列表

* feat(cli): 增强 Node.js 服务端适配器

- 添加 @esengine/network-server 依赖支持
- 生成完整的 ECS 游戏服务器项目结构
- 修复 network-server 包支持 ESM/CJS 双格式
- 添加 ws@8.18.0 解决 Node.js 24 兼容性问题
- 组件使用 @ECSComponent 装饰器注册
- tsconfig 启用 experimentalDecorators
2025-12-27 00:13:58 +08:00
yhh
4d6c2fe7ff Merge branch 'master' of https://github.com/esengine/esengine 2025-12-26 23:21:17 +08:00
github-actions[bot]
67c06720c5 chore: release packages (#353)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-26 23:17:14 +08:00
YHH
33e98b9a75 fix(cli): 修复 Cocos Creator 3.x 项目检测逻辑 (#352)
* docs(network): 添加网络模块文档和 CLI 支持

- 添加中英文网络模块文档
- 将 network、network-protocols、network-server 加入 CLI 模块列表

* fix(cli): 修复 Cocos Creator 3.x 项目检测逻辑

- 重构检测代码,提取通用辅助函数
- 优先检查 package.json 中的 creator.version 字段
- 添加 .creator 和 settings 目录检测
- 使用 getMajorVersion 统一版本号解析

* chore: add changeset
2025-12-26 23:14:23 +08:00
yhh
a42f2412d7 Merge branch 'master' of https://github.com/esengine/esengine 2025-12-26 23:04:47 +08:00
yhh
fdb19a33fb docs(network): 添加网络模块文档和 CLI 支持
- 添加中英文网络模块文档
- 将 network、network-protocols、network-server 加入 CLI 模块列表
2025-12-26 23:01:07 +08:00
github-actions[bot]
1e31e9101b chore: release packages (#351)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-26 22:32:12 +08:00
yhh
d66c18041e fix(spatial): 修复 GridAOI 可见性更新问题
- 修复 addObserver 时现有观察者无法检测到新实体的问题
- 修复实体远距离移动时观察者可见性未正确更新的问题
- 重构 demos 抽取公共测试工具
2025-12-26 22:23:03 +08:00
yhh
881ffad3bc feat(tools): 添加 CLI 模块管理命令和文档验证 demos
- CLI 新增 list/add/remove 命令管理项目模块
- 创建 demos 包验证模块文档正确性
- 包含 Timer/FSM/Pathfinding/Procgen/Spatial 5个模块的完整测试
2025-12-26 22:09:01 +08:00
YHH
4a16e30794 docs(modules): 添加框架模块文档 (#350)
* docs(modules): 添加框架模块文档

添加以下模块的完整文档:
- FSM (状态机): 状态定义、转换条件、优先级、事件监听
- Timer (定时器): 定时器调度、冷却系统、服务令牌
- Spatial (空间索引): GridSpatialIndex、AOI 兴趣区域管理
- Pathfinding (寻路): A* 算法、网格地图、导航网格、路径平滑
- Procgen (程序化生成): 噪声函数、种子随机数、加权随机

所有文档均基于实际源码 API 编写,包含:
- 快速开始示例
- 完整 API 参考
- 实际使用案例
- 蓝图节点说明
- 最佳实践建议

* docs(modules): 添加 Blueprint 模块文档和所有模块英文版

新增中文文档:
- Blueprint (蓝图可视化脚本): VM、自定义节点、组合系统、触发器

新增英文文档 (docs/en/modules/):
- FSM: State machine API, transitions, ECS integration
- Timer: Timers, cooldowns, service tokens
- Spatial: Grid spatial index, AOI management
- Pathfinding: A*, grid map, NavMesh, path smoothing
- Procgen: Noise functions, seeded random, weighted random
- Blueprint: Visual scripting, custom nodes, composition

所有文档均基于实际源码 API 编写。
2025-12-26 20:02:21 +08:00
YHH
76691cc198 docs: 重构文档结构,添加独立模块区域 (#349)
* docs: 重构文档结构,添加独立模块区域

- 新增 /modules/ 目录用于功能模块文档
- 移动 behavior-tree 从 /guide/ 到 /modules/
- 添加模块总览页面
- 更新导航栏添加"模块"入口
- 更新侧边栏:模块区域独立侧边栏
- 更新 i18n 配置支持新模块

* style(docs): 提高文字对比度
2025-12-26 19:15:08 +08:00
github-actions[bot]
27b9e174eb chore: release packages (#348)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-26 18:16:57 +08:00
YHH
ede440d277 chore: changeset for framework package configs (#347)
* chore: add changeset for framework package configs

* fix(docs): 修复 VitePress 配置中的包路径
2025-12-26 18:13:12 +08:00
YHH
5cb83f0743 fix(framework): 补充包配置 peerDeps/repository/keywords (#346)
* fix(framework): 补充 peerDependencies, repository 和 keywords 配置

- fsm: 添加 peerDeps, repository, keywords
- timer: 添加 peerDeps, repository, keywords
- spatial: 添加 peerDeps, repository, keywords
- procgen: 添加 peerDeps, repository, keywords
- pathfinding: 移除重复的 dependencies,添加 repository, keywords

* chore: update pnpm-lock.yaml
2025-12-26 18:05:37 +08:00
yhh
7cbf92b8c7 fix(docs): 修复 tsconfig.json 中的 references 路径 2025-12-26 17:49:40 +08:00
YHH
a049bbe2f5 fix: add private:true to packages not meant for npm (#345)
* fix(ci): run on all PRs with conditional skip

- Remove paths filter from pull_request trigger
- Add check-changes job to detect code changes
- Skip full CI if no code files changed
- Satisfies branch protection while avoiding unnecessary builds

* fix: add private:true to packages not meant for npm

Prevents changesets from trying to publish internal packages:
- engine/* (8 packages)
- rendering/* (8 packages)
- physics/* (2 packages)
- streaming/* (1 package)
- tools/sdk, tools/worker-generator
2025-12-26 17:45:00 +08:00
github-actions[bot]
ec72df7af5 chore: release packages (#343)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: YHH <359807859@qq.com>
2025-12-26 17:18:01 +08:00
YHH
9327c1cef5 fix(ci): run on all PRs with conditional skip (#344)
- Remove paths filter from pull_request trigger
- Add check-changes job to detect code changes
- Skip full CI if no code files changed
- Satisfies branch protection while avoiding unnecessary builds
2025-12-26 17:15:02 +08:00
YHH
da5bf2116a fix(changesets): remove network-protocols and build-config from ignore (#342)
* fix(changesets): remove network-protocols and build-config from ignore

These packages are dependencies of non-ignored packages:
- @esengine/network depends on @esengine/network-protocols
- Multiple packages depend on @esengine/build-config (private, won't publish)

* fix(ci): add .changeset to CI trigger paths
2025-12-26 17:05:41 +08:00
YHH
67e97f89c6 fix(ci): update typedoc path and add workflow_dispatch (#341)
* fix: update Laya examples, add CLI docs, fix changesets workflow

- Update Laya examples to use Laya 3.x Script pattern (@regClass)
- Add CLI tool quick start section to README (npx @esengine/cli init)
- Fix changesets workflow to only build framework packages
- Remove unnecessary Rust/WASM build steps from changesets workflow
- Remove redundant 'pnpm build' from changeset:publish script

* docs: add CLI documentation and update Laya examples

- Add CLI quick start section to getting-started.md (zh/en)
- Update Laya examples to use Laya 3.x Script pattern

* fix(ci): update typedoc path and add workflow_dispatch

- Fix typedoc entry point: packages/core -> packages/framework/core
- Add workflow_dispatch to release-changesets for manual triggering
- Add deeper package paths to trigger on nested packages
2025-12-26 16:52:29 +08:00
YHH
31fd34b221 fix: update Laya examples, add CLI docs, fix changesets workflow (#340)
* fix: update Laya examples, add CLI docs, fix changesets workflow

- Update Laya examples to use Laya 3.x Script pattern (@regClass)
- Add CLI tool quick start section to README (npx @esengine/cli init)
- Fix changesets workflow to only build framework packages
- Remove unnecessary Rust/WASM build steps from changesets workflow
- Remove redundant 'pnpm build' from changeset:publish script

* docs: add CLI documentation and update Laya examples

- Add CLI quick start section to getting-started.md (zh/en)
- Update Laya examples to use Laya 3.x Script pattern
2025-12-26 16:37:37 +08:00
YHH
c4f7a13b74 feat(cli): add CLI tool for adding ECS framework to existing projects (#339)
* feat(cli): add CLI tool for adding ECS framework to existing projects

- Support Cocos Creator 2.x/3.x, LayaAir 3.x, and Node.js platforms
- Auto-detect project type based on directory structure
- Generate ECSManager with full configuration (debug, remote debug, WebSocket URL)
- Auto-install dependencies with npm/yarn/pnpm detection
- Platform-specific decorators and lifecycle methods

* chore: add changeset for @esengine/cli

* fix(ci): fix YAML syntax error in ai-issue-helper workflow

* fix(cli): resolve file system race conditions (CodeQL)

* chore(ci): remove unused and broken workflows

* fix(ci): fix YAML encoding in release.yml
2025-12-26 16:18:59 +08:00
YHH
155411e743 refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
2025-12-26 14:50:35 +08:00
YHH
a84ff902e4 fix: 修复 changesets 验证错误 (#337)
- 简化 ignore 配置,只保留 editor-app
- 设置内部包为 private: true
  - build-config, engine, ecs-engine-bindgen
  - editor-core, editor-runtime
  - 所有 *-editor 插件包

这样 changesets 可以正常工作,private 包不会被发布
2025-12-26 09:06:52 +08:00
YHH
54038e3250 feat: 配置 changesets 版本管理和自动发布 (#336)
- 安装 @changesets/cli 和 @changesets/changelog-github
- 配置 .changeset/config.json
- 添加 npm scripts
- 创建 release-changesets.yml GitHub Action
2025-12-25 23:07:44 +08:00
yhh
5544fca002 docs: update changelog for v2.4.2 2025-12-25 20:42:15 +08:00
yhh
88b5ffc0a7 fix(ci): don't mark npm package releases as latest 2025-12-25 20:37:50 +08:00
yhh
0c0a5f10f7 chore(core): release v2.4.2 2025-12-25 20:31:51 +08:00
YHH
56e322de7f refactor(core): 优化 PlatformWorkerPool 和 IncrementalSerializer 代码规范 (#335)
PlatformWorkerPool:
- 添加 IWorkerPoolStatus 导出接口
- 添加便捷 getter: isDestroyed, workerCount, isReady, hasPendingTasks
- 提取 createTask/completeTask 方法减少重复代码
- 添加 ERROR_POOL_DESTROYED 常量
- 添加代码段分隔符和双语注释

IncrementalSerializer:
- 所有公共 API 添加 @zh/@en 双语注释
- 类型改用 interface 并添加 readonly
- SceneDataChange.value 从 any 改为 unknown
- 导出 SceneSnapshot, IIncrementalStats 接口
- 提取 DEFAULT_OPTIONS 常量
- 优化 getIncrementalStats 从 6 次 filter 改为 2 次循环
2025-12-25 18:39:15 +08:00
YHH
c2ebd387f2 refactor(core): 优化 WorkerEntitySystem 实现 (#334)
重构改进:
- 分离 PlatformWorkerPool 到独立文件
- 使用 Map 管理 Worker 任务状态,替代 (worker as any)._currentTask
- 提取通用批次分割逻辑 splitIntoBatches
- 添加 IWorkerMessageData 接口提高类型安全
- 添加 WorkerState 枚举提高可读性
- 添加规范的双语注释 (@zh/@en)
- 导出新类型 IWorkerSystemConfig 和 ProcessingMode
- 保留 WorkerSystemConfig 类型别名向后兼容

代码组织:
- 按功能分组方法 (初始化/处理/批次/结果应用等)
- 减少 any 类型使用
- 统一命名风格
2025-12-25 18:08:17 +08:00
YHH
e5e647f1a4 feat(pathfinding): 添加寻路系统模块 (#333)
* feat(pathfinding): 添加寻路系统模块

实现完整的寻路系统,支持 A* 算法、网格地图、导航网格和路径平滑:

A* 寻路算法:
- 高效的二叉堆优先队列
- 可配置的启发式权重
- 最大搜索节点限制
- 支持对角移动和穿角避免

网格地图 (GridMap):
- 基于二维数组的网格地图
- 支持 4 方向和 8 方向移动
- 可变移动代价
- 从数组或字符串加载地图

导航网格 (NavMesh):
- 凸多边形导航网格
- 自动检测相邻多边形
- 漏斗算法路径优化
- 适合复杂地形

路径平滑:
- Bresenham 视线检测
- 射线投射视线检测
- 视线简化器 (移除不必要的拐点)
- Catmull-Rom 曲线平滑
- 组合平滑器

启发式函数:
- 曼哈顿距离 (4方向)
- 欧几里得距离 (任意方向)
- 切比雪夫距离 (8方向)
- 八角距离 (8方向,对角线√2)

蓝图节点 (8个):
- FindPath: 基础寻路
- FindPathSmooth: 平滑寻路
- IsWalkable: 检查可通行性
- GetPathLength: 获取路径点数
- GetPathDistance: 获取路径距离
- GetPathPoint: 获取路径点
- MoveAlongPath: 沿路径移动
- HasLineOfSight: 视线检测

* chore: update pnpm-lock.yaml for pathfinding package
2025-12-25 17:24:24 +08:00
YHH
4d501ba448 feat(effect): 添加效果系统模块 (#332)
实现 Buff/Debuff 效果管理和属性修改器系统:

核心功能:
- 效果容器管理效果的应用、移除和更新
- 支持持续时间类型: 永久/计时/条件
- 支持叠加规则: 刷新/叠加/独立/替换/忽略
- 效果标签系统用于分类和查询
- 效果优先级和互斥标签支持
- 效果事件系统 (应用/移除/叠加/刷新/跳动/过期)

修改器系统:
- 属性修改器支持加法/乘法/覆盖/最小值/最大值操作
- 优先级分层计算 (基础/加法/乘法/最终)
- 数值计算器自动按优先级应用修改器

蓝图节点 (12个):
- ApplyEffect: 应用效果
- RemoveEffect/RemoveEffectByTag: 移除效果
- HasEffect/HasEffectTag: 检查效果
- GetEffectStacks/GetEffectRemainingTime/GetEffectCount: 查询效果
- ClearAllEffects: 清除所有效果
- OnEffectApplied/OnEffectRemoved/OnEffectTick: 效果事件
2025-12-25 15:17:06 +08:00
YHH
275124b66c feat(procgen): 添加程序化生成工具包 (#331)
- 添加噪声函数 (Perlin, Simplex, Worley, FBM)
- 添加种子随机数生成器 (SeededRandom)
- 添加加权随机选择和洗牌工具
- 添加蓝图节点 (SampleNoise2D, SeededRandom, WeightedPick 等)
2025-12-25 14:33:19 +08:00
YHH
25936c19e9 feat(network): 添加状态同步和客户端预测模块 (#330)
* feat(network): 添加状态同步和客户端预测模块

- 添加状态快照接口和快照缓冲区实现
- 实现线性插值和赫尔米特插值器
- 实现客户端预测和服务器校正系统
- 添加网络相关蓝图节点 (IsLocalPlayer, IsServer 等)

* chore: update pnpm-lock.yaml
2025-12-25 14:03:12 +08:00
YHH
f43631a1e1 feat(spatial): 添加 AOI 兴趣区域系统 (#329)
添加 AOI (Area of Interest) 兴趣区域管理功能:

核心接口:
- IAOIManager<T> - AOI 管理器接口
- IAOIEvent<T> - AOI 事件类型
- IAOIObserverConfig - 观察者配置

实现:
- GridAOI<T> - 基于网格的 AOI 实现
- 实体进入/离开视野事件
- 视野范围配置
- 订阅者模式

蓝图节点:
- GetEntitiesInView - 获取视野内实体
- GetObserversOf - 获取能看到目标的观察者
- CanSee - 检查是否可见
- EventEntityEnterView - 实体进入视野事件
- EventEntityExitView - 实体离开视野事件

服务令牌:
- AOIManagerToken - AOI 管理器服务令牌
2025-12-25 13:24:26 +08:00
YHH
f8c181836e refactor(blueprint): 使用 ecs-framework 的 Entity 和 IScene 类型 (#328)
- 从 @esengine/ecs-framework 导入 Entity 和 IScene 类型
- 移除 ExecutionContext.ts 中的自定义 IEntity 和 IScene 接口
- 更新 BlueprintComponent.ts、BlueprintVM.ts、BlueprintSystem.ts 使用正确的类型

这使得 blueprint 包与 ecs-framework 的类型保持一致,避免重复定义接口。
2025-12-25 13:07:09 +08:00
YHH
840eb3452e feat(fsm): 添加有限状态机模块 (#327)
- 添加 IStateMachine 接口,支持泛型状态和上下文
- 添加 StateMachine 实现
- 支持状态定义 (onEnter/onExit/onUpdate)
- 支持转换定义和条件
- 支持事件监听 (onChange/onEnter/onExit)
- 添加 8 个蓝图节点:
  - GetCurrentState, TransitionTo, CanTransition
  - IsInState, WasInState, GetStateDuration
  - EvaluateTransitions, ResetStateMachine
2025-12-25 12:51:52 +08:00
YHH
0bf849e193 feat(blueprint): 蓝图组合系统 (#326)
* feat(blueprint): 添加蓝图组合系统

- 添加 IBlueprintFragment 接口和实现,支持可重用蓝图片段
- 添加 IBlueprintComposer 接口和实现,支持片段组合
- 添加 FragmentRegistry 片段注册表
- 支持暴露引脚连接和编译成完整蓝图
- 支持片段资产序列化格式

* fix(blueprint): 移除未使用的 ExposedPin 导入
2025-12-25 12:42:57 +08:00
YHH
ebb984d354 feat(timer): 添加定时器和冷却系统 (#325)
- 添加 ITimerService 接口和 TimerService 实现
- 支持一次性定时器和重复定时器
- 支持冷却系统 (startCooldown/isCooldownReady/getCooldownProgress)
- 添加 8 个蓝图节点:
  - StartCooldown, IsCooldownReady, GetCooldownProgress
  - ResetCooldown, GetCooldownInfo
  - HasTimer, CancelTimer, GetTimerRemaining
2025-12-25 12:29:59 +08:00
YHH
068ca4bf69 feat(spatial): 空间查询和索引系统 (#324)
* feat(spatial): 添加空间查询包

- ISpatialQuery: 空间查询接口
  - findInRadius, findInRect, findNearest, findKNearest
  - raycast, raycastFirst
  - IBounds, IRaycastHit, SpatialFilter 类型

- ISpatialIndex: 空间索引接口
  - insert, remove, update, clear, getAll

- GridSpatialIndex: 网格空间索引实现
  - 基于均匀网格的空间划分
  - 支持所有 ISpatialQuery 操作

- 工具函数
  - createBounds, isPointInBounds, boundsIntersect
  - distanceSquared, distance

* feat(spatial): 添加空间查询蓝图节点

- 添加 FindInRadius/FindInRect/FindNearest/FindKNearest 节点
- 添加 Raycast/RaycastFirst 射线检测节点
- 每个节点包含模板和执行器
- 使用 menuPath: ['Spatial', ...] 组织节点菜单
2025-12-25 12:15:06 +08:00
YHH
4089051731 feat(blueprint): 添加蓝图触发器系统 (#323)
* feat(blueprint): 添加蓝图触发器系统

- TriggerTypes: 定义触发器类型和上下文接口
  - tick/input/collision/message/timer/stateEnter/stateExit/custom
  - 各类型的上下文接口和工厂函数

- TriggerCondition: 触发条件系统
  - ITriggerCondition 接口
  - 复合条件 (CompositeCondition, NotCondition)
  - 通用条件 (AlwaysTrue, AlwaysFalse, TriggerType, EntityId)
  - 特定类型条件 (InputAction, MessageName, StateName, TimerId, CollisionEntity, CustomEvent)
  - ConditionBuilder 链式 API

- BlueprintTrigger: 触发器核心实现
  - IBlueprintTrigger 接口
  - BlueprintTrigger 实现类
  - TriggerRegistry 触发器注册表
  - 各类型触发器工厂函数

- TriggerDispatcher: 触发器调度系统
  - ITriggerDispatcher 调度器接口
  - TriggerDispatcher 实现
  - EntityTriggerManager 实体触发器管理器

* feat(blueprint): 添加触发器相关事件节点

- EventInput: 输入事件节点 (action/value/pressed/released)
- EventCollisionEnter/Exit: 碰撞事件节点
- EventMessage: 消息事件节点
- EventTimer: 定时器事件节点
- EventStateEnter/Exit: 状态机事件节点
2025-12-25 11:35:29 +08:00
YHH
6b8b65ae16 feat(script-runtime): 服务器端蓝图执行模块 (#322)
* feat(script-runtime): 添加服务器端蓝图执行模块

- ServerBlueprintVM: 服务器端蓝图虚拟机
- CPULimiter: CPU 时间和步数限制
- IntentCollector: 意图收集系统
- FileMemoryStore: 文件系统持久化
- ServerExecutionContext: 服务器执行上下文

* refactor(script-runtime): 分离引擎接口与游戏逻辑

- 重构 IntentTypes.ts 只保留基础 IIntent 接口和通用常量
- IntentCollector 改为泛型类,支持任意意图类型
- ServerExecutionContext 改为泛型类,支持任意游戏状态类型
- ServerBlueprintVM 改为泛型类,使用 TGameState 和 TIntent 类型参数
- 移除游戏特定类型(IUnitState, ISpawnerState 等),由游戏项目定义
- 添加 IntentKeyExtractor 机制用于防止重复意图

* feat(script-runtime): 添加服务器端游戏循环框架

- PlayerSession: 封装单个玩家的 VM、蓝图和 Memory 状态
- TickScheduler: 管理所有玩家会话,调度每 tick 的蓝图执行
- IIntentProcessor: 意图处理器接口,由游戏项目实现
- IntentProcessorBase: 意图处理器基类,提供常用处理模式
- IntentProcessorRegistry: 按类型注册意图处理器
- GameLoop: 完整的游戏主循环,协调各组件工作

* feat(script-runtime): 添加通用蓝图节点

Memory 节点:
- GetMemory: 读取玩家 Memory
- SetMemory: 写入玩家 Memory
- HasMemoryKey: 检查键是否存在
- DeleteMemory: 删除 Memory 键

Log 节点:
- Log: 记录日志
- Warn: 记录警告
- Error: 记录错误

Game 信息节点:
- GetTick: 获取当前 tick
- GetPlayerId: 获取玩家 ID
- GetDeltaTime: 获取增量时间
- GetGameState: 获取游戏状态

提供 registerScriptRuntimeNodes() 用于批量注册节点

* fix(script-runtime): 修复 CI 构建错误

- 更新 tsconfig.json 继承 tsconfig.base.json
- 添加 references 到 core 和 blueprint 包
- 更新 pnpm-lock.yaml

* fix(script-runtime): 修复 DTS 构建错误

- 添加 tsconfig.build.json 用于 tsup 构建
- 更新 tsup.config.ts 使用 tsconfig.build.json
- 分离构建配置和类型检查配置
2025-12-25 11:00:43 +08:00
YHH
a75c61c049 docs: 添加 Discord 社区链接 (#321) 2025-12-24 23:35:43 +08:00
YHH
770c05402d docs: 优化 README 包列表展示 (#320) 2025-12-24 23:33:12 +08:00
YHH
9d581ccd8d ci: 集成 Turborepo Remote Cache (#319) 2025-12-24 23:06:38 +08:00
YHH
235c432edb feat(network): 基于 TSRPC 的网络同步模块 (#318)
- network-protocols: 共享协议包,使用 TSRPC CLI 生成完整类型验证
- network: 浏览器客户端,提供 NetworkPlugin、NetworkService 和同步系统
- network-server: Node.js 服务端,提供 GameServer 和房间管理
2025-12-24 22:49:29 +08:00
YHH
dbc6793dc4 refactor: 代码规范化与依赖清理 (#317)
* refactor(deps): 统一编辑器包依赖配置 & 优化分层架构

- 将 ecs-engine-bindgen 提升为 Layer 1 核心包
- 统一 9 个编辑器包的依赖声明模式
- 清理废弃的包目录 (ui, ui-editor, network-*)

* refactor(tokens): 修复 PrefabService 令牌冲突 & 补充 module.json

- 将 editor-core 的 PrefabServiceToken 改名为 EditorPrefabServiceToken
  避免与 asset-system 的 PrefabServiceToken 冲突 (Symbol.for 冲突)
- 为 mesh-3d 添加 module.json
- 为 world-streaming 添加 module.json

* refactor(editor-core): 整理导出结构 & 添加 blueprint tokens.ts

- 按功能分组整理 editor-core 的 65 行导出
- 添加清晰的分组注释 (中英双语)
- 为 blueprint 添加占位符 tokens.ts

* chore(editor): 为 14 个编辑器插件包添加 module.json

统一编辑器包的模块配置,包含:
- isEditorPlugin 标识
- runtimeModule 关联
- exports 导出清单 (inspectors, panels, gizmos)

* refactor(core): 改进类型安全 - 减少 as any 使用

- 添加 GlobalTypes.ts 定义小游戏平台和 Chrome API 类型
- SoAStorage 使用 IComponentTypeMetadata 替代 as any
- PlatformDetector 使用类型安全的平台检测
- 添加 ISoAStorageStats/ISoAFieldStats 接口

* feat(editor): 添加 EditorServicesContext 解决 prop drilling

- 新增 contexts/EditorServicesContext.tsx 提供统一服务访问
- App.tsx 包裹 EditorServicesProvider
- 提供 useEditorServices/useMessageHub 等便捷 hooks
- SceneHierarchy 添加迁移注释,后续可移除 props

* docs(editor): 澄清 inspector 目录架构关系

- inspector/ 标记为内部实现,添加 @deprecated 警告
- inspectors/ 标记为公共 API 入口点
- 添加架构说明文档

* refactor(editor): 添加全局类型声明消除 window as any

- 创建 editor-app/src/global.d.ts 声明 Window 接口扩展
- 创建 editor-core/src/global.d.ts 声明 Window 接口扩展
- 更新 App.tsx 使用类型安全的 window 属性访问
- 更新 PluginLoader.ts 使用 window.__ESENGINE_PLUGINS__
- 更新 PluginSDKRegistry.ts 使用 window.__ESENGINE_SDK__
- 更新 UserCodeService.ts 使用类型安全的全局变量访问

* refactor(editor): 提取项目和场景操作到独立 hooks

- 创建 useProjectActions hook 封装项目操作
- 创建 useSceneActions hook 封装场景操作
- 为渐进式重构 App.tsx 做准备

* refactor(editor): 清理冗余代码和未使用文件

删除的目录和文件:
- application/state/ - 重复的状态管理(与 stores/ 重复)
- 8 个孤立 CSS 文件(对应组件不存在)
- AssetBrowser.tsx - 仅为 ContentBrowser 的向后兼容包装
- AssetPicker.tsx - 未被使用
- AssetPickerDialog.tsx (顶级) - 已被 dialogs/ 版本取代
- EntityInspector.tsx (顶级) - 已被 inspectors/views/ 版本取代

修复:
- 移除 App.tsx 中未使用的导入
- 更新 application/index.ts 移除已删除模块
- 修复 useProjectActions.ts 的 MutableRefObject 类型

* refactor(editor): 统一 inspectors 模块导出结构

- 在 inspectors/index.ts 重新导出 PropertyInspector
- 创建 inspectors/fields/index.ts barrel export
- 导出 views、fields、common 子模块
- 更新 EntityInspector 使用统一入口导入

* refactor(editor): 删除废弃的 Profiler 组件

删除未使用的组件(共 1059 行):
- ProfilerPanel.tsx (229 行)
- ProfilerWindow.tsx (589 行)
- ProfilerDockPanel.tsx (241 行)
- ProfilerPanel.css
- ProfilerDockPanel.css

保留:AdvancedProfiler + AdvancedProfilerWindow(正在使用)

* refactor(runtime-core): 统一依赖处理与插件状态管理

- 新增 DependencyUtils 统一拓扑排序和依赖验证
- 新增 PluginState 定义插件生命周期状态机
- 合并 UnifiedPluginLoader 到 PluginLoader
- 清理 index.ts 移除不必要的 Token re-exports
- 新增 RuntimeMode/UserCodeRealm/ImportMapGenerator

* refactor(editor-core): 使用统一的 ImportMapGenerator

- WebBuildPipeline 使用 runtime-core 的 generateImportMap
- UserCodeService 添加 ImportMap 相关接口

* feat(compiler): 增强 esbuild 查找策略

- 支持本地 node_modules、pnpm exec、npx、全局多种来源
- EngineService 使用 RuntimeMode

* refactor(runtime-core): 简化 GameRuntime 代码

- 合并 _disableGameLogicSystems/_enableGameLogicSystems 为 _setGameLogicSystemsEnabled
- 精简本地 Token 定义的注释

* refactor(editor-core): 引入 BaseRegistry 基类消除代码重复

- 新增 BaseRegistry 和 PrioritizedRegistry 基类
- 重构 CompilerRegistry, InspectorRegistry, FieldEditorRegistry
- 统一注册表的日志记录和错误处理

* refactor(editor-core): 扩展 BaseRegistry 重构

- ComponentInspectorRegistry 继承 PrioritizedRegistry
- EditorComponentRegistry 继承 BaseRegistry
- EntityCreationRegistry 继承 BaseRegistry
- PropertyRendererRegistry 继承 PrioritizedRegistry
- 导出 BaseRegistry 基类供外部使用
- 统一双语注释格式

* refactor(editor-core): 代码优雅性优化

CommandManager:
- 提取 tryMergeWithLast() 和 pushToUndoStack() 消除重复代码
- 统一双语注释格式

FileActionRegistry:
- 提取 normalizeExtension() 消除扩展名规范化重复
- 统一私有属性命名风格(_前缀)
- 使用 createRegistryToken 统一 Token 创建

BaseRegistry:
- 添加 IOrdered 接口
- 添加 sortByOrder() 排序辅助方法

EntityCreationRegistry:
- 使用 sortByOrder() 简化排序逻辑

* refactor(editor-core): 统一日志系统 & 代码规范优化

- GizmoRegistry: 使用 createLogger 替代 console.warn
- VirtualNodeRegistry: 使用 createLogger 替代 console.warn
- WindowRegistry: 使用 logger、添加 _ 前缀、导出 IWindowRegistry token
- EditorViewportService: 使用 createLogger 替代 console.warn
- ComponentActionRegistry: 使用 logger、添加 _ 前缀、返回值改进
- SettingsRegistry: 使用 logger、提取 ensureCategory/ensureSection 方法
- 添加 WindowRegistry 到主导出

* refactor(editor-core): ModuleRegistry 使用 logger 替代 console

* refactor(editor-core): SerializerRegistry/UIRegistry 添加 token 和 _ 前缀

* refactor(editor-core): UIRegistry 代码优雅性 & Token 命名统一

- UIRegistry: 提取 _sortByOrder 消除 6 处重复排序逻辑
- UIRegistry: 添加分节注释和双语文档
- FieldEditorRegistry: Token 重命名为 FieldEditorRegistryToken
- PropertyRendererRegistry: Token 重命名为 PropertyRendererRegistryToken

* refactor(core): 统一日志系统 - console 替换为 logger

- ComponentSerializer: 使用 logger 替代 console.warn
- ComponentRegistry: console.warn → logger.warn (已有 logger)
- SceneSerializer: 添加 logger,替换 console.warn/error
- SystemScheduler: 添加 logger,替换 console.warn
- VersionMigration: 添加 logger,替换所有 console.warn
- RuntimeModeService: console.error → logger.error
- Core.ts: _logger 改为 readonly,双语错误消息
- SceneSerializer 修复:使用 getComponentTypeName 替代 constructor.name

* fix(core): 修复 constructor.name 压缩后失效问题

- Scene.ts: 使用 system.systemName 替代 system.constructor.name
- CommandBuffer.ts: 使用 getComponentTypeName() 替代 constructor.name

* refactor(editor-core): 代码规范优化 - 私有方法命名 & 日志统一

- BuildService: console → logger
- FileActionRegistry: 添加 logger, 私有方法 _ 前缀
- SettingsRegistry: 私有方法 _ 前缀 (ensureCategory → _ensureCategory)

* refactor(core): Scene.ts 私有变量命名规范化

- logger → _logger (遵循私有变量 _ 前缀规范)

* refactor(editor-core): 服务类私有成员命名规范化

- CommandManager: 私有变量/方法添加 _ 前缀
  - undoStack/redoStack/config/isExecuting
  - tryMergeWithLast/pushToUndoStack
- LocaleService: 私有变量/方法添加 _ 前缀
  - currentLocale/translations/changeListeners
  - deepMerge/getNestedValue/loadSavedLocale/saveLocale

* refactor(core): 私有成员命名规范化 & 单例模式优化

- Component.ts: _idGenerator 私有静态变量规范化
- PlatformManager.ts: _instance, _adapter, _logger 规范化
- AutoProfiler.ts: _instance, _config 及所有私有方法规范化
- ProfilerSDK.ts: _instance, _config 及所有私有方法规范化
- ComponentPoolManager: _instance, _pools, _usageTracker 规范化
- GlobalEventBus: _instance 规范化
- 添加中英双语 JSDoc 注释

* refactor(editor-app,behavior-tree-editor): 私有成员 & 单例模式命名规范化

editor-app:
- EngineService: private static instance → _instance
- EditorEngineSync: 所有私有成员添加 _ 前缀
- RuntimeResolver: 所有私有成员和方法添加 _ 前缀
- SettingsService: 所有私有成员和方法添加 _ 前缀

behavior-tree-editor:
- GlobalBlackboardService: 所有私有成员和方法添加 _ 前缀
- NotificationService: private static instance → _instance
- NodeRegistryService: 所有私有成员和方法添加 _ 前缀
- TreeStateAdapter: private static instance → _instance

* fix(editor-runtime): 添加 editor-core 到 external 避免传递依赖问题

将 @esengine/editor-core 添加到 vite external 配置,
避免 editor-core → runtime-core → ecs-engine-bindgen 的传递依赖
被错误地打包进 editor-runtime.js,导致 CI 构建失败。

* fix(core): 修复空接口 lint 错误

将 IByteDanceMiniGameAPI、IAlipayMiniGameAPI、IBaiduMiniGameAPI 从空接口改为类型别名,修复 no-empty-object-type 规则报错
2025-12-24 20:57:08 +08:00
YHH
58f70a5783 refactor(core): 代码结构优化 & 添加独立 ECS 框架文档 (#316)
Core 库优化:
- Scene.ts: 提取 _runSystemPhase() 消除 update/lateUpdate 重复代码
- World.ts: 可选链简化、提取 _isSceneCleanupCandidate() 消除重复条件
- Entity.ts: 移除过度设计的 EntityComparer
- 清理方法内部冗余注释,保持代码自解释

文档改进:
- 为 core 包创建独立 README (中英文)
- 明确 ECS 框架可独立用于 Cocos/Laya 等引擎
- 添加 sparse-checkout 命令说明,支持只克隆 core 源码
- 主 README 添加 ECS 独立使用提示
2025-12-23 18:00:21 +08:00
YHH
828ff969e1 feat(3d): FBX/GLTF/OBJ 加载器与骨骼动画支持 (#315)
* feat(3d): FBX/GLTF/OBJ 加载器与骨骼动画支持

* chore: 更新 pnpm-lock.yaml

* fix: 移除未使用的变量和方法

* fix: 修复 mesh-3d-editor tsconfig 引用路径

* fix: 修复正则表达式 ReDoS 漏洞
2025-12-23 15:34:01 +08:00
yhh
49dd6a91c6 docs: v2.4.1 changelog 2025-12-23 15:22:33 +08:00
yhh
1e048d5c04 chore(core): bump version to 2.4.1 2025-12-23 15:20:19 +08:00
yhh
dff2ec564b fix(core): IntervalSystem时间累加bug修复 & 添加Core.paused文档
- 修复 onCheckProcessing() 在 update/lateUpdate 被调用两次导致时间累加翻倍的问题
- 添加 Core.paused 游戏暂停文档(中/英文)
- 新增 IntervalSystem 相关测试用例
2025-12-23 09:41:22 +08:00
yhh
2381919a5c fix(core): Cocos Creator 兼容性 - 类型导出修复
- 将 export interface 转换为 export type 以兼容 Rollup
- 分离类型导入/导出使用 import type 和 export type
- 修复 QueryCondition, QueryResult, SupportedTypedArray 等类型的重导出
2025-12-22 14:54:38 +08:00
yhh
66d9f428b3 feat: 3D 编辑器支持 - 网格、相机、Gizmo 2025-12-22 12:40:43 +08:00
YHH
a1e1189f9d feat(fairygui): FairyGUI 完整集成 (#314)
* feat(fairygui): FairyGUI ECS 集成核心架构

实现 FairyGUI 的 ECS 原生集成,完全替代旧 UI 系统:

核心类:
- GObject: UI 对象基类,支持变换、可见性、关联、齿轮
- GComponent: 容器组件,管理子对象和控制器
- GRoot: 根容器,管理焦点、弹窗、输入分发
- GGroup: 组容器,支持水平/垂直布局

抽象层:
- DisplayObject: 显示对象基类
- EventDispatcher: 事件分发
- Timer: 计时器
- Stage: 舞台,管理输入和缩放

布局系统:
- Relations: 约束关联管理
- RelationItem: 24 种关联类型

基础设施:
- Controller: 状态控制器
- Transition: 过渡动画
- ScrollPane: 滚动面板
- UIPackage: 包管理
- ByteBuffer: 二进制解析

* refactor(ui): 删除旧 UI 系统,使用 FairyGUI 替代

* feat(fairygui): 实现 UI 控件

- 添加显示类:Image、TextField、Graph
- 添加基础控件:GImage、GTextField、GGraph
- 添加交互控件:GButton、GProgressBar、GSlider
- 更新 IRenderCollector 支持 Graph 渲染
- 扩展 Controller 添加 selectedPageId
- 添加 STATE_CHANGED 事件类型

* feat(fairygui): 现代化架构重构

- 增强 EventDispatcher 支持类型安全、优先级和传播控制
- 添加 PropertyBinding 响应式属性绑定系统
- 添加 ServiceContainer 依赖注入容器
- 添加 UIConfig 全局配置系统
- 添加 UIObjectFactory 对象工厂
- 实现 RenderBridge 渲染桥接层
- 实现 Canvas2DBackend 作为默认渲染后端
- 扩展 IRenderCollector 支持更多图元类型

* feat(fairygui): 九宫格渲染和资源加载修复

- 修复 FGUIUpdateSystem 支持路径和 GUID 两种加载方式
- 修复 GTextInput 同时设置 _displayObject 和 _textField
- 实现九宫格渲染展开为 9 个子图元
- 添加 sourceWidth/sourceHeight 用于九宫格计算
- 添加 DOMTextRenderer 文本渲染层(临时方案)

* fix(fairygui): 修复 GGraph 颜色读取

* feat(fairygui): 虚拟节点 Inspector 和文本渲染支持

* fix(fairygui): 编辑器状态刷新和遗留引用修复

- 修复切换 FGUI 包后组件列表未刷新问题
- 修复切换组件后 viewport 未清理旧内容问题
- 修复虚拟节点在包加载后未刷新问题
- 重构为事件驱动架构,移除轮询机制
- 修复 @esengine/ui 遗留引用,统一使用 @esengine/fairygui

* fix: 移除 tsconfig 中的 @esengine/ui 引用
2025-12-22 10:52:54 +08:00
YHH
96b5403d14 refactor(render): 抽象图形后端并迁移渲染器 (#313)
* refactor(render): 抽象图形后端并迁移渲染器

- 新增 engine-shared 包,定义 GraphicsBackend trait 抽象层
- 实现 WebGL2Backend 作为首个后端实现
- 迁移 Renderer2D、SpriteBatch、GridRenderer、GizmoRenderer 使用新抽象
- 修复 VAO 创建时索引缓冲区绑定状态泄漏问题
- 新增 create_vertex_buffer_sized 方法支持预分配缓冲区

* fix(serialization): 修复序列化循环引用导致栈溢出

- 在 serializeValue 添加 WeakSet 检测循环引用
- 跳过已访问对象避免无限递归

* refactor(serialization): 提取 ValueSerializer 统一序列化逻辑

- 新增 ValueSerializer 模块,函数式设计
- 支持可扩展类型处理器注册
- 移除 ComponentSerializer/SceneSerializer 重复代码
- 内置 Date/Map/Set 类型支持

* fix: CodeQL 类型检查警告
2025-12-19 22:46:33 +08:00
YHH
4b74db3f2d refactor(render): 统一渲染API并修复双重渲染问题 (#312)
- 将 submitSpritesDirectly 重命名为 submitSprites,移除旧的对象数组 API
- RenderBatcher 采用 SoA 模式,预分配类型数组避免每帧 GC
- 拆分 worldBatcher 和 screenBatcher 修复 play 模式下的双重渲染
- 移除未使用的材质实例管理代码
2025-12-19 18:40:19 +08:00
YHH
e24c850568 refactor: 类型安全与接口清理 (#311)
* refactor: 分解 IEngineBridge 为单一职责接口

- 新增 ITextureService, IDynamicAtlasService, ICoordinateService, IRenderConfigService
- 移除 EngineBridgeToken,改用具体服务 Token
- 更新 camera, ui, particle 等模块使用新接口
- 优化装饰器类型安全,使用 Symbol-based metadata 访问模式

* refactor: 删除 plugin-types 包,统一 createServiceToken 实现

- 移动 IEditorModuleBase 接口到 engine-core
- 移除 engine-core 和 editor-core 对 plugin-types 的依赖
- 删除冗余的 plugin-types 包
- 统一使用 core 中基于 Symbol.for() 的 createServiceToken

* refactor: 统一 IPlugin 接口,移除 deprecated 别名

- 移除 engine-core、editor-core、runtime-core 中的 IPlugin 别名
- 模块插件统一使用 IRuntimePlugin(运行时)或 IEditorPlugin(编辑器)
- 保留 core 包中的 IPlugin 作为 ECS 核心插件接口(不同概念)
- 更新所有消费方使用正确的类型

* refactor: 重命名 editor-core ComponentRegistry 为 EditorComponentRegistry

- 消除与 core 包 ComponentRegistry(ECS 位掩码管理)的命名歧义
- editor-core 的 EditorComponentRegistry 专用于编辑器组件元数据
- 更新所有编辑器包使用新名称
2025-12-19 17:48:18 +08:00
YHH
ecdb8f2021 feat: UI输入框IME支持和编辑器Inspector重构 (#310)
UI系统改进:
- 添加 IMEHelper 支持中文/日文/韩文输入法
- UIInputFieldComponent 添加组合输入状态管理
- UIInputSystem 添加 IME 事件处理
- UIInputFieldRenderSystem 优化渲染逻辑
- UIRenderCollector 增强纹理处理

引擎改进:
- EngineBridge 添加新的渲染接口
- EngineRenderSystem 优化渲染流程
- Rust 引擎添加新的渲染功能

编辑器改进:
- 新增模块化 Inspector 组件架构
- EntityRefField 增强实体引用选择
- 优化 FlexLayoutDock 和 SceneHierarchy 样式
- 添加国际化文本
2025-12-19 15:45:14 +08:00
YHH
536c4c5593 refactor(ui): UI 系统架构重构 (#309)
* feat(ui): 动态图集系统与渲染调试增强

## 核心功能

### 动态图集系统 (Dynamic Atlas)
- 新增 DynamicAtlasManager:运行时纹理打包,支持 MaxRects 算法
- 新增 DynamicAtlasService:自动纹理加载与图集管理
- 新增 BinPacker:高效矩形打包算法
- 支持动态/固定两种扩展策略
- 自动 UV 重映射,实现 UI 元素合批渲染

### Frame Debugger 增强
- 新增合批分析面板,显示批次中断原因
- 新增 UI 元素层级信息(depth, worldOrderInLayer)
- 新增实体高亮功能,点击可在场景中定位
- 新增动态图集可视化面板
- 改进渲染原语详情展示

### 闪光效果 (Shiny Effect)
- 新增 UIShinyEffectComponent:UI 闪光参数配置
- 新增 UIShinyEffectSystem:材质覆盖驱动的闪光动画
- 新增 ShinyEffectComponent/System(Sprite 版本)

## 引擎层改进

### Rust 纹理管理扩展
- create_blank_texture:创建空白 GPU 纹理
- update_texture_region:局部纹理更新
- 支持动态图集的 GPU 端操作

### 材质系统
- 新增 effects/ 目录:ShinyEffect 等效果实现
- 新增 interfaces/ 目录:IMaterial 等接口定义
- 新增 mixins/ 目录:可组合的材质功能

### EngineBridge 扩展
- 新增 createBlankTexture/updateTextureRegion 方法
- 改进纹理加载回调机制

## UI 渲染改进
- UIRenderCollector:支持合批调试信息
- 稳定排序:addIndex 保证渲染顺序一致性
- 九宫格渲染优化
- 材质覆盖支持

## 其他改进
- 国际化:新增 Frame Debugger 相关翻译
- 编辑器:新增渲染调试入口
- 文档:新增架构设计文档目录

* refactor(ui): 引入新基础组件架构与渲染工具函数

Phase 1 重构 - 组件职责分离与代码复用:

新增基础组件层:
- UIGraphicComponent: 所有可视 UI 元素的基类(颜色、透明度、raycast)
- UIImageComponent: 纹理显示组件(支持简单、切片、平铺、填充模式)
- UISelectableComponent: 可交互元素的基类(状态管理、颜色过渡)

新增渲染工具:
- UIRenderUtils: 提取共享的坐标计算、边框渲染、阴影渲染等工具函数
- getUIRenderTransform: 统一的变换数据提取
- renderBorder/renderShadow: 复用的边框和阴影渲染逻辑

新增渲染系统:
- UIGraphicRenderSystem: 处理新基础组件的统一渲染器

重构现有系统:
- UIRectRenderSystem: 使用新工具函数,移除重复代码
- UIButtonRenderSystem: 使用新工具函数,移除重复代码

这些改动为后续统一渲染系统奠定基础。

* refactor(ui): UIProgressBarRenderSystem 使用渲染工具函数

- 使用 getUIRenderTransform 替代手动变换计算
- 使用 renderBorder 工具函数替代重复的边框渲染
- 使用 lerpColor 工具函数替代重复的颜色插值
- 简化方法签名,使用 UIRenderTransform 类型
- 移除约 135 行重复代码

* refactor(ui): Slider 和 ScrollView 渲染系统使用工具函数

- UISliderRenderSystem: 使用 getUIRenderTransform,简化方法签名
- UIScrollViewRenderSystem: 使用 getUIRenderTransform,简化方法签名
- 统一使用 UIRenderTransform 类型减少参数传递
- 消除重复的变换计算代码

* refactor(ui): 使用 UIWidgetMarker 消除硬编码组件依赖

- 新增 UIWidgetMarker 标记组件
- UIRectRenderSystem 改为检查标记而非硬编码4种组件类型
- 各 Widget 渲染系统自动添加标记组件
- 减少模块间耦合,提高可扩展性

* feat(ui): 实现 Canvas 隔离机制

- 新增 UICanvasComponent 定义 Canvas 渲染组
- UITransformComponent 添加 Canvas 相关字段:canvasEntityId, worldSortingLayer, pixelPerfect
- UILayoutSystem 传播 Canvas 设置给子元素
- UIRenderUtils 使用 Canvas 继承的排序层
- 支持嵌套 Canvas 和不同渲染模式

* refactor(ui): 统一纹理管理工具函数

Phase 4: 纹理管理统一

新增:
- UITextureUtils.ts: 统一的纹理描述符接口和验证函数
  - UITextureDescriptor: 支持 GUID/textureId/path 多种纹理源
  - isValidTextureGuid: GUID 验证
  - getTextureKey: 获取用于合批的纹理键
  - normalizeTextureDescriptor: 规范化各种输入格式
- utils/index.ts: 工具函数导出

修改:
- UIGraphicRenderSystem: 使用新的纹理工具函数
- index.ts: 导出纹理工具类型和函数

* refactor(ui): 实现统一的脏标记机制

Phase 5: Dirty 标记机制

新增:
- UIDirtyFlags.ts: 位标记枚举和追踪工具
  - UIDirtyFlags: Visual/Layout/Transform/Material/Text 标记
  - IDirtyTrackable: 脏追踪接口
  - DirtyTracker: 辅助工具类
  - 帧级别脏状态追踪 (markFrameDirty, isFrameDirty)

修改:
- UIGraphicComponent: 实现 IDirtyTrackable
  - 属性 setter 自动设置脏标记
  - 保留 setDirty/clearDirty 向后兼容
- UIImageComponent: 所有属性支持脏追踪
  - textureGuid/imageType/fillAmount 等变化自动标记
- UIGraphicRenderSystem: 使用 clearDirtyFlags()

导出:
- UIDirtyFlags, IDirtyTrackable, DirtyTracker
- markFrameDirty, isFrameDirty, clearFrameDirty

* refactor(ui): 移除过时的 dirty flag API

移除 UIGraphicComponent 中的兼容性 API:
- 移除 _isDirty getter/setter
- 移除 setDirty() 方法
- 移除 clearDirty() 方法

现在统一使用新的 dirty flag 系统:
- isDirty() / hasDirtyFlag(flags)
- markDirty(flags) / clearDirtyFlags()

* fix(ui): 修复两个 TODO 功能

1. 滑块手柄命中测试 (UIInputSystem)
   - UISliderComponent 添加 getHandleBounds() 计算手柄边界
   - UISliderComponent 添加 isPointInHandle() 精确命中测试
   - UIInputSystem.handleSlider() 使用精确测试更新悬停状态

2. 径向填充渲染 (UIGraphicRenderSystem)
   - 实现 renderRadialFill() 方法
   - 支持 radial90/radial180/radial360 三种模式
   - 支持 fillOrigin (top/right/bottom/left) 和 fillClockwise
   - 使用多段矩形近似饼形填充效果

* feat(ui): 完善 UI 系统架构和九宫格渲染

* fix(ui): 修复文本渲染层级问题并清理调试代码

- 修复纹理就绪后调用 invalidateUIRenderCaches() 导致的无限循环
- 移除 UITextRenderSystem、UIButtonRenderSystem、UIRectRenderSystem 中的首帧调试输出
- 移除 UILayoutSystem 中的布局调试日志
- 清理所有 __UI_RENDER_DEBUG__ 条件日志

* refactor(ui): 优化渲染批处理和输入框组件

渲染系统:
- 修复 RenderBatcher 保持渲染顺序
- 优化 Rust SpriteBatch 避免合并非连续精灵
- 增强 EngineRenderSystem 纹理就绪检测

输入框组件:
- 增强 UIInputFieldComponent 功能
- 改进 UIInputSystem 输入处理
- 新增 TextMeasureService 文本测量服务

* fix(ui): 修复九宫格首帧渲染和InputField输入问题

- 修复九宫格首帧 size=0x0 问题:
  - Viewport.tsx: 预览模式读取图片尺寸存储到 importSettings
  - AssetDatabase: ISpriteSettings 添加 width/height 字段
  - AssetMetadataService: getTextureSpriteInfo 使用元数据尺寸作为后备
  - UIRectRenderSystem: 当 atlasEntry 不存在时使用 spriteInfo 尺寸
  - WebBuildPipeline: 构建时包含 importSettings
  - AssetManager: 从 catalog 初始化时复制 importSettings
  - AssetTypes: IAssetCatalogEntry 添加 importSettings 字段

- 修复 InputField 无法输入问题:
  - UIRuntimeModule: manifest 添加 pluginExport: 'UIPlugin'
  - 确保预览模式正确加载 UI 插件并绑定 UIInputSystem

- 添加调试日志用于排查纹理加载问题

* fix(sprite): 修复类型导出错误

MaterialPropertyOverride 和 MaterialOverrides 应从 @esengine/material-system 导出

* fix(ui-editor): 补充 AnchorPreset 拉伸预设的映射

添加 StretchTop, StretchMiddle, StretchBottom, StretchLeft, StretchCenter, StretchRight 的位置和锚点值映射
2025-12-19 15:33:36 +08:00
yhh
958933cd76 fix(ci): 修正 SignPath artifact-configuration-slug 为 initial 2025-12-16 15:44:42 +08:00
yhh
fbc911463a Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-12-16 15:07:41 +08:00
yhh
5b7746af79 fix(ci): 修复 SignPath 代码签名 artifact 参数错误
SignPath GitHub Action 不支持 github-artifact-name 参数,
改用 github-artifact-id 并通过 GitHub API 获取 artifact ID。

- 移除 download-artifact 步骤(SignPath 直接从 GitHub 获取)
- 添加 get-artifact 步骤通过 API 获取 artifact ID
- 使用 github-artifact-id 替代无效的 github-artifact-name
2025-12-16 15:07:18 +08:00
YHH
9e195ae3fd fix(editor): 修复 Play/Stop 循环中的场景管理器和动态实体问题 (#307)
问题修复:
1. RuntimeSceneManager 在 Stop 后失效
   - 根因:SceneLoadTriggerSystem 闭包缓存了 sceneManager 引用
   - 修复:每次点击时动态从 Core.services 获取服务

2. Play 期间创建的动态实体(如 ClickFx 粒子)Stop 后残留
   - 根因:EntityList.removeAllEntities() 只清空 _entitiesToAdd 队列但没有销毁实体
   - 修复:先销毁待添加队列中的实体再清空

3. 场景切换后动态实体残留
   - 根因:editorSceneLoader 中 saveSceneSnapshot() 覆盖了初始快照
   - 修复:移除该调用,保持 Play 开始时的快照不被覆盖

架构改进:
- RuntimeSceneManager 新增 reset() 方法,区分会话重置和完全销毁
- Viewport 复用 RuntimeSceneManager 实例而非每次创建
- IRuntimeSceneManager 接口补充 setSceneLoader/setBaseUrl 方法
2025-12-16 15:07:11 +08:00
yhh
a18eb5aa3c fix(ci): 保持 Release 为 Draft 状态,防止自动发布 2025-12-16 13:16:33 +08:00
yhh
48d3d14af2 feat(ci): 改进 SignPath 代码签名集成
- 添加 SignPath 配置检查步骤
- 使用 test-signing 策略进行测试
- 即使签名跳过也能继续版本更新 PR
2025-12-16 13:11:59 +08:00
YHH
ed8f6e283b feat: 纹理路径稳定 ID 与架构改进 (#305)
* feat(asset-system): 实现路径稳定 ID 生成器

使用 FNV-1a hash 算法为纹理生成稳定的运行时 ID:
- 新增 _pathIdCache 静态缓存,跨 Play/Stop 循环保持稳定
- 新增 getStableIdForPath() 方法,相同路径永远返回相同 ID
- 修改 loadTextureForComponent/loadTextureByGuid 使用稳定 ID
- clearTextureMappings() 不再清除 _pathIdCache

这解决了 Play/Stop 后纹理 ID 失效的根本问题。

* fix(runtime-core): 移除 Play/Stop 循环中的 clearTextureMappings 调用

使用路径稳定 ID 后,不再需要在快照保存/恢复时清除纹理缓存:
- saveSceneSnapshot() 移除 clearTextureMappings() 调用
- restoreSceneSnapshot() 移除 clearTextureMappings() 调用
- 组件保存的 textureId 在 Play/Stop 后仍然有效

* fix(editor-core): 修复场景切换时的资源泄漏

在 openScene() 加载新场景前先卸载旧场景资源:
- 调用 sceneResourceManager.unloadSceneResources() 释放旧资源
- 使用引用计数机制,仅卸载不再被引用的资源
- 路径稳定 ID 缓存不受影响,保持 ID 稳定性

* fix(runtime-core): 修复 PluginManager 组件注册类型错误

将 ComponentRegistry 类改为 GlobalComponentRegistry 实例:
- registerComponents() 期望 IComponentRegistry 接口实例
- GlobalComponentRegistry 是 ComponentRegistry 的全局实例

* refactor(core): 提取 IComponentRegistry 接口

将组件注册表抽象为接口,支持场景级组件注册:
- 新增 IComponentRegistry 接口定义
- Scene 持有独立的 componentRegistry 实例
- 支持从 GlobalComponentRegistry 克隆
- 各系统支持传入自定义注册表

* refactor(engine-core): 改进插件服务注册机制

- 更新 IComponentRegistry 类型引用
- 优化 PluginServiceRegistry 服务管理

* refactor(modules): 适配新的组件注册接口

更新各模块 RuntimeModule 使用 IComponentRegistry 接口:
- audio, behavior-tree, camera
- sprite, tilemap, world-streaming

* fix(physics-rapier2d): 修复物理插件组件注册

- PhysicsEditorPlugin 添加 runtimeModule 引用
- 适配 IComponentRegistry 接口
- 修复物理组件在场景加载时未注册的问题

* feat(editor-core): 添加 UserCodeService 就绪信号机制

- 新增 waitForReady()/signalReady() API
- 支持等待用户脚本编译完成
- 解决场景加载时组件未注册的时序问题

* fix(editor-app): 在编译完成后调用 signalReady()

确保用户脚本编译完成后发出就绪信号:
- 编译成功后调用 userCodeService.signalReady()
- 编译失败也要发出信号,避免阻塞场景加载

* feat(editor-core): 改进编辑器核心服务

- EntityStoreService 添加调试日志
- AssetRegistryService 优化资产注册
- PluginManager 改进插件管理
- IFileAPI 添加 getFileMtime 接口

* feat(engine): 改进 Rust 纹理管理器

- 支持任意 ID 的纹理加载(非递增)
- 添加纹理状态追踪 API
- 优化纹理缓存清理机制
- 更新 TypeScript 绑定

* feat(ui): 添加场景切换和文本闪烁组件

新增组件:
- SceneLoadTriggerComponent: 场景切换触发器
- TextBlinkComponent: 文本闪烁效果

新增系统:
- SceneLoadTriggerSystem: 处理场景切换逻辑
- TextBlinkSystem: 处理文本闪烁动画

其他改进:
- UIRuntimeModule 适配新组件注册接口
- UI 渲染系统优化

* feat(editor-app): 添加外部文件修改检测

- 新增 ExternalModificationDialog 组件
- TauriFileAPI 支持 getFileMtime
- 场景文件被外部修改时提示用户

* feat(editor-app): 添加渲染调试面板

- 新增 RenderDebugService 和调试面板 UI
- App/ContentBrowser 添加调试日志
- TitleBar/Viewport 优化
- DialogManager 改进

* refactor(editor-app): 编辑器服务和组件优化

- EngineService 改进引擎集成
- EditorEngineSync 同步优化
- AssetFileInspector 改进
- VectorFieldEditors 优化
- InstantiatePrefabCommand 改进

* feat(i18n): 更新国际化翻译

- 添加新功能相关翻译
- 更新中文、英文、西班牙文

* feat(tauri): 添加文件修改时间查询命令

- 新增 get_file_mtime 命令
- 支持检测文件外部修改

* refactor(particle): 粒子系统改进

- 适配新的组件注册接口
- ParticleSystem 优化
- 添加单元测试

* refactor(platform): 平台适配层优化

- BrowserRuntime 改进
- 新增 RuntimeSceneManager 服务
- 导出优化

* refactor(asset-system-editor): 资产元数据改进

- AssetMetaFile 优化
- 导出调整

* fix(asset-system): 移除未使用的 TextureLoader 导入

* fix(tests): 更新测试以使用 GlobalComponentRegistry 实例

修复多个测试文件以适配 ComponentRegistry 从静态类变为实例类的变更:
- ComponentStorage.test.ts: 使用 GlobalComponentRegistry.reset()
- EntitySerializer.test.ts: 使用 GlobalComponentRegistry 实例
- IncrementalSerialization.test.ts: 使用 GlobalComponentRegistry 实例
- SceneSerializer.test.ts: 使用 GlobalComponentRegistry 实例
- ComponentRegistry.extended.test.ts: 使用 GlobalComponentRegistry,同时注册到 scene.componentRegistry
- SystemTypes.test.ts: 在 Scene 创建前注册组件
- QuerySystem.test.ts: mockScene 添加 componentRegistry
2025-12-16 12:46:14 +08:00
yhh
d834ca5e77 ci(editor): add SignPath OSS code signing for Windows builds
- Upload Windows artifacts (exe/msi) for SignPath signing
- Configure SignPath action with organization secrets
- Fix artifact name to 'windows-unsigned'
- Add bilingual comments for required secrets
2025-12-15 10:20:15 +08:00
yhh
85e95ec18c docs: add English entity documentation with EntityHandle 2025-12-15 10:00:44 +08:00
yhh
ff9bc00729 docs: 改进 EntityHandle 文档,添加实际使用场景示例 2025-12-15 09:59:12 +08:00
yhh
7451a78e60 docs: 补充 EntityHandle 实体句柄文档 2025-12-15 09:38:45 +08:00
yhh
23ee2393c6 chore(release): 准备发布 v2.4.0,改进 CI 发布流程
- 更新 @esengine/ecs-framework 版本号到 2.4.0
- 更新中英文 changelog
- CI: 支持标签触发自动发布(v* 或 package-v* 格式)
- CI: 保留手动触发选项
- CI: 标签模式下自动创建 GitHub Release
2025-12-15 09:33:51 +08:00
YHH
cd6ef222d1 feat(ecs): 核心系统改进 - 句柄、调度、变更检测与查询编译 (#304)
新增功能:
- EntityHandle: 轻量级实体句柄 (28位索引 + 20位代数)
- SystemScheduler: 声明式系统调度,支持 @Stage/@Before/@After/@InSet 装饰器
- EpochManager: 帧级变更检测
- CompiledQuery: 预编译类型安全查询

API 改进:
- EntitySystem 添加 getBefore()/getAfter()/getSets() getter 方法
- Entity 添加 markDirty() 辅助方法
- IScene 添加 epochManager 属性
- CommandBuffer.pendingCount 修正为返回实际操作数

文档更新:
- 更新系统调度和查询相关文档
2025-12-15 09:17:00 +08:00
yhh
b5158b6ac6 chore: 移除第三方引擎引用,改进 README 专业度 2025-12-13 20:56:22 +08:00
YHH
beaa1d09de feat: 预制体系统与架构改进 (#303)
* feat(prefab): 实现预制体系统和编辑器 UX 改进

## 预制体系统
- 新增 PrefabSerializer: 预制体序列化/反序列化
- 新增 PrefabInstanceComponent: 追踪预制体实例来源和修改
- 新增 PrefabService: 预制体核心服务
- 新增 PrefabLoader: 预制体资产加载器
- 新增预制体命令: Create/Instantiate/Apply/Revert/BreakLink

## 预制体编辑模式
- 支持双击 .prefab 文件进入编辑模式
- 预制体编辑模式工具栏 (保存/退出)
- 预制体实例指示器和操作菜单

## 编辑器 UX 改进
- SceneHierarchy 快捷键: F2 重命名, Ctrl+D 复制, ↑↓ 导航
- 支持双击实体名称内联编辑
- 删除实体时显示子节点数量警告
- 右键菜单添加重命名/复制选项及快捷键提示
- 布局持久化和重置功能

## Bug 修复
- 修复 editor-runtime 组件类重复导致的 TransformComponent 不识别问题
- 修复 .prefab-name 样式覆盖导致预制体工具栏文字不可见
- 修复 Inspector 资源字段高度不正确问题

* feat(editor): 改进编辑器 UX 交互体验

- ContentBrowser: 加载动画 spinner、搜索高亮、改进空状态设计
- SceneHierarchy: 选中项自动滚动到视图、搜索清除按钮
- PropertyInspector: 输入框本地状态管理、Enter/Escape 键处理
- EntityInspector: 组件折叠状态持久化、属性搜索清除按钮
- Viewport: 变换操作实时数值显示
- 国际化: 添加相关文本 (en/zh)

* fix(build): 修复 Web 构建资产加载和编辑器 UX 改进

构建系统修复:
- 修复 asset-catalog.json 字段名不匹配 (entries vs assets)
- 修复 BrowserFileSystemService 支持两种目录格式
- 修复 bundle 策略检测逻辑 (空对象判断)
- 修复 module.json 中 assetExtensions 声明和类型推断

行为树修复:
- 修复 BehaviorTreeExecutionSystem 使用 loadAsset 替代 loadAssetByPath
- 修复 BehaviorTreeAssetType 常量与 module.json 类型名一致 (behavior-tree)

编辑器 UX 改进:
- 构建完成对话框添加"打开文件夹"按钮
- 构建完成对话框样式优化 (圆形图标背景、按钮布局)
- SceneHierarchy 响应式布局 (窄窗口自动隐藏 Type 列)
- SceneHierarchy 隐藏滚动条

错误追踪:
- 添加全局错误处理器写入日志文件 (%TEMP%/esengine-editor-crash.log)
- 添加 append_to_log Tauri 命令

* feat(render): 修复 UI 渲染和点击特效系统

## UI 渲染修复
- 修复 GUID 验证 bug,使用统一的 isValidGUID() 函数
- 修复 UI 渲染顺序随机问题,Rust 端使用 IndexMap 替代 HashMap
- Web 运行时添加 assetPathResolver 支持 GUID 解析
- UIInteractableComponent.blockEvents 默认值改为 false

## 点击特效系统
- 新增 ClickFxComponent 和 ClickFxSystem
- 支持在点击位置播放粒子效果
- 支持多种触发模式和粒子轮换

## Camera 系统重构
- CameraSystem 从 ecs-engine-bindgen 移至 camera 包
- 新增 CameraManager 统一管理相机

## 编辑器改进
- 改进属性面板 UI 交互
- 粒子编辑器面板优化
- Transform 命令系统

* feat(render): 实现 Sorting Layer 系统和 Overlay 渲染层

- 新增 SortingLayerManager 管理排序层级 (Background, Default, Foreground, UI, Overlay)
- 实现 ISortable 接口,统一 Sprite、UI、Particle 的排序属性
- 修复粒子 Overlay 层被 UI 遮挡问题:添加独立的 Overlay Pass 在 UI 之后渲染
- 更新粒子资产格式:从 sortingOrder 改为 sortingLayer + orderInLayer
- 更新粒子编辑器面板支持新的排序属性
- 优化 UI 渲染系统使用新的排序层级

* feat(ci): 集成 SignPath 代码签名服务

- 添加 SignPath 自动签名工作流(Windows)
- 配置 release-editor.yml 支持代码签名
- 将构建改为草稿模式,等待签名完成后发布
- 添加证书文件到 .gitignore 防止泄露

* fix(asset): 修复 Web 构建资产路径解析和全局单例移除

## 资产路径修复
- 修复 Tauri 本地服务器 `/asset?path=...` 路径解析,正确与 root 目录连接
- BrowserPathResolver 支持两种模式:
  - 'proxy': 使用 /asset?path=... 格式(编辑器 Run in Browser)
  - 'direct': 使用直接路径 /assets/path.png(独立 Web 构建)
- BrowserRuntime 使用 'direct' 模式,无需 Tauri 代理

## 架构改进 - 移除全局单例
- 移除 globalAssetManager 导出,改用 AssetManagerToken 依赖注入
- 移除 globalPathResolver 导出,改用 PathResolutionService
- 移除 globalPathResolutionService 导出
- ParticleUpdateSystem/ClickFxSystem 通过 setAssetManager() 注入依赖
- EngineService 使用 new AssetManager() 替代全局实例

## 新增服务
- PathResolutionService: 统一路径解析接口
- RuntimeModeService: 运行时模式查询服务
- SerializationContext: EntityRef 序列化上下文

## 其他改进
- 完善 ServiceToken 注释说明本地定义的意图
- 导出 BrowserPathResolveMode 类型

* fix(build): 添加 world-streaming composite 设置修复类型检查

* fix(build): 移除 world-streaming 引用避免 composite 冲突

* fix(build): 将 const enum 改为 enum 兼容 isolatedModules

* fix(build): 添加缺失的 IAssetManager 导入
2025-12-13 19:44:08 +08:00
YHH
a716d8006c fix(build): 修复 Web 构建组件注册和用户脚本打包问题 (#302)
* refactor(build): 重构 Web 构建管线,支持配置驱动的 Import Maps

- 重构 WebBuildPipeline 支持 split-bundles 和 single-bundle 两种构建模式
- 使用 module.json 的 isCore 字段识别核心模块,消除硬编码列表
- 动态生成 Import Map,从模块清单的 name 字段获取包名映射
- 动态扫描 module.json 文件,不再依赖固定模块列表
- 添加 HTTP 服务器启动脚本 (start-server.bat/sh) 支持 ESM 模块
- 更新 BuildSettingsPanel UI 支持新的构建模式选项
- 添加多语言支持 (zh/en/es)

* fix(build): 修复 Web 构建组件注册和用户脚本打包问题

主要修复:
- 修复组件反序列化时找不到类型的问题
- @ECSComponent 装饰器现在自动注册到 ComponentRegistry
- 添加未使用装饰器的组件警告
- 构建管线自动扫描用户脚本(无需入口文件)

架构改进:
- 解决 Decorators ↔ ComponentRegistry 循环依赖
- 新建 ComponentTypeUtils.ts 作为底层无依赖模块
- 移除冗余的防御性 register 调用
- 统一 ComponentType 定义位置

* refactor(build): 统一 WASM 配置架构,移除硬编码

- 新增 wasmConfig 统一配置替代 wasmPaths/wasmBindings
- wasmConfig.files 支持多候选源路径和明确目标路径
- wasmConfig.runtimePath 指定运行时加载路径
- 重构 _copyWasmFiles 使用统一配置
- HTML 生成使用配置中的 runtimePath
- 移除 physics-rapier2d 的冗余 WASM 配置(由 rapier2d 负责)
- IBuildFileSystem 新增 deleteFile 方法

* feat(build): 单文件构建模式完善和场景配置驱动

## 主要改动

### 单文件构建(single-file mode)
- 修复 WASM 初始化问题,支持 initSync 同步初始化
- 配置驱动的 WASM 识别,通过 wasmConfig.isEngineCore 标识核心引擎模块
- 从 wasmConfig.files 动态获取 JS 绑定路径,消除硬编码

### 场景配置
- 构建验证:必须选择至少一个场景才能构建
- 自动扫描:项目加载时扫描 scenes 目录
- 抽取 _filterScenesByWhitelist 公共方法统一过滤逻辑

### 构建面板优化
- availableScenes prop 传递场景列表
- 场景复选框可点击切换启用状态
- 移除动态 import,使用 prop 传入数据

* chore(build): 补充构建相关的辅助改动

- 添加 BuildFileSystemService 的 listFilesByExtension 优化
- 更新 module.json 添加 externalDependencies 配置
- BrowserRuntime 支持 wasmModule 参数传递
- GameRuntime 添加 loadSceneFromData 方法
- Rust 构建命令更新
- 国际化文案更新

* feat(build): 持久化构建设置到项目配置

## 设计架构

### ProjectService 扩展
- 新增 BuildSettingsConfig 接口定义构建配置字段
- ProjectConfig 添加 buildSettings 字段
- 新增 getBuildSettings / updateBuildSettings 方法

### BuildSettingsPanel
- 组件挂载时从 projectService 加载已保存配置
- 设置变化时自动保存(500ms 防抖)
- 场景选择状态与项目配置同步

### 配置保存位置
保存在项目的 ecs-editor.config.json 中:
- scenes: 选中的场景列表
- buildMode: 构建模式
- companyName/productName/version: 产品信息
- developmentBuild/sourceMap: 构建选项

* fix(editor): Ctrl+S 仅在主编辑区域触发保存场景

- 模态窗口打开时跳过(构建设置、设置、关于等)
- 焦点在 input/textarea/contenteditable 时跳过

* fix(tests): 修复 ECS 测试中 Component 注册问题

- 为所有测试 Component 类添加 @ECSComponent 装饰器
- 移除 beforeEach 中的 ComponentRegistry.reset() 调用
- 将内联 Component 类移到文件顶层以支持装饰器
- 更新测试预期值匹配新的组件类型名称
- 添加缺失的 HierarchyComponent 导入

所有 1388 个测试现已通过。
2025-12-10 18:23:29 +08:00
YHH
1b0d38edce feat(i18n): 统一国际化系统架构,支持插件独立翻译 (#301)
* feat(i18n): 统一国际化系统架构,支持插件独立翻译

## 主要改动

### 核心架构
- 增强 LocaleService,支持插件命名空间翻译扩展
- 新增 editor-runtime/i18n 模块,提供 createPluginLocale/createPluginTranslator
- 新增 editor-core/tokens.ts,定义 LocaleServiceToken 等服务令牌
- 改进 PluginAPI 类型安全,使用 ServiceToken<T> 替代 any

### 编辑器本地化
- 扩展 en.ts/zh.ts 翻译文件,覆盖所有 UI 组件
- 新增 es.ts 西班牙语支持
- 重构 40+ 组件使用 useLocale() hook

### 插件本地化系统
- behavior-tree-editor: 新增 locales/ 和 useBTLocale hook
- material-editor: 新增 locales/ 和 useMaterialLocale hook
- particle-editor: 新增 locales/ 和 useParticleLocale hook
- tilemap-editor: 新增 locales/ 和 useTilemapLocale hook
- ui-editor: 新增 locales/ 和 useUILocale hook

### 类型安全改进
- 修复 Debug 工具使用公共接口替代 as any
- 修复 ChunkStreamingSystem 添加 forEachChunk 公共方法
- 修复 blueprint-editor 移除不必要的向后兼容代码

* fix(behavior-tree-editor): 使用 ServiceToken 模式修复服务解析

- 创建 BehaviorTreeServiceToken 遵循"谁定义接口,谁导出Token"原则
- 使用 ServiceToken.id (symbol) 注册服务到 ServiceContainer
- 更新 PluginSDKRegistry.resolveService 支持 ServiceToken 检测
- BehaviorTreeEditorPanel 现在使用类型安全的 PluginAPI.resolve

* fix(behavior-tree-editor): 使用 ServiceContainer.resolve 获取类注册的服务

* fix: 修复多个包的依赖和类型问题

- core: EntityDataCollector.getEntityDetails 使用 HierarchySystem 获取父实体
- ui-editor: 添加 @esengine/editor-runtime 依赖
- tilemap-editor: 添加 @esengine/editor-runtime 依赖
- particle-editor: 添加 @esengine/editor-runtime 依赖
2025-12-09 18:04:03 +08:00
YHH
995fa2d514 refactor(arch): 改进 ServiceToken 设计,统一服务获取模式 (#300)
* refactor(arch): 移除全局变量,使用 ServiceToken 模式

- 创建 PluginServiceRegistry 类,提供类型安全的服务注册/获取
- 添加 ProfilerServiceToken 和 CollisionLayerConfigToken
- 重构所有 __PROFILER_SERVICE__ 全局变量访问为 getProfilerService()
- 重构 __PHYSICS_RAPIER2D__ 全局变量访问为 CollisionLayerConfigToken
- 在 Core 类添加 pluginServices 静态属性
- 添加 getService.ts 辅助模块简化服务获取

这是 ServiceToken 模式重构的第一阶段,移除了最常用的两个全局变量。
后续可继续应用到其他模块(Camera/Audio 等)。

* refactor(arch): 改进 ServiceToken 设计,移除重复常量

- tokens.ts: 从 engine-core 导入 createServiceToken(符合规范)
- tokens.ts: Token 使用接口 IProfilerService 而非具体类
- 移除 AssetPickerDialog 和 ContentBrowser 中重复的 MANAGED_ASSET_DIRECTORIES
- 统一从 editor-core 导入 MANAGED_ASSET_DIRECTORIES

* fix(type): 修复 IProfilerService 接口与实现类型不匹配

- 将 ProfilerData 等数据类型移到 tokens.ts 以避免循环依赖
- ProfilerService 显式实现 IProfilerService 接口
- 更新使用方使用 IProfilerService 接口类型而非具体类

* refactor(type): 移除类型重导出,改进类型安全

- 删除 ProfilerService.ts 中的类型重导出,消费方直接从 tokens.ts 导入
- PanelDescriptor 接口添加 titleZh 属性,移除 App.tsx 中的 as any
- 改进 useDynamicIcon.ts 的类型安全,使用正确的 Record 类型

* refactor(arch): 为模块添加 ServiceToken 支持

- Material System: 创建 tokens.ts,定义 IMaterialManager 接口和 MaterialManagerToken
- Audio: 创建预留 tokens.ts 文件,为未来 AudioManager 服务扩展做准备
- Camera: 创建预留 tokens.ts 文件,为未来 CameraManager 服务扩展做准备

遵循"谁定义接口,谁导出 Token"原则,统一服务访问模式
2025-12-09 11:07:44 +08:00
yhh
c71a47f2b0 docs: 更新文档链接到 esengine.cn 2025-12-09 09:09:45 +08:00
YHH
6c99b811ec refactor(editor): 统一配置管理、完善插件卸载和热更新同步 (#298)
主要变更:

1. 统一配置管理 (EditorConfig)
   - 新增 EditorConfig 集中管理路径、文件名、全局变量等配置
   - 添加 SDK 模块配置系统 (ISDKModuleConfig)
   - 重构 PluginSDKRegistry 使用配置而非硬编码

2. 完善插件卸载机制
   - 扩展 PluginRegisteredResources 追踪运行时资源
   - 实现完整的 deactivatePluginRuntime 清理流程
   - ComponentRegistry 添加 unregister/getRegisteredComponents 方法

3. 热更新同步机制
   - 新增 HotReloadCoordinator 协调热更新过程
   - 热更新期间暂停 ECS 循环避免竞态条件
   - 支持超时保护和失败恢复
2025-12-09 09:06:29 +08:00
yhh
40a38b8b88 docs: 更新品牌形象和项目文档
- 新增 ES logo (docs/public/logo.svg)
- README 添加居中 logo、徽章和导航链接
- 更新 LICENSE 版权为 ESEngine Contributors
- SECURITY.md 添加英文版本,更新联系方式
- 移除不稳定的性能测试
2025-12-08 21:47:47 +08:00
yhh
ad96edfad0 fix: 恢复 @esengine/ecs-framework 包名
上一个提交错误地将 npm 包名也改了,这里恢复正确的包名。
只更新 GitHub 仓库 URL,不改变 npm 包名。
2025-12-08 21:26:35 +08:00
yhh
240b165970 chore: 更新仓库 URL (ecs-framework → esengine)
仓库已从 esengine/ecs-framework 重命名为 esengine/esengine
更新所有引用旧 URL 的文件
2025-12-08 21:23:37 +08:00
YHH
c3b7250f85 refactor(plugin): 重构插件系统架构,统一类型导入路径 (#296)
* refactor(plugin): 重构插件系统架构,统一类型导入路径

## 主要更改

### 新增 @esengine/plugin-types 包
- 提供打破循环依赖的最小类型定义
- 包含 ServiceToken, createServiceToken, PluginServiceRegistry, IEditorModuleBase

### engine-core 类型统一
- IPlugin<T> 泛型参数改为 T = unknown
- 所有运行时类型(IRuntimeModule, ModuleManifest, SystemContext)在此定义
- 新增 PluginServiceRegistry.ts 导出服务令牌相关类型

### editor-core 类型优化
- 重命名 IPluginLoader.ts 为 EditorModule.ts
- 新增 IEditorPlugin = IPlugin<IEditorModuleLoader> 类型别名
- 移除弃用别名 IPluginLoader, IRuntimeModuleLoader

### 编辑器插件更新
- 所有 9 个编辑器插件使用 IEditorPlugin 类型
- 统一从 editor-core 导入编辑器类型

### 服务令牌规范
- 各模块在 tokens.ts 中定义自己的服务接口和令牌
- 遵循"谁定义接口,谁导出 Token"原则

## 导入规范

| 场景 | 导入来源 |
|------|---------|
| 运行时模块 | @esengine/engine-core |
| 编辑器插件 | @esengine/editor-core |
| 服务令牌 | @esengine/engine-core |

* refactor(plugin): 完善服务令牌规范,统一运行时模块

## 更改内容

### 运行时模块优化
- particle: 使用 PluginServiceRegistry 获取依赖服务
- physics-rapier2d: 通过服务令牌注册/获取物理查询接口
- tilemap: 使用服务令牌获取物理系统依赖
- sprite: 导出服务令牌
- ui: 导出服务令牌
- behavior-tree: 使用服务令牌系统

### 资产系统增强
- IAssetManager 接口扩展
- 加载器使用服务令牌获取依赖

### 运行时核心
- GameRuntime 使用 PluginServiceRegistry
- 导出服务令牌相关类型

### 编辑器服务
- EngineService 适配新的服务令牌系统
- AssetRegistryService 优化

* fix: 修复 editor-app 和 behavior-tree-editor 中的类型引用

- editor-app/PluginLoader.ts: 使用 IPlugin 替代 IPluginLoader
- behavior-tree-editor: 使用 IEditorPlugin 替代 IPluginLoader

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

UIRuntimeModule 使用 EngineBridgeToken,需要声明对 ecs-engine-bindgen 的依赖

* fix(type): 解决 ServiceToken 跨包类型兼容性问题

- 在 engine-core 中直接定义 ServiceToken 和 PluginServiceRegistry
  而不是从 plugin-types 重新导出,确保 tsup 生成的类型声明
  以 engine-core 作为类型来源
- 移除 RuntimeResolver.ts 中的硬编码模块 ID 检查,
  改用 module.json 中的 name 配置
- 修复 pnpm-lock.yaml 中的依赖记录

* refactor(arch): 改进架构设计,移除硬编码

- 统一类型导出:editor-core 从 engine-core 导入 IEditorModuleBase
- RuntimeResolver: 将硬编码路径改为配置常量和搜索路径列表
- 添加跨平台安装路径支持(Windows/macOS/Linux)
- 使用 ENGINE_WASM_CONFIG 配置引擎 WASM 文件信息
- IBundlePackOptions 添加 preloadBundles 配置项

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

ParticleRuntimeModule 导入了 @esengine/ecs-engine-bindgen 的 tokens,
但 package.json 中未声明该依赖,导致 CI 构建失败。

* fix(physics-rapier2d): 移除不存在的 PhysicsSystemContext 导出

PhysicsRuntimeModule 中不存在该类型,导致 type-check 失败
2025-12-08 21:10:57 +08:00
yhh
2476379af1 docs: 更新 worker-generator 版本号为 v1.0.2 2025-12-08 18:34:49 +08:00
yhh
e0d659fe46 fix(worker-generator): 映射文件不再放入 workers 目录避免微信编译错误 2025-12-08 18:33:23 +08:00
yhh
9ff03c04f3 docs: 更新 worker-generator 版本号为 v1.0.1 2025-12-08 17:17:26 +08:00
yhh
7b45fbeab3 fix(worker-generator): 将 typescript 移至 dependencies 以修复运行时依赖问题 2025-12-08 17:12:30 +08:00
yhh
a733a53d3e chore(release): 准备发布 v2.3.2 和 worker-generator v1.0.0
- 更新 @esengine/ecs-framework 版本号到 2.3.2
- 更新中英文 changelog
2025-12-08 17:04:01 +08:00
YHH
dfd0dfc7f9 feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI (#297)
* feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI

- 新增 @esengine/worker-generator 包,用于从 WorkerEntitySystem 生成 Worker 文件
- WorkerEntitySystem 添加 workerScriptPath 配置项,支持预编译 Worker 脚本
- CLI 工具支持 --wechat 模式,自动转换 ES6+ 为 ES5 语法
- 修复微信小游戏 Worker 消息格式差异(res 直接是数据,无需 .data)
- 更新中英文文档,添加微信小游戏支持章节

* docs: 更新 changelog,添加 v2.3.1 说明并标注 v2.3.0 为废弃

* fix: 修复 CI 检查问题

- 移除 cli.ts 中未使用的 toKebabCase 函数
- 修复 generator.ts 中正则表达式的 ReDoS 风险(使用 [ \t] 替代 \s*)
- 更新 changelog 版本号(2.3.1 -> 2.3.2)

* docs: 移除未发布版本的 changelog 条目

* fix(worker-generator): 使用 TypeScript 编译器替代手写正则进行 ES5 转换

- 修复 CodeQL 检测的 ReDoS 安全问题
- 使用 ts.transpileModule 进行安全可靠的代码转换
- 移除所有可能导致回溯的正则表达式
2025-12-08 17:02:11 +08:00
YHH
52bbccd53c feat(particle): 添加粒子与 Rapier2D 物理碰撞集成 (#295)
* feat(particle): 添加粒子与 Rapier2D 物理碰撞集成

- 新增 Physics2DCollisionModule 模块,支持粒子与场景碰撞体交互
- 支持圆形重叠检测和射线检测两种模式
- 支持 Kill/Bounce/Stop 三种碰撞行为
- 修复 module.update() 参数顺序 bug
- 物理模块自动通过 context 注入,用户只需添加模块即可

* chore: update pnpm-lock.yaml for particle physics dependency

Update lockfile to include @esengine/physics-rapier2d as optional peer dependency.
2025-12-08 09:38:37 +08:00
YHH
d92c2a7b66 feat(asset): 增强资产管理系统和编辑器 UI (#291)
* fix(editor): 修复粒子实体创建和优化检视器

- 添加 effects 分类到右键菜单,修复粒子实体无法创建的问题
- 添加粒子效果的本地化标签
- 简化粒子组件检视器,优先显示资产文件选择
- 高级属性只在未选择资产时显示,且默认折叠
- 添加可折叠的属性分组提升用户体验

* fix(particle): 修复粒子系统在浏览器预览中的资产加载和渲染

- 添加粒子 Gizmo 支持,显示发射形状并响应 Transform 缩放/旋转
- 修复资产热重载:添加 reloadAsset() 方法和 assets:refresh 事件监听
- 修复 VectorFieldEditors 数值输入精度(step 改为 0.01)
- 修复浏览器预览中粒子资产加载失败的问题:
  - 将相对路径转换为绝对路径以正确复制资产文件
  - 使用原始 GUID 而非生成的 GUID 构建 asset catalog
  - 初始化全局 assetManager 单例的 catalog 和 loader
  - 在 GameRuntime 的 systemContext 中添加 engineIntegration
- 公开 AssetManager.initializeFromCatalog 方法供运行时使用

* feat(asset): 增强资产管理系统和编辑器 UI

主要改动:
- 添加 loaderType 字段支持显式指定加载器类型覆盖
- 添加 .particle 扩展名和类型映射
- 新增 MANAGED_ASSET_DIRECTORIES 常量和相关工具方法
- EngineService 使用全局 assetManager 并同步 AssetRegistry 数据
- 修复插件启用逻辑,defaultEnabled=true 的新插件不被旧配置禁用
- ContentBrowser 添加 GUID 管理目录指示和非托管目录警告
- AssetPickerDialog 和 AssetFileInspector UI 增强
2025-12-07 20:26:03 +08:00
YHH
568b327425 fix(particle): 修复粒子系统在浏览器预览中的资产加载和渲染 (#290)
* fix(editor): 修复粒子实体创建和优化检视器

- 添加 effects 分类到右键菜单,修复粒子实体无法创建的问题
- 添加粒子效果的本地化标签
- 简化粒子组件检视器,优先显示资产文件选择
- 高级属性只在未选择资产时显示,且默认折叠
- 添加可折叠的属性分组提升用户体验

* fix(particle): 修复粒子系统在浏览器预览中的资产加载和渲染

- 添加粒子 Gizmo 支持,显示发射形状并响应 Transform 缩放/旋转
- 修复资产热重载:添加 reloadAsset() 方法和 assets:refresh 事件监听
- 修复 VectorFieldEditors 数值输入精度(step 改为 0.01)
- 修复浏览器预览中粒子资产加载失败的问题:
  - 将相对路径转换为绝对路径以正确复制资产文件
  - 使用原始 GUID 而非生成的 GUID 构建 asset catalog
  - 初始化全局 assetManager 单例的 catalog 和 loader
  - 在 GameRuntime 的 systemContext 中添加 engineIntegration
- 公开 AssetManager.initializeFromCatalog 方法供运行时使用
2025-12-07 01:00:35 +08:00
YHH
1fb702169e feat(asset-system): 完善资源加载和场景资源管理 (#289)
- 添加 AudioLoader 支持音频资源加载 (mp3/wav/ogg/m4a/flac/aac)
- EngineIntegration 添加音频资源加载/卸载支持
- EngineIntegration 添加数据(JSON)资源加载/卸载支持
- SceneResourceManager 实现完整的引用计数机制
- SceneResourceManager 实现场景资源卸载,仅卸载无引用的资源
- 添加资源统计和引用计数查询接口
2025-12-06 14:47:35 +08:00
YHH
3617f40309 feat(asset): 统一资产引用使用 GUID 替代路径 (#287)
* feat(world-streaming): 添加世界流式加载系统

实现基于区块的世界流式加载系统,支持开放世界游戏:

运行时包 (@esengine/world-streaming):
- ChunkComponent: 区块实体组件,包含坐标、边界、状态
- StreamingAnchorComponent: 流式锚点组件(玩家/摄像机)
- ChunkLoaderComponent: 流式加载配置组件
- ChunkStreamingSystem: 区块加载/卸载调度系统
- ChunkCullingSystem: 区块可见性剔除系统
- ChunkManager: 区块生命周期管理服务
- SpatialHashGrid: 空间哈希网格
- ChunkSerializer: 区块序列化

编辑器包 (@esengine/world-streaming-editor):
- ChunkVisualizer: 区块可视化覆盖层
- ChunkLoaderInspectorProvider: 区块加载器检视器
- StreamingAnchorInspectorProvider: 流式锚点检视器
- WorldStreamingPlugin: 完整插件导出

* feat(asset): 统一资产引用使用 GUID 替代路径

将所有组件的资产引用字段从路径改为 GUID:
- SpriteComponent: texture -> textureGuid, material -> materialGuid
- SpriteAnimatorComponent: AnimationFrame.texture -> textureGuid
- UIRenderComponent: texture -> textureGuid
- UIButtonComponent: normalTexture -> normalTextureGuid 等
- AudioSourceComponent: clip -> clipGuid
- ParticleSystemComponent: 已使用 textureGuid

修复 AssetRegistryService 注册问题和路径规范化,
添加渲染系统的 GUID 解析支持。

* fix(sprite-editor): 更新 material 为 materialGuid

* fix(editor-app): 更新 AnimationFrame.texture 为 textureGuid
2025-12-06 14:08:48 +08:00
YHH
0c03b13d74 feat(world-streaming): 添加世界流式加载系统 (#288)
实现基于区块的世界流式加载系统,支持开放世界游戏:

运行时包 (@esengine/world-streaming):
- ChunkComponent: 区块实体组件,包含坐标、边界、状态
- StreamingAnchorComponent: 流式锚点组件(玩家/摄像机)
- ChunkLoaderComponent: 流式加载配置组件
- ChunkStreamingSystem: 区块加载/卸载调度系统
- ChunkCullingSystem: 区块可见性剔除系统
- ChunkManager: 区块生命周期管理服务
- SpatialHashGrid: 空间哈希网格
- ChunkSerializer: 区块序列化

编辑器包 (@esengine/world-streaming-editor):
- ChunkVisualizer: 区块可视化覆盖层
- ChunkLoaderInspectorProvider: 区块加载器检视器
- StreamingAnchorInspectorProvider: 流式锚点检视器
- WorldStreamingPlugin: 完整插件导出
2025-12-06 13:56:01 +08:00
YHH
3cbfa1e4cb fix(editor): 修复右键菜单和粒子编辑器问题 (#286)
- 修复右键菜单被状态栏遮挡的问题
- 修复右键菜单边界检测,考虑标题栏和状态栏高度
- 调整右键菜单结构:新建文件夹 → 资源类型 → 工具操作
- 修复 Particle 插件默认未启用的问题(defaultEnabled 的新插件不被旧配置禁用)
- 修复 SizeOverLifetime 模块在预览中无效果的问题
- 移除 MaterialEditorModule 中的重复模板注册
2025-12-06 11:56:25 +08:00
yhh
397f79caa5 docs: 更新 v2.3.0 版本文档和 changelog
- 添加 v2.3.0 changelog(中英文)
- 更新文档版本号从 v2.2.22+ 到 v2.3.0+
- 更新 package.json 版本至 2.3.0
- vitepress 配置添加 ignoreDeadLinks
2025-12-06 10:44:08 +08:00
yhh
972c1d5357 Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-12-06 10:15:13 +08:00
YHH
32d35ef2ee feat(particle): 添加完整粒子系统和粒子编辑器 (#284)
* feat(editor-core): 添加用户系统自动注册功能

- IUserCodeService 新增 registerSystems/unregisterSystems/getRegisteredSystems 方法
- UserCodeService 实现系统检测、实例化和场景注册逻辑
- ServiceRegistry 在预览开始时注册用户系统,停止时移除
- 热更新时自动重新加载用户系统
- 更新 System 脚本模板添加 @ECSSystem 装饰器

* feat(editor-core): 添加编辑器脚本支持(Inspector/Gizmo)

- registerEditorExtensions 实际注册用户 Inspector 和 Gizmo
- 添加 unregisterEditorExtensions 方法
- ServiceRegistry 在项目加载时编译并加载编辑器脚本
- 项目关闭时自动清理编辑器扩展
- 添加 Inspector 和 Gizmo 脚本创建模板

* feat(particle): 添加粒子系统和粒子编辑器

新增两个包:
- @esengine/particle: 粒子系统核心库
- @esengine/particle-editor: 粒子编辑器 UI

粒子系统功能:
- ECS 组件架构,支持播放/暂停/重置控制
- 7种发射形状:点、圆、环、矩形、边缘、线、锥形
- 5个动画模块:颜色渐变、缩放曲线、速度控制、旋转、噪声
- 纹理动画模块支持精灵表动画
- 3种混合模式:Normal、Additive、Multiply
- 11个内置预设:火焰、烟雾、爆炸、雨、雪等
- 对象池优化,支持粒子复用

编辑器功能:
- 实时 Canvas 预览,支持全屏和鼠标跟随
- 点击触发爆发效果(用于测试爆炸类特效)
- 渐变编辑器:可视化颜色关键帧编辑
- 曲线编辑器:支持缩放曲线和缓动函数
- 预设浏览器:快速应用内置预设
- 模块开关:独立启用/禁用各个模块
- Vector2 样式输入(重力 X/Y)

* feat(particle): 完善粒子系统核心功能

1. Burst 定时爆发系统
   - BurstConfig 接口支持时间、数量、循环次数、间隔
   - 运行时自动处理定时爆发
   - 支持无限循环爆发

2. 速度曲线模块 (VelocityOverLifetimeModule)
   - 6种曲线类型:Constant、Linear、EaseIn、EaseOut、EaseInOut、Custom
   - 自定义关键帧曲线支持
   - 附加速度 X/Y
   - 轨道速度和径向速度

3. 碰撞边界模块 (CollisionModule)
   - 矩形和圆形边界类型
   - 3种碰撞行为:Kill、Bounce、Wrap
   - 反弹系数和最小速度阈值
   - 反弹时生命损失

* feat(particle): 添加力场模块、碰撞模块和世界/本地空间支持

- 新增 ForceFieldModule 支持风力、吸引点、漩涡、湍流四种力场类型
- 新增 SimulationSpace 枚举支持世界空间和本地空间切换
- ParticleSystemComponent 集成力场模块和空间模式
- 粒子编辑器添加 Collision 和 ForceField 模块的 UI 编辑支持
- 新增 Vortex、Leaves、Bouncing 三个预设展示新功能
- 编辑器预览实现完整的碰撞和力场效果

* fix(particle): 移除未使用的 transform 循环变量
2025-12-05 23:03:31 +08:00
yhh
57e165779e Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-12-05 22:59:24 +08:00
YHH
690d7859c8 feat(core): 添加持久化实体支持跨场景迁移 (#285)
实现实体生命周期策略,允许标记实体为持久化,在场景切换时自动迁移到新场景。

主要变更:
- 新增 EEntityLifecyclePolicy 枚举(SceneLocal/Persistent)
- Entity 添加 setPersistent()、setSceneLocal()、isPersistent API
- Scene 添加 findPersistentEntities()、extractPersistentEntities()、receiveMigratedEntities()
- SceneManager.setScene() 自动处理持久化实体迁移
- 添加完整的中英文文档和 21 个测试用例
2025-12-05 22:54:41 +08:00
yhh
8f9a7d8581 feat(core): 添加持久化实体支持跨场景迁移
实现实体生命周期策略,允许标记实体为持久化,在场景切换时自动迁移到新场景。

主要变更:
- 新增 EEntityLifecyclePolicy 枚举(SceneLocal/Persistent)
- Entity 添加 setPersistent()、setSceneLocal()、isPersistent API
- Scene 添加 findPersistentEntities()、extractPersistentEntities()、receiveMigratedEntities()
- SceneManager.setScene() 自动处理持久化实体迁移
- 添加完整的中英文文档和 21 个测试用例
2025-12-05 22:46:53 +08:00
YHH
3d5fcc1a55 feat(editor-core): 添加用户系统自动注册功能 (#283)
* feat(editor-core): 添加用户系统自动注册功能

- IUserCodeService 新增 registerSystems/unregisterSystems/getRegisteredSystems 方法
- UserCodeService 实现系统检测、实例化和场景注册逻辑
- ServiceRegistry 在预览开始时注册用户系统,停止时移除
- 热更新时自动重新加载用户系统
- 更新 System 脚本模板添加 @ECSSystem 装饰器

* feat(editor-core): 添加编辑器脚本支持(Inspector/Gizmo)

- registerEditorExtensions 实际注册用户 Inspector 和 Gizmo
- 添加 unregisterEditorExtensions 方法
- ServiceRegistry 在项目加载时编译并加载编辑器脚本
- 项目关闭时自动清理编辑器扩展
- 添加 Inspector 和 Gizmo 脚本创建模板
2025-12-05 18:28:11 +08:00
YHH
823e0c1d94 feat(engine-core): 添加统一输入系统 (#282)
* perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令

ReactiveQuery 快照优化:
- 添加快照机制,避免每帧拷贝数组
- 只在实体列表变化时创建新快照
- 静态场景下多个系统共享同一快照

CommandBuffer 延迟命令系统:
- 支持延迟添加/移除组件、销毁实体、设置实体激活状态
- 每个系统拥有独立的 commands 属性
- 命令在帧末统一执行,避免迭代过程中修改实体列表

Scene 更新:
- 在 lateUpdate 后自动刷新所有系统的命令缓冲区

文档:
- 更新系统文档,添加 CommandBuffer 使用说明

* fix(ci): upgrade first-interaction action to v1.3.0

Fix Docker build failure in welcome workflow.

* fix(ci): upgrade pnpm/action-setup to v4 and fix unused import

- Upgrade pnpm/action-setup@v2 to v4 in all workflow files
- Remove unused CommandType import in CommandBuffer.test.ts

* fix(ci): remove duplicate pnpm version specification

* feat(engine-core): 添加统一输入系统

添加完整的输入系统,支持平台抽象:

- IPlatformInputSubsystem: 扩展接口支持键盘/鼠标/滚轮事件
- WebInputSubsystem: 浏览器实现,支持事件绑定/解绑
- InputManager: 全局输入状态管理器(键盘、鼠标、触摸)
- InputSystem: ECS 系统,连接平台事件到 InputManager
- GameRuntime 集成: 自动创建 InputSystem 并绑定平台子系统

使用方式:
```typescript
import { Input, MouseButton } from '@esengine/engine-core';

if (Input.isKeyDown('KeyW')) { /* 移动 */ }
if (Input.isKeyJustPressed('Space')) { /* 跳跃 */ }
if (Input.isMouseButtonDown(MouseButton.Left)) { /* 射击 */ }
```

* fix(runtime-core): 添加缺失的 platform-common 依赖

* fix(runtime-core): 移除 platform-web 依赖避免循环依赖

* fix(runtime-core): 使用工厂函数注入 InputSubsystem 避免循环依赖

- BrowserPlatformAdapter 通过 inputSubsystemFactory 配置接收输入子系统
- 在 IPlatformInputSubsystem 接口添加可选的 dispose 方法
- 移除对 @esengine/platform-web 的直接依赖
2025-12-05 18:15:50 +08:00
YHH
13a149c3a2 perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令 (#281)
* perf(core): 优化 EntitySystem 迭代性能,添加 CommandBuffer 延迟命令

ReactiveQuery 快照优化:
- 添加快照机制,避免每帧拷贝数组
- 只在实体列表变化时创建新快照
- 静态场景下多个系统共享同一快照

CommandBuffer 延迟命令系统:
- 支持延迟添加/移除组件、销毁实体、设置实体激活状态
- 每个系统拥有独立的 commands 属性
- 命令在帧末统一执行,避免迭代过程中修改实体列表

Scene 更新:
- 在 lateUpdate 后自动刷新所有系统的命令缓冲区

文档:
- 更新系统文档,添加 CommandBuffer 使用说明

* fix(ci): upgrade first-interaction action to v1.3.0

Fix Docker build failure in welcome workflow.

* fix(ci): upgrade pnpm/action-setup to v4 and fix unused import

- Upgrade pnpm/action-setup@v2 to v4 in all workflow files
- Remove unused CommandType import in CommandBuffer.test.ts

* fix(ci): remove duplicate pnpm version specification
2025-12-05 17:24:33 +08:00
github-actions[bot]
dd130eacb0 chore(editor): bump version to 1.0.14 (#280)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-12-05 16:45:52 +08:00
YHH
d0238add2d fix: 当 esbuild 未打包时创建占位文件避免 Tauri 构建失败 (#276) 2025-12-05 16:24:26 +08:00
yhh
fe96d72ac6 docs: 补充 v2.2.21 changelog 中遗漏的迭代安全修复 (#272) 2025-12-05 16:17:18 +08:00
yhh
b2b8df9340 chore(core): release v2.2.21
- 优化 HierarchySystem 性能 (#279)
- 更新 changelog
2025-12-05 16:10:24 +08:00
YHH
2d56eaf11a perf(core): 优化 HierarchySystem 避免每帧遍历所有实体 (#279)
使用脏实体集合代替每帧遍历所有实体,静态场景下 process() 从 O(n) 优化为 O(1)。

性能提升:
- 1000 实体静态场景: 81.79μs -> 0.07μs (快 1168 倍)
- 10000 实体静态场景: 939.43μs -> 0.56μs (快 1677 倍)
- 服务端模拟 (100房间 x 100实体): 2.7ms -> 1.4ms 每 tick

改动:
- 新增 dirtyEntities Set 追踪需要更新缓存的实体
- process() 只遍历脏实体
- markCacheDirty() 将实体加入脏集合
- onAdded() 在实体注册时将脏实体加入集合
- onRemoved() 将实体从脏集合移除
2025-12-05 16:07:53 +08:00
YHH
2cb9c471f9 fix(docs): 修正 v2.2.16 组件生命周期描述 (#278) 2025-12-05 16:01:07 +08:00
YHH
e8fc7f497b docs: 添加 core 库更新日志 (v2.2.16 - v2.2.20) (#277) 2025-12-05 15:10:14 +08:00
YHH
6702f0bfad feat(editor): 完善用户代码热更新和环境检测 (#275)
* fix: 更新 bundle-runtime 脚本使用正确的 platform-web 输出文件

原脚本引用的 runtime.browser.js 不存在,改为使用 index.mjs

* feat(editor): 完善用户代码热更新和环境检测

## 热更新改进
- 添加 hotReloadInstances() 方法,通过更新原型链实现真正的热更新
- 组件实例保留数据,仅更新方法
- ComponentRegistry 支持热更新时替换同名组件类

## 环境检测
- 启动时检测 esbuild 可用性
- 在启动页面底部显示环境状态指示器
- 添加 check_environment Rust 命令和前端 API

## esbuild 打包
- 将 esbuild 二进制文件打包到应用中
- 用户无需全局安装 esbuild
- 支持 Windows/macOS/Linux 平台

## 文件监视优化
- 添加 300ms 防抖,避免重复编译
- 修复路径分隔符混合问题

## 资源打包修复
- 修复 Tauri 资源配置,保留 engine 目录结构
- 添加 src-tauri/bin/ 和 src-tauri/engine/ 到 gitignore

* fix: 将热更新模式改为可选,修复测试失败

- ComponentRegistry 添加 hotReloadEnabled 标志,默认禁用
- 只有启用热更新模式时才会替换同名组件类
- 编辑器环境自动启用热更新模式
- reset() 方法中重置热更新标志

* test: 添加热更新模式的测试用例
2025-12-05 14:24:09 +08:00
YHH
d7454e3ca4 feat(engine): 添加编辑器模式标志控制编辑器UI显示 (#274)
* feat(engine): 添加编辑器模式标志控制编辑器UI显示

- 在 Rust 引擎中添加 isEditor 标志,控制网格、gizmos、坐标轴指示器的显示
- 运行时模式下自动隐藏所有编辑器专用 UI
- 编辑器预览和浏览器运行时通过 setEditorMode(false) 禁用编辑器 UI
- 添加 Scene.isEditorMode 延迟组件生命周期回调,直到 begin() 调用
- 修复用户组件注册到 Core ComponentRegistry 以支持序列化
- 修复 Run in Browser 时用户组件加载问题

* fix: 复制引擎模块的类型定义文件到 dist/engine

* fix: 修复用户项目 tsconfig paths 类型定义路径

- 从 module.json 读取实际包名而不是使用目录名
- 修复 .d.ts 文件复制逻辑,支持 .mjs 扩展名
2025-12-04 22:43:26 +08:00
YHH
0d9bab910e feat(editor): 实现用户脚本编译加载和自动重编译 (#273) 2025-12-04 19:32:51 +08:00
YHH
3d16bbdc64 fix: 修复 process/lateProcess 迭代时组件变化导致跳过实体的问题 (#272)
- 在 update() 和 lateUpdate() 中创建实体数组副本,防止迭代过程中数组被修改
- lateUpdate() 现在重新查询实体以获取 update 阶段添加的新实体
- 添加 lawn-mower-demo 场景测试用例验证修复
- 更新中英文文档说明 onAdded/onRemoved 同步调用时机和 process/lateProcess 安全性
2025-12-04 15:11:01 +08:00
YHH
b4e7ba2abd fix: 修复项目切换时运行时和系统重复初始化问题 (#267)
* feat(editor): 添加 GitHub Discussions 社区论坛功能

* chore: 更新 pnpm-lock.yaml

* chore: 删除测试图片

* refactor: 改用 Imgur 图床上传图片

* fix: 修复项目切换时运行时和系统重复初始化问题

* fix: 修复多个编辑器问题

* feat: 添加脚本编辑器配置和类型定义支持

* feat: 实现资产 .meta 文件自动生成功能
2025-12-04 14:04:39 +08:00
github-actions[bot]
374b26f7c6 chore(core): release v2.2.20 (#271)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-12-04 13:01:47 +08:00
YHH
dbebb4f4fb fix: 修复系统 onAdded 回调受注册顺序影响的问题 (#270) 2025-12-04 12:56:19 +08:00
github-actions[bot]
eec89b626c chore(core): release v2.2.19 (#268)
Co-authored-by: esengine <18465053+esengine@users.noreply.github.com>
2025-12-04 11:36:12 +08:00
yhh
763d23e960 fix: 改用 Imgur 图床并删除测试图片 2025-12-04 09:57:33 +08:00
YHH
3b56ed17fe feat(editor): 添加 GitHub Discussions 社区论坛功能 (#266)
* feat(editor): 添加 GitHub Discussions 社区论坛功能

* chore: 更新 pnpm-lock.yaml
2025-12-04 09:51:04 +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
2786 changed files with 422748 additions and 58443 deletions

8
.changeset/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

57
.changeset/config.json Normal file
View File

@@ -0,0 +1,57 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
"changelog": [
"@changesets/changelog-github",
{ "repo": "esengine/esengine" }
],
"commit": false,
"fixed": [],
"linked": [
["@esengine/ecs-framework", "@esengine/ecs-framework-math"]
],
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": [
"@esengine/engine-core",
"@esengine/runtime-core",
"@esengine/asset-system",
"@esengine/material-system",
"@esengine/ecs-engine-bindgen",
"@esengine/script-runtime",
"@esengine/platform-common",
"@esengine/platform-web",
"@esengine/platform-wechat",
"@esengine/sprite",
"@esengine/camera",
"@esengine/particle",
"@esengine/tilemap",
"@esengine/mesh-3d",
"@esengine/effect",
"@esengine/audio",
"@esengine/fairygui",
"@esengine/physics-rapier2d",
"@esengine/rapier2d",
"@esengine/world-streaming",
"@esengine/editor-core",
"@esengine/editor-runtime",
"@esengine/editor-app",
"@esengine/sprite-editor",
"@esengine/camera-editor",
"@esengine/particle-editor",
"@esengine/tilemap-editor",
"@esengine/mesh-3d-editor",
"@esengine/fairygui-editor",
"@esengine/physics-rapier2d-editor",
"@esengine/behavior-tree-editor",
"@esengine/blueprint-editor",
"@esengine/asset-system-editor",
"@esengine/material-editor",
"@esengine/shader-editor",
"@esengine/world-streaming-editor",
"@esengine/node-editor",
"@esengine/sdk",
"@esengine/worker-generator",
"@esengine/engine"
]
}

2
.github/FUNDING.yml vendored
View File

@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://github.com/esengine/ecs-framework/blob/master/sponsor/alipay.jpg', 'https://github.com/esengine/ecs-framework/blob/master/sponsor/wechatpay.png'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://github.com/esengine/esengine/blob/master/sponsor/alipay.jpg', 'https://github.com/esengine/esengine/blob/master/sponsor/wechatpay.png'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -5,7 +5,7 @@ contact_links:
about: 查看完整文档和教程 / View full documentation and tutorials
- name: 🤖 AI 文档助手 / AI Documentation Assistant
url: https://deepwiki.com/esengine/ecs-framework
url: https://deepwiki.com/esengine/esengine
about: 使用 AI 助手快速找到答案 / Use AI assistant to quickly find answers
- name: 💬 QQ 交流群 / QQ Group
@@ -13,5 +13,5 @@ contact_links:
about: 加入社区交流群 / Join the community group
- name: 🌟 GitHub Discussions
url: https://github.com/esengine/ecs-framework/discussions
url: https://github.com/esengine/esengine/discussions
about: 参与社区讨论 / Join community discussions

View File

@@ -8,12 +8,12 @@ body:
value: |
💡 提示:如果是简单问题,可以先查看:
- [📚 文档](https://esengine.github.io/ecs-framework/)
- [📖 AI 文档助手](https://deepwiki.com/esengine/ecs-framework)
- [📖 AI 文档助手](https://deepwiki.com/esengine/esengine)
- [💬 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)
- [📖 AI Documentation](https://deepwiki.com/esengine/esengine)
- type: textarea
id: question

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

@@ -0,0 +1,13 @@
name: "CodeQL Config"
# Paths to exclude from analysis
paths-ignore:
- thirdparty
- "**/node_modules"
- "**/dist"
- "**/bin"
- "**/tests"
- "**/*.test.ts"
- "**/*.spec.ts"
- "**/test"
- "**/__tests__"

32
.github/labeler.yml vendored
View File

@@ -1,32 +0,0 @@
# 自动标签配置
# 根据 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'

View File

@@ -1,73 +0,0 @@
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"

View File

@@ -1,61 +0,0 @@
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');

View File

@@ -1,85 +0,0 @@
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

@@ -1,56 +0,0 @@
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.'
});

View File

@@ -1,160 +0,0 @@
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

@@ -8,30 +8,47 @@ on:
- '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'
- 'jest.config.*'
- '.github/workflows/ci.yml'
# Run on all PRs to satisfy branch protection, but skip build if no code changes
jobs:
test:
# Check if we need to run the full CI
check-changes:
runs-on: ubuntu-latest
outputs:
should-run: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
code:
- 'packages/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'turbo.json'
- 'jest.config.*'
ci:
needs: check-changes
if: needs.check-changes.outputs.should-run == 'true'
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -40,48 +57,35 @@ jobs:
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
run: pnpm install --no-frozen-lockfile
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Install wasm-pack
run: cargo install wasm-pack
- name: Build core package first
run: pnpm run build:core
- name: Build engine WASM package
# 构建 framework 包 (可独立发布的通用库,无外部依赖)
- name: Build framework packages
run: |
cd packages/engine
pnpm run build
pnpm --filter @esengine/ecs-framework build
pnpm --filter @esengine/ecs-framework-math build
pnpm --filter @esengine/behavior-tree build
pnpm --filter @esengine/blueprint build
pnpm --filter @esengine/fsm build
pnpm --filter @esengine/timer build
pnpm --filter @esengine/spatial build
pnpm --filter @esengine/procgen build
pnpm --filter @esengine/pathfinding build
pnpm --filter @esengine/network-protocols build
pnpm --filter @esengine/rpc build
pnpm --filter @esengine/network 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/
# 类型检查 (仅 framework 包)
- name: Type check (framework packages)
run: pnpm run type-check:framework
- name: Build dependent packages for type declarations
run: |
cd packages/platform-common && pnpm run build
cd ../asset-system && pnpm run build
cd ../components && pnpm run build
- name: Build ecs-engine-bindgen
run: |
cd packages/ecs-engine-bindgen && pnpm run build
- name: Type check
run: pnpm run type-check
- name: Lint check
run: pnpm run lint
# Lint 检查 (仅 framework 包)
- name: Lint check (framework packages)
run: pnpm run lint:framework
# 测试 (仅 framework 包)
- name: Run tests with coverage
run: pnpm run test:ci
run: pnpm run test:ci:framework
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
@@ -92,39 +96,18 @@ jobs:
name: codecov-umbrella
fail_ci_if_error: false
build:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build project
run: pnpm run build
- name: Build npm package
run: pnpm run build:npm
# 构建 npm 包 (core 和 math)
- name: Build npm packages
run: |
pnpm run build:npm:core
pnpm run build:npm:math
# 上传构建产物
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: |
bin/
dist/
packages/framework/**/dist/
packages/framework/**/bin/
retention-days: 7

View File

@@ -1,146 +0,0 @@
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`);
}

View File

@@ -15,9 +15,7 @@ jobs:
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -30,7 +28,7 @@ jobs:
- name: Run tests with coverage
run: |
cd packages/core
cd packages/framework/core
pnpm run test:coverage
- name: Upload coverage to Codecov
@@ -38,7 +36,7 @@ jobs:
continue-on-error: true
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/core/coverage/coverage-final.json
files: ./packages/framework/core/coverage/coverage-final.json
flags: core
name: core-coverage
fail_ci_if_error: false
@@ -48,4 +46,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: packages/core/coverage/
path: packages/framework/core/coverage/

View File

@@ -31,6 +31,7 @@ jobs:
with:
languages: ${{ matrix.language }}
queries: security-and-quality
config-file: ./.github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@v3

View File

@@ -18,9 +18,7 @@ jobs:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4

View File

@@ -30,9 +30,7 @@ jobs:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -58,7 +56,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
path: docs/dist
deploy:
environment:

View File

@@ -1,23 +0,0 @@
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

View File

@@ -1,28 +0,0 @@
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>

View File

@@ -0,0 +1,73 @@
name: Release (Changesets)
on:
push:
branches:
- master
paths:
- '.changeset/**'
- 'packages/*/package.json'
- 'packages/*/*/package.json'
- 'packages/*/*/*/package.json'
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build framework packages
run: |
# Only build packages managed by Changesets (not in ignore list)
pnpm --filter "@esengine/ecs-framework" build
pnpm --filter "@esengine/ecs-framework-math" build
pnpm --filter "@esengine/behavior-tree" build
pnpm --filter "@esengine/blueprint" build
pnpm --filter "@esengine/fsm" build
pnpm --filter "@esengine/timer" build
pnpm --filter "@esengine/spatial" build
pnpm --filter "@esengine/procgen" build
pnpm --filter "@esengine/pathfinding" build
pnpm --filter "@esengine/network-protocols" build
pnpm --filter "@esengine/rpc" build
pnpm --filter "@esengine/network" build
pnpm --filter "@esengine/server" build
pnpm --filter "@esengine/cli" build
pnpm --filter "create-esengine-server" build
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
version: pnpm changeset:version
publish: pnpm changeset:publish
title: 'chore: release packages'
commit: 'chore: release packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -34,9 +34,7 @@ jobs:
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -52,7 +50,7 @@ jobs:
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: packages/editor-app/src-tauri
workspaces: packages/editor/editor-app/src-tauri
cache-on-failure: true
- name: Install dependencies (Ubuntu)
@@ -67,82 +65,149 @@ jobs:
- name: Update version in config files (for manual trigger)
if: github.event_name == 'workflow_dispatch'
run: |
cd packages/editor-app
cd packages/editor/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: Cache TypeScript build
uses: actions/cache@v4
with:
path: |
packages/core/bin
packages/editor-core/dist
packages/behavior-tree/bin
key: ${{ runner.os }}-ts-build-${{ hashFiles('packages/core/src/**', 'packages/editor-core/src/**', 'packages/behavior-tree/src/**') }}
restore-keys: |
${{ runner.os }}-ts-build-
- name: Build core package
run: pnpm run build:core
- name: Build editor-core package
run: |
cd packages/editor-core
pnpm run build
- name: Build behavior-tree package
run: |
cd packages/behavior-tree
pnpm run build
- name: Install wasm-pack
run: cargo install wasm-pack
- name: Build engine package (Rust WASM)
run: |
cd packages/engine
pnpm run build
# 使用 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/
mkdir -p packages/engine/ecs-engine-bindgen/src/wasm
cp packages/rust/engine/pkg/es_engine.js packages/engine/ecs-engine-bindgen/src/wasm/
cp packages/rust/engine/pkg/es_engine.d.ts packages/engine/ecs-engine-bindgen/src/wasm/
cp packages/rust/engine/pkg/es_engine_bg.wasm packages/engine/ecs-engine-bindgen/src/wasm/
cp packages/rust/engine/pkg/es_engine_bg.wasm.d.ts packages/engine/ecs-engine-bindgen/src/wasm/
- name: Build dependent packages for ecs-engine-bindgen
- name: Bundle runtime files for Tauri
run: |
cd packages/platform-common && pnpm run build
cd ../asset-system && pnpm run build
cd ../components && pnpm run build
- name: Build ecs-engine-bindgen package
run: |
cd packages/ecs-engine-bindgen
pnpm run build
cd packages/editor/editor-app
node scripts/bundle-runtime.mjs
- name: Build Tauri app
id: tauri
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
projectPath: packages/editor/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
releaseDraft: true
prerelease: false
includeUpdaterJson: true
updaterJsonKeepUniversal: false
args: ${{ matrix.platform == 'macos-latest' && format('--target {0}', matrix.target) || '' }}
# 构建成功后,创建 PR 更新版本号
update-version-pr:
# Windows 构建上传 artifact 供 SignPath 签名
- name: Upload Windows artifacts for signing
if: matrix.platform == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: windows-unsigned
path: |
packages/editor/editor-app/src-tauri/target/release/bundle/nsis/*.exe
packages/editor/editor-app/src-tauri/target/release/bundle/msi/*.msi
retention-days: 1
# SignPath 代码签名Windows
# SignPath OSS code signing for Windows
#
# 配置步骤 | Setup Steps:
# 1. 在 SignPath 门户创建项目 | Create project in SignPath portal
# 2. 导入 .signpath/artifact-configuration.xml | Import artifact configuration
# 3. 使用 'test-signing' 策略测试 | Use 'test-signing' policy for testing
# 生产环境改为 'release-signing' | Change to 'release-signing' for production
# 4. 配置 GitHub Secrets | Configure GitHub Secrets:
# - SIGNPATH_API_TOKEN: API token from SignPath
# - SIGNPATH_ORGANIZATION_ID: Your organization ID
#
# 文档 | Documentation: https://about.signpath.io/documentation/trusted-build-systems/github
sign-windows:
needs: build-tauri
if: github.event_name == 'workflow_dispatch' && success()
runs-on: ubuntu-latest
# 只有在构建成功时才运行 | Only run on successful build
if: success()
steps:
- name: Check SignPath configuration
id: check-signpath
run: |
if [ -n "${{ secrets.SIGNPATH_API_TOKEN }}" ] && [ -n "${{ secrets.SIGNPATH_ORGANIZATION_ID }}" ]; then
echo "enabled=true" >> $GITHUB_OUTPUT
echo "SignPath is configured, proceeding with code signing"
else
echo "enabled=false" >> $GITHUB_OUTPUT
echo "SignPath secrets not configured, skipping code signing"
echo "To enable: add SIGNPATH_API_TOKEN and SIGNPATH_ORGANIZATION_ID secrets"
fi
- name: Checkout
if: steps.check-signpath.outputs.enabled == 'true'
uses: actions/checkout@v4
- name: Get artifact ID
if: steps.check-signpath.outputs.enabled == 'true'
id: get-artifact
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# 获取 windows-unsigned artifact 的 ID
ARTIFACT_ID=$(gh api \
-H "Accept: application/vnd.github+json" \
"/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \
--jq '.artifacts[] | select(.name == "windows-unsigned") | .id')
if [ -z "$ARTIFACT_ID" ]; then
echo "Error: Could not find artifact 'windows-unsigned'"
exit 1
fi
echo "artifact-id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
echo "Found artifact ID: $ARTIFACT_ID"
- name: Submit to SignPath for code signing
if: steps.check-signpath.outputs.enabled == 'true'
id: signpath
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
project-slug: 'ecs-framework'
signing-policy-slug: 'test-signing'
artifact-configuration-slug: 'initial'
github-artifact-id: ${{ steps.get-artifact.outputs.artifact-id }}
wait-for-completion: true
wait-for-completion-timeout-in-seconds: 600
output-artifact-directory: './signed'
- name: Upload signed artifacts to release
if: steps.check-signpath.outputs.enabled == 'true'
uses: softprops/action-gh-release@v1
with:
files: ./signed/*
tag_name: ${{ github.event_name == 'workflow_dispatch' && format('editor-v{0}', github.event.inputs.version) || github.ref_name }}
# 保持 Draft 状态,需要手动发布 | Keep as draft, require manual publish
draft: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 构建成功后,创建 PR 更新版本号
# Create PR to update version after successful build
update-version-pr:
needs: [build-tauri, sign-windows]
# 即使签名跳过也要运行 | Run even if signing is skipped
if: github.event_name == 'workflow_dispatch' && !failure()
runs-on: ubuntu-latest
steps:
@@ -156,7 +221,7 @@ jobs:
- name: Update version files
run: |
cd packages/editor-app
cd packages/editor/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
@@ -169,16 +234,16 @@ jobs:
delete-branch: true
title: "chore(editor): Release v${{ github.event.inputs.version }}"
body: |
## 🚀 Release v${{ github.event.inputs.version }}
## 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 }}`
- Updated `packages/editor/editor-app/package.json` → `${{ github.event.inputs.version }}`
- Updated `packages/editor/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 }})
- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/editor-v${{ github.event.inputs.version }})
---
*This PR was automatically created by the release workflow.*

View File

@@ -1,29 +1,43 @@
name: Release NPM Packages
on:
# Tag trigger: supports v* and {package}-v* formats
push:
tags:
- 'v*'
- 'core-v*'
- 'behavior-tree-v*'
- 'editor-core-v*'
- 'node-editor-v*'
- 'blueprint-v*'
- 'tilemap-v*'
- 'physics-rapier2d-v*'
- 'worker-generator-v*'
# Manual trigger option
workflow_dispatch:
inputs:
package:
description: '选择要发布的包'
description: 'Select package to publish'
required: true
type: choice
options:
- core
- behavior-tree
- editor-core
- node-editor
- blueprint
- tilemap
- physics-rapier2d
- worker-generator
version_type:
description: '版本更新类型'
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
- custom
custom_version:
description: '自定义版本号 (仅当选择 custom 时使用,例如: 2.2.9)'
required: false
type: string
permissions:
contents: write
@@ -32,7 +46,7 @@ permissions:
jobs:
release-package:
name: Release ${{ github.event.inputs.package }} Package
name: Release Package
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -41,10 +55,42 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Parse tag or input
id: parse
run: |
if [ "${{ github.event_name }}" = "push" ]; then
# Parse package and version from tag
TAG="${GITHUB_REF#refs/tags/}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
# Parse format: v1.0.0 or package-v1.0.0
if [[ "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then
PACKAGE="core"
VERSION="${BASH_REMATCH[1]}"
elif [[ "$TAG" =~ ^([a-z-]+)-v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then
PACKAGE="${BASH_REMATCH[1]}"
VERSION="${BASH_REMATCH[2]}"
else
echo "::error::Invalid tag format: $TAG"
echo "Expected: v1.0.0 or package-v1.0.0"
exit 1
fi
echo "package=$PACKAGE" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "mode=tag" >> $GITHUB_OUTPUT
echo "Package: $PACKAGE"
echo "Version: $VERSION"
else
# Manual trigger: read from package.json and bump version
PACKAGE="${{ github.event.inputs.package }}"
echo "package=$PACKAGE" >> $GITHUB_OUTPUT
echo "mode=manual" >> $GITHUB_OUTPUT
echo "version_type=${{ github.event.inputs.version_type }}" >> $GITHUB_OUTPUT
fi
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -56,62 +102,124 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build core package (if needed)
if: ${{ github.event.inputs.package == 'behavior-tree' || github.event.inputs.package == 'editor-core' }}
- name: Verify version (tag mode)
if: steps.parse.outputs.mode == 'tag'
run: |
cd packages/core
pnpm run build
PACKAGE="${{ steps.parse.outputs.package }}"
EXPECTED_VERSION="${{ steps.parse.outputs.version }}"
# - name: Run tests
# run: |
# cd packages/${{ github.event.inputs.package }}
# npm run test:ci
# Get version from package.json
ACTUAL_VERSION=$(node -p "require('./packages/$PACKAGE/package.json').version")
- name: Update version
if [ "$EXPECTED_VERSION" != "$ACTUAL_VERSION" ]; then
echo "::error::Version mismatch!"
echo "Tag version: $EXPECTED_VERSION"
echo "package.json version: $ACTUAL_VERSION"
echo ""
echo "Please update packages/$PACKAGE/package.json to version $EXPECTED_VERSION before tagging."
exit 1
fi
echo "Version verified: $EXPECTED_VERSION"
- name: Bump version (manual mode)
if: steps.parse.outputs.mode == 'manual'
id: bump
run: |
PACKAGE="${{ steps.parse.outputs.package }}"
cd packages/$PACKAGE
CURRENT=$(node -p "require('./package.json').version")
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
case "${{ steps.parse.outputs.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
# Update package.json
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 "version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "Bumped version: $CURRENT -> $NEW_VERSION"
- name: Set final version
id: version
run: |
cd packages/${{ github.event.inputs.package }}
if [ "${{ github.event.inputs.version_type }}" = "custom" ]; then
npm version ${{ github.event.inputs.custom_version }} --no-git-tag-version --allow-same-version
if [ "${{ steps.parse.outputs.mode }}" = "tag" ]; then
echo "value=${{ steps.parse.outputs.version }}" >> $GITHUB_OUTPUT
else
npm version ${{ github.event.inputs.version_type }} --no-git-tag-version
echo "value=${{ steps.bump.outputs.version }}" >> $GITHUB_OUTPUT
fi
NEW_VERSION=$(node -p "require('./package.json').version")
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "发布版本: $NEW_VERSION"
- name: Build core package (if needed)
if: ${{ steps.parse.outputs.package != 'core' && steps.parse.outputs.package != 'node-editor' && steps.parse.outputs.package != 'worker-generator' }}
run: |
cd packages/framework/core
pnpm run build
- name: Build node-editor package (if needed for blueprint)
if: ${{ steps.parse.outputs.package == 'blueprint' }}
run: |
cd packages/node-editor
pnpm run build
- name: Build package
run: |
cd packages/${{ github.event.inputs.package }}
cd packages/${{ steps.parse.outputs.package }}
pnpm run build:npm
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
cd packages/${{ github.event.inputs.package }}/dist
npm publish --access public
cd packages/${{ steps.parse.outputs.package }}/dist
pnpm publish --access public --no-git-checks
- name: Create Pull Request
- name: Create GitHub Release (tag mode)
if: steps.parse.outputs.mode == 'tag'
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.parse.outputs.tag }}
name: "${{ steps.parse.outputs.package }} v${{ steps.version.outputs.value }}"
make_latest: false
body: |
## @esengine/${{ steps.parse.outputs.package }} v${{ steps.version.outputs.value }}
**NPM**: [@esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }}](https://www.npmjs.com/package/@esengine/${{ steps.parse.outputs.package }}/v/${{ steps.version.outputs.value }})
```bash
npm install @esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }}
```
---
*Auto-released by GitHub Actions*
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Pull Request (manual mode)
if: steps.parse.outputs.mode == 'manual'
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 }}
commit-message: "chore(${{ steps.parse.outputs.package }}): release v${{ steps.version.outputs.value }}"
branch: release/${{ steps.parse.outputs.package }}-v${{ steps.version.outputs.value }}
delete-branch: true
title: "chore(${{ github.event.inputs.package }}): Release v${{ steps.version.outputs.new_version }}"
title: "chore(${{ steps.parse.outputs.package }}): Release v${{ steps.version.outputs.value }}"
body: |
## 🚀 Release v${{ steps.version.outputs.new_version }}
## Release v${{ steps.version.outputs.value }}
此 PR 更新 `@esengine/${{ github.event.inputs.package }}` 包的版本号
This PR updates `@esengine/${{ steps.parse.outputs.package }}` package version.
### 变更
- ✅ 已发布到 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 }}`
### Changes
- Published to npm: [@esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }}](https://www.npmjs.com/package/@esengine/${{ steps.parse.outputs.package }}/v/${{ steps.version.outputs.value }})
- Updated `packages/${{ steps.parse.outputs.package }}/package.json` to `${{ steps.version.outputs.value }}`
---
*此 PR 由发布工作流自动创建*
*This PR was automatically created by the release workflow*
labels: |
release
${{ github.event.inputs.package }}
${{ steps.parse.outputs.package }}
automated pr

View File

@@ -1,48 +0,0 @@
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: 8
- 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

View File

@@ -1,58 +0,0 @@
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).

15
.gitignore vendored
View File

@@ -18,6 +18,9 @@ dist/
.cache/
.build-cache/
# Turborepo
.turbo/
# IDE 配置
.idea/
.vscode/
@@ -45,6 +48,14 @@ logs/
.env.test.local
.env.production.local
# 代码签名证书(敏感文件)
certs/
*.pfx
*.p12
*.cer
*.pem
*.key
# 测试覆盖率
coverage/
*.lcov
@@ -79,3 +90,7 @@ docs/.vitepress/dist/
# Tauri 捆绑输出
**/src-tauri/target/release/bundle/
**/src-tauri/target/debug/bundle/
# Rust 构建产物
**/engine-shared/target/
external/

2
.npmrc Normal file
View File

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

View File

@@ -37,9 +37,6 @@ This project follows the [Conventional Commits](https://www.conventionalcommits.
- **core**: 核心包 @esengine/ecs-framework
- **math**: 数学库包
- **network-client**: 网络客户端包
- **network-server**: 网络服务端包
- **network-shared**: 网络共享包
- **editor**: 编辑器
- **docs**: 文档
@@ -122,9 +119,9 @@ npm run format
## 问题反馈 / Issue Reporting
如果你发现了 bug 或有新功能建议,请[创建 Issue](https://github.com/esengine/ecs-framework/issues/new)。
如果你发现了 bug 或有新功能建议,请[创建 Issue](https://github.com/esengine/esengine/issues/new)。
If you find a bug or have a feature request, please [create an 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/esengine/issues/new).
## 许可证 / License

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 ECS Framework
Copyright (c) 2025 ESEngine Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

440
README.md
View File

@@ -1,90 +1,98 @@
# ECS Framework
<h1 align="center">
<img src="https://raw.githubusercontent.com/esengine/esengine/master/docs/public/logo.svg" alt="ESEngine" width="180">
<br>
ESEngine
</h1>
[![CI](https://github.com/esengine/ecs-framework/workflows/CI/badge.svg)](https://github.com/esengine/ecs-framework/actions)
[![codecov](https://codecov.io/gh/esengine/ecs-framework/graph/badge.svg)](https://codecov.io/gh/esengine/ecs-framework)
[![npm version](https://badge.fury.io/js/%40esengine%2Fecs-framework.svg)](https://badge.fury.io/js/%40esengine%2Fecs-framework)
[![npm downloads](https://img.shields.io/npm/dm/@esengine/ecs-framework.svg)](https://www.npmjs.com/package/@esengine/ecs-framework)
[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@esengine/ecs-framework)](https://bundlephobia.com/package/@esengine/ecs-framework)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![All Contributors](https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square)](#contributors)
[![GitHub stars](https://img.shields.io/github/stars/esengine/ecs-framework?style=social)](https://github.com/esengine/ecs-framework/stargazers)
[![DeepWiki](https://img.shields.io/badge/_AI_文档-DeepWiki-6366f1?style=flat&logo=gitbook&logoColor=white)](https://deepwiki.com/esengine/ecs-framework)
<p align="center">
<strong>Modular Game Framework for TypeScript</strong>
</p>
<div align="center">
<p align="center">
<a href="https://www.npmjs.com/package/@esengine/ecs-framework"><img src="https://img.shields.io/npm/v/@esengine/ecs-framework?style=flat-square&color=blue" alt="npm"></a>
<a href="https://github.com/esengine/esengine/actions"><img src="https://img.shields.io/github/actions/workflow/status/esengine/esengine/ci.yml?branch=master&style=flat-square" alt="build"></a>
<a href="https://github.com/esengine/esengine/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license"></a>
<a href="https://github.com/esengine/esengine/stargazers"><img src="https://img.shields.io/github/stars/esengine/esengine?style=flat-square" alt="stars"></a>
<img src="https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript">
</p>
<p>一个高性能的 TypeScript ECS (Entity-Component-System) 框架,专为现代游戏开发而设计。</p>
<p align="center">
<b>English</b> | <a href="./README_CN.md">中文</a>
</p>
<p>A high-performance TypeScript ECS (Entity-Component-System) framework designed for modern game development.</p>
</div>
<p align="center">
<a href="https://esengine.cn/">Documentation</a> ·
<a href="https://esengine.cn/api/README">API Reference</a> ·
<a href="./examples/">Examples</a>
</p>
---
## 📊 项目统计 / Project Stats
## What is ESEngine?
<div align="center">
ESEngine is a collection of **engine-agnostic game development modules** for TypeScript. Use them with Cocos Creator, Laya, Phaser, PixiJS, or any JavaScript game engine.
[![Star History Chart](https://api.star-history.com/svg?repos=esengine/ecs-framework&type=Date)](https://star-history.com/#esengine/ecs-framework&Date)
</div>
<div align="center">
<a href="https://github.com/esengine/ecs-framework/graphs/contributors">
<img src="https://contrib.rocks/image?repo=esengine/ecs-framework" />
</a>
</div>
### 📈 下载趋势 / Download Trends
<div align="center">
[![NPM Downloads](https://img.shields.io/npm/dt/@esengine/ecs-framework?label=Total%20Downloads&style=for-the-badge&color=blue)](https://www.npmjs.com/package/@esengine/ecs-framework)
[![NPM Trends](https://img.shields.io/npm/dm/@esengine/ecs-framework?label=Monthly%20Downloads&style=for-the-badge&color=success)](https://npmtrends.com/@esengine/ecs-framework)
</div>
---
## 特性
- **高性能** - 针对大规模实体优化支持SoA存储和批量处理
- **多线程计算** - Worker系统支持真正的并行处理充分利用多核CPU性能
- **类型安全** - 完整的TypeScript支持编译时类型检查
- **现代架构** - 支持多World、多Scene的分层架构设计
- **开发友好** - 内置调试工具和性能监控
- **跨平台** - 支持Cocos Creator、Laya引擎和Web平台
## 安装
The core is a high-performance **ECS (Entity-Component-System)** framework, accompanied by optional modules for AI, networking, physics, and more.
```bash
npm install @esengine/ecs-framework
```
## 快速开始
## Features
| Module | Description | Engine Required |
|--------|-------------|:---------------:|
| **ECS Core** | Entity-Component-System framework with reactive queries | No |
| **Behavior Tree** | AI behavior trees with visual editor support | No |
| **Blueprint** | Visual scripting system | No |
| **FSM** | Finite state machine | No |
| **Timer** | Timer and cooldown systems | No |
| **Spatial** | Spatial indexing and queries (QuadTree, Grid) | No |
| **Pathfinding** | A* and navigation mesh pathfinding | No |
| **Procgen** | Procedural generation (noise, random, sampling) | No |
| **RPC** | High-performance RPC communication framework | No |
| **Server** | Game server framework with rooms, auth, rate limiting | No |
| **Network** | Client networking with prediction, AOI, delta compression | No |
| **Transaction** | Game transaction system with Redis/Memory storage | No |
| **World Streaming** | Open world chunk loading and streaming | No |
> All framework modules can be used standalone with any rendering engine.
## Quick Start
### Using CLI (Recommended)
The easiest way to add ECS to your existing project:
```bash
# In your project directory
npx @esengine/cli init
```
The CLI automatically detects your project type (Cocos Creator 2.x/3.x, LayaAir 3.x, or Node.js) and generates the necessary integration code.
### Manual Setup
```typescript
import { Core, Scene, Component, EntitySystem, ECSComponent, ECSSystem, Matcher, Time } from '@esengine/ecs-framework';
import {
Core, Scene, Entity, Component, EntitySystem,
Matcher, Time, ECSComponent, ECSSystem
} from '@esengine/ecs-framework';
// 定义组件
// Define components (data only)
@ECSComponent('Position')
class Position extends Component {
constructor(public x = 0, public y = 0) {
super();
}
x = 0;
y = 0;
}
@ECSComponent('Velocity')
class Velocity extends Component {
constructor(public dx = 0, public dy = 0) {
super();
}
dx = 0;
dy = 0;
}
// 创建系统
// Define system (logic)
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
@@ -93,182 +101,212 @@ class MovementSystem extends EntitySystem {
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const position = entity.getComponent(Position)!;
const velocity = entity.getComponent(Velocity)!;
position.x += velocity.dx * Time.deltaTime;
position.y += velocity.dy * Time.deltaTime;
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
pos.x += vel.dx * Time.deltaTime;
pos.y += vel.dy * Time.deltaTime;
}
}
}
// 创建场景并启动
class GameScene extends Scene {
protected initialize(): void {
this.addSystem(new MovementSystem());
// Initialize
Core.create();
const scene = new Scene();
scene.addSystem(new MovementSystem());
const player = this.createEntity("Player");
player.addComponent(new Position(100, 100));
player.addComponent(new Velocity(50, 0));
const player = scene.createEntity('Player');
player.addComponent(new Position());
player.addComponent(new Velocity());
Core.setScene(scene);
// Integrate with your game loop
function gameLoop(currentTime: number) {
Core.update(currentTime / 1000);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
## Using with Other Engines
ESEngine's framework modules are designed to work alongside your preferred rendering engine:
### With Cocos Creator
```typescript
import { Component as CCComponent, _decorator } from 'cc';
import { Core, Scene, Matcher, EntitySystem } from '@esengine/ecs-framework';
import { BehaviorTreeExecutionSystem } from '@esengine/behavior-tree';
const { ccclass } = _decorator;
@ccclass('GameManager')
export class GameManager extends CCComponent {
private ecsScene!: Scene;
start() {
Core.create();
this.ecsScene = new Scene();
// Add ECS systems
this.ecsScene.addSystem(new BehaviorTreeExecutionSystem());
this.ecsScene.addSystem(new MyGameSystem());
Core.setScene(this.ecsScene);
}
update(dt: number) {
Core.update(dt);
}
}
```
// 启动游戏
Core.create();
Core.setScene(new GameScene());
### With Laya 3.x
// 游戏循环中更新
function gameLoop(deltaTime: number) {
Core.update(deltaTime);
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { FSMSystem } from '@esengine/fsm';
const { regClass } = Laya;
@regClass()
export class ECSManager extends Laya.Script {
private ecsScene = new Scene();
onAwake(): void {
Core.create();
this.ecsScene.addSystem(new FSMSystem());
Core.setScene(this.ecsScene);
}
onUpdate(): void {
Core.update(Laya.timer.delta / 1000);
}
onDestroy(): void {
Core.destroy();
}
}
```
## 核心特性
## Packages
- **实体查询** - 使用 Matcher API 进行高效的实体过滤
- **事件系统** - 类型安全的事件发布/订阅机制
- **性能优化** - SoA 存储优化,支持大规模实体处理
- **多线程支持** - Worker系统实现真正的并行计算充分利用多核CPU
- **多场景** - 支持 World/Scene 分层架构
- **时间管理** - 内置定时器和时间控制系统
### Framework (Engine-Agnostic)
## 🏗️ 架构设计 / Architecture
These packages have **zero rendering dependencies** and work with any engine:
```mermaid
graph TB
A[Core 核心] --> B[World 世界]
B --> C[Scene 场景]
C --> D[EntityManager 实体管理器]
C --> E[SystemManager 系统管理器]
D --> F[Entity 实体]
F --> G[Component 组件]
E --> H[EntitySystem 实体系统]
E --> I[WorkerSystem 工作线程系统]
style A fill:#e1f5ff
style B fill:#fff3e0
style C fill:#f3e5f5
style D fill:#e8f5e9
style E fill:#fff9c4
style F fill:#ffebee
style G fill:#e0f2f1
style H fill:#fce4ec
style I fill:#f1f8e9
```bash
npm install @esengine/ecs-framework # Core ECS
npm install @esengine/behavior-tree # AI behavior trees
npm install @esengine/blueprint # Visual scripting
npm install @esengine/fsm # State machines
npm install @esengine/timer # Timers & cooldowns
npm install @esengine/spatial # Spatial indexing
npm install @esengine/pathfinding # Pathfinding
npm install @esengine/procgen # Procedural generation
npm install @esengine/rpc # RPC framework
npm install @esengine/server # Game server
npm install @esengine/network # Client networking
npm install @esengine/transaction # Transaction system
npm install @esengine/world-streaming # World streaming
```
## 平台支持
### ESEngine Runtime (Optional)
支持主流游戏引擎和 Web 平台:
If you want a complete engine solution with rendering:
- **Cocos Creator**
- **Laya 引擎**
- **原生 Web** - 浏览器环境直接运行
- **小游戏平台** - 微信、支付宝等小游戏
| Category | Packages |
|----------|----------|
| **Core** | `engine-core`, `asset-system`, `material-system` |
| **Rendering** | `sprite`, `tilemap`, `particle`, `camera`, `mesh-3d` |
| **Physics** | `physics-rapier2d` |
| **Platform** | `platform-web`, `platform-wechat` |
## ECS Framework Editor
### Editor (Optional)
跨平台桌面编辑器,提供可视化开发和调试工具。
A visual editor built with Tauri for scene management:
### 主要功能
- Download from [Releases](https://github.com/esengine/esengine/releases)
- [Build from source](./packages/editor/editor-app/README.md)
- Supports behavior tree editing, tilemap painting, visual scripting
- **场景管理** - 可视化场景层级和实体管理
- **组件检视** - 实时查看和编辑实体组件
- **性能分析** - 内置 Profiler 监控系统性能
- **插件系统** - 可扩展的插件架构
- **远程调试** - 连接运行中的游戏进行实时调试
- **自动更新** - 支持热更新,自动获取最新版本
## Project Structure
### 下载
```
esengine/
├── packages/
│ ├── framework/ # Engine-agnostic modules (NPM publishable)
│ │ ├── core/ # ECS Framework
│ │ ├── math/ # Math utilities
│ │ ├── behavior-tree/ # AI behavior trees
│ │ ├── blueprint/ # Visual scripting
│ │ ├── fsm/ # Finite state machine
│ │ ├── timer/ # Timer system
│ │ ├── spatial/ # Spatial queries
│ │ ├── pathfinding/ # Pathfinding
│ │ ├── procgen/ # Procedural generation
│ │ ├── rpc/ # RPC framework
│ │ ├── server/ # Game server
│ │ ├── network/ # Client networking
│ │ ├── transaction/ # Transaction system
│ │ └── world-streaming/ # World streaming
│ │
│ ├── engine/ # ESEngine runtime
│ ├── rendering/ # Rendering modules
│ ├── physics/ # Physics modules
│ ├── editor/ # Visual editor
│ └── rust/ # WASM renderer
├── docs/ # Documentation
└── examples/ # Examples
```
[![Latest Release](https://img.shields.io/github/v/release/esengine/ecs-framework?label=下载最新版本&style=for-the-badge)](https://github.com/esengine/ecs-framework/releases/latest)
## Building from Source
支持 Windows、macOS (Intel & Apple Silicon)
```bash
git clone https://github.com/esengine/esengine.git
cd esengine
### 截图
pnpm install
pnpm build
<img src="screenshots/main_screetshot.png" alt="ECS Framework Editor" width="800">
# Type check framework packages
pnpm type-check:framework
<details>
<summary>查看更多截图</summary>
# Run tests
pnpm test
```
**性能分析器**
<img src="screenshots/performance_profiler.png" alt="Performance Profiler" width="600">
## Documentation
**插件管理**
<img src="screenshots/plugin_manager.png" alt="Plugin Manager" width="600">
- [ECS Framework Guide](./packages/framework/core/README.md)
- [Behavior Tree Guide](./packages/framework/behavior-tree/README.md)
- [Editor Setup Guide](./packages/editor/editor-app/README.md) ([中文](./packages/editor/editor-app/README_CN.md))
- [API Reference](https://esengine.cn/api/README)
**设置界面**
<img src="screenshots/settings.png" alt="Settings" width="600">
## Community
</details>
- [GitHub Issues](https://github.com/esengine/esengine/issues) - Bug reports and feature requests
- [GitHub Discussions](https://github.com/esengine/esengine/discussions) - Questions and ideas
- [Discord](https://discord.gg/gCAgzXFW) - Chat with the community
## 示例项目
## Contributing
- [Worker系统演示](https://esengine.github.io/ecs-framework/demos/worker-system/) - 多线程物理系统演示,展示高性能并行计算
- [割草机演示](https://github.com/esengine/lawn-mower-demo) - 完整的游戏示例
Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
## 文档
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
- [📚 AI智能文档](https://deepwiki.com/esengine/ecs-framework) - AI助手随时解答你的问题
- [快速入门](https://esengine.github.io/ecs-framework/guide/getting-started.html) - 详细教程和平台集成
- [完整指南](https://esengine.github.io/ecs-framework/guide/) - ECS 概念和使用指南
- [API 参考](https://esengine.github.io/ecs-framework/api/) - 完整 API 文档
## License
## 生态系统
ESEngine is licensed under the [MIT License](LICENSE). Free for personal and commercial use.
- [路径寻找](https://github.com/esengine/ecs-astar) - A*、BFS、Dijkstra 算法
- [AI 系统](https://github.com/esengine/BehaviourTree-ai) - 行为树、效用 AI
---
## 💪 支持项目 / Support the Project
如果这个项目对你有帮助,请考虑:
If this project helps you, please consider:
<div align="center">
[![GitHub Sponsors](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-ea4aaa?style=for-the-badge&logo=github)](https://github.com/sponsors/esengine)
[![Star on GitHub](https://img.shields.io/badge/⭐_Star-on_GitHub-yellow?style=for-the-badge&logo=github)](https://github.com/esengine/ecs-framework)
</div>
- ⭐ 给项目点个 Star
- 🐛 报告 Bug 或提出新功能
- 📝 改进文档
- 💖 成为赞助者
## 社区与支持
- [问题反馈](https://github.com/esengine/ecs-framework/issues) - Bug 报告和功能建议
- [讨论区](https://github.com/esengine/ecs-framework/discussions) - 提问、分享想法
- [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - ecs游戏框架交流
## 贡献者 / Contributors
感谢所有为这个项目做出贡献的人!
Thanks goes to these wonderful people:
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/esengine"><img src="https://avatars.githubusercontent.com/esengine?s=100" width="100px;" alt="esengine"/><br /><sub><b>esengine</b></sub></a><br /><a href="#maintenance-esengine" title="Maintenance">🚧</a> <a href="https://github.com/esengine/ecs-framework/commits?author=esengine" title="Code">💻</a> <a href="#design-esengine" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/foxling"><img src="https://avatars.githubusercontent.com/foxling?s=100" width="100px;" alt="LING YE"/><br /><sub><b>LING YE</b></sub></a><br /><a href="https://github.com/esengine/ecs-framework/commits?author=foxling" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MirageTank"><img src="https://avatars.githubusercontent.com/MirageTank?s=100" width="100px;" alt="MirageTank"/><br /><sub><b>MirageTank</b></sub></a><br /><a href="https://github.com/esengine/ecs-framework/commits?author=MirageTank" title="Code">💻</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
本项目遵循 [all-contributors](https://github.com/all-contributors/all-contributors) 规范。欢迎任何形式的贡献!
## 许可证
[MIT](LICENSE) © 2025 ECS Framework
<p align="center">
Made with care by the ESEngine community
</p>

313
README_CN.md Normal file
View File

@@ -0,0 +1,313 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/esengine/esengine/master/docs/public/logo.svg" alt="ESEngine" width="180">
<br>
ESEngine
</h1>
<p align="center">
<strong>TypeScript 模块化游戏框架</strong>
</p>
<p align="center">
<a href="https://www.npmjs.com/package/@esengine/ecs-framework"><img src="https://img.shields.io/npm/v/@esengine/ecs-framework?style=flat-square&color=blue" alt="npm"></a>
<a href="https://github.com/esengine/esengine/actions"><img src="https://img.shields.io/github/actions/workflow/status/esengine/esengine/ci.yml?branch=master&style=flat-square" alt="build"></a>
<a href="https://github.com/esengine/esengine/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license"></a>
<a href="https://github.com/esengine/esengine/stargazers"><img src="https://img.shields.io/github/stars/esengine/esengine?style=flat-square" alt="stars"></a>
<img src="https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript">
</p>
<p align="center">
<a href="./README.md">English</a> | <b>中文</b>
</p>
<p align="center">
<a href="https://esengine.cn/">文档</a> ·
<a href="https://esengine.cn/api/README">API 参考</a> ·
<a href="./examples/">示例</a>
</p>
---
## ESEngine 是什么?
ESEngine 是一套**引擎无关的游戏开发模块**,可与 Cocos Creator、Laya、Phaser、PixiJS 等任何 JavaScript 游戏引擎配合使用。
核心是一个高性能的 **ECS实体-组件-系统)** 框架,配套 AI、网络、物理等可选模块。
```bash
npm install @esengine/ecs-framework
```
## 功能模块
| 模块 | 描述 | 需要渲染引擎 |
|------|------|:----------:|
| **ECS 核心** | 实体-组件-系统框架,支持响应式查询 | 否 |
| **行为树** | AI 行为树,支持可视化编辑 | 否 |
| **蓝图** | 可视化脚本系统 | 否 |
| **状态机** | 有限状态机 | 否 |
| **定时器** | 定时器和冷却系统 | 否 |
| **空间索引** | 空间查询(四叉树、网格) | 否 |
| **寻路** | A* 和导航网格寻路 | 否 |
| **程序化生成** | 噪声、随机、采样等生成算法 | 否 |
| **RPC** | 高性能 RPC 通信框架 | 否 |
| **服务端** | 游戏服务器框架,支持房间、认证、速率限制 | 否 |
| **网络** | 客户端网络支持预测、AOI、增量压缩 | 否 |
| **事务系统** | 游戏事务系统,支持 Redis/内存存储 | 否 |
| **世界流送** | 开放世界分块加载和流送 | 否 |
> 所有框架模块都可以独立使用,无需依赖特定渲染引擎。
## 快速开始
### 使用 CLI推荐
在现有项目中添加 ECS 的最简单方式:
```bash
# 在项目目录中运行
npx @esengine/cli init
```
CLI 会自动检测项目类型Cocos Creator 2.x/3.x、LayaAir 3.x 或 Node.js并生成相应的集成代码。
### 手动配置
```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);
// 集成到你的游戏循环
function gameLoop(currentTime: number) {
Core.update(currentTime / 1000);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
## 与其他引擎配合使用
ESEngine 的框架模块设计为可与你喜欢的渲染引擎配合使用:
### 与 Cocos Creator 配合
```typescript
import { Component as CCComponent, _decorator } from 'cc';
import { Core, Scene, Matcher, EntitySystem } from '@esengine/ecs-framework';
import { BehaviorTreeExecutionSystem } from '@esengine/behavior-tree';
const { ccclass } = _decorator;
@ccclass('GameManager')
export class GameManager extends CCComponent {
private ecsScene!: Scene;
start() {
Core.create();
this.ecsScene = new Scene();
// 添加 ECS 系统
this.ecsScene.addSystem(new BehaviorTreeExecutionSystem());
this.ecsScene.addSystem(new MyGameSystem());
Core.setScene(this.ecsScene);
}
update(dt: number) {
Core.update(dt);
}
}
```
### 与 Laya 3.x 配合
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { FSMSystem } from '@esengine/fsm';
const { regClass } = Laya;
@regClass()
export class ECSManager extends Laya.Script {
private ecsScene = new Scene();
onAwake(): void {
Core.create();
this.ecsScene.addSystem(new FSMSystem());
Core.setScene(this.ecsScene);
}
onUpdate(): void {
Core.update(Laya.timer.delta / 1000);
}
onDestroy(): void {
Core.destroy();
}
}
```
## 包列表
### 框架包(引擎无关)
这些包**零渲染依赖**,可与任何引擎配合使用:
```bash
npm install @esengine/ecs-framework # ECS 核心
npm install @esengine/behavior-tree # AI 行为树
npm install @esengine/blueprint # 可视化脚本
npm install @esengine/fsm # 状态机
npm install @esengine/timer # 定时器和冷却
npm install @esengine/spatial # 空间索引
npm install @esengine/pathfinding # 寻路
npm install @esengine/procgen # 程序化生成
npm install @esengine/rpc # RPC 框架
npm install @esengine/server # 游戏服务器
npm install @esengine/network # 客户端网络
npm install @esengine/transaction # 事务系统
npm install @esengine/world-streaming # 世界流送
```
### ESEngine 运行时(可选)
如果你需要完整的引擎解决方案:
| 分类 | 包名 |
|------|------|
| **核心** | `engine-core`, `asset-system`, `material-system` |
| **渲染** | `sprite`, `tilemap`, `particle`, `camera`, `mesh-3d` |
| **物理** | `physics-rapier2d` |
| **平台** | `platform-web`, `platform-wechat` |
### 编辑器(可选)
基于 Tauri 构建的可视化编辑器:
- 从 [Releases](https://github.com/esengine/esengine/releases) 下载
- [从源码构建](./packages/editor/editor-app/README.md)
- 支持行为树编辑、Tilemap 绘制、可视化脚本
## 项目结构
```
esengine/
├── packages/
│ ├── framework/ # 引擎无关模块(可发布到 NPM
│ │ ├── core/ # ECS 框架
│ │ ├── math/ # 数学工具
│ │ ├── behavior-tree/ # AI 行为树
│ │ ├── blueprint/ # 可视化脚本
│ │ ├── fsm/ # 有限状态机
│ │ ├── timer/ # 定时器系统
│ │ ├── spatial/ # 空间查询
│ │ ├── pathfinding/ # 寻路
│ │ ├── procgen/ # 程序化生成
│ │ ├── rpc/ # RPC 框架
│ │ ├── server/ # 游戏服务器
│ │ ├── network/ # 客户端网络
│ │ ├── transaction/ # 事务系统
│ │ └── world-streaming/ # 世界流送
│ │
│ ├── engine/ # ESEngine 运行时
│ ├── rendering/ # 渲染模块
│ ├── physics/ # 物理模块
│ ├── editor/ # 可视化编辑器
│ └── rust/ # WASM 渲染器
├── docs/ # 文档
└── examples/ # 示例
```
## 从源码构建
```bash
git clone https://github.com/esengine/esengine.git
cd esengine
pnpm install
pnpm build
# 框架包类型检查
pnpm type-check:framework
# 运行测试
pnpm test
```
## 文档
- [ECS 框架指南](./packages/framework/core/README.md)
- [行为树指南](./packages/framework/behavior-tree/README.md)
- [编辑器启动指南](./packages/editor/editor-app/README_CN.md) ([English](./packages/editor/editor-app/README.md))
- [API 参考](https://esengine.cn/api/README)
## 社区
- [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - 中文社区
- [Discord](https://discord.gg/gCAgzXFW) - 国际社区
- [GitHub Issues](https://github.com/esengine/esengine/issues) - Bug 反馈和功能建议
- [GitHub Discussions](https://github.com/esengine/esengine/discussions) - 问题和想法
## 贡献
欢迎贡献代码!提交 PR 前请阅读贡献指南。
1. Fork 仓库
2. 创建功能分支 (`git checkout -b feature/amazing-feature`)
3. 提交修改 (`git commit -m 'Add amazing feature'`)
4. 推送分支 (`git push origin feature/amazing-feature`)
5. 发起 Pull Request
## 许可证
ESEngine 基于 [MIT 协议](LICENSE) 开源,个人和商业使用均免费。
---
<p align="center">
由 ESEngine 社区用心打造
</p>

View File

@@ -1,13 +1,71 @@
# Security Policy / 安全政策
**English** | [中文](#安全政策-1)
## Supported Versions
We provide security updates for the following versions:
| Version | Supported |
| ------- | ------------------ |
| 2.x.x | :white_check_mark: |
| 1.x.x | :x: |
## Reporting a Vulnerability
If you discover a security vulnerability, please report it through the following channels:
### Reporting Channels
- **GitHub Security Advisories**: [Report a vulnerability](https://github.com/esengine/esengine/security/advisories/new) (Recommended)
- **Email**: security@esengine.dev
### Reporting Guidelines
1. **Do NOT** report security vulnerabilities in public issues
2. Provide a detailed description of the vulnerability, including:
- Affected versions
- Steps to reproduce
- Potential impact
- Suggested fix (if available)
### Response Timeline
- **Acknowledgment**: Within 72 hours
- **Initial Assessment**: Within 1 week
- **Fix Release**: Typically within 2-4 weeks, depending on severity
### Process
1. We will confirm the existence and severity of the vulnerability
2. Develop and test a fix
3. Release a security update
4. Publicly disclose the vulnerability details after the fix is released
## Security Best Practices
When using ESEngine, please follow these security recommendations:
- Always use the latest stable version
- Regularly update dependencies
- Disable debug mode in production
- Validate all external input data
- Do not store sensitive information on the client side
---
# 安全政策
[English](#security-policy--安全政策) | **中文**
## 支持的版本
我们为以下版本提供安全更新:
| 版本 | 支持状态 |
| ------- | ------------------ |
| 2.0.x | :white_check_mark: |
| 1.0.x | :x: |
| 2.x.x | :white_check_mark: |
| 1.x.x | :x: |
## 报告漏洞
@@ -15,10 +73,10 @@
### 报告渠道
- **邮箱**: [安全邮箱将在实际部署时提供]
- **GitHub**: 创建私有安全报告(推荐)
- **GitHub 安全公告**: [报告漏洞](https://github.com/esengine/esengine/security/advisories/new)(推荐)
- **邮箱**: security@esengine.dev
### 报告流程
### 报告指南
1. **不要**在公开的 issue 中报告安全漏洞
2. 提供详细的漏洞描述,包括:
@@ -40,9 +98,9 @@
3. 发布安全更新
4. 在修复发布后,会在相关渠道公布漏洞详情
### 安全最佳实践
## 安全最佳实践
使用 ECS Framework 时,请遵循以下安全建议:
使用 ESEngine 时,请遵循以下安全建议:
- 始终使用最新的稳定版本
- 定期更新依赖项
@@ -50,4 +108,6 @@
- 验证所有外部输入数据
- 不要在客户端存储敏感信息
感谢您帮助保持 ECS Framework 的安全性!
感谢您帮助保持 ESEngine 的安全性!
Thank you for helping keep ESEngine secure!

21
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

View File

@@ -1,240 +0,0 @@
import { defineConfig } from 'vitepress'
import Icons from 'unplugin-icons/vite'
import { readFileSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const corePackageJson = JSON.parse(
readFileSync(join(__dirname, '../../packages/core/package.json'), 'utf-8')
)
export default defineConfig({
vite: {
plugins: [
Icons({
compiler: 'vue3',
autoInstall: true
})
],
server: {
fs: {
allow: ['..']
},
middlewareMode: false,
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin'
}
}
},
title: 'ECS Framework',
description: '高性能TypeScript ECS框架 - 为游戏开发而生',
lang: 'zh-CN',
themeConfig: {
nav: [
{ text: '首页', link: '/' },
{ text: '快速开始', link: '/guide/getting-started' },
{ text: '指南', link: '/guide/' },
{ text: 'API', link: '/api/README' },
{
text: '示例',
items: [
{ text: 'Worker系统演示', link: '/examples/worker-system-demo' },
{ text: '割草机演示', link: 'https://github.com/esengine/lawn-mower-demo' }
]
},
{
text: `v${corePackageJson.version}`,
link: 'https://github.com/esengine/ecs-framework/releases'
}
],
sidebar: {
'/guide/': [
{
text: '开始使用',
items: [
{ text: '快速开始', link: '/guide/getting-started' },
{ text: '指南概览', link: '/guide/' }
]
},
{
text: '核心概念',
collapsed: false,
items: [
{ text: '实体类 (Entity)', link: '/guide/entity' },
{ text: '组件系统 (Component)', link: '/guide/component' },
{ text: '实体查询系统', link: '/guide/entity-query' },
{
text: '系统架构 (System)',
link: '/guide/system',
items: [
{ text: 'Worker系统 (多线程)', link: '/guide/worker-system' }
]
},
{
text: '场景管理 (Scene)',
link: '/guide/scene',
items: [
{ text: 'SceneManager', link: '/guide/scene-manager' },
{ text: 'WorldManager', link: '/guide/world-manager' }
]
},
{
text: '行为树系统 (Behavior Tree)',
link: '/guide/behavior-tree/',
items: [
{ text: '快速开始', link: '/guide/behavior-tree/getting-started' },
{ text: '核心概念', link: '/guide/behavior-tree/core-concepts' },
{ text: '编辑器指南', link: '/guide/behavior-tree/editor-guide' },
{ text: '编辑器工作流', link: '/guide/behavior-tree/editor-workflow' },
{ text: '自定义动作组件', link: '/guide/behavior-tree/custom-actions' },
{ text: 'Cocos Creator集成', link: '/guide/behavior-tree/cocos-integration' },
{ text: 'Laya引擎集成', link: '/guide/behavior-tree/laya-integration' },
{ text: '高级用法', link: '/guide/behavior-tree/advanced-usage' },
{ text: '最佳实践', link: '/guide/behavior-tree/best-practices' }
]
},
{ text: '序列化系统 (Serialization)', link: '/guide/serialization' },
{ text: '事件系统 (Event)', link: '/guide/event-system' },
{ text: '时间和定时器 (Time)', link: '/guide/time-and-timers' },
{ text: '日志系统 (Logger)', link: '/guide/logging' }
]
},
{
text: '高级特性',
collapsed: false,
items: [
{ text: '服务容器 (Service Container)', link: '/guide/service-container' },
{ text: '插件系统 (Plugin System)', link: '/guide/plugin-system' }
]
},
{
text: '平台适配器',
link: '/guide/platform-adapter',
collapsed: false,
items: [
{ text: '浏览器适配器', link: '/guide/platform-adapter/browser' },
{ text: '微信小游戏适配器', link: '/guide/platform-adapter/wechat-minigame' },
{ text: 'Node.js适配器', link: '/guide/platform-adapter/nodejs' }
]
}
],
'/examples/': [
{
text: '示例',
items: [
{ text: '示例概览', link: '/examples/' },
{ text: 'Worker系统演示', link: '/examples/worker-system-demo' }
]
}
],
'/api/': [
{
text: 'API 参考',
items: [
{ text: '概述', link: '/api/README' },
{
text: '核心类',
collapsed: false,
items: [
{ text: 'Core', link: '/api/classes/Core' },
{ text: 'Scene', link: '/api/classes/Scene' },
{ text: 'World', link: '/api/classes/World' },
{ text: 'Entity', link: '/api/classes/Entity' },
{ text: 'Component', link: '/api/classes/Component' },
{ text: 'EntitySystem', link: '/api/classes/EntitySystem' }
]
},
{
text: '系统类',
collapsed: true,
items: [
{ text: 'PassiveSystem', link: '/api/classes/PassiveSystem' },
{ text: 'ProcessingSystem', link: '/api/classes/ProcessingSystem' },
{ text: 'IntervalSystem', link: '/api/classes/IntervalSystem' }
]
},
{
text: '工具类',
collapsed: true,
items: [
{ text: 'Matcher', link: '/api/classes/Matcher' },
{ text: 'Time', link: '/api/classes/Time' },
{ text: 'PerformanceMonitor', link: '/api/classes/PerformanceMonitor' },
{ text: 'DebugManager', link: '/api/classes/DebugManager' }
]
},
{
text: '接口',
collapsed: true,
items: [
{ text: 'IScene', link: '/api/interfaces/IScene' },
{ text: 'IComponent', link: '/api/interfaces/IComponent' },
{ text: 'ISystemBase', link: '/api/interfaces/ISystemBase' },
{ text: 'ICoreConfig', link: '/api/interfaces/ICoreConfig' }
]
},
{
text: '装饰器',
collapsed: true,
items: [
{ text: '@ECSComponent', link: '/api/functions/ECSComponent' },
{ text: '@ECSSystem', link: '/api/functions/ECSSystem' }
]
},
{
text: '枚举',
collapsed: true,
items: [
{ text: 'ECSEventType', link: '/api/enumerations/ECSEventType' },
{ text: 'LogLevel', link: '/api/enumerations/LogLevel' }
]
}
]
}
]
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/esengine/ecs-framework' }
],
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2025 ECS Framework'
},
editLink: {
pattern: 'https://github.com/esengine/ecs-framework/edit/master/docs/:path',
text: '在 GitHub 上编辑此页'
},
search: {
provider: 'local'
},
outline: {
level: [2, 3],
label: '目录'
}
},
head: [
['meta', { name: 'theme-color', content: '#646cff' }],
['link', { rel: 'icon', href: '/favicon.ico' }]
],
base: '/ecs-framework/',
cleanUrls: true,
markdown: {
lineNumbers: true,
theme: {
light: 'github-light',
dark: 'github-dark'
}
}
})

49
docs/README.md Normal file
View File

@@ -0,0 +1,49 @@
# Starlight Starter Kit: Basics
[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build)
```
npm create astro@latest -- --template starlight
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro + Starlight project, you'll see the following folders and files:
```
.
├── public/
├── src/
│ ├── assets/
│ ├── content/
│ │ └── docs/
│ └── content.config.ts
├── astro.config.mjs
├── package.json
└── tsconfig.json
```
Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
Images can be added to `src/assets/` and embedded in Markdown with a relative link.
Static assets, like favicons, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Check out [Starlights docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).

345
docs/astro.config.mjs Normal file
View File

@@ -0,0 +1,345 @@
// @ts-check
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import vue from '@astrojs/vue';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
integrations: [
starlight({
title: 'ESEngine',
logo: {
src: './src/assets/logo.svg',
replacesTitle: false,
},
social: [
{ icon: 'github', label: 'GitHub', href: 'https://github.com/esengine/esengine' }
],
defaultLocale: 'root',
locales: {
root: {
label: '简体中文',
lang: 'zh-CN',
},
en: {
label: 'English',
lang: 'en',
},
},
sidebar: [
{
label: '快速开始',
translations: { en: 'Getting Started' },
items: [
{ label: '快速入门', slug: 'guide/getting-started', translations: { en: 'Quick Start' } },
{ label: '指南概览', slug: 'guide', translations: { en: 'Guide Overview' } },
],
},
{
label: '核心概念',
translations: { en: 'Core Concepts' },
items: [
{
label: '实体',
translations: { en: 'Entity' },
items: [
{ label: '概述', slug: 'guide/entity', translations: { en: 'Overview' } },
{ label: '组件操作', slug: 'guide/entity/component-operations', translations: { en: 'Component Operations' } },
{ label: '实体句柄', slug: 'guide/entity/entity-handle', translations: { en: 'Entity Handle' } },
{ label: '生命周期', slug: 'guide/entity/lifecycle', translations: { en: 'Lifecycle' } },
],
},
{ label: '层级结构', slug: 'guide/hierarchy', translations: { en: 'Hierarchy' } },
{
label: '组件',
translations: { en: 'Component' },
items: [
{ label: '概述', slug: 'guide/component', translations: { en: 'Overview' } },
{ label: '生命周期', slug: 'guide/component/lifecycle', translations: { en: 'Lifecycle' } },
{ label: 'EntityRef 装饰器', slug: 'guide/component/entity-ref', translations: { en: 'EntityRef' } },
{ label: '最佳实践', slug: 'guide/component/best-practices', translations: { en: 'Best Practices' } },
],
},
{
label: '实体查询',
translations: { en: 'Entity Query' },
items: [
{ label: '概述', slug: 'guide/entity-query', translations: { en: 'Overview' } },
{ label: 'Matcher API', slug: 'guide/entity-query/matcher-api', translations: { en: 'Matcher API' } },
{ label: '编译查询', slug: 'guide/entity-query/compiled-query', translations: { en: 'Compiled Query' } },
{ label: '最佳实践', slug: 'guide/entity-query/best-practices', translations: { en: 'Best Practices' } },
],
},
{
label: '系统',
translations: { en: 'System' },
items: [
{ label: '概述', slug: 'guide/system', translations: { en: 'Overview' } },
{ label: '系统类型', slug: 'guide/system/types', translations: { en: 'System Types' } },
{ label: '生命周期', slug: 'guide/system/lifecycle', translations: { en: 'Lifecycle' } },
{ label: '命令缓冲区', slug: 'guide/system/command-buffer', translations: { en: 'Command Buffer' } },
{ label: '系统调度', slug: 'guide/system/scheduling', translations: { en: 'Scheduling' } },
{ label: '变更检测', slug: 'guide/system/change-detection', translations: { en: 'Change Detection' } },
{ label: '最佳实践', slug: 'guide/system/best-practices', translations: { en: 'Best Practices' } },
],
},
{
label: '场景',
translations: { en: 'Scene' },
items: [
{ label: '概述', slug: 'guide/scene', translations: { en: 'Overview' } },
{ label: '生命周期', slug: 'guide/scene/lifecycle', translations: { en: 'Lifecycle' } },
{ label: '实体管理', slug: 'guide/scene/entity-management', translations: { en: 'Entity Management' } },
{ label: '系统管理', slug: 'guide/scene/system-management', translations: { en: 'System Management' } },
{ label: '事件系统', slug: 'guide/scene/events', translations: { en: 'Events' } },
{ label: '调试与监控', slug: 'guide/scene/debugging', translations: { en: 'Debugging' } },
{ label: '最佳实践', slug: 'guide/scene/best-practices', translations: { en: 'Best Practices' } },
],
},
{
label: '序列化',
translations: { en: 'Serialization' },
items: [
{ label: '概述', slug: 'guide/serialization', translations: { en: 'Overview' } },
{ label: '装饰器与继承', slug: 'guide/serialization/decorators', translations: { en: 'Decorators & Inheritance' } },
{ label: '增量序列化', slug: 'guide/serialization/incremental', translations: { en: 'Incremental' } },
{ label: '版本迁移', slug: 'guide/serialization/migration', translations: { en: 'Migration' } },
{ label: '使用场景', slug: 'guide/serialization/use-cases', translations: { en: 'Use Cases' } },
],
},
{ label: '事件系统', slug: 'guide/event-system', translations: { en: 'Event System' } },
{ label: '时间与定时器', slug: 'guide/time-and-timers', translations: { en: 'Time & Timers' } },
{ label: '日志系统', slug: 'guide/logging', translations: { en: 'Logging' } },
],
},
{
label: '高级功能',
translations: { en: 'Advanced Features' },
items: [
{
label: '服务容器',
translations: { en: 'Service Container' },
items: [
{ label: '概述', slug: 'guide/service-container', translations: { en: 'Overview' } },
{ label: '内置服务', slug: 'guide/service-container/built-in-services', translations: { en: 'Built-in Services' } },
{ label: '依赖注入', slug: 'guide/service-container/dependency-injection', translations: { en: 'Dependency Injection' } },
{ label: 'PluginServiceRegistry', slug: 'guide/service-container/plugin-service-registry', translations: { en: 'PluginServiceRegistry' } },
{ label: '高级用法', slug: 'guide/service-container/advanced', translations: { en: 'Advanced' } },
],
},
{
label: '插件系统',
translations: { en: 'Plugin System' },
items: [
{ label: '概述', slug: 'guide/plugin-system', translations: { en: 'Overview' } },
{ label: '插件开发', slug: 'guide/plugin-system/development', translations: { en: 'Development' } },
{ label: '服务与系统', slug: 'guide/plugin-system/services-systems', translations: { en: 'Services & Systems' } },
{ label: '依赖管理', slug: 'guide/plugin-system/dependencies', translations: { en: 'Dependencies' } },
{ label: '插件管理', slug: 'guide/plugin-system/management', translations: { en: 'Management' } },
{ label: '示例插件', slug: 'guide/plugin-system/examples', translations: { en: 'Examples' } },
{ label: '最佳实践', slug: 'guide/plugin-system/best-practices', translations: { en: 'Best Practices' } },
],
},
{
label: 'Worker 系统',
translations: { en: 'Worker System' },
items: [
{ label: '概述', slug: 'guide/worker-system', translations: { en: 'Overview' } },
{ label: '配置选项', slug: 'guide/worker-system/configuration', translations: { en: 'Configuration' } },
{ label: '完整示例', slug: 'guide/worker-system/examples', translations: { en: 'Examples' } },
{ label: '微信小游戏', slug: 'guide/worker-system/wechat', translations: { en: 'WeChat' } },
{ label: '最佳实践', slug: 'guide/worker-system/best-practices', translations: { en: 'Best Practices' } },
],
},
],
},
{
label: '平台适配器',
translations: { en: 'Platform Adapters' },
items: [
{ label: '概览', slug: 'guide/platform-adapter', translations: { en: 'Overview' } },
{ label: '浏览器', slug: 'guide/platform-adapter/browser', translations: { en: 'Browser' } },
{ label: '微信小游戏', slug: 'guide/platform-adapter/wechat-minigame', translations: { en: 'WeChat Mini Game' } },
{ label: 'Node.js', slug: 'guide/platform-adapter/nodejs', translations: { en: 'Node.js' } },
],
},
{
label: '模块',
translations: { en: 'Modules' },
items: [
{ label: '模块总览', slug: 'modules', translations: { en: 'Modules Overview' } },
{
label: '行为树',
translations: { en: 'Behavior Tree' },
items: [
{ label: '概述', slug: 'modules/behavior-tree', translations: { en: 'Overview' } },
{ label: '快速开始', slug: 'modules/behavior-tree/getting-started', translations: { en: 'Getting Started' } },
{ label: '核心概念', slug: 'modules/behavior-tree/core-concepts', translations: { en: 'Core Concepts' } },
{ label: '编辑器指南', slug: 'modules/behavior-tree/editor-guide', translations: { en: 'Editor Guide' } },
{ label: '编辑器工作流', slug: 'modules/behavior-tree/editor-workflow', translations: { en: 'Editor Workflow' } },
{ label: '资产管理', slug: 'modules/behavior-tree/asset-management', translations: { en: 'Asset Management' } },
{ label: '自定义节点', slug: 'modules/behavior-tree/custom-actions', translations: { en: 'Custom Actions' } },
{ label: '高级用法', slug: 'modules/behavior-tree/advanced-usage', translations: { en: 'Advanced Usage' } },
{ label: '最佳实践', slug: 'modules/behavior-tree/best-practices', translations: { en: 'Best Practices' } },
{ label: 'Cocos 集成', slug: 'modules/behavior-tree/cocos-integration', translations: { en: 'Cocos Integration' } },
{ label: 'Laya 集成', slug: 'modules/behavior-tree/laya-integration', translations: { en: 'Laya Integration' } },
{ label: 'Node.js 使用', slug: 'modules/behavior-tree/nodejs-usage', translations: { en: 'Node.js Usage' } },
],
},
{
label: '状态机',
translations: { en: 'FSM' },
items: [
{ label: '概述', slug: 'modules/fsm', translations: { en: 'Overview' } },
{ label: 'API 参考', slug: 'modules/fsm/api', translations: { en: 'API Reference' } },
{ label: '实际示例', slug: 'modules/fsm/examples', translations: { en: 'Examples' } },
],
},
{
label: '定时器',
translations: { en: 'Timer' },
items: [
{ label: '概述', slug: 'modules/timer', translations: { en: 'Overview' } },
{ label: 'API 参考', slug: 'modules/timer/api', translations: { en: 'API Reference' } },
{ label: '实际示例', slug: 'modules/timer/examples', translations: { en: 'Examples' } },
{ label: '最佳实践', slug: 'modules/timer/best-practices', translations: { en: 'Best Practices' } },
],
},
{
label: '空间索引',
translations: { en: 'Spatial' },
items: [
{ label: '概述', slug: 'modules/spatial', translations: { en: 'Overview' } },
{ label: '空间索引 API', slug: 'modules/spatial/spatial-index', translations: { en: 'Spatial Index API' } },
{ label: 'AOI 兴趣区域', slug: 'modules/spatial/aoi', translations: { en: 'AOI' } },
{ label: '实际示例', slug: 'modules/spatial/examples', translations: { en: 'Examples' } },
{ label: '工具与优化', slug: 'modules/spatial/utilities', translations: { en: 'Utilities' } },
],
},
{
label: '寻路',
translations: { en: 'Pathfinding' },
items: [
{ label: '概述', slug: 'modules/pathfinding', translations: { en: 'Overview' } },
{ label: '网格地图 API', slug: 'modules/pathfinding/grid-map', translations: { en: 'Grid Map API' } },
{ label: '导航网格 API', slug: 'modules/pathfinding/navmesh', translations: { en: 'NavMesh API' } },
{ label: '路径平滑', slug: 'modules/pathfinding/smoothing', translations: { en: 'Path Smoothing' } },
{ label: '实际示例', slug: 'modules/pathfinding/examples', translations: { en: 'Examples' } },
],
},
{
label: '蓝图',
translations: { en: 'Blueprint' },
items: [
{ label: '概述', slug: 'modules/blueprint', translations: { en: 'Overview' } },
{ label: '虚拟机 API', slug: 'modules/blueprint/vm', translations: { en: 'VM API' } },
{ label: '自定义节点', slug: 'modules/blueprint/custom-nodes', translations: { en: 'Custom Nodes' } },
{ label: '内置节点', slug: 'modules/blueprint/nodes', translations: { en: 'Built-in Nodes' } },
{ label: '蓝图组合', slug: 'modules/blueprint/composition', translations: { en: 'Composition' } },
{ label: '实际示例', slug: 'modules/blueprint/examples', translations: { en: 'Examples' } },
],
},
{
label: '程序生成',
translations: { en: 'Procgen' },
items: [
{ label: '概述', slug: 'modules/procgen', translations: { en: 'Overview' } },
{ label: '噪声函数', slug: 'modules/procgen/noise', translations: { en: 'Noise Functions' } },
{ label: '种子随机数', slug: 'modules/procgen/random', translations: { en: 'Seeded Random' } },
{ label: '采样工具', slug: 'modules/procgen/sampling', translations: { en: 'Sampling' } },
{ label: '实际示例', slug: 'modules/procgen/examples', translations: { en: 'Examples' } },
],
},
{
label: 'RPC 通信',
translations: { en: 'RPC' },
items: [
{ label: '概述', slug: 'modules/rpc', translations: { en: 'Overview' } },
{ label: '服务端', slug: 'modules/rpc/server', translations: { en: 'Server' } },
{ label: '客户端', slug: 'modules/rpc/client', translations: { en: 'Client' } },
{ label: '编解码', slug: 'modules/rpc/codec', translations: { en: 'Codec' } },
],
},
{
label: '网络同步',
translations: { en: 'Network' },
items: [
{ label: '概述', slug: 'modules/network', translations: { en: 'Overview' } },
{ label: '客户端', slug: 'modules/network/client', translations: { en: 'Client' } },
{ label: '服务器', slug: 'modules/network/server', translations: { en: 'Server' } },
{ label: '认证系统', slug: 'modules/network/auth', translations: { en: 'Authentication' } },
{ label: '速率限制', slug: 'modules/network/rate-limit', translations: { en: 'Rate Limiting' } },
{ label: '状态同步', slug: 'modules/network/sync', translations: { en: 'State Sync' } },
{ label: '客户端预测', slug: 'modules/network/prediction', translations: { en: 'Prediction' } },
{ label: 'AOI 兴趣区域', slug: 'modules/network/aoi', translations: { en: 'AOI' } },
{ label: '增量压缩', slug: 'modules/network/delta', translations: { en: 'Delta Compression' } },
{ label: 'API 参考', slug: 'modules/network/api', translations: { en: 'API Reference' } },
],
},
{
label: '事务系统',
translations: { en: 'Transaction' },
items: [
{ label: '概述', slug: 'modules/transaction', translations: { en: 'Overview' } },
{ label: '核心概念', slug: 'modules/transaction/core', translations: { en: 'Core Concepts' } },
{ label: '存储层', slug: 'modules/transaction/storage', translations: { en: 'Storage Layer' } },
{ label: '操作', slug: 'modules/transaction/operations', translations: { en: 'Operations' } },
{ label: '分布式事务', slug: 'modules/transaction/distributed', translations: { en: 'Distributed' } },
],
},
{
label: '世界流式加载',
translations: { en: 'World Streaming' },
items: [
{ label: '概述', slug: 'modules/world-streaming', translations: { en: 'Overview' } },
{ label: '区块管理', slug: 'modules/world-streaming/chunk-manager', translations: { en: 'Chunk Manager' } },
{ label: '流式系统', slug: 'modules/world-streaming/streaming-system', translations: { en: 'Streaming System' } },
{ label: '序列化', slug: 'modules/world-streaming/serialization', translations: { en: 'Serialization' } },
{ label: '实际示例', slug: 'modules/world-streaming/examples', translations: { en: 'Examples' } },
],
},
],
},
{
label: '示例',
translations: { en: 'Examples' },
items: [
{ label: '示例总览', slug: 'examples', translations: { en: 'Examples Overview' } },
{ label: 'Worker 系统演示', slug: 'examples/worker-system-demo', translations: { en: 'Worker System Demo' } },
],
},
{
label: 'API 参考',
translations: { en: 'API Reference' },
autogenerate: { directory: 'api' },
},
{
label: '更新日志',
translations: { en: 'Changelog' },
items: [
{ label: '@esengine/ecs-framework', link: 'https://github.com/esengine/esengine/blob/master/packages/framework/core/CHANGELOG.md', attrs: { target: '_blank' } },
{ label: '@esengine/behavior-tree', link: 'https://github.com/esengine/esengine/blob/master/packages/framework/behavior-tree/CHANGELOG.md', attrs: { target: '_blank' } },
{ label: '@esengine/fsm', link: 'https://github.com/esengine/esengine/blob/master/packages/framework/fsm/CHANGELOG.md', attrs: { target: '_blank' } },
{ label: '@esengine/timer', link: 'https://github.com/esengine/esengine/blob/master/packages/framework/timer/CHANGELOG.md', attrs: { target: '_blank' } },
{ label: '@esengine/network', link: 'https://github.com/esengine/esengine/blob/master/packages/framework/network/CHANGELOG.md', attrs: { target: '_blank' } },
{ label: '@esengine/transaction', link: 'https://github.com/esengine/esengine/blob/master/packages/framework/transaction/CHANGELOG.md', attrs: { target: '_blank' } },
{ label: '@esengine/rpc', link: 'https://github.com/esengine/esengine/blob/master/packages/framework/rpc/CHANGELOG.md', attrs: { target: '_blank' } },
{ label: '@esengine/cli', link: 'https://github.com/esengine/esengine/blob/master/packages/tools/cli/CHANGELOG.md', attrs: { target: '_blank' } },
],
},
],
customCss: ['./src/styles/custom.css'],
head: [
{ tag: 'meta', attrs: { name: 'theme-color', content: '#646cff' } },
],
components: {
Head: './src/components/Head.astro',
ThemeSelect: './src/components/ThemeSelect.astro',
},
}),
vue(),
],
vite: {
plugins: [tailwindcss()],
},
});

View File

@@ -1,658 +0,0 @@
# 组件系统
在 ECS 架构中组件Component是数据和行为的载体。组件定义了实体具有的属性和功能是 ECS 架构的核心构建块。
## 基本概念
组件是继承自 `Component` 抽象基类的具体类,用于:
- 存储实体的数据(如位置、速度、健康值等)
- 定义与数据相关的行为方法
- 提供生命周期回调钩子
- 支持序列化和调试
## 创建组件
### 基础组件定义
```typescript
import { Component, ECSComponent } from '@esengine/ecs-framework';
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
@ECSComponent('Health')
class Health extends Component {
current: number;
max: number;
constructor(max: number = 100) {
super();
this.max = max;
this.current = max;
}
// 组件可以包含行为方法
takeDamage(damage: number): void {
this.current = Math.max(0, this.current - damage);
}
heal(amount: number): void {
this.current = Math.min(this.max, this.current + amount);
}
isDead(): boolean {
return this.current <= 0;
}
}
```
### 组件装饰器
**必须使用 `@ECSComponent` 装饰器**,这确保了:
- 组件在代码混淆后仍能正确识别
- 提供稳定的类型名称用于序列化和调试
- 框架能正确管理组件注册
```typescript
// 正确的用法
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// 错误的用法 - 没有装饰器
class BadComponent extends Component {
// 这样定义的组件可能在生产环境出现问题
}
```
## 组件生命周期
组件提供了生命周期钩子,可以重写来执行特定的逻辑:
```typescript
@ECSComponent('ExampleComponent')
class ExampleComponent extends Component {
private resource: SomeResource | null = null;
/**
* 组件被添加到实体时调用
* 用于初始化资源、建立引用等
*/
onAddedToEntity(): void {
console.log(`组件 ${this.constructor.name} 已添加实体ID: ${this.entityId}`);
this.resource = new SomeResource();
}
/**
* 组件从实体移除时调用
* 用于清理资源、断开引用等
*/
onRemovedFromEntity(): void {
console.log(`组件 ${this.constructor.name} 已移除`);
if (this.resource) {
this.resource.cleanup();
this.resource = null;
}
}
}
```
## 组件与实体的关系
组件存储了所属实体的ID (`entityId`)而不是直接引用实体对象。这是ECS数据导向设计的体现避免了循环引用。
在实际使用中,**应该在 System 中处理实体和组件的交互**,而不是在组件内部:
```typescript
@ECSComponent('Health')
class Health extends Component {
current: number;
max: number;
constructor(max: number = 100) {
super();
this.max = max;
this.current = max;
}
isDead(): boolean {
return this.current <= 0;
}
}
@ECSComponent('Damage')
class Damage extends Component {
value: number;
constructor(value: number) {
super();
this.value = value;
}
}
// 推荐:在 System 中处理逻辑
class DamageSystem extends EntitySystem {
constructor() {
super(new Matcher().all(Health, Damage));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health)!;
const damage = entity.getComponent(Damage)!;
health.current -= damage.value;
if (health.isDead()) {
entity.destroy();
}
// 应用伤害后移除 Damage 组件
entity.removeComponent(damage);
}
}
}
```
## 组件属性
每个组件都有一些内置属性:
```typescript
@ECSComponent('ExampleComponent')
class ExampleComponent extends Component {
someData: string = "example";
onAddedToEntity(): void {
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
console.log(`所属实体ID: ${this.entityId}`); // 所属实体的ID
}
}
```
如果需要访问实体对象,应该在 System 中进行:
```typescript
class ExampleSystem extends EntitySystem {
constructor() {
super(new Matcher().all(ExampleComponent));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const comp = entity.getComponent(ExampleComponent)!;
console.log(`实体名称: ${entity.name}`);
console.log(`组件数据: ${comp.someData}`);
}
}
}
```
## 复杂组件示例
### 状态机组件
```typescript
enum EntityState {
Idle,
Moving,
Attacking,
Dead
}
@ECSComponent('StateMachine')
class StateMachine extends Component {
private _currentState: EntityState = EntityState.Idle;
private _previousState: EntityState = EntityState.Idle;
private _stateTimer: number = 0;
get currentState(): EntityState {
return this._currentState;
}
get previousState(): EntityState {
return this._previousState;
}
get stateTimer(): number {
return this._stateTimer;
}
changeState(newState: EntityState): void {
if (this._currentState !== newState) {
this._previousState = this._currentState;
this._currentState = newState;
this._stateTimer = 0;
}
}
updateTimer(deltaTime: number): void {
this._stateTimer += deltaTime;
}
isInState(state: EntityState): boolean {
return this._currentState === state;
}
}
```
### 配置数据组件
```typescript
interface WeaponData {
damage: number;
range: number;
fireRate: number;
ammo: number;
}
@ECSComponent('WeaponConfig')
class WeaponConfig extends Component {
data: WeaponData;
constructor(weaponData: WeaponData) {
super();
this.data = { ...weaponData }; // 深拷贝避免共享引用
}
// 提供便捷的访问方法
getDamage(): number {
return this.data.damage;
}
canFire(): boolean {
return this.data.ammo > 0;
}
consumeAmmo(): boolean {
if (this.data.ammo > 0) {
this.data.ammo--;
return true;
}
return false;
}
}
```
## 最佳实践
### 1. 保持组件简单
```typescript
// 好的组件设计 - 单一职责
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
y: number = 0;
}
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// 避免的组件设计 - 职责过多
@ECSComponent('GameObject')
class GameObject extends Component {
x: number;
y: number;
dx: number;
dy: number;
health: number;
damage: number;
sprite: string;
// 太多不相关的属性
}
```
### 2. 使用构造函数初始化
```typescript
@ECSComponent('Transform')
class Transform extends Component {
x: number;
y: number;
rotation: number;
scale: number;
constructor(x = 0, y = 0, rotation = 0, scale = 1) {
super();
this.x = x;
this.y = y;
this.rotation = rotation;
this.scale = scale;
}
}
```
### 3. 明确的类型定义
```typescript
interface InventoryItem {
id: string;
name: string;
quantity: number;
type: 'weapon' | 'consumable' | 'misc';
}
@ECSComponent('Inventory')
class Inventory extends Component {
items: InventoryItem[] = [];
maxSlots: number;
constructor(maxSlots: number = 20) {
super();
this.maxSlots = maxSlots;
}
addItem(item: InventoryItem): boolean {
if (this.items.length < this.maxSlots) {
this.items.push(item);
return true;
}
return false;
}
removeItem(itemId: string): InventoryItem | null {
const index = this.items.findIndex(item => item.id === itemId);
if (index !== -1) {
return this.items.splice(index, 1)[0];
}
return null;
}
}
```
### 4. 引用其他实体
当组件需要关联其他实体时(如父子关系、跟随目标等),**推荐方式是存储实体ID**,然后在 System 中查找:
```typescript
@ECSComponent('Follower')
class Follower extends Component {
targetId: number;
followDistance: number = 50;
constructor(targetId: number) {
super();
this.targetId = targetId;
}
}
// 在 System 中查找目标实体并处理逻辑
class FollowerSystem extends EntitySystem {
constructor() {
super(new Matcher().all(Follower, Position));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const follower = entity.getComponent(Follower)!;
const position = entity.getComponent(Position)!;
// 通过场景查找目标实体
const target = entity.scene?.findEntityById(follower.targetId);
if (target) {
const targetPos = target.getComponent(Position);
if (targetPos) {
// 跟随逻辑
const dx = targetPos.x - position.x;
const dy = targetPos.y - position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > follower.followDistance) {
// 移动靠近目标
}
}
}
}
}
}
```
这种方式的优势:
- 组件保持简单,只存储基本数据类型
- 符合数据导向设计
- 在 System 中统一处理查找和逻辑
- 易于理解和维护
**避免在组件中直接存储实体引用**
```typescript
// 错误示范:直接存储实体引用
@ECSComponent('BadFollower')
class BadFollower extends Component {
target: Entity; // 实体销毁后仍持有引用,可能导致内存泄漏
}
```
## 高级特性
### EntityRef 装饰器 - 自动引用追踪
框架提供了 `@EntityRef` 装饰器用于**特殊场景**下安全地存储实体引用。这是一个高级特性,一般情况下推荐使用存储ID的方式。
#### 什么时候需要 EntityRef
在以下场景中,`@EntityRef` 可以简化代码:
1. **父子关系**: 需要在组件中直接访问父实体或子实体
2. **复杂关联**: 实体之间有多个引用关系
3. **频繁访问**: 需要在多处访问引用的实体,使用ID查找会有性能开销
#### 核心特性
`@EntityRef` 装饰器通过 **ReferenceTracker** 自动追踪引用关系:
- 当被引用的实体销毁时,所有指向它的 `@EntityRef` 属性自动设为 `null`
- 防止跨场景引用(会输出警告并拒绝设置)
- 防止引用已销毁的实体(会输出警告并设为 `null`)
- 使用 WeakRef 避免内存泄漏(自动GC支持)
- 组件移除时自动清理引用注册
#### 基本用法
```typescript
import { Component, ECSComponent, EntityRef, Entity } from '@esengine/ecs-framework';
@ECSComponent('Parent')
class ParentComponent extends Component {
@EntityRef()
parent: Entity | null = null;
}
// 使用示例
const scene = new Scene();
const parent = scene.createEntity('Parent');
const child = scene.createEntity('Child');
const comp = child.addComponent(new ParentComponent());
comp.parent = parent;
console.log(comp.parent); // Entity { name: 'Parent' }
// 当 parent 被销毁时comp.parent 自动变为 null
parent.destroy();
console.log(comp.parent); // null
```
#### 多个引用属性
一个组件可以有多个 `@EntityRef` 属性:
```typescript
@ECSComponent('Combat')
class CombatComponent extends Component {
@EntityRef()
target: Entity | null = null;
@EntityRef()
ally: Entity | null = null;
@EntityRef()
lastAttacker: Entity | null = null;
}
// 使用示例
const player = scene.createEntity('Player');
const enemy = scene.createEntity('Enemy');
const npc = scene.createEntity('NPC');
const combat = player.addComponent(new CombatComponent());
combat.target = enemy;
combat.ally = npc;
// enemy 销毁后,只有 target 变为 nullally 仍然有效
enemy.destroy();
console.log(combat.target); // null
console.log(combat.ally); // Entity { name: 'NPC' }
```
#### 安全检查
`@EntityRef` 提供了多重安全检查:
```typescript
const scene1 = new Scene();
const scene2 = new Scene();
const entity1 = scene1.createEntity('Entity1');
const entity2 = scene2.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
// 跨场景引用会失败
comp.parent = entity2; // 输出错误日志comp.parent 为 null
console.log(comp.parent); // null
// 引用已销毁的实体会失败
const entity3 = scene1.createEntity('Entity3');
entity3.destroy();
comp.parent = entity3; // 输出警告日志comp.parent 为 null
console.log(comp.parent); // null
```
#### 实现原理
`@EntityRef` 使用以下机制实现自动引用追踪:
1. **ReferenceTracker**: Scene 持有一个引用追踪器,记录所有实体引用关系
2. **WeakRef**: 使用弱引用存储组件,避免循环引用导致内存泄漏
3. **属性拦截**: 通过 `Object.defineProperty` 拦截 getter/setter
4. **自动清理**: 实体销毁时,ReferenceTracker 遍历所有引用并设为 null
```typescript
// 简化的实现原理
class ReferenceTracker {
// entityId -> 引用该实体的所有组件记录
private _references: Map<number, Set<{ component: WeakRef<Component>, propertyKey: string }>>;
// 实体销毁时调用
clearReferencesTo(entityId: number): void {
const records = this._references.get(entityId);
if (records) {
for (const record of records) {
const component = record.component.deref();
if (component) {
// 将组件的引用属性设为 null
(component as any)[record.propertyKey] = null;
}
}
this._references.delete(entityId);
}
}
}
```
#### 性能考虑
`@EntityRef` 会带来一些性能开销:
- **写入开销**: 每次设置引用时需要更新 ReferenceTracker
- **内存开销**: ReferenceTracker 需要维护引用映射表
- **销毁开销**: 实体销毁时需要遍历所有引用并清理
对于大多数场景,这些开销是可以接受的。但如果有**大量实体和频繁的引用变更**,存储ID可能更高效。
#### 最佳实践
```typescript
// 推荐:适合使用 @EntityRef 的场景 - 父子关系
@ECSComponent('Transform')
class Transform extends Component {
@EntityRef()
parent: Entity | null = null;
position: { x: number, y: number } = { x: 0, y: 0 };
// 可以直接访问父实体的组件
getWorldPosition(): { x: number, y: number } {
if (!this.parent) {
return { ...this.position };
}
const parentTransform = this.parent.getComponent(Transform);
if (parentTransform) {
const parentPos = parentTransform.getWorldPosition();
return {
x: parentPos.x + this.position.x,
y: parentPos.y + this.position.y
};
}
return { ...this.position };
}
}
// 不推荐:不适合使用 @EntityRef 的场景 - 大量动态目标
@ECSComponent('AITarget')
class AITarget extends Component {
@EntityRef()
target: Entity | null = null; // 如果目标频繁变化用ID更好
updateCooldown: number = 0;
}
// 推荐这种场景用ID更好
@ECSComponent('AITarget')
class AITargetBetter extends Component {
targetId: number | null = null; // 存储ID
updateCooldown: number = 0;
}
```
#### 调试支持
ReferenceTracker 提供了调试接口:
```typescript
// 查看某个实体被哪些组件引用
const references = scene.referenceTracker.getReferencesTo(entity.id);
console.log(`实体 ${entity.name}${references.length} 个组件引用`);
// 获取完整的调试信息
const debugInfo = scene.referenceTracker.getDebugInfo();
console.log(debugInfo);
```
#### 总结
- **推荐做法**: 大部分情况使用存储ID + System查找的方式
- **EntityRef 适用场景**: 父子关系、复杂关联、组件内需要直接访问引用实体的场景
- **核心优势**: 自动清理、防止悬空引用、代码更简洁
- **注意事项**: 有性能开销,不适合大量动态引用的场景
组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。

View File

@@ -1,501 +0,0 @@
# 实体查询系统
实体查询是 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 {
// 处理可以战斗的活着的实体
}
}
```
### 按标签查询
```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
```
## 相关 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 参考

View File

@@ -1,288 +0,0 @@
# 实体类
在 ECS 架构中实体Entity是游戏世界中的基本对象。实体本身不包含游戏逻辑或数据它只是一个容器用来组合不同的组件来实现各种功能。
## 基本概念
实体是一个轻量级的对象,主要用于:
- 作为组件的容器
- 提供唯一标识ID
- 管理组件的生命周期
## 创建实体
**重要提示:实体必须通过场景创建,不支持手动创建!**
实体必须通过场景的 `createEntity()` 方法来创建,这样才能确保:
- 实体被正确添加到场景的实体管理系统中
- 实体被添加到查询系统中,供系统使用
- 实体获得正确的场景引用
- 触发相关的生命周期事件
```typescript
// 正确的方式:通过场景创建实体
const player = scene.createEntity("Player");
// ❌ 错误的方式:手动创建实体
// const entity = new Entity("MyEntity", 1); // 这样创建的实体系统无法管理
```
## 添加组件
实体通过添加组件来获得功能:
```typescript
import { Component, ECSComponent } from '@esengine/ecs-framework';
// 定义位置组件
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
// 定义健康组件
@ECSComponent('Health')
class Health extends Component {
current: number = 100;
max: number = 100;
constructor(max: number = 100) {
super();
this.max = max;
this.current = max;
}
}
// 给实体添加组件
const player = scene.createEntity("Player");
player.addComponent(new Position(100, 200));
player.addComponent(new Health(150));
```
## 获取组件
```typescript
// 获取组件(传入组件类,不是实例)
const position = player.getComponent(Position); // 返回 Position | null
const health = player.getComponent(Health); // 返回 Health | null
// 检查组件是否存在
if (position) {
console.log(`玩家位置: x=${position.x}, y=${position.y}`);
}
// 检查是否有某个组件
if (player.hasComponent(Position)) {
console.log("玩家有位置组件");
}
// 获取所有组件实例(只读属性)
const allComponents = player.components; // readonly Component[]
// 获取指定类型的所有组件(支持同类型多组件)
const allHealthComponents = player.getComponents(Health); // Health[]
// 获取或创建组件(如果不存在则自动创建)
const position = player.getOrCreateComponent(Position, 0, 0); // 传入构造参数
const health = player.getOrCreateComponent(Health, 100); // 如果存在则返回现有的,不存在则创建新的
```
## 移除组件
```typescript
// 方式1通过组件类型移除
const removedHealth = player.removeComponentByType(Health);
if (removedHealth) {
console.log("健康组件已被移除");
}
// 方式2通过组件实例移除
const healthComponent = player.getComponent(Health);
if (healthComponent) {
player.removeComponent(healthComponent);
}
// 批量移除多种组件类型
const removedComponents = player.removeComponentsByTypes([Position, Health]);
// 检查组件是否被移除
if (!player.hasComponent(Health)) {
console.log("健康组件已被移除");
}
```
## 实体查找
场景提供了多种方式来查找实体:
### 通过名称查找
```typescript
// 查找单个实体
const player = scene.findEntity("Player");
// 或使用别名方法
const player2 = scene.getEntityByName("Player");
if (player) {
console.log("找到玩家实体");
}
```
### 通过 ID 查找
```typescript
// 通过实体 ID 查找
const entity = scene.findEntityById(123);
```
### 通过标签查找
实体支持标签系统,用于快速分类和查找:
```typescript
// 设置标签
player.tag = 1; // 玩家标签
enemy.tag = 2; // 敌人标签
// 通过标签查找所有相关实体
const players = scene.findEntitiesByTag(1);
const enemies = scene.findEntitiesByTag(2);
// 或使用别名方法
const allPlayers = scene.getEntitiesByTag(1);
```
## 实体生命周期
```typescript
// 销毁实体
player.destroy();
// 检查实体是否已销毁
if (player.isDestroyed) {
console.log("实体已被销毁");
}
```
## 实体事件
实体的组件变化会触发事件:
```typescript
// 监听组件添加事件
scene.eventSystem.on('component:added', (data) => {
console.log('组件已添加:', data);
});
// 监听实体创建事件
scene.eventSystem.on('entity:created', (data) => {
console.log('实体已创建:', data.entityName);
});
```
## 性能优化
### 批量创建实体
框架提供了高性能的批量创建方法:
```typescript
// 批量创建 100 个子弹实体(高性能版本)
const bullets = scene.createEntities(100, "Bullet");
// 为每个子弹添加组件
bullets.forEach((bullet, index) => {
bullet.addComponent(new Position(Math.random() * 800, Math.random() * 600));
bullet.addComponent(new Velocity(Math.random() * 100 - 50, Math.random() * 100 - 50));
});
```
`createEntities()` 方法会:
- 批量分配实体 ID
- 批量添加到实体列表
- 优化查询系统更新
- 减少系统缓存清理次数
## 最佳实践
### 1. 合理的组件粒度
```typescript
// 好的做法:功能单一的组件
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
y: number = 0;
}
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// 避免:功能过于复杂的组件
@ECSComponent('Player')
class Player extends Component {
// 避免在一个组件中包含太多不相关的属性
x: number;
y: number;
health: number;
inventory: Item[];
skills: Skill[];
}
```
### 2. 使用装饰器
始终使用 `@ECSComponent` 装饰器:
```typescript
@ECSComponent('Transform')
class Transform extends Component {
// 组件实现
}
```
### 3. 合理命名
```typescript
// 清晰的实体命名
const mainCharacter = scene.createEntity("MainCharacter");
const enemy1 = scene.createEntity("Goblin_001");
const collectible = scene.createEntity("HealthPotion");
```
### 4. 及时清理
```typescript
// 不再需要的实体应该及时销毁
if (enemy.getComponent(Health).current <= 0) {
enemy.destroy();
}
```
## 调试实体
框架提供了调试功能来帮助开发:
```typescript
// 获取实体调试信息
const debugInfo = entity.getDebugInfo();
console.log('实体信息:', debugInfo);
// 列出实体的所有组件
entity.components.forEach(component => {
console.log('组件:', component.constructor.name);
});
```
实体是 ECS 架构的核心概念之一,理解如何正确使用实体将帮助你构建高效、可维护的游戏代码。

View File

@@ -1,677 +0,0 @@
# 微信小游戏适配器
## 概述
微信小游戏平台适配器专为微信小游戏环境设计处理微信小游戏的特殊限制和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('收到内存警告,开始清理资源');
// 清理不必要的资源
});
}
```

View File

@@ -1,643 +0,0 @@
# 插件系统
插件系统允许你以模块化的方式扩展 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 初始化和基础使用

View File

@@ -1,675 +0,0 @@
# 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) 文档。

View File

@@ -1,661 +0,0 @@
# 场景管理
在 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 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。

View File

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

View File

@@ -1,638 +0,0 @@
# 服务容器
服务容器ServiceContainer是 ECS Framework 的依赖注入容器,负责管理框架中所有服务的注册、解析和生命周期。通过服务容器,你可以实现松耦合的架构设计,提高代码的可测试性和可维护性。
## 概述
### 什么是服务容器
服务容器是一个轻量级的依赖注入DI容器它提供了
- **服务注册**: 将服务类型注册到容器中
- **服务解析**: 从容器中获取服务实例
- **生命周期管理**: 自动管理服务实例的创建和销毁
- **依赖注入**: 自动解析服务之间的依赖关系
### 核心概念
#### 服务Service
服务是实现了 `IService` 接口的类,必须提供 `dispose()` 方法用于资源清理:
```typescript
import { IService } from '@esengine/ecs-framework';
class MyService implements IService {
constructor() {
// 初始化逻辑
}
dispose(): void {
// 清理资源
}
}
```
#### 生命周期
服务容器支持两种生命周期:
- **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');
}
}
```
### @Inject 装饰器
在构造函数中注入依赖:
```typescript
import { Injectable, Inject, IService } from '@esengine/ecs-framework';
@Injectable()
class PlayerService implements IService {
constructor(
@Inject(DataService) private data: DataService,
@Inject(GameService) private game: GameService
) {
// data 和 game 会自动从容器中解析
}
dispose(): void {
// 清理资源
}
}
```
### 注册可注入服务
使用 `registerInjectable` 自动处理依赖注入:
```typescript
import { registerInjectable } from '@esengine/ecs-framework';
// 注册服务(会自动解析@Inject依赖
registerInjectable(Core.services, PlayerService);
// 解析时会自动注入依赖
const player = Core.services.resolve(PlayerService);
```
### @Updatable 装饰器
标记服务为可更新的,使其在每帧自动被调用:
```typescript
import { Injectable, Updatable, IService, IUpdatable } from '@esengine/ecs-framework';
@Injectable()
@Updatable() // 默认优先级为0
class PhysicsService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧更新物理模拟
}
dispose(): void {
// 清理资源
}
}
// 指定更新优先级(数值越小越先执行)
@Injectable()
@Updatable(10)
class RenderService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧渲染
}
dispose(): void {
// 清理资源
}
}
```
使用 `@Updatable` 装饰器的服务会被 Core 自动调用,无需手动管理:
```typescript
// Core.update() 会自动调用所有@Updatable服务的update方法
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有可更新服务
}
```
## 自定义服务
### 创建自定义服务
实现 `IService` 接口并注册到容器:
```typescript
import { IService } from '@esengine/ecs-framework';
class AudioService implements IService {
private sounds: Map<string, HTMLAudioElement> = new Map();
play(soundId: string) {
const sound = this.sounds.get(soundId);
if (sound) {
sound.play();
}
}
load(soundId: string, url: string) {
const audio = new Audio(url);
this.sounds.set(soundId, audio);
}
dispose(): void {
// 停止所有音效并清理
for (const sound of this.sounds.values()) {
sound.pause();
sound.src = '';
}
this.sounds.clear();
}
}
// 注册自定义服务
Core.services.registerSingleton(AudioService);
// 使用服务
const audio = Core.services.resolve(AudioService);
audio.load('jump', '/sounds/jump.mp3');
audio.play('jump');
```
### 服务间依赖
服务可以依赖其他服务:
```typescript
@Injectable()
class ConfigService implements IService {
private config: any = {};
get(key: string) {
return this.config[key];
}
dispose(): void {
this.config = {};
}
}
@Injectable()
class NetworkService implements IService {
constructor(
@Inject(ConfigService) private config: ConfigService
) {
// 使用配置服务
const apiUrl = this.config.get('apiUrl');
}
dispose(): void {
// 清理网络连接
}
}
// 注册服务(按依赖顺序)
registerInjectable(Core.services, ConfigService);
registerInjectable(Core.services, NetworkService);
```
## 高级用法
### 服务替换(测试)
在测试中替换真实服务为模拟服务:
```typescript
// 测试代码
class MockDataService implements IService {
getData(key: string) {
return 'mock data';
}
dispose(): void {}
}
// 注册模拟服务(用于测试)
Core.services.registerInstance(DataService, new MockDataService());
```
### 循环依赖检测
服务容器会自动检测循环依赖:
```typescript
// A 依赖 BB 依赖 A
@Injectable()
class ServiceA implements IService {
constructor(@Inject(ServiceB) b: ServiceB) {}
dispose(): void {}
}
@Injectable()
class ServiceB implements IService {
constructor(@Inject(ServiceA) a: ServiceA) {}
dispose(): void {}
}
// 解析时会抛出错误: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
```
### 获取所有服务
```typescript
// 获取所有已注册的服务类型
const types = Core.services.getRegisteredServices();
// 获取所有已实例化的服务实例
const instances = Core.services.getAll();
```
### 服务清理
```typescript
// 注销单个服务
Core.services.unregister(MyService);
// 清空所有服务会调用每个服务的dispose方法
Core.services.clear();
```
## 最佳实践
### 服务命名
服务类名应该以 `Service``Manager` 结尾,清晰表达其职责:
```typescript
class PlayerService implements IService {}
class AudioManager implements IService {}
class NetworkService implements IService {}
```
### 资源清理
始终在 `dispose()` 方法中清理资源:
```typescript
class ResourceService implements IService {
private resources: Map<string, Resource> = new Map();
dispose(): void {
// 释放所有资源
for (const resource of this.resources.values()) {
resource.release();
}
this.resources.clear();
}
}
```
### 避免过度使用
不要把所有类都注册为服务,服务应该是:
- 全局单例或需要共享状态
- 需要在多处使用
- 生命周期需要管理
- 需要依赖注入
对于简单的工具类或数据类,直接创建实例即可。
### 依赖方向
保持清晰的依赖方向,避免循环依赖:
```
高层服务 -> 中层服务 -> 底层服务
GameLogic -> DataService -> ConfigService
```
## 常见问题
### 服务未注册错误
**问题**: `Error: Service MyService is not registered`
**解决**:
```typescript
// 确保服务已注册
Core.services.registerSingleton(MyService);
// 或者使用tryResolve
const service = Core.services.tryResolve(MyService);
if (!service) {
console.log('Service not found');
}
```
### 循环依赖错误
**问题**: `Circular dependency detected`
**解决**: 重新设计服务依赖关系,引入中间服务或使用事件系统解耦。
### 何时使用单例 vs 瞬时
- **单例**: 管理器类、配置、缓存、状态管理
- **瞬时**: 命令对象、请求处理器、临时任务
## 相关链接
- [插件系统](./plugin-system.md) - 使用服务容器注册插件服务
- [快速开始](./getting-started.md) - Core 初始化和基础使用
- [系统架构](./system.md) - 在系统中使用服务

View File

@@ -1,650 +0,0 @@
# 系统架构
在 ECS 架构中系统System是处理业务逻辑的地方。系统负责对拥有特定组件组合的实体执行操作是 ECS 架构的逻辑处理单元。
## 基本概念
系统是继承自 `EntitySystem` 抽象基类的具体类,用于:
- 定义实体的处理逻辑(如移动、碰撞检测、渲染等)
- 根据组件组合筛选需要处理的实体
- 提供生命周期管理和性能监控
- 管理实体的添加、移除事件
## 系统类型
框架提供了几种不同类型的系统基类:
### EntitySystem - 基础系统
最基础的系统类,所有其他系统都继承自它:
```typescript
import { EntitySystem, ECSSystem, Matcher } from '@esengine/ecs-framework';
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
// 使用 Matcher 定义需要处理的实体条件
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
if (position && velocity) {
position.x += velocity.dx * Time.deltaTime;
position.y += velocity.dy * Time.deltaTime;
}
}
}
}
```
### ProcessingSystem - 处理系统
适用于不需要逐个处理实体的系统:
```typescript
@ECSSystem('Physics')
class PhysicsSystem extends ProcessingSystem {
constructor() {
super(); // 不需要指定 Matcher
}
public processSystem(): void {
// 执行物理世界步进
this.physicsWorld.step(Time.deltaTime);
}
}
```
### PassiveSystem - 被动系统
被动系统不进行主动处理,主要用于监听实体的添加和移除事件:
```typescript
@ECSSystem('EntityTracker')
class EntityTrackerSystem extends PassiveSystem {
constructor() {
super(Matcher.all(Health));
}
protected onAdded(entity: Entity): void {
console.log(`生命值实体被添加: ${entity.name}`);
}
protected onRemoved(entity: Entity): void {
console.log(`生命值实体被移除: ${entity.name}`);
}
}
```
### IntervalSystem - 间隔系统
按固定时间间隔执行的系统:
```typescript
@ECSSystem('AutoSave')
class AutoSaveSystem extends IntervalSystem {
constructor() {
// 每 5 秒执行一次
super(5.0, Matcher.all(SaveData));
}
protected process(entities: readonly Entity[]): void {
console.log('执行自动保存...');
// 保存游戏数据
this.saveGameData(entities);
}
private saveGameData(entities: readonly Entity[]): void {
// 保存逻辑
}
}
```
### WorkerEntitySystem - 多线程系统
基于Web Worker的多线程处理系统适用于计算密集型任务能够充分利用多核CPU性能。
Worker系统提供了真正的并行计算能力支持SharedArrayBuffer优化并具有自动降级支持。特别适合物理模拟、粒子系统、AI计算等场景。
**详细内容请参考:[Worker系统](/guide/worker-system)**
## 实体匹配器 (Matcher)
Matcher 用于定义系统需要处理哪些实体。它提供了灵活的条件组合:
### 基本匹配条件
```typescript
// 必须同时拥有 Position 和 Velocity 组件
const matcher1 = Matcher.all(Position, Velocity);
// 至少拥有 Health 或 Shield 组件之一
const matcher2 = Matcher.any(Health, Shield);
// 不能拥有 Dead 组件
const matcher3 = Matcher.none(Dead);
```
### 复合匹配条件
```typescript
// 复杂的组合条件
const complexMatcher = Matcher.all(Position, Velocity)
.any(Player, Enemy)
.none(Dead, Disabled);
@ECSSystem('Combat')
class CombatSystem extends EntitySystem {
constructor() {
super(complexMatcher);
}
}
```
### 特殊匹配条件
```typescript
// 按标签匹配
const tagMatcher = Matcher.byTag(1); // 匹配标签为 1 的实体
// 按名称匹配
const nameMatcher = Matcher.byName("Player"); // 匹配名称为 "Player" 的实体
// 单组件匹配
const componentMatcher = Matcher.byComponent(Health); // 匹配拥有 Health 组件的实体
```
## 系统生命周期
系统提供了完整的生命周期回调:
```typescript
@ECSSystem('Example')
class ExampleSystem extends EntitySystem {
protected onInitialize(): void {
console.log('系统初始化');
// 系统被添加到场景时调用,用于初始化资源
}
protected onBegin(): void {
// 每帧处理开始前调用
}
protected process(entities: readonly Entity[]): void {
// 主要的处理逻辑
for (const entity of entities) {
// 处理每个实体
}
}
protected lateProcess(entities: readonly Entity[]): void {
// 主处理之后的后期处理
}
protected onEnd(): void {
// 每帧处理结束后调用
}
protected onDestroy(): void {
console.log('系统销毁');
// 系统从场景移除时调用,用于清理资源
}
}
```
## 实体事件监听
系统可以监听实体的添加和移除事件:
```typescript
@ECSSystem('EnemyManager')
class EnemyManagerSystem extends EntitySystem {
private enemyCount = 0;
constructor() {
super(Matcher.all(Enemy, Health));
}
protected onAdded(entity: Entity): void {
this.enemyCount++;
console.log(`敌人加入战斗,当前敌人数量: ${this.enemyCount}`);
// 可以在这里为新敌人设置初始状态
const health = entity.getComponent(Health);
if (health) {
health.current = health.max;
}
}
protected onRemoved(entity: Entity): void {
this.enemyCount--;
console.log(`敌人被移除,剩余敌人数量: ${this.enemyCount}`);
// 检查是否所有敌人都被消灭
if (this.enemyCount === 0) {
this.scene?.eventSystem.emitSync('all_enemies_defeated');
}
}
}
```
## 系统属性和方法
### 重要属性
```typescript
@ECSSystem('Example')
class ExampleSystem extends EntitySystem {
showSystemInfo(): void {
console.log(`系统名称: ${this.systemName}`); // 系统名称
console.log(`更新顺序: ${this.updateOrder}`); // 更新时序
console.log(`是否启用: ${this.enabled}`); // 启用状态
console.log(`实体数量: ${this.entities.length}`); // 匹配的实体数量
console.log(`所属场景: ${this.scene?.name}`); // 所属场景
}
}
```
### 实体访问
```typescript
protected process(entities: readonly Entity[]): void {
// 方式1使用参数中的实体列表
for (const entity of entities) {
// 处理实体
}
// 方式2使用 this.entities 属性(与参数相同)
for (const entity of this.entities) {
// 处理实体
}
}
```
### 控制系统执行
```typescript
@ECSSystem('Conditional')
class ConditionalSystem extends EntitySystem {
private shouldProcess = true;
protected onCheckProcessing(): boolean {
// 返回 false 时跳过本次处理
return this.shouldProcess && this.entities.length > 0;
}
public pause(): void {
this.shouldProcess = false;
}
public resume(): void {
this.shouldProcess = true;
}
}
```
## 事件系统集成
系统可以方便地监听和发送事件:
```typescript
@ECSSystem('GameLogic')
class GameLogicSystem extends EntitySystem {
protected onInitialize(): void {
// 添加事件监听器(系统销毁时自动清理)
this.addEventListener('player_died', this.onPlayerDied.bind(this));
this.addEventListener('level_complete', this.onLevelComplete.bind(this));
}
private onPlayerDied(data: any): void {
console.log('玩家死亡,重新开始游戏');
// 处理玩家死亡逻辑
}
private onLevelComplete(data: any): void {
console.log('关卡完成,加载下一关');
// 处理关卡完成逻辑
}
protected process(entities: readonly Entity[]): void {
// 在处理过程中发送事件
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health && health.current <= 0) {
this.scene?.eventSystem.emitSync('entity_died', { entity });
}
}
}
}
```
## 性能监控
系统内置了性能监控功能:
```typescript
@ECSSystem('Performance')
class PerformanceSystem extends EntitySystem {
protected onEnd(): void {
// 获取性能数据
const perfData = this.getPerformanceData();
if (perfData) {
console.log(`执行时间: ${perfData.executionTime.toFixed(2)}ms`);
}
// 获取性能统计
const stats = this.getPerformanceStats();
if (stats) {
console.log(`平均执行时间: ${stats.averageTime.toFixed(2)}ms`);
}
}
public resetPerformance(): void {
this.resetPerformanceData();
}
}
```
## 系统管理
### 添加系统到场景
框架提供了两种方式添加系统:传入实例或传入类型(自动依赖注入)。
```typescript
// 在场景子类中添加系统
class GameScene extends Scene {
protected initialize(): void {
// 方式1传入实例
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 方式2传入类型自动依赖注入
this.addEntityProcessor(PhysicsSystem);
// 设置系统更新顺序
const movementSystem = this.getSystem(MovementSystem);
if (movementSystem) {
movementSystem.updateOrder = 1;
}
}
}
```
### 系统依赖注入
系统实现了 `IService` 接口,支持通过依赖注入获取其他服务或系统:
```typescript
import { ECSSystem, Injectable, Inject } from '@esengine/ecs-framework';
@Injectable()
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
constructor(
@Inject(CollisionService) private collision: CollisionService
) {
super(Matcher.all(Transform, RigidBody));
}
protected process(entities: readonly Entity[]): void {
// 使用注入的服务
this.collision.detectCollisions(entities);
}
// 实现 IService 接口的 dispose 方法
public dispose(): void {
// 清理资源
}
}
// 使用时传入类型即可,框架会自动注入依赖
class GameScene extends Scene {
protected initialize(): void {
// 自动依赖注入
this.addEntityProcessor(PhysicsSystem);
}
}
```
注意事项:
- 使用 `@Injectable()` 装饰器标记需要依赖注入的系统
- 在构造函数参数中使用 `@Inject()` 装饰器声明依赖
- 系统必须实现 `dispose()` 方法IService 接口要求)
- 使用 `addEntityProcessor(类型)` 而不是 `addSystem(new 类型())` 来启用依赖注入
### 系统更新顺序
```typescript
@ECSSystem('Input')
class InputSystem extends EntitySystem {
constructor() {
super(Matcher.all(InputComponent));
this.updateOrder = -100; // 输入系统优先执行
}
}
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.all(RigidBody));
this.updateOrder = 0; // 默认顺序
}
}
@ECSSystem('Render')
class RenderSystem extends EntitySystem {
constructor() {
super(Matcher.all(Sprite, Transform));
this.updateOrder = 100; // 渲染系统最后执行
}
}
```
## 复杂系统示例
### 碰撞检测系统
```typescript
@ECSSystem('Collision')
class CollisionSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Collider));
}
protected process(entities: readonly Entity[]): void {
// 简单的 n² 碰撞检测
for (let i = 0; i < entities.length; i++) {
for (let j = i + 1; j < entities.length; j++) {
this.checkCollision(entities[i], entities[j]);
}
}
}
private checkCollision(entityA: Entity, entityB: Entity): void {
const transformA = entityA.getComponent(Transform);
const transformB = entityB.getComponent(Transform);
const colliderA = entityA.getComponent(Collider);
const colliderB = entityB.getComponent(Collider);
if (this.isColliding(transformA, colliderA, transformB, colliderB)) {
// 发送碰撞事件
this.scene?.eventSystem.emitSync('collision', {
entityA,
entityB
});
}
}
private isColliding(transformA: Transform, colliderA: Collider,
transformB: Transform, colliderB: Collider): boolean {
// 碰撞检测逻辑
return false; // 简化示例
}
}
```
### 状态机系统
```typescript
@ECSSystem('StateMachine')
class StateMachineSystem extends EntitySystem {
constructor() {
super(Matcher.all(StateMachine));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const stateMachine = entity.getComponent(StateMachine);
if (stateMachine) {
stateMachine.updateTimer(Time.deltaTime);
this.updateState(entity, stateMachine);
}
}
}
private updateState(entity: Entity, stateMachine: StateMachine): void {
switch (stateMachine.currentState) {
case EntityState.Idle:
this.handleIdleState(entity, stateMachine);
break;
case EntityState.Moving:
this.handleMovingState(entity, stateMachine);
break;
case EntityState.Attacking:
this.handleAttackingState(entity, stateMachine);
break;
}
}
private handleIdleState(entity: Entity, stateMachine: StateMachine): void {
// 空闲状态逻辑
}
private handleMovingState(entity: Entity, stateMachine: StateMachine): void {
// 移动状态逻辑
}
private handleAttackingState(entity: Entity, stateMachine: StateMachine): void {
// 攻击状态逻辑
}
}
```
## 最佳实践
### 1. 系统单一职责
```typescript
// ✅ 好的系统设计 - 职责单一
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Position, Velocity));
}
}
@ECSSystem('Rendering')
class RenderingSystem extends EntitySystem {
constructor() {
super(Matcher.all(Sprite, Transform));
}
}
// ❌ 避免的系统设计 - 职责过多
@ECSSystem('GameSystem')
class GameSystem extends EntitySystem {
// 一个系统处理移动、渲染、音效等多种逻辑
}
```
### 2. 使用装饰器
**必须使用 `@ECSSystem` 装饰器**
```typescript
// ✅ 正确的用法
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
// 系统实现
}
// ❌ 错误的用法 - 没有装饰器
class BadSystem extends EntitySystem {
// 这样定义的系统可能在生产环境出现问题
}
```
### 3. 合理的更新顺序
```typescript
// 按逻辑顺序设置系统的更新时序
@ECSSystem('Input')
class InputSystem extends EntitySystem {
constructor() {
super();
this.updateOrder = -100; // 最先处理输入
}
}
@ECSSystem('Logic')
class GameLogicSystem extends EntitySystem {
constructor() {
super();
this.updateOrder = 0; // 处理游戏逻辑
}
}
@ECSSystem('Render')
class RenderSystem extends EntitySystem {
constructor() {
super();
this.updateOrder = 100; // 最后进行渲染
}
}
```
### 4. 避免在系统间直接引用
```typescript
// ❌ 避免:系统间直接引用
@ECSSystem('Bad')
class BadSystem extends EntitySystem {
private otherSystem: SomeOtherSystem; // 避免直接引用其他系统
}
// ✅ 推荐:通过事件系统通信
@ECSSystem('Good')
class GoodSystem extends EntitySystem {
protected process(entities: readonly Entity[]): void {
// 通过事件系统与其他系统通信
this.scene?.eventSystem.emitSync('data_updated', { entities });
}
}
```
### 5. 及时清理资源
```typescript
@ECSSystem('Resource')
class ResourceSystem extends EntitySystem {
private resources: Map<string, any> = new Map();
protected onDestroy(): void {
// 清理资源
for (const [key, resource] of this.resources) {
if (resource.dispose) {
resource.dispose();
}
}
this.resources.clear();
}
}
```
系统是 ECS 架构的逻辑处理核心,正确设计和使用系统能让你的游戏代码更加模块化、高效和易于维护。

View File

@@ -1,608 +0,0 @@
# 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框架提供了强大的并行计算能力让你能够充分利用现代多核处理器的性能为复杂的游戏逻辑和计算密集型任务提供了高效的解决方案。

View File

@@ -1,761 +0,0 @@
# 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)。

View File

@@ -1,23 +0,0 @@
---
layout: home
hero:
name: "ECS Framework"
text: "高性能ECS框架"
tagline: "为Javascript游戏开发而设计"
actions:
- theme: brand
text: 快速开始
link: /guide/getting-started
- theme: alt
text: 查看示例
link: https://github.com/esengine/lawn-mower-demo
features:
- title: 高性能
details: 支持大规模实体处理
- title: 类型安全
details: 完整的TypeScript支持编译时类型检查
- title: 模块化设计
details: 核心功能独立打包,支持多平台
---

22
docs/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "@esengine/docs",
"private": true,
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.37.1",
"@astrojs/vue": "^5.1.3",
"@tailwindcss/vite": "^4.1.18",
"astro": "^5.6.1",
"sharp": "^0.34.2",
"tailwindcss": "^4.1.18",
"vue": "^3.5.26"
}
}

1
docs/public/CNAME Normal file
View File

@@ -0,0 +1 @@
esengine.cn

1
docs/public/favicon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" d="M81 36 64 0 47 36l-1 2-9-10a6 6 0 0 0-9 9l10 10h-2L0 64l36 17h2L28 91a6 6 0 1 0 9 9l9-10 1 2 17 36 17-36v-2l9 10a6 6 0 1 0 9-9l-9-9 2-1 36-17-36-17-2-1 9-9a6 6 0 1 0-9-9l-9 10v-2Zm-17 2-2 5c-4 8-11 15-19 19l-5 2 5 2c8 4 15 11 19 19l2 5 2-5c4-8 11-15 19-19l5-2-5-2c-8-4-15-11-19-19l-2-5Z" clip-rule="evenodd"/><path d="M118 19a6 6 0 0 0-9-9l-3 3a6 6 0 1 0 9 9l3-3Zm-96 4c-2 2-6 2-9 0l-3-3a6 6 0 1 1 9-9l3 3c3 2 3 6 0 9Zm0 82c-2-2-6-2-9 0l-3 3a6 6 0 1 0 9 9l3-3c3-2 3-6 0-9Zm96 4a6 6 0 0 1-9 9l-3-3a6 6 0 1 1 9-9l3 3Z"/><style>path{fill:#000}@media (prefers-color-scheme:dark){path{fill:#fff}}</style></svg>

After

Width:  |  Height:  |  Size: 696 B

45
docs/public/logo.svg Normal file
View File

@@ -0,0 +1,45 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<!-- Dark gradient background -->
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2d2d2d"/>
<stop offset="100%" style="stop-color:#1a1a1a"/>
</linearGradient>
<!-- Clean white text -->
<linearGradient id="whiteGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#ffffff"/>
<stop offset="100%" style="stop-color:#e8e8e8"/>
</linearGradient>
<!-- Subtle inner shadow -->
<filter id="innerShadow">
<feOffset dx="0" dy="2"/>
<feGaussianBlur stdDeviation="1" result="offset-blur"/>
<feComposite operator="out" in="SourceGraphic" in2="offset-blur" result="inverse"/>
<feFlood flood-color="black" flood-opacity="0.2" result="color"/>
<feComposite operator="in" in="color" in2="inverse" result="shadow"/>
<feComposite operator="over" in="shadow" in2="SourceGraphic"/>
</filter>
</defs>
<!-- Background -->
<rect width="512" height="512" fill="url(#bgGrad)"/>
<!-- Subtle border -->
<rect x="1" y="1" width="510" height="510" fill="none" stroke="#3d3d3d" stroke-width="2"/>
<!-- ES Text -->
<g filter="url(#innerShadow)">
<!-- E -->
<polygon points="72,120 72,392 240,392 240,340 140,340 140,282 220,282 220,230 140,230 140,172 240,172 240,120"
fill="url(#whiteGrad)"/>
<!-- S -->
<path d="M 280 172 Q 280 120 340 120 L 420 120 Q 450 120 450 160 L 450 186 L 398 186 L 398 168 Q 398 158 384 158 L 350 158 Q 320 158 320 188 Q 320 218 350 218 L 400 218 Q 450 218 450 274 L 450 332 Q 450 392 390 392 L 310 392 Q 270 392 270 340 L 270 314 L 322 314 L 322 340 Q 322 354 340 354 L 384 354 Q 404 354 404 324 L 404 290 Q 404 260 380 260 L 330 260 Q 280 260 280 208 Z"
fill="url(#whiteGrad)"/>
</g>
<!-- Accent line -->
<rect x="72" y="424" width="368" height="4" fill="#ffffff" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

45
docs/src/assets/logo.svg Normal file
View File

@@ -0,0 +1,45 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<!-- Dark gradient background -->
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2d2d2d"/>
<stop offset="100%" style="stop-color:#1a1a1a"/>
</linearGradient>
<!-- Clean white text -->
<linearGradient id="whiteGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#ffffff"/>
<stop offset="100%" style="stop-color:#e8e8e8"/>
</linearGradient>
<!-- Subtle inner shadow -->
<filter id="innerShadow">
<feOffset dx="0" dy="2"/>
<feGaussianBlur stdDeviation="1" result="offset-blur"/>
<feComposite operator="out" in="SourceGraphic" in2="offset-blur" result="inverse"/>
<feFlood flood-color="black" flood-opacity="0.2" result="color"/>
<feComposite operator="in" in="color" in2="inverse" result="shadow"/>
<feComposite operator="over" in="shadow" in2="SourceGraphic"/>
</filter>
</defs>
<!-- Background -->
<rect width="512" height="512" fill="url(#bgGrad)"/>
<!-- Subtle border -->
<rect x="1" y="1" width="510" height="510" fill="none" stroke="#3d3d3d" stroke-width="2"/>
<!-- ES Text -->
<g filter="url(#innerShadow)">
<!-- E -->
<polygon points="72,120 72,392 240,392 240,340 140,340 140,282 220,282 220,230 140,230 140,172 240,172 240,120"
fill="url(#whiteGrad)"/>
<!-- S -->
<path d="M 280 172 Q 280 120 340 120 L 420 120 Q 450 120 450 160 L 450 186 L 398 186 L 398 168 Q 398 158 384 158 L 350 158 Q 320 158 320 188 Q 320 218 350 218 L 400 218 Q 450 218 450 274 L 450 332 Q 450 392 390 392 L 310 392 Q 270 392 270 340 L 270 314 L 322 314 L 322 340 Q 322 354 340 354 L 384 354 Q 404 354 404 324 L 404 290 Q 404 260 380 260 L 330 260 Q 280 260 280 208 Z"
fill="url(#whiteGrad)"/>
</g>
<!-- Accent line -->
<rect x="72" y="424" width="368" height="4" fill="#ffffff" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,20 @@
---
import type { Props } from '@astrojs/starlight/props';
import Default from '@astrojs/starlight/components/Head.astro';
---
<Default {...Astro.props}><slot /></Default>
<!-- Preload fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
<!-- Force dark mode -->
<script is:inline>
document.documentElement.dataset.theme = 'dark';
localStorage.setItem('starlight-theme', 'dark');
</script>

View File

@@ -0,0 +1,3 @@
---
// Empty component to disable theme selection (dark mode only)
---

View File

@@ -0,0 +1,7 @@
import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
};

View File

@@ -0,0 +1,64 @@
---
title: API 参考
description: ESEngine 完整 API 文档
---
# API 参考
ESEngine 提供完整的 TypeScript API 文档,涵盖所有核心类、接口和方法。
## 核心模块
### 基础类
| 类名 | 描述 |
|------|------|
| [Core](/api/classes/Core) | 框架核心单例,管理整个 ECS 生命周期 |
| [Scene](/api/classes/Scene) | 场景类,包含实体和系统 |
| [World](/api/classes/World) | 游戏世界,可包含多个场景 |
| [Entity](/api/classes/Entity) | 实体类,组件的容器 |
| [Component](/api/classes/Component) | 组件基类,纯数据容器 |
### 系统类
| 类名 | 描述 |
|------|------|
| [EntitySystem](/api/classes/EntitySystem) | 实体系统基类 |
| [ProcessingSystem](/api/classes/ProcessingSystem) | 处理系统,逐个处理实体 |
| [IntervalSystem](/api/classes/IntervalSystem) | 间隔执行系统 |
| [PassiveSystem](/api/classes/PassiveSystem) | 被动系统,不自动执行 |
### 工具类
| 类名 | 描述 |
|------|------|
| [Matcher](/api/classes/Matcher) | 实体匹配器,用于过滤实体 |
| [Time](/api/classes/Time) | 时间管理器 |
| [PerformanceMonitor](/api/classes/PerformanceMonitor) | 性能监控 |
## 装饰器
| 装饰器 | 描述 |
|--------|------|
| [@ECSComponent](/api/functions/ECSComponent) | 组件装饰器,用于注册组件 |
| [@ECSSystem](/api/functions/ECSSystem) | 系统装饰器,用于注册系统 |
## 枚举
| 枚举 | 描述 |
|------|------|
| [ECSEventType](/api/enumerations/ECSEventType) | ECS 事件类型 |
| [LogLevel](/api/enumerations/LogLevel) | 日志级别 |
## 接口
| 接口 | 描述 |
|------|------|
| [IScene](/api/interfaces/IScene) | 场景接口 |
| [IComponent](/api/interfaces/IComponent) | 组件接口 |
| [ISystemBase](/api/interfaces/ISystemBase) | 系统基础接口 |
| [ICoreConfig](/api/interfaces/ICoreConfig) | Core 配置接口 |
:::tip[API 文档生成]
完整 API 文档由 TypeDoc 自动生成,详见 GitHub 仓库中的 `/docs/api` 目录。
:::

View File

@@ -0,0 +1,20 @@
---
title: "Examples"
description: "ESEngine example projects and demos"
---
Explore example projects to learn ESEngine best practices.
## Available Examples
### [Worker System Demo](/en/examples/worker-system-demo/)
Demonstrates how to use Web Workers for parallel processing, offloading heavy computations from the main thread.
## External Examples
### [Lawn Mower Demo](https://github.com/esengine/lawn-mower-demo)
A complete game demo showcasing ESEngine features including:
- Entity-Component-System architecture
- Behavior tree AI
- Scene management
- Platform adaptation

View File

@@ -0,0 +1,312 @@
---
title: "Best Practices"
description: "Component design patterns and complex examples"
---
## Design Principles
### 1. Keep Components Simple
```typescript
// ✅ Good component design - single responsibility
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
y: number = 0;
}
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// ❌ Avoid this design - too many responsibilities
@ECSComponent('GameObject')
class GameObject extends Component {
x: number;
y: number;
dx: number;
dy: number;
health: number;
damage: number;
sprite: string;
// Too many unrelated properties
}
```
### 2. Use Constructor for Initialization
```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. Clear Type Definitions
```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. Referencing Other Entities
When components need to reference other entities (like parent-child relationships, follow targets), **the recommended approach is to store entity IDs**, then look up in System:
```typescript
@ECSComponent('Follower')
class Follower extends Component {
targetId: number;
followDistance: number = 50;
constructor(targetId: number) {
super();
this.targetId = targetId;
}
}
// Look up target entity and handle logic in 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)!;
// Look up target entity through scene
const target = entity.scene?.findEntityById(follower.targetId);
if (target) {
const targetPos = target.getComponent(Position);
if (targetPos) {
// Follow logic
const dx = targetPos.x - position.x;
const dy = targetPos.y - position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > follower.followDistance) {
// Move closer to target
}
}
}
}
}
}
```
Advantages of this approach:
- Components stay simple, only store basic data types
- Follows data-oriented design
- Unified lookup and logic handling in System
- Easy to understand and maintain
**Avoid storing entity references directly in components**:
```typescript
// ❌ Wrong example: Storing entity reference directly
@ECSComponent('BadFollower')
class BadFollower extends Component {
target: Entity; // Still holds reference after entity destroyed, may cause memory leak
}
```
## Complex Component Examples
### State Machine Component
```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;
}
}
```
### Configuration Data Component
```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 }; // Deep copy to avoid shared reference
}
// Provide convenience methods
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;
}
}
```
### Tag Components
```typescript
// Tag components: No data, only for identification
@ECSComponent('Player')
class PlayerTag extends Component {}
@ECSComponent('Enemy')
class EnemyTag extends Component {}
@ECSComponent('Dead')
class DeadTag extends Component {}
// Use tags for querying
class EnemySystem extends EntitySystem {
constructor() {
super(Matcher.all(EnemyTag, Health).none(DeadTag));
}
}
```
### Buffer Components
```typescript
// Event/command buffer component
@ECSComponent('DamageBuffer')
class DamageBuffer extends Component {
damages: { amount: number; source: number; timestamp: number }[] = [];
addDamage(amount: number, sourceId: number): void {
this.damages.push({
amount,
source: sourceId,
timestamp: Date.now()
});
}
clear(): void {
this.damages.length = 0;
}
getTotalDamage(): number {
return this.damages.reduce((sum, d) => sum + d.amount, 0);
}
}
```
## FAQ
### Q: How large should a component be?
**A**: Follow single responsibility principle. If a component contains unrelated data, split into multiple components.
### Q: Can components have methods?
**A**: Yes, but they should be data-related helper methods (like `isDead()`), not business logic. Business logic goes in Systems.
### Q: How to handle dependencies between components?
**A**: Handle inter-component interactions in Systems, don't directly access other components within a component.
### Q: When to use EntityRef?
**A**: Only when you need frequent access to referenced entity and the reference relationship is stable (like parent-child). Storing IDs is better for most cases.
---
Components are the data carriers of ECS architecture. Properly designing components makes your game code more modular, maintainable, and performant.

View File

@@ -0,0 +1,228 @@
---
title: "EntityRef Decorator"
description: "Safe entity reference tracking mechanism"
---
The framework provides the `@EntityRef` decorator for **special scenarios** to safely store entity references. This is an advanced feature; storing IDs is recommended for most cases.
## When Do You Need EntityRef?
`@EntityRef` can simplify code in these scenarios:
1. **Parent-Child Relationships**: Need to directly access parent or child entities in components
2. **Complex Associations**: Multiple reference relationships between entities
3. **Frequent Access**: Need to access referenced entity in multiple places, ID lookup has performance overhead
## Core Features
The `@EntityRef` decorator automatically tracks references through **ReferenceTracker**:
- When the referenced entity is destroyed, all `@EntityRef` properties pointing to it are automatically set to `null`
- Prevents cross-scene references (outputs warning and refuses to set)
- Prevents references to destroyed entities (outputs warning and sets to `null`)
- Uses WeakRef to avoid memory leaks (automatic GC support)
- Automatically cleans up reference registration when component is removed
## Basic Usage
```typescript
import { Component, ECSComponent, EntityRef, Entity } from '@esengine/ecs-framework';
@ECSComponent('Parent')
class ParentComponent extends Component {
@EntityRef()
parent: Entity | null = null;
}
// Usage example
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' }
// When parent is destroyed, comp.parent automatically becomes null
parent.destroy();
console.log(comp.parent); // null
```
## Multiple Reference Properties
A component can have multiple `@EntityRef` properties:
```typescript
@ECSComponent('Combat')
class CombatComponent extends Component {
@EntityRef()
target: Entity | null = null;
@EntityRef()
ally: Entity | null = null;
@EntityRef()
lastAttacker: Entity | null = null;
}
// Usage example
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;
// After enemy is destroyed, only target becomes null, ally remains valid
enemy.destroy();
console.log(combat.target); // null
console.log(combat.ally); // Entity { name: 'NPC' }
```
## Safety Checks
`@EntityRef` provides multiple safety checks:
```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());
// Cross-scene reference fails
comp.parent = entity2; // Outputs error log, comp.parent is null
console.log(comp.parent); // null
// Reference to destroyed entity fails
const entity3 = scene1.createEntity('Entity3');
entity3.destroy();
comp.parent = entity3; // Outputs warning log, comp.parent is null
console.log(comp.parent); // null
```
## Implementation Principle
`@EntityRef` uses the following mechanisms for automatic reference tracking:
1. **ReferenceTracker**: Scene holds a reference tracker that records all entity reference relationships
2. **WeakRef**: Uses weak references to store components, avoiding memory leaks from circular references
3. **Property Interception**: Intercepts getter/setter through `Object.defineProperty`
4. **Automatic Cleanup**: When entity is destroyed, ReferenceTracker traverses all references and sets them to null
```typescript
// Simplified implementation principle
class ReferenceTracker {
// entityId -> all component records referencing this entity
private _references: Map<number, Set<{ component: WeakRef<Component>, propertyKey: string }>>;
// Called when entity is destroyed
clearReferencesTo(entityId: number): void {
const records = this._references.get(entityId);
if (records) {
for (const record of records) {
const component = record.component.deref();
if (component) {
// Set component's reference property to null
(component as any)[record.propertyKey] = null;
}
}
this._references.delete(entityId);
}
}
}
```
## Performance Considerations
`@EntityRef` introduces some performance overhead:
- **Write Overhead**: Need to update ReferenceTracker each time a reference is set
- **Memory Overhead**: ReferenceTracker needs to maintain reference mapping table
- **Destroy Overhead**: Need to traverse all references and clean up when entity is destroyed
For most scenarios, this overhead is acceptable. But with **many entities and frequent reference changes**, storing IDs may be more efficient.
## Debug Support
ReferenceTracker provides debug interfaces:
```typescript
// View which components reference an entity
const references = scene.referenceTracker.getReferencesTo(entity.id);
console.log(`Entity ${entity.name} is referenced by ${references.length} components`);
// Get complete debug info
const debugInfo = scene.referenceTracker.getDebugInfo();
console.log(debugInfo);
```
## Comparison with Storing IDs
### Storing IDs (Recommended for Most Cases)
```typescript
@ECSComponent('Follower')
class Follower extends Component {
targetId: number | null = null;
}
// Look up in System
class FollowerSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const follower = entity.getComponent(Follower)!;
const target = entity.scene?.findEntityById(follower.targetId);
if (target) {
// Follow logic
}
}
}
}
```
### Using EntityRef (For Complex Associations)
```typescript
@ECSComponent('Transform')
class Transform extends Component {
@EntityRef()
parent: Entity | null = null;
position: { x: number, y: number } = { x: 0, y: 0 };
// Can directly access parent entity's component
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 };
}
}
```
## Summary
| Approach | Use Case | Pros | Cons |
|----------|----------|------|------|
| Store ID | Most cases | Simple, no extra overhead | Need to lookup in System |
| @EntityRef | Parent-child, complex associations | Auto-cleanup, cleaner code | Has performance overhead |
- **Recommended**: Use store ID + System lookup for most cases
- **EntityRef Use Cases**: Parent-child relationships, complex associations, when component needs direct access to referenced entity
- **Core Advantage**: Automatic cleanup, prevents dangling references, cleaner code
- **Considerations**: Has performance overhead, not suitable for many dynamic references

View File

@@ -0,0 +1,226 @@
---
title: "Component System"
description: "ECS component basics and creation methods"
---
In ECS architecture, Components are carriers of data and behavior. Components define the properties and functionality that entities possess, and are the core building blocks of ECS architecture.
## Basic Concepts
Components are concrete classes that inherit from the `Component` abstract base class, used for:
- Storing entity data (such as position, velocity, health, etc.)
- Defining behavior methods related to the data
- Providing lifecycle callback hooks
- Supporting serialization and debugging
## Creating Components
### Basic Component Definition
```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;
}
// Components can contain behavior methods
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 Decorator
`@ECSComponent` is a required decorator for component classes, providing type identification and metadata management.
### Why It's Required
| Feature | Description |
|---------|-------------|
| **Type Identification** | Provides stable type name that remains correct after code obfuscation |
| **Serialization Support** | Uses this name as type identifier during serialization/deserialization |
| **Component Registration** | Auto-registers to ComponentRegistry, assigns unique bitmask |
| **Debug Support** | Shows readable component names in debug tools and logs |
### Basic Syntax
```typescript
@ECSComponent(typeName: string)
```
- `typeName`: Component's type name, recommended to use same or similar name as class name
### Usage Examples
```typescript
// ✅ Correct usage
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// ✅ Recommended: Keep type name consistent with class name
@ECSComponent('PlayerController')
class PlayerController extends Component {
speed: number = 5;
}
// ❌ Wrong usage - no decorator
class BadComponent extends Component {
// Components defined this way may have issues in production:
// 1. Class name changes after minification, can't serialize correctly
// 2. Component not registered to framework, queries may fail
}
```
### Using with @Serializable
When components need serialization support, use `@ECSComponent` and `@Serializable` together:
```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;
// Fields without @Serialize() won't be serialized
private _cachedData: any = null;
}
```
> **Note**: `@ECSComponent`'s `typeName` and `@Serializable`'s `typeId` can differ. If `@Serializable` doesn't specify `typeId`, it defaults to `@ECSComponent`'s `typeName`.
### Type Name Uniqueness
Each component's type name should be unique:
```typescript
// ❌ Wrong: Two components using the same type name
@ECSComponent('Health')
class HealthComponent extends Component { }
@ECSComponent('Health') // Conflict!
class EnemyHealthComponent extends Component { }
// ✅ Correct: Use different type names
@ECSComponent('PlayerHealth')
class PlayerHealthComponent extends Component { }
@ECSComponent('EnemyHealth')
class EnemyHealthComponent extends Component { }
```
## Component Properties
Each component has some built-in properties:
```typescript
@ECSComponent('ExampleComponent')
class ExampleComponent extends Component {
someData: string = "example";
onAddedToEntity(): void {
console.log(`Component ID: ${this.id}`); // Unique component ID
console.log(`Entity ID: ${this.entityId}`); // Owning entity's ID
}
}
```
## Component and Entity Relationship
Components store the owning entity's ID (`entityId`), not a direct entity reference. This is a reflection of ECS's data-oriented design, avoiding circular references.
In practice, **entity and component interactions should be handled in Systems**, not within components:
```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;
}
}
// Recommended: Handle logic in 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();
}
// Remove Damage component after applying damage
entity.removeComponent(damage);
}
}
}
```
## More Topics
- [Lifecycle](/en/guide/component/lifecycle) - Component lifecycle hooks
- [EntityRef Decorator](/en/guide/component/entity-ref) - Safe entity references
- [Best Practices](/en/guide/component/best-practices) - Component design patterns and examples

View File

@@ -0,0 +1,182 @@
---
title: "Component Lifecycle"
description: "Component lifecycle hooks and events"
---
Components provide lifecycle hooks that can be overridden to execute specific logic.
## Lifecycle Methods
```typescript
@ECSComponent('ExampleComponent')
class ExampleComponent extends Component {
private resource: SomeResource | null = null;
/**
* Called when component is added to entity
* Use for initializing resources, establishing references, etc.
*/
onAddedToEntity(): void {
console.log(`Component ${this.constructor.name} added, Entity ID: ${this.entityId}`);
this.resource = new SomeResource();
}
/**
* Called when component is removed from entity
* Use for cleaning up resources, breaking references, etc.
*/
onRemovedFromEntity(): void {
console.log(`Component ${this.constructor.name} removed`);
if (this.resource) {
this.resource.cleanup();
this.resource = null;
}
}
}
```
## Lifecycle Order
```
Entity created
addComponent() called
onAddedToEntity() triggered
Component in normal use...
removeComponent() or entity.destroy() called
onRemovedFromEntity() triggered
Component removed/destroyed
```
## Practical Use Cases
### Resource Management
```typescript
@ECSComponent('TextureComponent')
class TextureComponent extends Component {
private _texture: Texture | null = null;
texturePath: string = '';
onAddedToEntity(): void {
// Load texture resource
this._texture = TextureManager.load(this.texturePath);
}
onRemovedFromEntity(): void {
// Release texture resource
if (this._texture) {
TextureManager.release(this._texture);
this._texture = null;
}
}
get texture(): Texture | null {
return this._texture;
}
}
```
### Event Listening
```typescript
@ECSComponent('InputListener')
class InputListener extends Component {
private _boundHandler: ((e: KeyboardEvent) => void) | null = null;
onAddedToEntity(): void {
this._boundHandler = this.handleKeyDown.bind(this);
window.addEventListener('keydown', this._boundHandler);
}
onRemovedFromEntity(): void {
if (this._boundHandler) {
window.removeEventListener('keydown', this._boundHandler);
this._boundHandler = null;
}
}
private handleKeyDown(e: KeyboardEvent): void {
// Handle keyboard input
}
}
```
### Registering with External Systems
```typescript
@ECSComponent('PhysicsBody')
class PhysicsBody extends Component {
private _body: PhysicsWorld.Body | null = null;
onAddedToEntity(): void {
// Create rigid body in physics world
this._body = PhysicsWorld.createBody({
entityId: this.entityId,
type: 'dynamic'
});
}
onRemovedFromEntity(): void {
// Remove rigid body from physics world
if (this._body) {
PhysicsWorld.removeBody(this._body);
this._body = null;
}
}
}
```
## Important Notes
### Avoid Accessing Other Components in Lifecycle
```typescript
@ECSComponent('BadComponent')
class BadComponent extends Component {
onAddedToEntity(): void {
// ⚠️ Not recommended: Other components may not be added yet
const other = this.entity?.getComponent(OtherComponent);
if (other) {
// May be null
}
}
}
```
### Recommended: Use System to Handle Inter-Component Interactions
```typescript
@ECSSystem('InitializationSystem')
class InitializationSystem extends EntitySystem {
constructor() {
super(Matcher.all(ComponentA, ComponentB));
}
// Use onAdded event to ensure both components exist
onAdded(entity: Entity): void {
const a = entity.getComponent(ComponentA)!;
const b = entity.getComponent(ComponentB)!;
// Safely initialize interaction
a.linkTo(b);
}
onRemoved(entity: Entity): void {
// Cleanup
}
}
```
## Comparison with System Lifecycle
| Feature | Component Lifecycle | System Lifecycle |
|---------|---------------------|------------------|
| Trigger Timing | When component added/removed | When match conditions met |
| Use Case | Resource init/cleanup | Business logic processing |
| Access Other Components | Not recommended | Safe |
| Access Scene | Limited | Full |

View File

@@ -0,0 +1,225 @@
---
title: "Best Practices"
description: "Entity query optimization and practical applications"
---
## Design Principles
### 1. Prefer EntitySystem
```typescript
// ✅ Recommended: Use EntitySystem
class GoodSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(HealthComponent));
}
protected process(entities: readonly Entity[]): void {
// Automatically get matching entities, updated each frame
}
}
// ❌ Not recommended: Manual query in update
class BadSystem extends EntitySystem {
constructor() {
super(Matcher.empty());
}
protected process(entities: readonly Entity[]): void {
// Manual query each frame wastes performance
const result = this.scene!.querySystem.queryAll(HealthComponent);
for (const entity of result.entities) {
// ...
}
}
}
```
### 2. Use none() for Exclusions
```typescript
// Exclude dead enemies
class EnemyAISystem extends EntitySystem {
constructor() {
super(
Matcher.empty()
.all(EnemyTag, AIComponent)
.none(DeadTag) // Don't process dead enemies
);
}
}
```
### 3. Use Tags for Query Optimization
```typescript
// ❌ Bad: Query all entities then filter
const allEntities = scene.querySystem.getAllEntities();
const players = allEntities.filter(e => e.hasComponent(PlayerTag));
// ✅ Good: Query directly by tag
const players = scene.querySystem.queryByTag(Tags.PLAYER).entities;
```
### 4. Avoid Overly Complex Query Conditions
```typescript
// ❌ Not recommended: Too complex
super(
Matcher.empty()
.all(A, B, C, D)
.any(E, F, G)
.none(H, I, J)
);
// ✅ Recommended: Split into multiple simple systems
class SystemAB extends EntitySystem {
constructor() {
super(Matcher.empty().all(A, B));
}
}
class SystemCD extends EntitySystem {
constructor() {
super(Matcher.empty().all(C, D));
}
}
```
## Practical Applications
### Scenario 1: Physics System
```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)!;
// Apply gravity
rigidbody.velocity.y -= 9.8 * Time.deltaTime;
// Update position
transform.position.x += rigidbody.velocity.x * Time.deltaTime;
transform.position.y += rigidbody.velocity.y * Time.deltaTime;
}
}
}
```
### Scenario 2: Render System
```typescript
class RenderSystem extends EntitySystem {
constructor() {
super(
Matcher.empty()
.all(TransformComponent, SpriteComponent)
.none(InvisibleTag) // Exclude invisible entities
);
}
protected process(entities: readonly Entity[]): void {
// Sort by z-order
const sorted = entities.slice().sort((a, b) => {
const zA = a.getComponent(TransformComponent)!.z;
const zB = b.getComponent(TransformComponent)!.z;
return zA - zB;
});
// Render entities
for (const entity of sorted) {
const transform = entity.getComponent(TransformComponent)!;
const sprite = entity.getComponent(SpriteComponent)!;
renderer.drawSprite(sprite.texture, transform.position);
}
}
}
```
### Scenario 3: Collision Detection
```typescript
class CollisionSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(TransformComponent, ColliderComponent));
}
protected process(entities: readonly Entity[]): void {
// Simple O(n²) collision detection
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)) {
// Trigger collision event
scene.eventSystem.emit('collision', { entityA: a, entityB: b });
}
}
}
```
### Scenario 4: One-Time Query
```typescript
// Execute one-time query outside of systems
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;
}
}
```
## Performance Statistics
```typescript
const stats = querySystem.getStats();
console.log('Total queries:', stats.queryStats.totalQueries);
console.log('Cache hit rate:', stats.queryStats.cacheHitRate);
console.log('Cache size:', stats.cacheStats.size);
```
## Related APIs
- [Matcher](/api/classes/Matcher/) - Query condition descriptor API reference
- [QuerySystem](/api/classes/QuerySystem/) - Query system API reference
- [EntitySystem](/api/classes/EntitySystem/) - Entity system API reference
- [Entity](/api/classes/Entity/) - Entity API reference

View File

@@ -0,0 +1,133 @@
---
title: "Compiled Query"
description: "CompiledQuery type-safe query tool"
---
> **v2.4.0+**
CompiledQuery is a lightweight query tool providing type-safe component access and change detection support. Suitable for ad-hoc queries, tool development, and simple iteration scenarios.
## Basic Usage
```typescript
// Create compiled query
const query = scene.querySystem.compile(Position, Velocity);
// Type-safe iteration - component parameters auto-infer types
query.forEach((entity, pos, vel) => {
pos.x += vel.vx * deltaTime;
pos.y += vel.vy * deltaTime;
});
// Get entity count
console.log(`Matched entities: ${query.count}`);
// Get first matched entity
const first = query.first();
if (first) {
const [entity, pos, vel] = first;
console.log(`First entity: ${entity.name}`);
}
```
## Change Detection
CompiledQuery supports epoch-based change detection:
```typescript
class RenderSystem extends EntitySystem {
private _query: CompiledQuery<[typeof Transform, typeof Sprite]>;
private _lastEpoch = 0;
protected onInitialize(): void {
this._query = this.scene!.querySystem.compile(Transform, Sprite);
}
protected process(entities: readonly Entity[]): void {
// Only process entities where Transform or Sprite changed
this._query.forEachChanged(this._lastEpoch, (entity, transform, sprite) => {
this.updateRenderData(entity, transform, sprite);
});
// Save current epoch as next check starting point
this._lastEpoch = this.scene!.epochManager.current;
}
private updateRenderData(entity: Entity, transform: Transform, sprite: Sprite): void {
// Update render data
}
}
```
## Functional API
CompiledQuery provides rich functional APIs:
```typescript
const query = scene.querySystem.compile(Position, Health);
// map - Transform entity data
const positions = query.map((entity, pos, health) => ({
x: pos.x,
y: pos.y,
healthPercent: health.current / health.max
}));
// filter - Filter entities
const lowHealthEntities = query.filter((entity, pos, health) => {
return health.current < health.max * 0.2;
});
// find - Find first matching entity
const target = query.find((entity, pos, health) => {
return health.current > 0 && pos.x > 100;
});
// toArray - Convert to array
const allData = query.toArray();
for (const [entity, pos, health] of allData) {
console.log(`${entity.name}: ${pos.x}, ${pos.y}`);
}
// any/empty - Check for matches
if (query.any()) {
console.log('Has matching entities');
}
if (query.empty()) {
console.log('No matching entities');
}
```
## CompiledQuery vs EntitySystem
| Feature | CompiledQuery | EntitySystem |
|---------|---------------|--------------|
| **Purpose** | Lightweight query tool | Complete system logic |
| **Lifecycle** | None | Full (onInitialize, onDestroy, etc.) |
| **Scheduling Integration** | None | Supports @Stage, @Before, @After |
| **Change Detection** | forEachChanged | forEachChanged |
| **Event Listening** | None | addEventListener |
| **Command Buffer** | None | this.commands |
| **Type-Safe Components** | forEach params auto-infer | Need manual getComponent |
| **Use Cases** | Ad-hoc queries, tools, prototypes | Core game logic |
**Selection Advice**:
- Use **EntitySystem** for core game logic (movement, combat, AI, etc.)
- Use **CompiledQuery** for one-time queries, tool development, or simple iteration
## API Reference
| Method | Description |
|--------|-------------|
| `forEach(callback)` | Iterate all matched entities, type-safe component params |
| `forEachChanged(sinceEpoch, callback)` | Only iterate changed entities |
| `first()` | Get first matched entity and components |
| `toArray()` | Convert to [entity, ...components] array |
| `map(callback)` | Map transformation |
| `filter(predicate)` | Filter entities |
| `find(predicate)` | Find first entity meeting condition |
| `any()` | Whether any matches exist |
| `empty()` | Whether no matches exist |
| `count` | Number of matched entities |
| `entities` | Matched entity list (read-only) |

View File

@@ -0,0 +1,244 @@
---
title: "Entity Query System"
description: "ECS entity query core concepts and basic usage"
---
Entity querying is one of the core features of ECS architecture. This guide introduces how to use Matcher and QuerySystem to query and filter entities.
## Core Concepts
### Matcher - Query Condition Descriptor
Matcher is a chainable API used to describe entity query conditions. It doesn't execute queries itself but passes conditions to EntitySystem or QuerySystem.
### QuerySystem - Query Execution Engine
QuerySystem is responsible for actually executing queries, using reactive query mechanisms internally for automatic performance optimization.
## Using Matcher in EntitySystem
This is the most common usage. EntitySystem automatically filters and processes entities matching conditions through Matcher.
### Basic Usage
```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() {
// Method 1: Use Matcher.empty().all()
super(Matcher.empty().all(PositionComponent, VelocityComponent));
// Method 2: Use Matcher.all() directly (equivalent)
// 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;
}
}
}
// Add to scene
scene.addEntityProcessor(new MovementSystem());
```
### Matcher Chainable API
#### all() - Must Include All Components
```typescript
class HealthSystem extends EntitySystem {
constructor() {
// Entity must have both Health and Position components
super(Matcher.empty().all(HealthComponent, PositionComponent));
}
protected process(entities: readonly Entity[]): void {
// Only process entities with both components
}
}
```
#### any() - Include At Least One Component
```typescript
class DamageableSystem extends EntitySystem {
constructor() {
// Entity must have at least Health or Shield
super(Matcher.any(HealthComponent, ShieldComponent));
}
protected process(entities: readonly Entity[]): void {
// Process entities with health or shield
}
}
```
#### none() - Must Not Include Components
```typescript
class AliveEntitySystem extends EntitySystem {
constructor() {
// Entity must not have DeadTag component
super(Matcher.all(HealthComponent).none(DeadTag));
}
protected process(entities: readonly Entity[]): void {
// Only process living entities
}
}
```
#### Combined Conditions
```typescript
class CombatSystem extends EntitySystem {
constructor() {
super(
Matcher.empty()
.all(PositionComponent, HealthComponent) // Must have position and health
.any(WeaponComponent, MagicComponent) // At least weapon or magic
.none(DeadTag, FrozenTag) // Not dead or frozen
);
}
protected process(entities: readonly Entity[]): void {
// Process living entities that can fight
}
}
```
#### nothing() - Match No Entities
Used for systems that only need lifecycle methods (`onBegin`, `onEnd`) but don't process entities.
```typescript
class FrameTimerSystem extends EntitySystem {
constructor() {
// Match no entities
super(Matcher.nothing());
}
protected onBegin(): void {
// Execute at frame start
Performance.markFrameStart();
}
protected process(entities: readonly Entity[]): void {
// Never called because no matching entities
}
protected onEnd(): void {
// Execute at frame end
Performance.markFrameEnd();
}
}
```
#### empty() vs nothing()
| Method | Behavior | Use Case |
|--------|----------|----------|
| `Matcher.empty()` | Match **all** entities | Process all entities in scene |
| `Matcher.nothing()` | Match **no** entities | Only need lifecycle callbacks |
## Using QuerySystem Directly
If you don't need to create a system, you can use Scene's querySystem directly.
### Basic Query Methods
```typescript
// Get scene's query system
const querySystem = scene.querySystem;
// Query entities with all specified components
const result1 = querySystem.queryAll(PositionComponent, VelocityComponent);
console.log(`Found ${result1.count} moving entities`);
console.log(`Query time: ${result1.executionTime.toFixed(2)}ms`);
// Query entities with any specified component
const result2 = querySystem.queryAny(WeaponComponent, MagicComponent);
console.log(`Found ${result2.count} combat units`);
// Query entities without specified components
const result3 = querySystem.queryNone(DeadTag);
console.log(`Found ${result3.count} living entities`);
```
### Query by Tag and Name
```typescript
// Query by tag
const playerResult = querySystem.queryByTag(Tags.PLAYER);
for (const player of playerResult.entities) {
console.log('Player:', player.name);
}
// Query by name
const bossResult = querySystem.queryByName('Boss');
if (bossResult.count > 0) {
const boss = bossResult.entities[0];
console.log('Found Boss:', boss);
}
// Query by single component
const healthResult = querySystem.queryByComponent(HealthComponent);
console.log(`${healthResult.count} entities have health`);
```
## Performance Optimization
### Automatic Caching
QuerySystem uses reactive queries internally with automatic caching:
```typescript
// First query, executes actual query
const result1 = querySystem.queryAll(PositionComponent);
console.log('fromCache:', result1.fromCache); // false
// Second same query, uses cache
const result2 = querySystem.queryAll(PositionComponent);
console.log('fromCache:', result2.fromCache); // true
```
### Automatic Updates on Entity Changes
Query cache updates automatically when entities add/remove components:
```typescript
// Query entities with weapons
const before = querySystem.queryAll(WeaponComponent);
console.log('Before:', before.count); // Assume 5
// Add weapon to entity
const enemy = scene.createEntity('Enemy');
enemy.addComponent(new WeaponComponent());
// Query again, automatically includes new entity
const after = querySystem.queryAll(WeaponComponent);
console.log('After:', after.count); // Now 6
```
## More Topics
- [Matcher API](/en/guide/entity-query/matcher-api) - Complete Matcher API reference
- [Compiled Query](/en/guide/entity-query/compiled-query) - CompiledQuery advanced usage
- [Best Practices](/en/guide/entity-query/best-practices) - Query optimization and practical applications

View File

@@ -0,0 +1,118 @@
---
title: "Matcher API"
description: "Complete Matcher API reference"
---
## Static Creation Methods
| Method | Description | Example |
|--------|-------------|---------|
| `Matcher.all(...types)` | Must include all specified components | `Matcher.all(Position, Velocity)` |
| `Matcher.any(...types)` | Include at least one specified component | `Matcher.any(Health, Shield)` |
| `Matcher.none(...types)` | Must not include any specified components | `Matcher.none(Dead)` |
| `Matcher.byTag(tag)` | Query by tag | `Matcher.byTag(1)` |
| `Matcher.byName(name)` | Query by name | `Matcher.byName("Player")` |
| `Matcher.byComponent(type)` | Query by single component | `Matcher.byComponent(Health)` |
| `Matcher.empty()` | Create empty matcher (matches all entities) | `Matcher.empty()` |
| `Matcher.nothing()` | Match no entities | `Matcher.nothing()` |
| `Matcher.complex()` | Create complex query builder | `Matcher.complex()` |
## Chainable Methods
| Method | Description | Example |
|--------|-------------|---------|
| `.all(...types)` | Add required components | `.all(Position)` |
| `.any(...types)` | Add optional components (at least one) | `.any(Weapon, Magic)` |
| `.none(...types)` | Add excluded components | `.none(Dead)` |
| `.exclude(...types)` | Alias for `.none()` | `.exclude(Disabled)` |
| `.one(...types)` | Alias for `.any()` | `.one(Player, Enemy)` |
| `.withTag(tag)` | Add tag condition | `.withTag(1)` |
| `.withName(name)` | Add name condition | `.withName("Boss")` |
| `.withComponent(type)` | Add single component condition | `.withComponent(Health)` |
## Utility Methods
| Method | Description |
|--------|-------------|
| `.getCondition()` | Get query condition (read-only) |
| `.isEmpty()` | Check if empty condition |
| `.isNothing()` | Check if nothing matcher |
| `.clone()` | Clone matcher |
| `.reset()` | Reset all conditions |
| `.toString()` | Get string representation |
## Common Combination Examples
```typescript
// Basic movement system
Matcher.all(Position, Velocity)
// Attackable living entities
Matcher.all(Position, Health)
.any(Weapon, Magic)
.none(Dead, Disabled)
// All tagged enemies
Matcher.byTag(Tags.ENEMY)
.all(AIComponent)
// System only needing lifecycle
Matcher.nothing()
```
## Query by Tag
```typescript
class PlayerSystem extends EntitySystem {
constructor() {
// Query entities with specific tag
super(Matcher.empty().withTag(Tags.PLAYER));
}
protected process(entities: readonly Entity[]): void {
// Only process player entities
}
}
```
## Query by Name
```typescript
class BossSystem extends EntitySystem {
constructor() {
// Query entities with specific name
super(Matcher.empty().withName('Boss'));
}
protected process(entities: readonly Entity[]): void {
// Only process entities named 'Boss'
}
}
```
## Important Notes
### Matcher is Immutable
```typescript
const matcher = Matcher.empty().all(PositionComponent);
// Chain calls return new Matcher instances
const matcher2 = matcher.any(VelocityComponent);
// Original matcher unchanged
console.log(matcher === matcher2); // false
```
### Query Results are Read-Only
```typescript
const result = querySystem.queryAll(PositionComponent);
// Don't modify returned array
result.entities.push(someEntity); // Wrong!
// If modification needed, copy first
const mutableArray = [...result.entities];
mutableArray.push(someEntity); // Correct
```

View File

@@ -0,0 +1,273 @@
---
title: "Component Operations"
description: "Detailed guide to adding, getting, and removing entity components"
---
Entities gain functionality by adding components. This section details all component operation APIs.
## Adding Components
### addComponent
Add an already-created component instance:
```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;
}
}
const player = scene.createEntity("Player");
const position = new Position(100, 200);
player.addComponent(position);
```
### createComponent
Pass the component type and constructor arguments directly—the entity creates the instance (recommended):
```typescript
// Create and add component
const position = player.createComponent(Position, 100, 200);
const health = player.createComponent(Health, 150);
// Equivalent to:
// const position = new Position(100, 200);
// player.addComponent(position);
```
### addComponents
Add multiple components at once:
```typescript
const components = player.addComponents([
new Position(100, 200),
new Health(150),
new Velocity(0, 0)
]);
```
:::note[Important Notes]
- An entity cannot have two components of the same type—an exception will be thrown
- The entity must be added to a scene before adding components
:::
## Getting Components
### getComponent
Get a component of a specific type:
```typescript
// Returns Position | null
const position = player.getComponent(Position);
if (position) {
position.x += 10;
position.y += 20;
}
```
### hasComponent
Check if an entity has a specific component type:
```typescript
if (player.hasComponent(Position)) {
const position = player.getComponent(Position)!;
// Use ! because we confirmed it exists
}
```
### getComponents
Get all components of a specific type (for multi-component scenarios):
```typescript
const allHealthComponents = player.getComponents(Health);
```
### getComponentByType
Get components with inheritance support using `instanceof` checking:
```typescript
// Find CompositeNodeComponent or any subclass
const composite = entity.getComponentByType(CompositeNodeComponent);
if (composite) {
// composite could be SequenceNode, SelectorNode, etc.
}
```
Difference from `getComponent()`:
| Method | Lookup Method | Performance | Use Case |
|--------|---------------|-------------|----------|
| `getComponent` | Exact type match (bitmask) | High | Known exact type |
| `getComponentByType` | `instanceof` check | Lower | Need inheritance support |
### getOrCreateComponent
Get or create a component—automatically creates if it doesn't exist:
```typescript
// Ensure entity has Position component
const position = player.getOrCreateComponent(Position, 0, 0);
position.x = 100;
// If exists, returns existing component
// If not, creates new component with (0, 0) args
```
### components Property
Get all entity components (read-only):
```typescript
const allComponents = player.components; // readonly Component[]
allComponents.forEach(component => {
console.log(component.constructor.name);
});
```
## Removing Components
### removeComponent
Remove by component instance:
```typescript
const healthComponent = player.getComponent(Health);
if (healthComponent) {
player.removeComponent(healthComponent);
}
```
### removeComponentByType
Remove by component type:
```typescript
const removedHealth = player.removeComponentByType(Health);
if (removedHealth) {
console.log("Health component removed");
}
```
### removeComponentsByTypes
Remove multiple component types at once:
```typescript
const removedComponents = player.removeComponentsByTypes([
Position,
Health,
Velocity
]);
```
### removeAllComponents
Remove all components:
```typescript
player.removeAllComponents();
```
## Change Detection
### markDirty
Mark components as modified for frame-level change detection:
```typescript
const pos = entity.getComponent(Position)!;
pos.x = 100;
entity.markDirty(pos);
// Or mark multiple components
const vel = entity.getComponent(Velocity)!;
entity.markDirty(pos, vel);
```
Use with reactive queries:
```typescript
// Query for components modified this frame
const changedQuery = scene.createReactiveQuery({
all: [Position],
changed: [Position] // Only match modified this frame
});
for (const entity of changedQuery.getEntities()) {
// Handle entities with position changes
}
```
## Component Mask
Each entity maintains a component bitmask for efficient `hasComponent` checks:
```typescript
// Get component mask (internal use)
const mask = entity.componentMask;
```
## Complete Example
```typescript
import { Component, ECSComponent, Scene } from '@esengine/ecs-framework';
@ECSComponent('Position')
class Position extends Component {
constructor(public x = 0, public y = 0) { super(); }
}
@ECSComponent('Health')
class Health extends Component {
constructor(public current = 100, public max = 100) { super(); }
}
// Create entity and add components
const player = scene.createEntity("Player");
player.createComponent(Position, 100, 200);
player.createComponent(Health, 150, 150);
// Get and modify component
const position = player.getComponent(Position);
if (position) {
position.x += 10;
player.markDirty(position);
}
// Get or create component
const velocity = player.getOrCreateComponent(Velocity, 0, 0);
// Check component existence
if (player.hasComponent(Health)) {
const health = player.getComponent(Health)!;
health.current -= 10;
}
// Remove component
player.removeComponentByType(Velocity);
// List all components
console.log(player.components.map(c => c.constructor.name));
```
## Next Steps
- [Entity Handle](/en/guide/entity/entity-handle/) - Safe cross-frame entity references
- [Component System](/en/guide/component/) - Component definition and lifecycle

View File

@@ -0,0 +1,265 @@
---
title: "Entity Handle"
description: "Using EntityHandle to safely reference entities and avoid referencing destroyed entities"
---
Entity handles (EntityHandle) provide a safe way to reference entities, solving the "referencing destroyed entities" problem.
## The Problem
Imagine your AI system needs to track a target enemy:
```typescript
// ❌ Wrong: Storing entity reference directly
class AISystem extends EntitySystem {
private targetEnemy: Entity | null = null;
setTarget(enemy: Entity) {
this.targetEnemy = enemy;
}
process() {
if (this.targetEnemy) {
// Danger! Enemy might be destroyed but reference still exists
// Worse: This memory location might be reused by a new entity
const health = this.targetEnemy.getComponent(Health);
// Might operate on wrong entity!
}
}
}
```
## What is EntityHandle
EntityHandle is a numeric entity identifier containing:
- **Index**: Entity's position in the array
- **Generation**: Number of times the entity slot has been reused
When an entity is destroyed, even if its index is reused by a new entity, the generation increases, invalidating old handles.
```typescript
import { EntityHandle, NULL_HANDLE, isValidHandle } from '@esengine/ecs-framework';
// Each entity gets a handle when created
const handle: EntityHandle = entity.handle;
// Null handle constant
const emptyHandle = NULL_HANDLE;
// Check if handle is non-null
if (isValidHandle(handle)) {
// Handle is valid
}
```
## The Correct Approach
```typescript
import { EntityHandle, NULL_HANDLE, isValidHandle } from '@esengine/ecs-framework';
class AISystem extends EntitySystem {
// ✅ Store handle instead of entity reference
private targetHandle: EntityHandle = NULL_HANDLE;
setTarget(enemy: Entity) {
this.targetHandle = enemy.handle;
}
process() {
if (!isValidHandle(this.targetHandle)) {
return; // No target
}
// Get entity via handle (auto-validates)
const enemy = this.scene.findEntityByHandle(this.targetHandle);
if (!enemy) {
// Enemy destroyed, clear reference
this.targetHandle = NULL_HANDLE;
return;
}
// Safe operation
const health = enemy.getComponent(Health);
if (health) {
// Deal damage to enemy
}
}
}
```
## API Reference
### Getting Handle
```typescript
// Get handle from entity
const handle = entity.handle;
```
### Validating Handle
```typescript
import { isValidHandle, NULL_HANDLE } from '@esengine/ecs-framework';
// Check if handle is non-null
if (isValidHandle(handle)) {
// ...
}
// Check if entity is alive
const alive = scene.handleManager.isAlive(handle);
```
### Getting Entity by Handle
```typescript
// Returns Entity | null
const entity = scene.findEntityByHandle(handle);
if (entity) {
// Entity exists and is valid
}
```
## Complete Example: Skill Target Locking
```typescript
import {
EntitySystem,
Entity,
EntityHandle,
NULL_HANDLE,
isValidHandle
} from '@esengine/ecs-framework';
@ECSSystem('SkillTargeting')
class SkillTargetingSystem extends EntitySystem {
// Store multiple target handles
private lockedTargets: Map<number, EntityHandle> = new Map();
// Lock target
lockTarget(casterId: number, target: Entity) {
this.lockedTargets.set(casterId, target.handle);
}
// Get locked target
getLockedTarget(casterId: number): Entity | null {
const handle = this.lockedTargets.get(casterId);
if (!handle || !isValidHandle(handle)) {
return null;
}
const target = this.scene.findEntityByHandle(handle);
if (!target) {
// Target dead, clear lock
this.lockedTargets.delete(casterId);
}
return target;
}
// Cast skill
castSkill(caster: Entity) {
const target = this.getLockedTarget(caster.id);
if (!target) {
console.log('Target lost, skill cancelled');
return;
}
const health = target.getComponent(Health);
if (health) {
health.current -= 10;
}
}
// Clear target for specific caster
clearTarget(casterId: number) {
this.lockedTargets.delete(casterId);
}
}
```
## Usage Guidelines
| Scenario | Recommended Approach |
|----------|---------------------|
| Same-frame temporary use | Direct `Entity` reference |
| Cross-frame storage (AI target, skill target) | Use `EntityHandle` |
| Serialization/save | Use `EntityHandle` (numeric type) |
| Network sync | Use `EntityHandle` (directly transferable) |
## Performance Considerations
- EntityHandle is a numeric type with small memory footprint
- `findEntityByHandle` is O(1) operation
- Safer and more reliable than checking `entity.isDestroyed` every frame
## Common Patterns
### Optional Target Reference
```typescript
class FollowComponent extends Component {
private _targetHandle: EntityHandle = NULL_HANDLE;
setTarget(target: Entity | null) {
this._targetHandle = target?.handle ?? NULL_HANDLE;
}
getTarget(scene: IScene): Entity | null {
if (!isValidHandle(this._targetHandle)) {
return null;
}
return scene.findEntityByHandle(this._targetHandle);
}
hasTarget(): boolean {
return isValidHandle(this._targetHandle);
}
}
```
### Multi-Target Tracking
```typescript
class MultiTargetComponent extends Component {
private targets: EntityHandle[] = [];
addTarget(target: Entity) {
this.targets.push(target.handle);
}
removeTarget(target: Entity) {
const index = this.targets.indexOf(target.handle);
if (index >= 0) {
this.targets.splice(index, 1);
}
}
getValidTargets(scene: IScene): Entity[] {
const valid: Entity[] = [];
const stillValid: EntityHandle[] = [];
for (const handle of this.targets) {
const entity = scene.findEntityByHandle(handle);
if (entity) {
valid.push(entity);
stillValid.push(handle);
}
}
// Clean up invalid handles
this.targets = stillValid;
return valid;
}
}
```
## Next Steps
- [Lifecycle](/en/guide/entity/lifecycle/) - Entity destruction and persistence
- [Entity Reference](/en/guide/component/entity-ref/) - Entity reference decorators in components

View File

@@ -0,0 +1,174 @@
---
title: "Entity Overview"
description: "Basic concepts and usage of entities in ECS architecture"
---
In ECS architecture, an Entity is a fundamental object in the game world. Entities contain no game logic or data themselves—they are simply containers that combine different components to achieve various functionalities.
## Basic Concepts
An entity is a lightweight object primarily used for:
- Acting as a container for components
- Providing unique identifiers (ID and persistentId)
- Managing component lifecycles
:::tip[About Parent-Child Hierarchy]
Parent-child relationships between entities are managed through `HierarchyComponent` and `HierarchySystem`, not built-in Entity properties. This design follows ECS composition principles—only entities that need hierarchy relationships add this component.
See the [Hierarchy System](/en/guide/hierarchy/) documentation for details.
:::
## Creating Entities
**Entities must be created through the scene, not manually.**
```typescript
// Correct: Create entity through scene
const player = scene.createEntity("Player");
// ❌ Wrong: Manual creation
// const entity = new Entity("MyEntity", 1);
```
Creating through the scene ensures:
- Entity is properly added to the scene's entity management system
- Entity is added to the query system for use by systems
- Entity gets the correct scene reference
- Related lifecycle events are triggered
### Batch Creation
The framework provides high-performance batch creation:
```typescript
// Batch create 100 bullet entities
const bullets = scene.createEntities(100, "Bullet");
bullets.forEach((bullet, index) => {
bullet.createComponent(Position, Math.random() * 800, Math.random() * 600);
bullet.createComponent(Velocity, Math.random() * 100, Math.random() * 100);
});
```
`createEntities()` batches ID allocation, optimizes query system updates, and reduces system cache clearing.
## Entity Identifiers
Each entity has three types of identifiers:
| Property | Type | Description |
|----------|------|-------------|
| `id` | `number` | Runtime unique identifier for fast lookups |
| `persistentId` | `string` | GUID for maintaining reference consistency during serialization |
| `handle` | `EntityHandle` | Lightweight handle, see [Entity Handle](/en/guide/entity/entity-handle/) |
```typescript
const entity = scene.createEntity("Player");
console.log(entity.id); // 1
console.log(entity.persistentId); // "a1b2c3d4-..."
console.log(entity.handle); // Numeric handle
```
## Entity Properties
### Name and Tag
```typescript
// Name - for debugging and lookup
entity.name = "Player";
// Tag - for fast categorization and querying
entity.tag = 1; // Player tag
enemy.tag = 2; // Enemy tag
```
### State Control
```typescript
// Enable/disable state
entity.enabled = false;
// Active state
entity.active = false;
// Update order (lower values have higher priority)
entity.updateOrder = 10;
```
## Finding Entities
The scene provides multiple ways to find entities:
```typescript
// Find by name
const player = scene.findEntity("Player");
// Or use alias
const player2 = scene.getEntityByName("Player");
// Find by ID
const entity = scene.findEntityById(123);
// Find all entities by tag
const enemies = scene.findEntitiesByTag(2);
// Or use alias
const allEnemies = scene.getEntitiesByTag(2);
// Find by handle
const entity = scene.findEntityByHandle(handle);
```
## Entity Events
Entity changes trigger events:
```typescript
// Listen for component additions
scene.eventSystem.on('component:added', (data) => {
console.log(`${data.entityName} added ${data.componentType}`);
});
// Listen for component removals
scene.eventSystem.on('component:removed', (data) => {
console.log(`${data.entityName} removed ${data.componentType}`);
});
// Listen for entity creation
scene.eventSystem.on('entity:created', (data) => {
console.log(`Entity created: ${data.entityName}`);
});
// Listen for active state changes
scene.eventSystem.on('entity:activeChanged', (data) => {
console.log(`${data.entity.name} active: ${data.active}`);
});
```
## Debugging
```typescript
// Get entity debug info
const debugInfo = entity.getDebugInfo();
console.log(debugInfo);
// {
// name: "Player",
// id: 1,
// persistentId: "a1b2c3d4-...",
// enabled: true,
// active: true,
// destroyed: false,
// componentCount: 3,
// componentTypes: ["Position", "Health", "Velocity"],
// ...
// }
// Entity string representation
console.log(entity.toString());
// "Entity[Player:1:a1b2c3d4]"
```
## Next Steps
- [Component Operations](/en/guide/entity/component-operations/) - Add, get, and remove components
- [Entity Handle](/en/guide/entity/entity-handle/) - Safe entity reference method
- [Lifecycle](/en/guide/entity/lifecycle/) - Destruction and persistence

View File

@@ -0,0 +1,238 @@
---
title: "Lifecycle"
description: "Entity lifecycle management, destruction, and persistence"
---
Entity lifecycle includes three phases: creation, runtime, and destruction. This section covers how to properly manage entity lifecycles.
## Destroying Entities
### Basic Destruction
```typescript
// Destroy entity
player.destroy();
// Check if entity is destroyed
if (player.isDestroyed) {
console.log("Entity has been destroyed");
}
```
When destroying an entity:
1. All components are removed (triggering `onRemovedFromEntity` callbacks)
2. Entity is removed from query systems
3. Entity is removed from scene entity list
4. All reference tracking is cleaned up
### Conditional Destruction
```typescript
// Common pattern: Destroy when health depleted
const health = enemy.getComponent(Health);
if (health && health.current <= 0) {
enemy.destroy();
}
```
### Destruction Safety
Destruction is idempotent—multiple calls won't cause errors:
```typescript
player.destroy();
player.destroy(); // Safe, no error
```
## Persistent Entities
By default, entities are destroyed during scene transitions. Persistence allows entities to survive across scenes.
### Setting Persistence
```typescript
// Method 1: Chain call
const player = scene.createEntity('Player')
.setPersistent()
.createComponent(PlayerComponent);
// Method 2: Separate call
player.setPersistent();
// Check persistence
if (player.isPersistent) {
console.log("This is a persistent entity");
}
```
### Removing Persistence
```typescript
// Restore to scene-local entity
player.setSceneLocal();
```
### Lifecycle Policies
Entities have two lifecycle policies:
| Policy | Description |
|--------|-------------|
| `SceneLocal` | Default, destroyed with scene |
| `Persistent` | Survives scene transitions |
```typescript
import { EEntityLifecyclePolicy } from '@esengine/ecs-framework';
// Get current policy
const policy = entity.lifecyclePolicy;
if (policy === EEntityLifecyclePolicy.Persistent) {
// Persistent entity
}
```
### Use Cases
Persistent entities are suitable for:
- Player characters
- Global managers
- UI entities
- Game state that needs to survive scene transitions
```typescript
// Player character
const player = scene.createEntity('Player')
.setPersistent();
// Game manager
const gameManager = scene.createEntity('GameManager')
.setPersistent()
.createComponent(GameStateComponent);
// Score manager
const scoreManager = scene.createEntity('ScoreManager')
.setPersistent()
.createComponent(ScoreComponent);
```
## Scene Transition Behavior
```typescript
// Scene manager switches scenes
sceneManager.loadScene('Level2');
// During transition:
// 1. SceneLocal entities are destroyed
// 2. Persistent entities migrate to new scene
// 3. New scene entities are created
```
:::caution[Note]
Persistent entities automatically migrate to the new scene during transitions, but other non-persistent entities they reference may be destroyed. Use [EntityHandle](/en/guide/entity/entity-handle/) to safely handle this situation.
:::
## Reference Cleanup
The framework provides reference tracking that automatically cleans up references when entities are destroyed:
```typescript
// Reference tracker cleans up all references to this entity on destruction
scene.referenceTracker?.clearReferencesTo(entity.id);
```
Using the `@entityRef` decorator handles this automatically:
```typescript
class FollowComponent extends Component {
@entityRef()
targetId: number | null = null;
}
// When target is destroyed, targetId is automatically set to null
```
See [Component References](/en/guide/component/entity-ref/) for details.
## Best Practices
### 1. Destroy Unneeded Entities Promptly
```typescript
// Destroy bullets that fly off screen
if (position.x < 0 || position.x > screenWidth) {
bullet.destroy();
}
```
### 2. Use Object Pools Instead of Frequent Create/Destroy
```typescript
class BulletPool {
private pool: Entity[] = [];
acquire(scene: Scene): Entity {
if (this.pool.length > 0) {
const bullet = this.pool.pop()!;
bullet.enabled = true;
return bullet;
}
return scene.createEntity('Bullet');
}
release(bullet: Entity) {
bullet.enabled = false;
this.pool.push(bullet);
}
}
```
### 3. Use Persistence Sparingly
Only use persistence for entities that truly need to survive scene transitions—too many persistent entities increase memory usage.
### 4. Clean Up References Before Destruction
```typescript
// Notify related systems before destruction
const aiSystem = scene.getSystem(AISystem);
aiSystem?.clearTarget(enemy.id);
enemy.destroy();
```
## Lifecycle Events
You can listen to entity destruction events:
```typescript
// Method 1: Through event system
scene.eventSystem.on('entity:destroyed', (data) => {
console.log(`Entity ${data.entityName} destroyed`);
});
// Method 2: In component
class MyComponent extends Component {
onRemovedFromEntity() {
console.log('Component removed, entity may be destroying');
// Clean up resources
}
}
```
## Debugging
```typescript
// Get entity status
const debugInfo = entity.getDebugInfo();
console.log({
destroyed: debugInfo.destroyed,
enabled: debugInfo.enabled,
active: debugInfo.active
});
```
## Next Steps
- [Component Operations](/en/guide/entity/component-operations/) - Adding and removing components
- [Scene Management](/en/guide/scene/) - Scene switching and management

View File

@@ -0,0 +1,260 @@
---
title: "Event System"
description: "Type-safe event system with sync/async events, priorities, and batching"
---
The ECS framework includes a powerful type-safe event system supporting sync/async events, priorities, batching, and more advanced features. The event system is the core mechanism for inter-component and inter-system communication.
## Basic Concepts
The event system implements a publish-subscribe pattern with these core concepts:
- **Event Publisher**: Object that emits events
- **Event Listener**: Object that listens for and handles specific events
- **Event Type**: String identifier to distinguish different event types
- **Event Data**: Related information carried by the event
## Basic Usage
### Using Event System in Scene
Each scene has a built-in event system:
```typescript
class GameScene extends Scene {
protected initialize(): void {
// Listen for events
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(`Player died, cause: ${data.cause}`);
}
private onEnemySpawned(data: { enemy: Entity, position: { x: number, y: number } }): void {
console.log('Enemy spawned at:', data.position);
}
private onScoreChanged(data: { newScore: number, oldScore: number }): void {
console.log(`Score changed: ${data.oldScore} -> ${data.newScore}`);
}
// Emit events in systems
someGameLogic(): void {
this.eventSystem.emitSync('score_changed', {
newScore: 1000,
oldScore: 800
});
}
}
```
### Using Events in Systems
Systems can conveniently listen for and send events:
```typescript
@ECSSystem('Combat')
class CombatSystem extends EntitySystem {
constructor() {
super(Matcher.all(Health, Combat));
}
protected onInitialize(): void {
// Use system's event listener method (auto-cleanup)
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'
});
}
}
```
## Advanced Features
### One-Time Listeners
```typescript
// Listen only once
this.eventSystem.once('game_start', this.onGameStart.bind(this));
// Or use configuration object
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this), {
once: true
});
```
### Priority Control
```typescript
// Higher priority listeners execute first
this.eventSystem.on('damage_dealt', this.onDamageDealt.bind(this), {
priority: 100 // High priority
});
this.eventSystem.on('damage_dealt', this.updateUI.bind(this), {
priority: 0 // Default priority
});
this.eventSystem.on('damage_dealt', this.logDamage.bind(this), {
priority: -100 // Low priority, executes last
});
```
### Async Event Handling
```typescript
protected initialize(): void {
this.eventSystem.onAsync('save_game', this.onSaveGame.bind(this));
}
private async onSaveGame(data: { saveSlot: number }): Promise<void> {
console.log(`Saving game to slot ${data.saveSlot}`);
await this.saveGameData(data.saveSlot);
console.log('Game saved');
}
// Emit async event
public async triggerSave(): Promise<void> {
await this.eventSystem.emit('save_game', { saveSlot: 1 });
console.log('All async save operations complete');
}
```
### Batch Processing
For high-frequency events, use batching to improve performance:
```typescript
protected onInitialize(): void {
// Configure batch processing for position updates
this.scene?.eventSystem.setBatchConfig('position_updated', {
batchSize: 50,
delay: 16,
enabled: true
});
// Listen for batch events
this.addEventListener('position_updated:batch', this.onPositionBatch.bind(this));
}
private onPositionBatch(batchData: any): void {
console.log(`Batch processing ${batchData.count} position updates`);
for (const event of batchData.events) {
this.updateMinimap(event.entityId, event.position);
}
}
```
## Global Event Bus
For cross-scene event communication:
```typescript
import { GlobalEventBus } from '@esengine/ecs-framework';
class GameManager {
private eventBus = GlobalEventBus.getInstance();
constructor() {
this.eventBus.on('player_level_up', this.onPlayerLevelUp.bind(this));
this.eventBus.on('achievement_unlocked', this.onAchievementUnlocked.bind(this));
}
private onPlayerLevelUp(data: { level: number }): void {
console.log(`Player leveled up to ${data.level}!`);
}
}
```
## Best Practices
### 1. Event Naming Convention
```typescript
// ✅ Good naming
this.eventSystem.emitSync('player:health_changed', data);
this.eventSystem.emitSync('enemy:spawned', data);
this.eventSystem.emitSync('ui:score_updated', data);
// ❌ Avoid
this.eventSystem.emitSync('event1', data);
this.eventSystem.emitSync('update', data);
```
### 2. Type-Safe Event Data
```typescript
interface PlayerHealthChangedEvent {
entityId: number;
oldHealth: number;
newHealth: number;
cause: 'damage' | 'healing';
}
class HealthSystem extends EntitySystem {
private onHealthChanged(data: PlayerHealthChangedEvent): void {
// TypeScript provides full type checking
console.log(`Health changed: ${data.oldHealth} -> ${data.newHealth}`);
}
}
```
### 3. Avoid Event Loops
```typescript
// ❌ Avoid: May cause infinite loop
private onScoreChanged(data: any): void {
this.scene?.eventSystem.emitSync('score_changed', newData); // Dangerous!
}
// ✅ Correct: Use guard flag
private isProcessingScore = false;
private onScoreChanged(data: any): void {
if (this.isProcessingScore) return;
this.isProcessingScore = true;
this.updateUI(data);
this.isProcessingScore = false;
}
```
### 4. Clean Up Event Listeners
```typescript
class TemporaryUI {
private listenerId: string;
constructor(scene: Scene) {
this.listenerId = scene.eventSystem.on('ui_update', this.onUpdate.bind(this));
}
public destroy(): void {
if (this.listenerId) {
scene.eventSystem.off('ui_update', this.listenerId);
}
}
}
```

View File

@@ -0,0 +1,443 @@
---
title: "Quick Start"
---
This guide will help you get started with ECS Framework, from installation to creating your first ECS application.
## Installation
### Using CLI (Recommended)
The easiest way to add ECS to your existing project:
```bash
# In your project directory
npx @esengine/cli init
```
The CLI automatically detects your project type (Cocos Creator 2.x/3.x, LayaAir 3.x, or Node.js) and generates the necessary integration code, including:
- `ECSManager` component/script - Manages ECS lifecycle
- Example components and systems - Helps you get started quickly
- Automatic dependency installation
### Manual NPM Installation
If you prefer manual configuration:
```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 3.x Engine Integration
Using `Laya.Script` component to manage ECS lifecycle is recommended:
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
const { regClass } = Laya;
@regClass()
export class ECSManager extends Laya.Script {
private ecsScene = new GameScene();
onAwake(): void {
// Initialize ECS
Core.create({ debug: true });
Core.setScene(this.ecsScene);
}
onUpdate(): void {
// Auto-updates global services and scene
Core.update(Laya.timer.delta / 1000);
}
onDestroy(): void {
// Cleanup resources
Core.destroy();
}
}
```
In Laya IDE, attach the `ECSManager` script to a node in your 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)
```

View File

@@ -0,0 +1,202 @@
---
title: "Hierarchy System"
description: "Parent-child entity relationships using component-based design"
---
In game development, parent-child hierarchy relationships between entities are common requirements. ECS Framework manages hierarchy relationships through a component-based approach using `HierarchyComponent` and `HierarchySystem`, fully adhering to ECS composition principles.
## Design Philosophy
### Why Not Built-in Hierarchy in Entity?
Traditional game object models build hierarchy into entities. ECS Framework chose a component-based approach because:
1. **ECS Composition Principle**: Hierarchy is a "feature" that should be added through components, not inherent to all entities
2. **On-Demand Usage**: Only entities that need hierarchy add `HierarchyComponent`
3. **Data-Logic Separation**: `HierarchyComponent` stores data, `HierarchySystem` handles logic
4. **Serialization-Friendly**: Hierarchy as component data can be easily serialized/deserialized
## Basic Concepts
### HierarchyComponent
Component storing hierarchy relationship data:
```typescript
import { HierarchyComponent } from '@esengine/ecs-framework';
interface HierarchyComponent {
parentId: number | null; // Parent entity ID, null means root
childIds: number[]; // Child entity ID list
depth: number; // Depth in hierarchy (maintained by system)
bActiveInHierarchy: boolean; // Active in hierarchy (maintained by system)
}
```
### HierarchySystem
System handling hierarchy logic, provides all hierarchy operation APIs:
```typescript
import { HierarchySystem } from '@esengine/ecs-framework';
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
```
## Quick Start
### Add System to Scene
```typescript
import { Scene, HierarchySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
this.addSystem(new HierarchySystem());
}
}
```
### Establish Parent-Child Relationships
```typescript
const parent = scene.createEntity("Parent");
const child1 = scene.createEntity("Child1");
const child2 = scene.createEntity("Child2");
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
// Set parent-child relationship (auto-adds HierarchyComponent)
hierarchySystem.setParent(child1, parent);
hierarchySystem.setParent(child2, parent);
```
### Query Hierarchy
```typescript
// Get parent entity
const parentEntity = hierarchySystem.getParent(child1);
// Get all children
const children = hierarchySystem.getChildren(parent);
// Get child count
const count = hierarchySystem.getChildCount(parent);
// Check if has children
const hasKids = hierarchySystem.hasChildren(parent);
// Get depth in hierarchy
const depth = hierarchySystem.getDepth(child1); // Returns 1
```
## API Reference
### Parent-Child Operations
```typescript
// Set parent
hierarchySystem.setParent(child, parent);
// Move to root (no parent)
hierarchySystem.setParent(child, null);
// Insert child at position
hierarchySystem.insertChildAt(parent, child, 0);
// Remove child (becomes root)
hierarchySystem.removeChild(parent, child);
// Remove all children
hierarchySystem.removeAllChildren(parent);
```
### Hierarchy Queries
```typescript
// Get root of entity
const root = hierarchySystem.getRoot(deepChild);
// Get all root entities
const roots = hierarchySystem.getRootEntities();
// Check ancestor/descendant relationships
const isAncestor = hierarchySystem.isAncestorOf(grandparent, child);
const isDescendant = hierarchySystem.isDescendantOf(child, grandparent);
```
### Hierarchy Traversal
```typescript
// Find child by name
const child = hierarchySystem.findChild(parent, "ChildName");
// Recursive search
const deepChild = hierarchySystem.findChild(parent, "DeepChild", true);
// Find children by tag
const tagged = hierarchySystem.findChildrenByTag(parent, TAG_ENEMY, true);
// Iterate children
hierarchySystem.forEachChild(parent, (child) => {
console.log(child.name);
}, true); // true for recursive
```
### Hierarchy State
```typescript
// Check if active in hierarchy (considers all ancestors)
const activeInHierarchy = hierarchySystem.isActiveInHierarchy(child);
// Get depth (root = 0)
const depth = hierarchySystem.getDepth(entity);
```
## Complete Example
```typescript
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);
console.log(`Player depth: ${this.hierarchySystem.getDepth(player)}`); // 0
console.log(`Weapon depth: ${this.hierarchySystem.getDepth(weapon)}`); // 2
}
}
```
## Best Practices
1. **Avoid Deep Nesting**: System limits max depth to 32 levels
2. **Batch Operations**: Set up all parent-child relationships at once when building complex hierarchies
3. **On-Demand Addition**: Only add `HierarchyComponent` to entities that truly need hierarchy
4. **Cache System Reference**: Avoid getting `HierarchySystem` on every call
```typescript
// Good practice
class MySystem extends EntitySystem {
private hierarchySystem!: HierarchySystem;
onAddedToScene() {
this.hierarchySystem = this.scene!.getEntityProcessor(HierarchySystem)!;
}
}
```

View File

@@ -0,0 +1,45 @@
---
title: "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.

View File

@@ -0,0 +1,225 @@
---
title: "Logging System"
description: "Multi-level logging with colors, prefixes, and flexible configuration"
---
The ECS framework provides a powerful hierarchical logging system supporting multiple log levels, color output, custom prefixes, and flexible configuration options.
## Basic Concepts
- **Log Levels**: Debug < Info < Warn < Error < Fatal < None
- **Logger**: Named log outputter, each module can have its own logger
- **Logger Manager**: Singleton managing all loggers globally
- **Color Config**: Supports console color output
## Log Levels
```typescript
import { LogLevel } from '@esengine/ecs-framework';
LogLevel.Debug // 0 - Debug information
LogLevel.Info // 1 - General information
LogLevel.Warn // 2 - Warning information
LogLevel.Error // 3 - Error information
LogLevel.Fatal // 4 - Fatal errors
LogLevel.None // 5 - No output
```
## Basic Usage
### Using Default Logger
```typescript
import { Logger } from '@esengine/ecs-framework';
class GameSystem extends EntitySystem {
protected process(entities: readonly Entity[]): void {
Logger.debug('Processing entities:', entities.length);
Logger.info('System running normally');
Logger.warn('Performance issue detected');
Logger.error('Error during processing', new Error('Example'));
Logger.fatal('Fatal error, system stopping');
}
}
```
### Creating Named Logger
```typescript
import { createLogger } from '@esengine/ecs-framework';
class MovementSystem extends EntitySystem {
private logger = createLogger('MovementSystem');
protected process(entities: readonly Entity[]): void {
this.logger.info(`Processing ${entities.length} moving entities`);
for (const entity of entities) {
const position = entity.getComponent(Position);
this.logger.debug(`Entity ${entity.id} moved to (${position.x}, ${position.y})`);
}
}
}
```
## Log Configuration
### Set Global Log Level
```typescript
import { setGlobalLogLevel, LogLevel } from '@esengine/ecs-framework';
// Development: show all logs
setGlobalLogLevel(LogLevel.Debug);
// Production: show warnings and above
setGlobalLogLevel(LogLevel.Warn);
// Disable all logs
setGlobalLogLevel(LogLevel.None);
```
### Custom Logger Configuration
```typescript
import { ConsoleLogger, LogLevel } from '@esengine/ecs-framework';
// Development logger
const debugLogger = new ConsoleLogger({
level: LogLevel.Debug,
enableTimestamp: true,
enableColors: true,
prefix: 'DEV'
});
// Production logger
const productionLogger = new ConsoleLogger({
level: LogLevel.Error,
enableTimestamp: true,
enableColors: false,
prefix: 'PROD'
});
```
## Color Configuration
```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
});
```
## Advanced Features
### Hierarchical Loggers
```typescript
import { LoggerManager } from '@esengine/ecs-framework';
const manager = LoggerManager.getInstance();
// Create child loggers
const movementLogger = manager.createChildLogger('GameSystems', 'Movement');
const renderLogger = manager.createChildLogger('GameSystems', 'Render');
// Child logger shows full path: [GameSystems.Movement]
movementLogger.debug('Movement system initialized');
```
### Third-Party Logger Integration
```typescript
import { setLoggerFactory } from '@esengine/ecs-framework';
// Integrate with Winston
setLoggerFactory((name?: string) => winston.createLogger({ /* ... */ }));
// Integrate with Pino
setLoggerFactory((name?: string) => pino({ name }));
// Integrate with NestJS Logger
setLoggerFactory((name?: string) => new Logger(name));
```
### Custom Output
```typescript
const fileLogger = new ConsoleLogger({
level: LogLevel.Info,
output: (level: LogLevel, message: string) => {
this.writeToFile(LogLevel[level], message);
}
});
```
## Best Practices
### 1. Choose Appropriate Log Levels
```typescript
// Debug - Detailed debug info
this.logger.debug('Variable values', { x: 10, y: 20 });
// Info - Important state changes
this.logger.info('System startup complete');
// Warn - Abnormal but non-fatal
this.logger.warn('Resource not found, using default');
// Error - Errors but program can continue
this.logger.error('Save failed, will retry', new Error('Network timeout'));
// Fatal - Fatal errors, program cannot continue
this.logger.fatal('Out of memory, exiting');
```
### 2. Structured Log Data
```typescript
this.logger.info('User action', {
userId: 12345,
action: 'move',
position: { x: 100, y: 200 },
timestamp: Date.now()
});
```
### 3. Avoid Performance Issues
```typescript
// ✅ Check log level before expensive computation
if (this.logger.debug) {
const expensiveData = this.calculateExpensiveDebugInfo();
this.logger.debug('Debug info', expensiveData);
}
```
### 4. Environment-Based Configuration
```typescript
class LoggingConfiguration {
public static setupLogging(): void {
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment) {
setGlobalLogLevel(LogLevel.Debug);
setLoggerColors({
debug: Colors.CYAN,
info: Colors.GREEN,
warn: Colors.YELLOW,
error: Colors.RED,
fatal: Colors.BRIGHT_RED
});
} else {
setGlobalLogLevel(LogLevel.Warn);
LoggerManager.getInstance().resetColors();
}
}
}
```

View File

@@ -0,0 +1,364 @@
---
title: "persistent-entity"
---
# Persistent Entity
> **Version**: v2.3.0+
Persistent Entity is a special type of entity that automatically migrates to the new scene during scene transitions. It is suitable for game objects that need to maintain state across scenes, such as players, game managers, audio managers, etc.
## Basic Concepts
In the ECS framework, entities have two lifecycle policies:
| Policy | Description | Default |
|--------|-------------|---------|
| `SceneLocal` | Scene-local entity, destroyed when scene changes | ✓ |
| `Persistent` | Persistent entity, automatically migrates during scene transitions | |
## Quick Start
### Creating a Persistent Entity
```typescript
import { Scene } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
// Create a persistent player entity
const player = this.createEntity('Player').setPersistent();
player.addComponent(new Position(100, 200));
player.addComponent(new PlayerData('Hero', 500));
// Create a normal enemy entity (destroyed when scene changes)
const enemy = this.createEntity('Enemy');
enemy.addComponent(new Position(300, 200));
enemy.addComponent(new EnemyAI());
}
}
```
### Behavior During Scene Transitions
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// Initial scene
class Level1Scene extends Scene {
protected initialize(): void {
// Player - persistent, will migrate to the next scene
const player = this.createEntity('Player').setPersistent();
player.addComponent(new Position(0, 0));
player.addComponent(new Health(100));
// Enemy - scene-local, destroyed when scene changes
const enemy = this.createEntity('Enemy');
enemy.addComponent(new Position(100, 100));
}
}
// Target scene
class Level2Scene extends Scene {
protected initialize(): void {
// New enemy
const enemy = this.createEntity('Boss');
enemy.addComponent(new Position(200, 200));
}
public onStart(): void {
// Player has automatically migrated to this scene
const player = this.findEntity('Player');
console.log(player !== null); // true
// Position and health data are fully preserved
const position = player?.getComponent(Position);
const health = player?.getComponent(Health);
console.log(position?.x, position?.y); // 0, 0
console.log(health?.value); // 100
}
}
// Switch scenes
Core.create({ debug: true });
Core.setScene(new Level1Scene());
// Later switch to Level2
Core.loadScene(new Level2Scene());
// Player entity migrates automatically, Enemy entity is destroyed
```
## API Reference
### Entity Methods
#### setPersistent()
Marks the entity as persistent, preventing destruction during scene transitions.
```typescript
public setPersistent(): this
```
**Returns**: Returns the entity itself for method chaining
**Example**:
```typescript
const player = scene.createEntity('Player')
.setPersistent();
player.addComponent(new Position(100, 200));
```
#### setSceneLocal()
Restores the entity to scene-local policy (default).
```typescript
public setSceneLocal(): this
```
**Returns**: Returns the entity itself for method chaining
**Example**:
```typescript
// Dynamically cancel persistence
player.setSceneLocal();
```
#### isPersistent
Checks if the entity is persistent.
```typescript
public get isPersistent(): boolean
```
**Example**:
```typescript
if (entity.isPersistent) {
console.log('This is a persistent entity');
}
```
#### lifecyclePolicy
Gets the entity's lifecycle policy.
```typescript
public get lifecyclePolicy(): EEntityLifecyclePolicy
```
**Example**:
```typescript
import { EEntityLifecyclePolicy } from '@esengine/ecs-framework';
if (entity.lifecyclePolicy === EEntityLifecyclePolicy.Persistent) {
console.log('Persistent entity');
}
```
### Scene Methods
#### findPersistentEntities()
Finds all persistent entities in the scene.
```typescript
public findPersistentEntities(): Entity[]
```
**Returns**: Array of persistent entities
**Example**:
```typescript
const persistentEntities = scene.findPersistentEntities();
console.log(`Scene has ${persistentEntities.length} persistent entities`);
```
#### extractPersistentEntities()
Extracts and removes all persistent entities from the scene (typically called internally by the framework).
```typescript
public extractPersistentEntities(): Entity[]
```
**Returns**: Array of extracted persistent entities
#### receiveMigratedEntities()
Receives migrated entities (typically called internally by the framework).
```typescript
public receiveMigratedEntities(entities: Entity[]): void
```
**Parameters**:
- `entities` - Array of entities to receive
## Use Cases
### 1. Player Entity Across Levels
```typescript
class PlayerSetupScene extends Scene {
protected initialize(): void {
// Player maintains state across all levels
const player = this.createEntity('Player').setPersistent();
player.addComponent(new Transform(0, 0));
player.addComponent(new Health(100));
player.addComponent(new Inventory());
player.addComponent(new PlayerStats());
}
}
class Level1 extends Scene { /* ... */ }
class Level2 extends Scene { /* ... */ }
class Level3 extends Scene { /* ... */ }
// Player entity automatically migrates between all levels
Core.setScene(new PlayerSetupScene());
// ... game progresses
Core.loadScene(new Level1());
// ... level complete
Core.loadScene(new Level2());
// Player data (health, inventory, stats) fully preserved
```
### 2. Global Managers
```typescript
class BootstrapScene extends Scene {
protected initialize(): void {
// Audio manager - persists across scenes
const audioManager = this.createEntity('AudioManager').setPersistent();
audioManager.addComponent(new AudioController());
// Achievement manager - persists across scenes
const achievementManager = this.createEntity('AchievementManager').setPersistent();
achievementManager.addComponent(new AchievementTracker());
// Game settings - persists across scenes
const settings = this.createEntity('GameSettings').setPersistent();
settings.addComponent(new SettingsData());
}
}
```
### 3. Dynamically Toggling Persistence
```typescript
class GameScene extends Scene {
protected initialize(): void {
// Initially created as a normal entity
const companion = this.createEntity('Companion');
companion.addComponent(new Transform(0, 0));
companion.addComponent(new CompanionAI());
// Listen for recruitment event
this.eventSystem.on('companion:recruited', () => {
// After recruitment, become persistent
companion.setPersistent();
console.log('Companion joined the party, will follow player across scenes');
});
// Listen for dismissal event
this.eventSystem.on('companion:dismissed', () => {
// After dismissal, restore to scene-local
companion.setSceneLocal();
console.log('Companion left the party, will no longer persist across scenes');
});
}
}
```
## Best Practices
### 1. Clearly Identify Persistent Entities
```typescript
// Recommended: Mark immediately when creating
const player = this.createEntity('Player').setPersistent();
// Not recommended: Marking after creation (easy to forget)
const player = this.createEntity('Player');
// ... lots of code ...
player.setPersistent(); // Easy to forget
```
### 2. Use Persistence Appropriately
```typescript
// ✓ Entities suitable for persistence
const player = this.createEntity('Player').setPersistent(); // Player
const gameManager = this.createEntity('GameManager').setPersistent(); // Global manager
const audioManager = this.createEntity('AudioManager').setPersistent(); // Audio system
// ✗ Entities that should NOT be persistent
const bullet = this.createEntity('Bullet'); // Temporary objects
const enemy = this.createEntity('Enemy'); // Level-specific enemies
const particle = this.createEntity('Particle'); // Effect particles
```
### 3. Check Migrated Entities
```typescript
class NewScene extends Scene {
public onStart(): void {
// Check if expected persistent entities exist
const player = this.findEntity('Player');
if (!player) {
console.error('Player entity did not migrate correctly!');
// Handle error case
}
}
}
```
### 4. Avoid Circular References
```typescript
// ✗ Avoid: Persistent entity referencing scene-local entity
class BadScene extends Scene {
protected initialize(): void {
const player = this.createEntity('Player').setPersistent();
const enemy = this.createEntity('Enemy');
// Dangerous: player is persistent but enemy is not
// After scene change, enemy is destroyed, reference becomes invalid
player.addComponent(new TargetComponent(enemy));
}
}
// ✓ Recommended: Use ID references or event system
class GoodScene extends Scene {
protected initialize(): void {
const player = this.createEntity('Player').setPersistent();
const enemy = this.createEntity('Enemy');
// Store ID instead of direct reference
player.addComponent(new TargetComponent(enemy.id));
// Or use event system for communication
}
}
```
## Important Notes
1. **Destroyed entities will not migrate**: If an entity is destroyed before scene transition, it will not migrate even if marked as persistent.
2. **Component data is fully preserved**: All components and their state are preserved during migration.
3. **Scene reference is updated**: After migration, the entity's `scene` property will point to the new scene.
4. **Query system is updated**: Migrated entities are automatically registered in the new scene's query system.
5. **Delayed transitions also work**: Persistent entities migrate when using `Core.loadScene()` for delayed transitions as well.
## Related Documentation
- [Scene](./scene) - Learn the basics of scenes
- [SceneManager](./scene-manager) - Learn about scene transitions
- [WorldManager](./world-manager) - Learn about multi-world management

View File

@@ -0,0 +1,291 @@
---
title: "Platform Adapter"
---
## Overview
The ECS framework provides a platform adapter interface that allows users to implement custom platform adapters for different runtime environments.
**The core library only provides interface definitions. Platform adapter implementations should be copied from the documentation.**
## Why No Separate Adapter Packages?
1. **Flexibility**: Different projects may have different platform adaptation needs. Copying code allows users to freely modify as needed
2. **Reduce Dependencies**: Avoid introducing unnecessary dependency packages, keeping the core framework lean
3. **Customization**: Users can customize according to specific runtime environments and requirements
## Supported Platforms
### [Browser Adapter](./platform-adapter/browser/)
Supports all modern browser environments, including Chrome, Firefox, Safari, Edge, etc.
**Feature Support**:
- Worker (Web Worker)
- SharedArrayBuffer (requires COOP/COEP)
- Transferable Objects
- Module Worker (modern browsers)
**Use Cases**: Web games, Web applications, PWA
---
### [WeChat Mini Game Adapter](./platform-adapter/wechat-minigame/)
Designed specifically for the WeChat Mini Game environment, handling special restrictions and APIs.
**Feature Support**:
- Worker (max 1, requires game.json configuration)
- SharedArrayBuffer (not supported)
- Transferable Objects (not supported)
- WeChat Device Info API
**Use Cases**: WeChat Mini Game development
---
### [Node.js Adapter](./platform-adapter/nodejs/)
Provides support for Node.js server environments, suitable for game servers and compute servers.
**Feature Support**:
- Worker Threads
- SharedArrayBuffer
- Transferable Objects
- Complete system information
**Use Cases**: Game servers, compute servers, CLI tools
---
## Core Interfaces
### 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 Interface
```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';
}
```
## Usage
### 1. Choose the Appropriate Platform Adapter
Select the corresponding adapter based on your runtime environment:
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
// Browser environment
if (typeof window !== 'undefined') {
const { BrowserAdapter } = await import('./platform/BrowserAdapter');
PlatformManager.getInstance().registerAdapter(new BrowserAdapter());
}
// WeChat Mini Game environment
else if (typeof wx !== 'undefined') {
const { WeChatMiniGameAdapter } = await import('./platform/WeChatMiniGameAdapter');
PlatformManager.getInstance().registerAdapter(new WeChatMiniGameAdapter());
}
// Node.js environment
else if (typeof process !== 'undefined' && process.versions?.node) {
const { NodeAdapter } = await import('./platform/NodeAdapter');
PlatformManager.getInstance().registerAdapter(new NodeAdapter());
}
```
### 2. Check Adapter Status
```typescript
const manager = PlatformManager.getInstance();
// Check if adapter is registered
if (manager.hasAdapter()) {
const adapter = manager.getAdapter();
console.log('Current platform:', adapter.name);
console.log('Platform version:', adapter.version);
// Check feature support
console.log('Worker support:', manager.supportsFeature('worker'));
console.log('SharedArrayBuffer support:', manager.supportsFeature('shared-array-buffer'));
}
```
## Creating Custom Adapters
If existing platform adapters don't meet your needs, you can create custom adapters:
### 1. Implement the Interface
```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 {
// Implement your Worker support check logic
return false;
}
public isSharedArrayBufferSupported(): boolean {
// Implement your SharedArrayBuffer support check logic
return false;
}
public getHardwareConcurrency(): number {
// Return your platform's concurrency count
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. Register Custom Adapter
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { CustomAdapter } from './CustomAdapter';
const customAdapter = new CustomAdapter();
PlatformManager.getInstance().registerAdapter(customAdapter);
```
## Best Practices
### 1. Platform Detection Order
Recommend detecting and registering platform adapters in this order:
```typescript
async function initializePlatform() {
const manager = PlatformManager.getInstance();
try {
// 1. WeChat Mini Game (highest priority, most distinctive environment)
if (typeof wx !== 'undefined' && wx.getSystemInfoSync) {
const { WeChatMiniGameAdapter } = await import('./platform/WeChatMiniGameAdapter');
manager.registerAdapter(new WeChatMiniGameAdapter());
return;
}
// 2. Node.js environment
if (typeof process !== 'undefined' && process.versions?.node) {
const { NodeAdapter } = await import('./platform/NodeAdapter');
manager.registerAdapter(new NodeAdapter());
return;
}
// 3. Browser environment (last check, broadest coverage)
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
const { BrowserAdapter } = await import('./platform/BrowserAdapter');
manager.registerAdapter(new BrowserAdapter());
return;
}
// 4. Unknown environment, use default adapter
console.warn('Unrecognized platform environment, using default adapter');
manager.registerAdapter(new CustomAdapter());
} catch (error) {
console.error('Platform adapter initialization failed:', error);
throw error;
}
}
```
### 2. Feature Degradation Handling
```typescript
function createWorkerSystem() {
const manager = PlatformManager.getInstance();
if (!manager.hasAdapter()) {
throw new Error('No platform adapter registered');
}
const config: WorkerSystemConfig = {
enableWorker: manager.supportsFeature('worker'),
workerCount: manager.supportsFeature('worker') ?
manager.getAdapter().getHardwareConcurrency() : 1,
useSharedArrayBuffer: manager.supportsFeature('shared-array-buffer')
};
// If Worker not supported, automatically degrade to synchronous processing
if (!config.enableWorker) {
console.info('Current platform does not support Worker, using synchronous processing mode');
}
return new PhysicsSystem(config);
}
```
### 3. Error Handling
```typescript
try {
await initializePlatform();
// Validate adapter functionality
const manager = PlatformManager.getInstance();
const adapter = manager.getAdapter();
console.log(`Platform adapter initialized: ${adapter.name} v${adapter.version}`);
} catch (error) {
console.error('Platform initialization failed:', error);
// Provide fallback solution
const fallbackAdapter = new CustomAdapter();
PlatformManager.getInstance().registerAdapter(fallbackAdapter);
console.warn('Using fallback adapter to continue running');
}
```

View File

@@ -0,0 +1,372 @@
---
title: "Browser Adapter"
---
## Overview
The browser platform adapter provides support for standard web browser environments, including modern browsers like Chrome, Firefox, Safari, Edge, etc.
## Feature Support
- **Worker**: Supports Web Worker and Module Worker
- **SharedArrayBuffer**: Supported (requires COOP/COEP headers)
- **Transferable Objects**: Fully supported
- **High-Resolution Time**: Uses `performance.now()`
- **Basic Info**: Browser version and basic configuration
## Complete Implementation
```typescript
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig
} from '@esengine/ecs-framework';
/**
* Browser platform adapter
* Supports standard web browser environments
*/
export class BrowserAdapter implements IPlatformAdapter {
public readonly name = 'browser';
public readonly version: string;
constructor() {
this.version = this.getBrowserInfo();
}
/**
* Check if Worker is supported
*/
public isWorkerSupported(): boolean {
return typeof Worker !== 'undefined';
}
/**
* Check if SharedArrayBuffer is supported
*/
public isSharedArrayBufferSupported(): boolean {
return typeof SharedArrayBuffer !== 'undefined' && this.checkSharedArrayBufferEnabled();
}
/**
* Get hardware concurrency (CPU core count)
*/
public getHardwareConcurrency(): number {
return navigator.hardwareConcurrency || 4;
}
/**
* Create Worker
*/
public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
if (!this.isWorkerSupported()) {
throw new Error('Browser does not support Worker');
}
try {
return new BrowserWorker(script, options);
} catch (error) {
throw new Error(`Failed to create browser Worker: ${(error as Error).message}`);
}
}
/**
* Create SharedArrayBuffer
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
if (!this.isSharedArrayBufferSupported()) {
return null;
}
try {
return new SharedArrayBuffer(length);
} catch (error) {
console.warn('SharedArrayBuffer creation failed:', error);
return null;
}
}
/**
* Get high-resolution timestamp
*/
public getHighResTimestamp(): number {
return performance.now();
}
/**
* Get platform configuration
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: this.getHardwareConcurrency(),
supportsModuleWorker: false,
supportsTransferableObjects: true,
maxSharedArrayBufferSize: 1024 * 1024 * 1024, // 1GB
workerScriptPrefix: '',
limitations: {
noEval: false,
requiresWorkerInit: false
}
};
}
/**
* Get browser information
*/
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';
}
/**
* Check if SharedArrayBuffer is actually available
*/
private checkSharedArrayBufferEnabled(): boolean {
try {
new SharedArrayBuffer(8);
return true;
} catch {
return false;
}
}
}
/**
* Browser Worker wrapper
*/
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 has been terminated');
}
try {
if (transfer && transfer.length > 0) {
this.worker.postMessage(message, transfer);
} else {
this.worker.postMessage(message);
}
} catch (error) {
throw new Error(`Failed to send message to 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('Failed to terminate Worker:', error);
}
}
}
/**
* Create browser Worker
*/
private createBrowserWorker(script: string, options: WorkerCreationOptions): Worker {
try {
// Create Blob URL
const blob = new Blob([script], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
// Create Worker
const worker = new Worker(url, {
type: options.type || 'classic',
credentials: options.credentials,
name: options.name
});
// Clean up Blob URL (delayed to ensure Worker has loaded)
setTimeout(() => {
URL.revokeObjectURL(url);
}, 1000);
return worker;
} catch (error) {
throw new Error(`Cannot create browser Worker: ${(error as Error).message}`);
}
}
}
```
## Usage
### 1. Copy the Code
Copy the above code to your project, e.g., `src/platform/BrowserAdapter.ts`.
### 2. Register the Adapter
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { BrowserAdapter } from './platform/BrowserAdapter';
// Create and register browser adapter
const browserAdapter = new BrowserAdapter();
PlatformManager.registerAdapter(browserAdapter);
// Framework will automatically detect and use the appropriate adapter
```
### 3. Use WorkerEntitySystem
The browser adapter works with WorkerEntitySystem, and the framework automatically handles Worker script creation:
```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
};
}
// This function is automatically serialized and executed in Worker
protected workerProcess(entities, deltaTime, config) {
return entities.map(entity => {
// Apply gravity
entity.vy += config.gravity * deltaTime;
// Update position
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. Verify Adapter Status
```typescript
// Verify adapter is working properly
const adapter = new BrowserAdapter();
console.log('Adapter name:', adapter.name);
console.log('Browser version:', adapter.version);
console.log('Worker support:', adapter.isWorkerSupported());
console.log('SharedArrayBuffer support:', adapter.isSharedArrayBufferSupported());
console.log('CPU core count:', adapter.getHardwareConcurrency());
```
## Important Notes
### SharedArrayBuffer Support
SharedArrayBuffer requires special security configuration:
1. **HTTPS**: Must be used in a secure context
2. **COOP/COEP Headers**: Requires correct cross-origin isolation headers
```html
<!-- Set in HTML -->
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin">
<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp">
```
Or set in server configuration:
```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```
### Browser Compatibility
- **Worker**: All modern browsers supported
- **Module Worker**: Chrome 80+, Firefox 114+
- **SharedArrayBuffer**: Chrome 68+, Firefox 79+ (requires COOP/COEP)
- **Transferable Objects**: All modern browsers supported
## Performance Optimization Tips
1. **Worker Pool**: Reuse Worker instances to avoid frequent creation and destruction
2. **Data Transfer**: Use Transferable Objects to reduce data copying
3. **SharedArrayBuffer**: Use SharedArrayBuffer for large data sharing
4. **Module Worker**: Use module Workers in supported browsers for better code organization
## Debugging Tips
```typescript
// Check browser support
const adapter = new BrowserAdapter();
console.log('Worker support:', adapter.isWorkerSupported());
console.log('SharedArrayBuffer support:', adapter.isSharedArrayBufferSupported());
console.log('Hardware concurrency:', adapter.getHardwareConcurrency());
console.log('Platform config:', adapter.getPlatformConfig());
```

View File

@@ -0,0 +1,560 @@
---
title: "Node.js Adapter"
---
## Overview
The Node.js platform adapter provides support for Node.js server environments, suitable for game servers, compute servers, or other server applications that need ECS architecture.
## Feature Support
- **Worker**: Supported (via `worker_threads` module)
- **SharedArrayBuffer**: Supported (Node.js 16.17.0+)
- **Transferable Objects**: Fully supported
- **High-Resolution Time**: Uses `process.hrtime.bigint()`
- **Device Info**: Complete system and process information
## Complete Implementation
```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 platform adapter
* Supports Node.js server environments
*/
export class NodeAdapter implements IPlatformAdapter {
public readonly name = 'nodejs';
public readonly version: string;
constructor() {
this.version = process.version;
}
/**
* Check if Worker is supported
*/
public isWorkerSupported(): boolean {
try {
// Check if worker_threads module is available
return typeof worker_threads !== 'undefined' && typeof Worker !== 'undefined';
} catch {
return false;
}
}
/**
* Check if SharedArrayBuffer is supported
*/
public isSharedArrayBufferSupported(): boolean {
// Node.js supports SharedArrayBuffer
return typeof SharedArrayBuffer !== 'undefined';
}
/**
* Get hardware concurrency (CPU core count)
*/
public getHardwareConcurrency(): number {
return os.cpus().length;
}
/**
* Create Worker
*/
public createWorker(script: string, options: WorkerCreationOptions = {}): PlatformWorker {
if (!this.isWorkerSupported()) {
throw new Error('Node.js environment does not support Worker Threads');
}
try {
return new NodeWorker(script, options);
} catch (error) {
throw new Error(`Failed to create Node.js Worker: ${(error as Error).message}`);
}
}
/**
* Create SharedArrayBuffer
*/
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
if (!this.isSharedArrayBufferSupported()) {
return null;
}
try {
return new SharedArrayBuffer(length);
} catch (error) {
console.warn('SharedArrayBuffer creation failed:', error);
return null;
}
}
/**
* Get high-resolution timestamp (nanoseconds)
*/
public getHighResTimestamp(): number {
// Return milliseconds, consistent with browser performance.now()
return Number(process.hrtime.bigint()) / 1000000;
}
/**
* Get platform configuration
*/
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: this.getHardwareConcurrency(),
supportsModuleWorker: true, // Node.js supports ES modules
supportsTransferableObjects: true,
maxSharedArrayBufferSize: this.getMaxSharedArrayBufferSize(),
workerScriptPrefix: '',
limitations: {
noEval: false, // Node.js supports 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
}
};
}
/**
* Get Node.js device information
*/
public getDeviceInfo(): NodeDeviceInfo {
const cpus = os.cpus();
const networkInterfaces = os.networkInterfaces();
const userInfo = os.userInfo();
return {
// System info
platform: os.platform(),
arch: os.arch(),
type: os.type(),
release: os.release(),
version: os.version(),
hostname: os.hostname(),
// CPU info
cpus: cpus.map(cpu => ({
model: cpu.model,
speed: cpu.speed,
times: cpu.times
})),
// Memory info
totalMemory: os.totalmem(),
freeMemory: os.freemem(),
usedMemory: os.totalmem() - os.freemem(),
// Load info
loadAverage: os.loadavg(),
// Network interfaces
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 info
process: {
pid: process.pid,
ppid: process.ppid,
version: process.version,
versions: process.versions,
uptime: process.uptime()
},
// User info
userInfo: {
uid: userInfo.uid,
gid: userInfo.gid,
username: userInfo.username,
homedir: userInfo.homedir,
shell: userInfo.shell
}
};
}
/**
* Get SharedArrayBuffer maximum size limit
*/
private getMaxSharedArrayBufferSize(): number {
const totalMemory = os.totalmem();
// Limit to 50% of total system memory
return Math.floor(totalMemory * 0.5);
}
}
/**
* Node.js Worker wrapper
*/
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 {
// Determine if script is a file path or script content
if (this.isFilePath(script)) {
// Use file path directly
this.scriptPath = script;
this.isTemporaryFile = false;
} else {
// Write script content to temporary file
this.scriptPath = this.writeScriptToFile(script, options.name);
this.isTemporaryFile = true;
}
// Create Worker
this.worker = new Worker(this.scriptPath, {
// Node.js Worker options
workerData: options.name ? { name: options.name } : undefined
});
} catch (error) {
throw new Error(`Failed to create Node.js Worker: ${(error as Error).message}`);
}
}
/**
* Determine if string is a file path
*/
private isFilePath(script: string): boolean {
// Check if it looks like a file path
return (script.endsWith('.js') || script.endsWith('.mjs') || script.endsWith('.ts')) &&
!script.includes('\n') &&
!script.includes(';') &&
script.length < 500; // File paths are typically not too long
}
/**
* Write script content to temporary file
*/
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(`Failed to write Worker script file: ${(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 has been terminated');
}
try {
if (transfer && transfer.length > 0) {
// Node.js Worker supports Transferable Objects
this.worker.postMessage(message, transfer);
} else {
this.worker.postMessage(message);
}
} catch (error) {
throw new Error(`Failed to send message to 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) => {
// Convert Error to ErrorEvent format
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';
// Clean up temporary script file
this.cleanupScriptFile();
} catch (error) {
console.error('Failed to terminate Node.js Worker:', error);
}
}
}
/**
* Clean up temporary script file
*/
private cleanupScriptFile(): void {
// Only clean up temporarily created files, not user-provided file paths
if (this.scriptPath && this.isTemporaryFile) {
try {
fs.unlinkSync(this.scriptPath);
} catch (error) {
console.warn('Failed to clean up Worker script file:', error);
}
}
}
}
```
## Usage
### 1. Copy the Code
Copy the above code to your project, e.g., `src/platform/NodeAdapter.ts`.
### 2. Register the Adapter
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { NodeAdapter } from './platform/NodeAdapter';
// Check if in Node.js environment
if (typeof process !== 'undefined' && process.versions && process.versions.node) {
const nodeAdapter = new NodeAdapter();
PlatformManager.getInstance().registerAdapter(nodeAdapter);
}
```
### 3. Use WorkerEntitySystem
The Node.js adapter works with WorkerEntitySystem, and the framework automatically handles Worker script creation:
```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, // Use all CPU cores
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
};
}
// This function is automatically serialized and executed in Worker
protected workerProcess(entities, deltaTime, config) {
return entities.map(entity => {
// Apply gravity
entity.vy += config.gravity * deltaTime;
// Update position
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. Get System Information
```typescript
const manager = PlatformManager.getInstance();
if (manager.hasAdapter()) {
const adapter = manager.getAdapter();
const deviceInfo = adapter.getDeviceInfo();
console.log('Node.js version:', deviceInfo.process?.version);
console.log('CPU core count:', deviceInfo.cpus?.length);
console.log('Total memory:', Math.round(deviceInfo.totalMemory! / 1024 / 1024), 'MB');
console.log('Available memory:', Math.round(deviceInfo.freeMemory! / 1024 / 1024), 'MB');
}
```
## Official Documentation Reference
Node.js Worker Threads related official documentation:
- [Worker Threads Official Docs](https://nodejs.org/api/worker_threads.html)
- [SharedArrayBuffer Support](https://nodejs.org/api/globals.html#class-sharedarraybuffer)
- [OS Module Docs](https://nodejs.org/api/os.html)
- [Process Module Docs](https://nodejs.org/api/process.html)
## Important Notes
### Worker Threads Requirements
- **Node.js Version**: Requires Node.js 10.5.0+ (12+ recommended)
- **Module Type**: Supports both CommonJS and ES modules
- **Thread Limit**: Theoretically unlimited, but recommended not to exceed 2x CPU core count
### Performance Optimization Tips
#### 1. Worker Pool Management
```typescript
class ServerPhysicsSystem extends WorkerEntitySystem {
constructor() {
const cpuCount = os.cpus().length;
super(Matcher.all(Transform, Velocity), {
enableWorker: true,
workerCount: Math.min(cpuCount * 2, 16), // Max 16 Workers
entitiesPerWorker: 1000, // 1000 entities per Worker
useSharedArrayBuffer: true,
systemConfig: {
gravity: 9.8,
timeStep: 1/60
}
});
}
}
```
#### 2. Memory Management
```typescript
class MemoryMonitor {
public static checkMemoryUsage(): void {
const used = process.memoryUsage();
console.log('Memory usage:');
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`);
// Trigger warning when memory usage is too high
if (used.heapUsed > used.heapTotal * 0.9) {
console.warn('Memory usage too high, consider optimizing or restarting');
}
}
}
// Check memory usage periodically
setInterval(() => {
MemoryMonitor.checkMemoryUsage();
}, 30000); // Check every 30 seconds
```
#### 3. Server Environment Optimization
```typescript
// Set process title
process.title = 'ecs-game-server';
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Promise rejection:', reason);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('Received SIGTERM signal, shutting down server...');
// Clean up resources
process.exit(0);
});
```
## Debugging Tips
```typescript
// Check Node.js environment support
const adapter = new NodeAdapter();
console.log('Node.js version:', adapter.version);
console.log('Worker support:', adapter.isWorkerSupported());
console.log('SharedArrayBuffer support:', adapter.isSharedArrayBufferSupported());
console.log('CPU core count:', adapter.getHardwareConcurrency());
// Get detailed configuration
const config = adapter.getPlatformConfig();
console.log('Platform config:', JSON.stringify(config, null, 2));
// System resource monitoring
const deviceInfo = adapter.getDeviceInfo();
console.log('System load:', deviceInfo.loadAverage);
console.log('Network interfaces:', Object.keys(deviceInfo.networkInterfaces!));
```

View File

@@ -0,0 +1,485 @@
---
title: "WeChat Mini Game Adapter"
---
## Overview
The WeChat Mini Game platform adapter is designed specifically for the WeChat Mini Game environment, handling special restrictions and APIs.
## Feature Support
| Feature | Support | Notes |
|---------|---------|-------|
| **Worker** | Supported | Requires precompiled file, configure `workerScriptPath` |
| **SharedArrayBuffer** | Not Supported | WeChat Mini Game environment doesn't support this |
| **Transferable Objects** | Not Supported | Only serializable objects supported |
| **High-Resolution Time** | Supported | Uses `wx.getPerformance()` |
| **Device Info** | Supported | Complete WeChat Mini Game device info |
## WorkerEntitySystem Usage
### Important: WeChat Mini Game Worker Restrictions
WeChat Mini Game Workers have the following restrictions:
- **Worker scripts must be in the code package**, cannot be dynamically generated
- **Must be configured in `game.json`** with `workers` directory
- **Maximum of 1 Worker** can be created
Therefore, when using `WorkerEntitySystem`, there are two approaches:
1. **Recommended: Use CLI tool** to automatically generate Worker files
2. Manually create Worker files
### Method 1: Use CLI Tool for Auto-Generation (Recommended)
We provide the `@esengine/worker-generator` tool that can automatically extract `workerProcess` functions from your TypeScript code and generate WeChat Mini Game compatible Worker files.
#### Installation
```bash
pnpm add -D @esengine/worker-generator
# or
npm install --save-dev @esengine/worker-generator
```
#### Usage
```bash
# Scan src directory, generate Worker files to workers directory
npx esengine-worker-gen --src ./src --out ./workers --wechat
# View help
npx esengine-worker-gen --help
```
#### Parameter Reference
| Parameter | Description | Default |
|-----------|-------------|---------|
| `-s, --src <dir>` | Source code directory | `./src` |
| `-o, --out <dir>` | Output directory | `./workers` |
| `-w, --wechat` | Generate WeChat Mini Game compatible code | `false` |
| `-m, --mapping` | Generate worker-mapping.json | `true` |
| `-t, --tsconfig <path>` | TypeScript config file path | Auto-detect |
| `-v, --verbose` | Verbose output | `false` |
#### Example Output
```
ESEngine Worker Generator
Source directory: /project/src
Output directory: /project/workers
WeChat mode: Yes
Scanning for WorkerEntitySystem classes...
Found 1 WorkerEntitySystem class(es):
- PhysicsSystem (src/systems/PhysicsSystem.ts)
Generating Worker files...
Successfully generated 1 Worker file(s):
- PhysicsSystem -> workers/physics-system-worker.js
Usage:
1. Copy the generated files to your project's workers/ directory
2. Configure game.json (WeChat): { "workers": "workers" }
3. In your System constructor, add:
workerScriptPath: 'workers/physics-system-worker.js'
```
#### Integration in Build Process
```json
// package.json
{
"scripts": {
"build:workers": "esengine-worker-gen --src ./src --out ./workers --wechat",
"build": "pnpm build:workers && your-build-command"
}
}
```
### Method 2: Manually Create Worker Files
If you don't want to use the CLI tool, you can manually create Worker files.
Create `workers/entity-worker.js` in your project:
```javascript
// workers/entity-worker.js
// WeChat Mini Game WorkerEntitySystem Generic Worker Template
let sharedFloatArray = null;
worker.onMessage(function(e) {
const { type, id, entities, deltaTime, systemConfig, startIndex, endIndex, sharedBuffer } = e.data;
try {
// Handle SharedArrayBuffer initialization
if (type === 'init' && sharedBuffer) {
sharedFloatArray = new Float32Array(sharedBuffer);
worker.postMessage({ type: 'init', success: true });
return;
}
// Handle SharedArrayBuffer data
if (type === 'shared' && sharedFloatArray) {
processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig);
worker.postMessage({ id, result: null });
return;
}
// Traditional processing method
if (entities) {
const result = workerProcess(entities, deltaTime, systemConfig);
// Handle Promise return value
if (result && typeof result.then === 'function') {
result.then(function(finalResult) {
worker.postMessage({ id, result: finalResult });
}).catch(function(error) {
worker.postMessage({ id, error: error.message });
});
} else {
worker.postMessage({ id, result: result });
}
}
} catch (error) {
worker.postMessage({ id, error: error.message });
}
});
/**
* Entity processing function - Modify this function based on your business logic
* @param {Array} entities - Entity data array
* @param {number} deltaTime - Frame interval time
* @param {Object} systemConfig - System configuration
* @returns {Array} Processed entity data
*/
function workerProcess(entities, deltaTime, systemConfig) {
// ====== Write your processing logic here ======
// Example: Physics calculation
return entities.map(function(entity) {
// Apply gravity
entity.vy += (systemConfig.gravity || 100) * deltaTime;
// Update position
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
// Apply friction
entity.vx *= (systemConfig.friction || 0.95);
entity.vy *= (systemConfig.friction || 0.95);
return entity;
});
}
/**
* SharedArrayBuffer processing function (optional)
*/
function processSharedArrayBuffer(startIndex, endIndex, deltaTime, systemConfig) {
if (!sharedFloatArray) return;
// ====== Implement SharedArrayBuffer processing logic as needed ======
// Note: WeChat Mini Game doesn't support SharedArrayBuffer, this function typically won't be called
}
```
### Step 2: Configure game.json
Add workers configuration in `game.json`:
```json
{
"deviceOrientation": "portrait",
"showStatusBar": false,
"workers": "workers"
}
```
### Step 3: Use WorkerEntitySystem
```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, // WeChat Mini Game limits to 1 Worker
workerScriptPath: 'workers/entity-worker.js', // Specify precompiled Worker file
systemConfig: {
gravity: 100,
friction: 0.95
}
});
}
protected getDefaultEntityDataSize(): number {
return 6;
}
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
};
}
// Note: In WeChat Mini Game, this method won't be used
// Worker processing logic is in workers/entity-worker.js workerProcess function
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;
}
// SharedArrayBuffer related methods (not supported in WeChat Mini Game, can be omitted)
protected writeEntityToBuffer(data: PhysicsData, offset: number): void {}
protected readEntityFromBuffer(offset: number): PhysicsData | null { return null; }
}
```
### Temporarily Disable Worker (Fallback to Sync Mode)
If you encounter issues, you can temporarily disable Worker:
```typescript
class PhysicsSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Transform, Velocity), {
enableWorker: false, // Disable Worker, use main thread synchronous processing
// ... other config
});
}
}
```
## Complete Adapter Implementation
```typescript
import type {
IPlatformAdapter,
PlatformWorker,
WorkerCreationOptions,
PlatformConfig
} from '@esengine/ecs-framework';
/**
* WeChat Mini Game platform adapter
*/
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.SDKVersion || 'unknown';
}
public isWorkerSupported(): boolean {
return typeof wx !== 'undefined' && typeof wx.createWorker === 'function';
}
public isSharedArrayBufferSupported(): boolean {
return false;
}
public getHardwareConcurrency(): number {
return 1; // WeChat Mini Game max 1 Worker
}
public createWorker(scriptPath: string, options: WorkerCreationOptions = {}): PlatformWorker {
if (!this.isWorkerSupported()) {
throw new Error('WeChat Mini Game environment does not support Worker');
}
// scriptPath must be a file path in the code package
const worker = wx.createWorker(scriptPath, {
useExperimentalWorker: true
});
return new WeChatWorker(worker);
}
public createSharedArrayBuffer(length: number): SharedArrayBuffer | null {
return null;
}
public getHighResTimestamp(): number {
if (typeof wx !== 'undefined' && wx.getPerformance) {
return wx.getPerformance().now();
}
return Date.now();
}
public getPlatformConfig(): PlatformConfig {
return {
maxWorkerCount: 1,
supportsModuleWorker: false,
supportsTransferableObjects: false,
maxSharedArrayBufferSize: 0,
workerScriptPrefix: '',
limitations: {
noEval: true, // Important: Mark that dynamic scripts not supported
requiresWorkerInit: false,
memoryLimit: 512 * 1024 * 1024,
workerNotSupported: false,
workerLimitations: [
'Maximum of 1 Worker can be created',
'Worker scripts must be in the code package',
'workers path must be configured in game.json',
'workerScriptPath configuration required'
]
},
extensions: {
platform: 'wechat-minigame',
sdkVersion: this.systemInfo.SDKVersion
}
};
}
private getSystemInfo(): any {
if (typeof wx !== 'undefined' && wx.getSystemInfoSync) {
try {
return wx.getSystemInfoSync();
} catch (error) {
console.warn('Failed to get WeChat system info:', error);
}
}
return {};
}
}
/**
* WeChat Worker wrapper
*/
class WeChatWorker implements PlatformWorker {
private _state: 'running' | 'terminated' = 'running';
private worker: any;
constructor(worker: any) {
this.worker = worker;
}
public get state(): 'running' | 'terminated' {
return this._state;
}
public postMessage(message: any, transfer?: Transferable[]): void {
if (this._state === 'terminated') {
throw new Error('Worker has been terminated');
}
this.worker.postMessage(message);
}
public onMessage(handler: (event: { data: any }) => void): void {
this.worker.onMessage((res: any) => {
handler({ data: res });
});
}
public onError(handler: (error: ErrorEvent) => void): void {
if (this.worker.onError) {
this.worker.onError(handler);
}
}
public terminate(): void {
if (this._state === 'running') {
this.worker.terminate();
this._state = 'terminated';
}
}
}
```
## Register the Adapter
```typescript
import { PlatformManager } from '@esengine/ecs-framework';
import { WeChatMiniGameAdapter } from './platform/WeChatMiniGameAdapter';
// Register adapter at game startup
if (typeof wx !== 'undefined') {
const adapter = new WeChatMiniGameAdapter();
PlatformManager.getInstance().registerAdapter(adapter);
}
```
## Official Documentation Reference
- [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)
## Important Notes
### Worker Restrictions
| Restriction | Description |
|-------------|-------------|
| Quantity Limit | Maximum of 1 Worker can be created |
| Version Requirement | Requires base library 1.9.90 or above |
| Script Location | Must be in code package, dynamic generation not supported |
| Lifecycle | Must terminate() before creating new Worker |
### Memory Limits
- Typically limited to 256MB - 512MB
- Need to release unused resources promptly
- Recommend listening for memory warnings:
```typescript
wx.onMemoryWarning(() => {
console.warn('Received memory warning, starting resource cleanup');
// Clean up unnecessary resources
});
```
## Debugging Tips
```typescript
// Check Worker configuration
const adapter = PlatformManager.getInstance().getAdapter();
const config = adapter.getPlatformConfig();
console.log('Worker support:', adapter.isWorkerSupported());
console.log('Max Worker count:', config.maxWorkerCount);
console.log('Platform limitations:', config.limitations);
```

View File

@@ -0,0 +1,151 @@
---
title: "Best Practices"
description: "Plugin design guidelines and common issues"
---
## Naming Convention
```typescript
class MyPlugin implements IPlugin {
// Use lowercase letters and hyphens
readonly name = 'my-awesome-plugin'; // OK
// Follow semantic versioning
readonly version = '1.0.0'; // OK
}
```
## Resource Cleanup
Always clean up all resources created by the plugin in `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 {
// Add timer
this.timerId = setInterval(() => {
// ...
}, 1000);
// Add event listener
this.listener = () => {};
window.addEventListener('resize', this.listener);
}
uninstall(): void {
// Clear timer
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
}
// Remove event listener
if (this.listener) {
window.removeEventListener('resize', this.listener);
this.listener = undefined;
}
}
}
```
## Error Handling
```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; // Re-throw to let framework know installation failed
}
}
async uninstall(): Promise<void> {
try {
await this.cleanup();
} catch (error) {
console.error('Failed to cleanup plugin:', error);
// Don't block uninstall even if cleanup fails
}
}
private async loadConfig() { /* ... */ }
private async cleanup() { /* ... */ }
}
```
## Configuration
Allow users to configure plugin behavior:
```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 {}
}
// Usage
const plugin = new NetworkPlugin({
serverUrl: 'ws://localhost:8080',
autoReconnect: true,
timeout: 5000
});
await Core.installPlugin(plugin);
```
## Common Issues
### Plugin Installation Failed
**Causes**:
- Dependencies not satisfied
- Exception in install method
- Service registration conflict
**Solutions**:
1. Check if dependencies are installed
2. Review error logs
3. Ensure service names don't conflict
### Side Effects After Uninstall
**Cause**: Resources not properly cleaned in uninstall
**Solution**: Ensure uninstall cleans up:
- Timers
- Event listeners
- WebSocket connections
- System references
### When to Use Plugins
| Good for Plugins | Not Good for Plugins |
|------------------|---------------------|
| Optional features (debug tools, profiling) | Core game logic |
| Third-party integration (network libs, physics) | Simple utilities |
| Cross-project reusable modules | Project-specific features |

View File

@@ -0,0 +1,106 @@
---
title: "Dependency Management"
description: "Declare and check plugin dependencies"
---
## Declaring Dependencies
Plugins can declare dependencies on other plugins:
```typescript
class AdvancedPhysicsPlugin implements IPlugin {
readonly name = 'advanced-physics';
readonly version = '2.0.0';
// Declare dependency on base physics plugin
readonly dependencies = ['physics-plugin'] as const;
install(core: Core, services: ServiceContainer): void {
// Can safely use services from physics-plugin
const physicsService = services.resolve(PhysicsService);
// ...
}
uninstall(): void {
// Cleanup
}
}
```
## Dependency Checking
The framework automatically checks dependencies and throws an error if not satisfied:
```typescript
// Error: physics-plugin not installed
try {
await Core.installPlugin(new AdvancedPhysicsPlugin());
} catch (error) {
console.error(error);
// Plugin advanced-physics has unmet dependencies: physics-plugin
}
// Correct: install dependency first
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
```
## Uninstall Order
The framework checks dependencies to prevent uninstalling plugins required by others:
```typescript
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
// Error: physics-plugin is required by advanced-physics
try {
await Core.uninstallPlugin('physics-plugin');
} catch (error) {
console.error(error);
// Cannot uninstall plugin physics-plugin: it is required by advanced-physics
}
// Correct: uninstall dependent plugin first
await Core.uninstallPlugin('advanced-physics');
await Core.uninstallPlugin('physics-plugin');
```
## Dependency Graph Example
```
physics-plugin (base)
advanced-physics (depends on physics-plugin)
game-physics (depends on advanced-physics)
```
Install order: `physics-plugin``advanced-physics``game-physics`
Uninstall order: `game-physics``advanced-physics``physics-plugin`
## Multiple Dependencies
```typescript
class GamePlugin implements IPlugin {
readonly name = 'game';
readonly version = '1.0.0';
// Declare multiple dependencies
readonly dependencies = [
'physics-plugin',
'network-plugin',
'audio-plugin'
] as const;
install(core: Core, services: ServiceContainer): void {
// All dependencies are available
const physics = services.resolve(PhysicsService);
const network = services.resolve(NetworkService);
const audio = services.resolve(AudioService);
}
uninstall(): void {}
}
```

View File

@@ -0,0 +1,139 @@
---
title: "Plugin Development"
description: "IPlugin interface and lifecycle"
---
## IPlugin Interface
All plugins must implement the `IPlugin` interface:
```typescript
export interface IPlugin {
// Unique plugin name
readonly name: string;
// Plugin version (semver recommended)
readonly version: string;
// Dependencies on other plugins (optional)
readonly dependencies?: readonly string[];
// Called when plugin is installed
install(core: Core, services: ServiceContainer): void | Promise<void>;
// Called when plugin is uninstalled
uninstall(): void | Promise<void>;
}
```
## Lifecycle Methods
### install Method
Called when the plugin is installed, used for initialization:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 1. Register services
services.registerSingleton(MyService);
// 2. Access current scene
const scene = core.scene;
if (scene) {
// 3. Add systems
scene.addSystem(new MySystem());
}
// 4. Other initialization
console.log('Plugin initialized');
}
uninstall(): void {
// Cleanup logic
}
}
```
### uninstall Method
Called when the plugin is uninstalled, used for cleanup:
```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 {
// Cleanup service
if (this.myService) {
this.myService.dispose();
this.myService = undefined;
}
// Remove event listeners
// Release other resources
}
}
```
## Async Plugins
Both `install` and `uninstall` methods support async:
```typescript
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
// Async load resources
const config = await fetch('/plugin-config.json').then(r => r.json());
// Initialize service with loaded config
const service = new MyService(config);
services.registerInstance(MyService, service);
}
async uninstall(): Promise<void> {
// Async cleanup
await this.saveState();
}
private async saveState() {
// Save plugin state
}
}
// Usage
await Core.installPlugin(new AsyncPlugin());
```
## Lifecycle Flow
```
Install: Core.installPlugin(plugin)
Dependency check: Verify dependencies are satisfied
Call install(): Register services, add systems
State update: Mark as installed
Uninstall: Core.uninstallPlugin(name)
Dependency check: Verify not required by other plugins
Call uninstall(): Cleanup resources
State update: Remove from plugin list
```

View File

@@ -0,0 +1,188 @@
---
title: "Example Plugins"
description: "Complete plugin implementation examples"
---
## Network Sync Plugin
```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) {
// Handle network messages
}
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 {
// Register network service
services.registerSingleton(NetworkSyncService);
// Auto connect
const network = services.resolve(NetworkSyncService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// Service will auto-dispose
}
}
```
## Performance Analysis Plugin
```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();
// Periodic performance report
const timer = services.resolve(TimerManager);
timer.schedule(5.0, true, null, () => {
this.printReport(monitor);
});
}
uninstall(): void {
// Cleanup
}
private printReport(monitor: PerformanceMonitor) {
console.log('=== Performance Report ===');
console.log(`FPS: ${monitor.getFPS()}`);
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
}
}
```
## Debug Tools Plugin
```typescript
class DebugToolsPlugin implements IPlugin {
readonly name = 'debug-tools';
readonly version = '1.0.0';
private debugUI?: DebugUI;
install(core: Core, services: ServiceContainer): void {
// Create debug UI
this.debugUI = new DebugUI();
this.debugUI.mount(document.body);
// Register hotkey
window.addEventListener('keydown', this.handleKeyDown);
// Add debug system
const scene = core.scene;
if (scene) {
scene.addSystem(new DebugRenderSystem());
}
}
uninstall(): void {
// Remove UI
if (this.debugUI) {
this.debugUI.unmount();
this.debugUI = undefined;
}
// Remove event listener
window.removeEventListener('keydown', this.handleKeyDown);
}
private handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'F12') {
this.debugUI?.toggle();
}
};
}
```
## Audio Plugin
```typescript
class AudioPlugin implements IPlugin {
readonly name = 'audio';
readonly version = '1.0.0';
constructor(private config: { volume: number }) {}
install(core: Core, services: ServiceContainer): void {
const audioService = new AudioService(this.config);
services.registerInstance(AudioService, audioService);
// Add audio system
const scene = core.scene;
if (scene) {
scene.addSystem(new AudioSystem());
}
}
uninstall(): void {
// Stop all audio
const audio = Core.services.resolve(AudioService);
audio.stopAll();
}
}
// Usage
await Core.installPlugin(new AudioPlugin({ volume: 0.8 }));
```
## Input Manager Plugin
```typescript
class InputPlugin implements IPlugin {
readonly name = 'input';
readonly version = '1.0.0';
private inputManager?: InputManager;
install(core: Core, services: ServiceContainer): void {
this.inputManager = new InputManager();
services.registerInstance(InputManager, this.inputManager);
// Bind default keys
this.inputManager.bind('jump', ['Space', 'KeyW']);
this.inputManager.bind('attack', ['MouseLeft', 'KeyJ']);
// Add input system
const scene = core.scene;
if (scene) {
scene.addSystem(new InputSystem());
}
}
uninstall(): void {
if (this.inputManager) {
this.inputManager.dispose();
this.inputManager = undefined;
}
}
}
```

View File

@@ -0,0 +1,85 @@
---
title: "Plugin System"
description: "Extend ECS Framework in a modular way"
---
The plugin system allows you to extend ECS Framework functionality in a modular way. Through plugins, you can encapsulate specific features (like network sync, physics engines, debug tools) and reuse them across multiple projects.
## What is a Plugin
A plugin is a class that implements the `IPlugin` interface and can be dynamically installed into the framework at runtime. Plugins can:
- Register custom services to the service container
- Add systems to scenes
- Register custom components
- Extend framework functionality
## Plugin Benefits
| Benefit | Description |
|---------|-------------|
| **Modular** | Encapsulate functionality as independent modules |
| **Reusable** | Use the same plugin across multiple projects |
| **Decoupled** | Separate core framework from extensions |
| **Hot-swappable** | Dynamically install and uninstall at runtime |
## Quick Start
### Create a Plugin
```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');
}
}
```
### Install a Plugin
```typescript
import { Core } from '@esengine/ecs-framework';
Core.create({ debug: true });
// Install plugin
await Core.installPlugin(new DebugPlugin());
// Check if plugin is installed
if (Core.isPluginInstalled('debug-plugin')) {
console.log('Debug plugin is running');
}
```
### Uninstall a Plugin
```typescript
await Core.uninstallPlugin('debug-plugin');
```
### Get Plugin Instance
```typescript
const plugin = Core.getPlugin('debug-plugin');
if (plugin) {
console.log(`Plugin version: ${plugin.version}`);
}
```
## Next Steps
- [Development](./development/) - IPlugin interface and lifecycle
- [Services & Systems](./services-systems/) - Register services and add systems
- [Dependencies](./dependencies/) - Declare and check dependencies
- [Management](./management/) - Manage via Core and PluginManager
- [Examples](./examples/) - Complete examples
- [Best Practices](./best-practices/) - Design guidelines

View File

@@ -0,0 +1,93 @@
---
title: "Plugin Management"
description: "Manage plugins via Core and PluginManager"
---
## Via Core
Core class provides convenient plugin management methods:
```typescript
// Install plugin
await Core.installPlugin(myPlugin);
// Uninstall plugin
await Core.uninstallPlugin('plugin-name');
// Check if plugin is installed
if (Core.isPluginInstalled('plugin-name')) {
// ...
}
// Get plugin instance
const plugin = Core.getPlugin('plugin-name');
```
## Via PluginManager
You can also use the PluginManager service directly:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// Get all plugins
const allPlugins = pluginManager.getAllPlugins();
console.log(`Total plugins: ${allPlugins.length}`);
// Get plugin metadata
const metadata = pluginManager.getMetadata('my-plugin');
if (metadata) {
console.log(`State: ${metadata.state}`);
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
}
// Get all plugin metadata
const allMetadata = pluginManager.getAllMetadata();
for (const meta of allMetadata) {
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
}
```
## API Reference
### Core Static Methods
| Method | Description |
|--------|-------------|
| `installPlugin(plugin)` | Install plugin |
| `uninstallPlugin(name)` | Uninstall plugin |
| `isPluginInstalled(name)` | Check if installed |
| `getPlugin(name)` | Get plugin instance |
### PluginManager Methods
| Method | Description |
|--------|-------------|
| `getAllPlugins()` | Get all plugins |
| `getMetadata(name)` | Get plugin metadata |
| `getAllMetadata()` | Get all plugin metadata |
## Plugin States
```typescript
enum PluginState {
Pending = 'pending',
Installing = 'installing',
Installed = 'installed',
Uninstalling = 'uninstalling',
Failed = 'failed'
}
```
## Metadata Information
```typescript
interface PluginMetadata {
name: string;
version: string;
state: PluginState;
dependencies?: string[];
installedAt?: number;
error?: Error;
}
```

View File

@@ -0,0 +1,133 @@
---
title: "Services & Systems"
description: "Register services and add systems in plugins"
---
## Registering Services
Plugins can register their own services to the service container:
```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 {
// Register network service
services.registerSingleton(NetworkService);
// Resolve and use service
const network = services.resolve(NetworkService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// Service container will auto-call service's dispose method
}
}
```
## Service Registration Methods
| Method | Description |
|--------|-------------|
| `registerSingleton(Type)` | Register singleton service |
| `registerInstance(Type, instance)` | Register existing instance |
| `registerTransient(Type)` | Create new instance per resolve |
## Adding Systems
Plugins can add custom systems to scenes:
```typescript
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PhysicsBody));
}
protected process(entities: readonly Entity[]): void {
// Physics simulation logic
}
}
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 {
// Remove system
if (this.physicsSystem) {
const scene = Core.scene;
if (scene) {
scene.removeSystem(this.physicsSystem);
}
this.physicsSystem = undefined;
}
}
}
```
## Combined Usage
```typescript
class GamePlugin implements IPlugin {
readonly name = 'game-plugin';
readonly version = '1.0.0';
private systems: EntitySystem[] = [];
install(core: Core, services: ServiceContainer): void {
// 1. Register services
services.registerSingleton(ScoreService);
services.registerSingleton(AudioService);
// 2. Add systems
const scene = core.scene;
if (scene) {
const systems = [
new InputSystem(),
new MovementSystem(),
new ScoringSystem()
];
systems.forEach(system => {
scene.addSystem(system);
this.systems.push(system);
});
}
}
uninstall(): void {
// Remove all systems
const scene = Core.scene;
if (scene) {
this.systems.forEach(system => {
scene.removeSystem(system);
});
}
this.systems = [];
}
}
```

View File

@@ -0,0 +1,440 @@
---
title: "scene-manager"
---
# SceneManager
SceneManager is a lightweight scene manager provided by ECS Framework, suitable for 95% of game applications. It provides a simple and intuitive API with support for scene transitions and delayed loading.
## Use Cases
SceneManager is suitable for:
- Single-player games
- Simple multiplayer games
- Mobile games
- Games requiring scene transitions (menu, game, pause, etc.)
- Projects that don't need multi-World isolation
## Features
- Lightweight, zero extra overhead
- Simple and intuitive API
- Supports delayed scene transitions (avoids switching mid-frame)
- Automatic ECS fluent API management
- Automatic scene lifecycle handling
- Integrated with Core, auto-updated
- Supports [Persistent Entity](./persistent-entity) migration across scenes (v2.3.0+)
## Basic Usage
### Recommended: Using Core's Static Methods
This is the simplest and recommended approach, suitable for most applications:
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// 1. Initialize Core
Core.create({ debug: true });
// 2. Create and set scene
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// Add systems
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// Create initial entities
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Health(100));
}
public onStart(): void {
console.log("Game scene started");
}
}
// 3. Set scene
Core.setScene(new GameScene());
// 4. Game loop (Core.update automatically updates the scene)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Automatically updates all services and scenes
}
// Laya engine integration
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime);
});
// Cocos Creator integration
update(deltaTime: number) {
Core.update(deltaTime);
}
```
### Advanced: Using SceneManager Directly
If you need more control, you can use SceneManager directly:
```typescript
import { Core, SceneManager, Scene } from '@esengine/ecs-framework';
// Initialize Core
Core.create({ debug: true });
// Get SceneManager (already auto-created and registered by Core)
const sceneManager = Core.services.resolve(SceneManager);
// Set scene
const gameScene = new GameScene();
sceneManager.setScene(gameScene);
// Game loop (still use Core.update)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Core automatically calls sceneManager.update()
}
```
**Important**: Regardless of which approach you use, you should only call `Core.update()` in the game loop. It automatically updates SceneManager and scenes. You don't need to manually call `sceneManager.update()`.
## Scene Transitions
### Immediate Transition
Use `Core.setScene()` or `sceneManager.setScene()` to immediately switch scenes:
```typescript
// Method 1: Using Core (recommended)
Core.setScene(new MenuScene());
// Method 2: Using SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new MenuScene());
```
### Delayed Transition
Use `Core.loadScene()` or `sceneManager.loadScene()` for delayed scene transition, which takes effect on the next frame:
```typescript
// Method 1: Using Core (recommended)
Core.loadScene(new GameOverScene());
// Method 2: Using SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.loadScene(new GameOverScene());
```
When switching scenes from within a System, use delayed transitions:
```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) {
// Delayed transition to game over scene (takes effect next frame)
Core.loadScene(new GameOverScene());
// Current frame continues execution, won't interrupt current system processing
}
}
}
```
## API Reference
### Core Static Methods (Recommended)
#### Core.setScene()
Immediately switch scenes.
```typescript
public static setScene<T extends IScene>(scene: T): T
```
**Parameters**:
- `scene` - The scene instance to set
**Returns**:
- Returns the set scene instance
**Example**:
```typescript
const gameScene = Core.setScene(new GameScene());
console.log(gameScene.name);
```
#### Core.loadScene()
Delayed scene loading (switches on next frame).
```typescript
public static loadScene<T extends IScene>(scene: T): void
```
**Parameters**:
- `scene` - The scene instance to load
**Example**:
```typescript
Core.loadScene(new GameOverScene());
```
#### Core.scene
Get the currently active scene.
```typescript
public static get scene(): IScene | null
```
**Returns**:
- Current scene instance, or null if no scene
**Example**:
```typescript
const currentScene = Core.scene;
if (currentScene) {
console.log(`Current scene: ${currentScene.name}`);
}
```
### SceneManager Methods (Advanced)
If you need to use SceneManager directly, get it through the service container:
```typescript
const sceneManager = Core.services.resolve(SceneManager);
```
#### setScene()
Immediately switch scenes.
```typescript
public setScene<T extends IScene>(scene: T): T
```
#### loadScene()
Delayed scene loading.
```typescript
public loadScene<T extends IScene>(scene: T): void
```
#### currentScene
Get the current scene.
```typescript
public get currentScene(): IScene | null
```
#### hasScene
Check if there's an active scene.
```typescript
public get hasScene(): boolean
```
#### hasPendingScene
Check if there's a pending scene transition.
```typescript
public get hasPendingScene(): boolean
```
## Best Practices
### 1. Use Core's Static Methods
```typescript
// Recommended: Use Core's static methods
Core.setScene(new GameScene());
Core.loadScene(new MenuScene());
const currentScene = Core.scene;
// Not recommended: Don't directly use SceneManager unless you have special needs
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
### 2. Only Call Core.update()
```typescript
// Correct: Only call Core.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Automatically updates all services and scenes
}
// Incorrect: Don't manually call sceneManager.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime);
sceneManager.update(); // Duplicate update, will cause issues!
}
```
### 3. Use Delayed Transitions to Avoid Issues
When switching scenes from within a System, use `loadScene()` instead of `setScene()`:
```typescript
// Recommended: Delayed transition
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());
// Current frame continues processing other entities
}
}
}
}
// Not recommended: Immediate transition may cause issues
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());
// Scene switches immediately, other entities in current frame may not process correctly
}
}
}
}
```
### 4. Scene Responsibility Separation
Each scene should be responsible for only one specific game state:
```typescript
// Good design - clear responsibilities
class MenuScene extends Scene {
// Only handles menu-related logic
}
class GameScene extends Scene {
// Only handles gameplay logic
}
class PauseScene extends Scene {
// Only handles pause screen logic
}
// Avoid this design - mixed responsibilities
class MegaScene extends Scene {
// Contains menu, game, pause, and all other logic
}
```
### 5. Resource Management
Clean up resources in the scene's `unload()` method:
```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 {
// Cleanup resources
this.textures.clear();
this.sounds.clear();
console.log('Scene resources cleaned up');
}
}
```
### 6. Event-Driven Scene Transitions
Use the event system to trigger scene transitions, keeping code decoupled:
```typescript
class GameScene extends Scene {
protected initialize(): void {
// Listen to scene transition events
this.eventSystem.on('goto:menu', () => {
Core.loadScene(new MenuScene());
});
this.eventSystem.on('goto:gameover', (data) => {
Core.loadScene(new GameOverScene());
});
}
}
// Trigger events in System
class GameLogicSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
if (levelComplete) {
this.scene.eventSystem.emitSync('goto:gameover', {
score: 1000,
level: 5
});
}
}
}
```
## Architecture Overview
SceneManager's position in ECS Framework:
```
Core (Global Services)
└── SceneManager (Scene Management, auto-updated)
└── Scene (Current Scene)
├── EntitySystem (Systems)
├── Entity (Entities)
└── Component (Components)
```
## Comparison with WorldManager
| Feature | SceneManager | WorldManager |
|---------|--------------|--------------|
| Use Case | 95% of game applications | Advanced multi-world isolation scenarios |
| Complexity | Simple | Complex |
| Scene Count | Single scene (switchable) | Multiple Worlds, each with multiple scenes |
| Performance Overhead | Minimal | Higher |
| Usage | `Core.setScene()` | `worldManager.createWorld()` |
**When to use SceneManager**:
- Single-player games
- Simple multiplayer games
- Mobile games
- Scenes that need transitions but don't need to run simultaneously
**When to use WorldManager**:
- MMO game servers (one World per room)
- Game lobby systems (complete isolation per game room)
- Need to run multiple completely independent game instances
## Related Documentation
- [Persistent Entity](./persistent-entity) - Learn how to keep entities across scene transitions
- [WorldManager](./world-manager) - Learn about advanced multi-world isolation features
SceneManager provides simple yet powerful scene management capabilities for most games. Through Core's static methods, you can easily manage scene transitions.

View File

@@ -0,0 +1,179 @@
---
title: "Best Practices"
description: "Scene design patterns and complete examples"
---
## Scene Responsibility Separation
```typescript
// Good scene design - clear responsibilities
class MenuScene extends Scene {
// Only handles menu-related logic
}
class GameScene extends Scene {
// Only handles gameplay logic
}
class InventoryScene extends Scene {
// Only handles inventory logic
}
// Avoid this design - mixed responsibilities
class MegaScene extends Scene {
// Contains menu, game, inventory, and all other logic
}
```
## Resource Management
```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 {
// Cleanup resources
this.textures.clear();
this.sounds.clear();
console.log('Scene resources cleaned up');
}
private loadTexture(path: string): any { return null; }
private loadSound(path: string): any { return null; }
}
```
## Initialization Order
```typescript
class ProperInitScene extends Scene {
protected initialize(): void {
// 1. First set scene configuration
this.name = "GameScene";
// 2. Then add systems (by dependency order)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
// 3. Create entities last
this.createEntities();
// 4. Setup event listeners
this.setupEvents();
}
private createEntities(): void { /* ... */ }
private setupEvents(): void { /* ... */ }
}
```
## Complete Example
```typescript
import { Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// Define components
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// Define system
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;
}
}
}
}
// Define scene
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// Add systems
this.addSystem(new MovementSystem());
// Create player
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// Create enemies
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));
}
// Setup event listeners
this.eventSystem.on('player_died', () => {
console.log('Player died!');
});
}
public onStart(): void {
console.log('Game scene started');
}
public unload(): void {
console.log('Game scene unloaded');
this.eventSystem.clear();
}
}
// Use scene
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
## Design Principles
| Principle | Description |
|-----------|-------------|
| Single Responsibility | Each scene handles one game state |
| Resource Cleanup | Clean up all resources in `unload()` |
| System Order | Add systems: Input → Logic → Render |
| Event Decoupling | Use event system for scene communication |
| Layered Initialization | Config → Systems → Entities → Events |

View File

@@ -0,0 +1,124 @@
---
title: "Debugging & Monitoring"
description: "Scene statistics, performance monitoring and debugging"
---
Scene includes complete debugging and performance monitoring features.
## Scene Statistics
```typescript
class StatsScene extends Scene {
public showStats(): void {
const stats = this.getStats();
console.log(`Entity count: ${stats.entityCount}`);
console.log(`System count: ${stats.processorCount}`);
console.log('Component storage stats:', stats.componentStorageStats);
}
}
```
## Debug Information
```typescript
public showDebugInfo(): void {
const debugInfo = this.getDebugInfo();
console.log('Scene debug info:', debugInfo);
// Display all entity info
debugInfo.entities.forEach(entity => {
console.log(`Entity ${entity.name}(${entity.id}): ${entity.componentCount} components`);
console.log('Component types:', entity.componentTypes);
});
// Display all system info
debugInfo.processors.forEach(processor => {
console.log(`System ${processor.name}: processing ${processor.entityCount} entities`);
});
}
```
## Performance Monitoring
```typescript
class PerformanceScene extends Scene {
public showPerformance(): void {
// Get performance data
const perfData = this.performanceMonitor?.getPerformanceData();
if (perfData) {
console.log('FPS:', perfData.fps);
console.log('Frame time:', perfData.frameTime);
console.log('Entity update time:', perfData.entityUpdateTime);
console.log('System update time:', perfData.systemUpdateTime);
}
// Get performance report
const report = this.performanceMonitor?.generateReport();
if (report) {
console.log('Performance report:', report);
}
}
}
```
## API Reference
### getStats()
Returns scene statistics:
```typescript
interface SceneStats {
entityCount: number;
processorCount: number;
componentStorageStats: ComponentStorageStats;
}
```
### getDebugInfo()
Returns detailed debug information:
```typescript
interface DebugInfo {
entities: EntityDebugInfo[];
processors: ProcessorDebugInfo[];
}
interface EntityDebugInfo {
id: number;
name: string;
componentCount: number;
componentTypes: string[];
}
interface ProcessorDebugInfo {
name: string;
entityCount: number;
}
```
### performanceMonitor
Performance monitor interface:
```typescript
interface PerformanceMonitor {
getPerformanceData(): PerformanceData;
generateReport(): string;
}
interface PerformanceData {
fps: number;
frameTime: number;
entityUpdateTime: number;
systemUpdateTime: number;
}
```
## Debugging Tips
1. **Debug mode** - Enable with `Core.create({ debug: true })`
2. **Performance analysis** - Call `getStats()` periodically
3. **Memory monitoring** - Check `componentStorageStats` for issues
4. **System performance** - Use `performanceMonitor` to identify slow systems

View File

@@ -0,0 +1,125 @@
---
title: "Entity Management"
description: "Entity creation, finding and destruction in scenes"
---
Scene provides complete entity management APIs for creating, finding, and destroying entities.
## Creating Entities
### Single Entity
```typescript
class EntityScene extends Scene {
createGameEntities(): void {
// Create named entity
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
}
}
```
### Batch Creation
```typescript
class EntityScene extends Scene {
createBullets(): void {
// Batch create entities (high performance)
const bullets = this.createEntities(100, "Bullet");
// Add components to batch-created entities
bullets.forEach((bullet, index) => {
bullet.addComponent(new Position(index * 10, 100));
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
});
}
}
```
## Finding Entities
### By Name
```typescript
// Find by name (returns first match)
const player = this.findEntity("Player");
const player2 = this.getEntityByName("Player"); // Alias
if (player) {
console.log(`Found player: ${player.name}`);
}
```
### By ID
```typescript
// Find by unique ID
const entity = this.findEntityById(123);
if (entity) {
console.log(`Found entity: ${entity.id}`);
}
```
### By Tag
```typescript
// Find by tag (returns array)
const enemies = this.findEntitiesByTag(2);
const enemies2 = this.getEntitiesByTag(2); // Alias
console.log(`Found ${enemies.length} enemies`);
```
## Destroying Entities
### Single Entity
```typescript
const enemy = this.findEntity("Enemy_1");
if (enemy) {
enemy.destroy(); // Entity is automatically removed from scene
}
```
### All Entities
```typescript
// Destroy all entities in scene
this.destroyAllEntities();
```
## Entity Queries
Scene provides a component query system:
```typescript
class QueryScene extends Scene {
protected initialize(): void {
// Create test entities
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));
}
}
public queryEntities(): void {
// Query through QuerySystem
const entities = this.querySystem.query([Transform, Velocity]);
console.log(`Found ${entities.length} entities with Transform and Velocity`);
}
}
```
## API Reference
| Method | Returns | Description |
|--------|---------|-------------|
| `createEntity(name)` | `Entity` | Create single entity |
| `createEntities(count, prefix)` | `Entity[]` | Batch create entities |
| `findEntity(name)` | `Entity \| undefined` | Find by name |
| `findEntityById(id)` | `Entity \| undefined` | Find by ID |
| `findEntitiesByTag(tag)` | `Entity[]` | Find by tag |
| `destroyAllEntities()` | `void` | Destroy all entities |

View File

@@ -0,0 +1,122 @@
---
title: "Event System"
description: "Scene's built-in type-safe event system"
---
Scene includes a built-in type-safe event system for decoupled communication within scenes.
## Basic Usage
### Listening to Events
```typescript
class EventScene extends Scene {
protected initialize(): void {
// Listen to events
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('Player died event');
}
private onEnemySpawned(data: any): void {
console.log('Enemy spawned event');
}
private onLevelComplete(data: any): void {
console.log('Level complete event');
}
}
```
### Sending Events
```typescript
public triggerGameEvent(): void {
// Send event (synchronous)
this.eventSystem.emitSync('custom_event', {
message: "This is a custom event",
timestamp: Date.now()
});
// Send event (asynchronous)
this.eventSystem.emit('async_event', {
data: "Async event data"
});
}
```
## API Reference
| Method | Description |
|--------|-------------|
| `on(event, callback)` | Listen to event |
| `once(event, callback)` | Listen once (auto-unsubscribe) |
| `off(event, callback)` | Stop listening |
| `emitSync(event, data)` | Send event (synchronous) |
| `emit(event, data)` | Send event (asynchronous) |
| `clear()` | Clear all event listeners |
## Event Handling Patterns
### Centralized Event Management
```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 {
// Pause game logic
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = false;
}
});
}
private onGameResume(): void {
// Resume game logic
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = true;
}
});
}
private onPlayerInput(data: any): void {
// Handle player input
}
}
```
### Cleanup Event Listeners
Clean up event listeners on scene unload to avoid memory leaks:
```typescript
public unload(): void {
// Clear all event listeners
this.eventSystem.clear();
}
```
## Use Cases
| Category | Example Events |
|----------|----------------|
| Game State | `game_start`, `game_pause`, `game_over` |
| Player Actions | `player_died`, `player_jump`, `player_attack` |
| Enemy Actions | `enemy_spawned`, `enemy_killed` |
| Level Progress | `level_start`, `level_complete` |
| UI Interaction | `button_click`, `menu_open` |

View File

@@ -0,0 +1,91 @@
---
title: "Scene"
description: "Core container of ECS framework, managing entity, system and component lifecycles"
---
In the ECS architecture, a Scene is a container for the game world, responsible for managing the lifecycle of entities, systems, and components.
## Core Features
Scene is the core container of the ECS framework, providing:
- Entity creation, management, and destruction
- System registration and execution scheduling
- Component storage and querying
- Event system support
- Performance monitoring and debugging information
## Scene Management Options
ECS Framework provides two scene management approaches:
| Manager | Use Case | Features |
|---------|----------|----------|
| **SceneManager** | 95% of games | Lightweight, scene transitions |
| **WorldManager** | MMO servers, room systems | Multi-World, full isolation |
## Quick Start
### Inherit Scene Class
```typescript
import { Scene, EntitySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// Add systems
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// Create initial entities
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
}
public onStart(): void {
console.log("Game scene started");
}
public unload(): void {
console.log("Game scene unloaded");
}
}
```
### Using Scene Configuration
```typescript
import { ISceneConfig } from '@esengine/ecs-framework';
const config: ISceneConfig = {
name: "MainGame",
enableEntityDirectUpdate: false
};
class ConfiguredScene extends Scene {
constructor() {
super(config);
}
}
```
### Running a Scene
```typescript
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
## Next Steps
- [Lifecycle](./lifecycle/) - Scene lifecycle methods
- [Entity Management](./entity-management/) - Create, find, destroy entities
- [System Management](./system-management/) - System control
- [Events](./events/) - Scene event communication
- [Debugging](./debugging/) - Performance and debugging
- [Best Practices](./best-practices/) - Scene design patterns

View File

@@ -0,0 +1,103 @@
---
title: "Scene Lifecycle"
description: "Scene lifecycle methods and execution order"
---
Scene provides complete lifecycle management for proper resource initialization and cleanup.
## Lifecycle Methods
```typescript
class ExampleScene extends Scene {
protected initialize(): void {
// 1. Scene initialization: setup systems and initial entities
console.log("Scene initializing");
}
public onStart(): void {
// 2. Scene starts running: game logic begins execution
console.log("Scene starting");
}
public update(deltaTime: number): void {
// 3. Per-frame update (called by scene manager)
}
public unload(): void {
// 4. Scene unloading: cleanup resources
console.log("Scene unloading");
}
}
```
## Execution Order
| Phase | Method | Description |
|-------|--------|-------------|
| Initialize | `initialize()` | Setup systems and initial entities |
| Start | `begin()` / `onStart()` | Scene starts running |
| Update | `update()` | Per-frame update (auto-called) |
| End | `end()` / `unload()` | Cleanup resources |
## Lifecycle Example
```typescript
class GameScene extends Scene {
private resourcesLoaded = false;
protected initialize(): void {
this.name = "GameScene";
// 1. Add systems (by dependency order)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 2. Create initial entities
this.createPlayer();
this.createEnemies();
// 3. Setup event listeners
this.setupEvents();
}
public onStart(): void {
this.resourcesLoaded = true;
console.log("Scene resources loaded, game starting");
}
public unload(): void {
// Cleanup event listeners
this.eventSystem.clear();
// Cleanup other resources
this.resourcesLoaded = false;
console.log("Scene resources cleaned up");
}
private createPlayer(): void {
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
}
private createEnemies(): void {
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
}
}
private setupEvents(): void {
this.eventSystem.on('player_died', () => {
console.log('Player died');
});
}
}
```
## Notes
1. **initialize() called once** - For initial state setup
2. **onStart() on scene activation** - May be called multiple times
3. **unload() must cleanup resources** - Avoid memory leaks
4. **update() managed by framework** - No manual calls needed

View File

@@ -0,0 +1,115 @@
---
title: "System Management"
description: "System addition, removal and control in scenes"
---
Scene manages system registration, execution order, and lifecycle.
## Adding Systems
```typescript
class SystemScene extends Scene {
protected initialize(): void {
// Add system
const movementSystem = new MovementSystem();
this.addSystem(movementSystem);
// Set system update order (lower runs first)
movementSystem.updateOrder = 1;
// Add more systems
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
}
}
```
## Getting Systems
```typescript
// Get system of specific type
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
console.log("Found physics system");
}
```
## Removing Systems
```typescript
public removeUnnecessarySystems(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
this.removeSystem(physicsSystem);
}
}
```
## Controlling Systems
### Enable/Disable Systems
```typescript
public pausePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = false; // Disable system
}
}
public resumePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = true; // Enable system
}
}
```
### Get All Systems
```typescript
public getAllSystems(): EntitySystem[] {
return this.systems; // Get all registered systems
}
```
## System Organization Best Practice
Group systems by function:
```typescript
class OrganizedScene extends Scene {
protected initialize(): void {
// Add systems by function and dependencies
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());
}
}
```
## API Reference
| Method | Returns | Description |
|--------|---------|-------------|
| `addSystem(system)` | `void` | Add system to scene |
| `removeSystem(system)` | `void` | Remove system from scene |
| `getEntityProcessor(Type)` | `T \| undefined` | Get system by type |
| `systems` | `EntitySystem[]` | Get all systems |

View File

@@ -0,0 +1,165 @@
---
title: "Decorators & Inheritance"
description: "Advanced serialization decorator usage and component inheritance"
---
## Advanced Decorators
### Field Serialization Options
```typescript
@ECSComponent('Advanced')
@Serializable({ version: 1 })
class AdvancedComponent extends Component {
// Use alias
@Serialize({ alias: 'playerName' })
public name: string = '';
// Custom serializer
@Serialize({
serializer: (value: Date) => value.toISOString(),
deserializer: (value: string) => new Date(value)
})
public createdAt: Date = new Date();
// Ignore serialization
@IgnoreSerialization()
public cachedData: any = null;
}
```
### Collection Type Serialization
```typescript
@ECSComponent('Collections')
@Serializable({ version: 1 })
class CollectionsComponent extends Component {
// Map serialization
@SerializeAsMap()
public inventory: Map<string, number> = new Map();
// Set serialization
@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');
}
}
```
## Component Inheritance and Serialization
The framework fully supports component class inheritance. Subclasses automatically inherit parent class serialization fields while adding their own.
### Basic Inheritance
```typescript
// Base component
@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;
}
// Subclass component - automatically inherits parent's serialization fields
@ECSComponent('BoxCollider2D')
@Serializable({ version: 1, typeId: 'BoxCollider2D' })
class BoxCollider2DComponent extends Collider2DBase {
@Serialize()
public width: number = 1.0;
@Serialize()
public height: number = 1.0;
}
// Another subclass component
@ECSComponent('CircleCollider2D')
@Serializable({ version: 1, typeId: 'CircleCollider2D' })
class CircleCollider2DComponent extends Collider2DBase {
@Serialize()
public radius: number = 0.5;
}
```
### Inheritance Rules
1. **Field Inheritance**: Subclasses automatically inherit all `@Serialize()` marked fields from parent
2. **Independent Metadata**: Each subclass maintains independent serialization metadata; modifying subclass doesn't affect parent or other subclasses
3. **typeId Distinction**: Use `typeId` option to specify unique identifier for each class, ensuring correct component type recognition during deserialization
### Importance of Using typeId
When using component inheritance, it's **strongly recommended** to set a unique `typeId` for each class:
```typescript
// ✅ Recommended: Explicitly specify typeId
@Serializable({ version: 1, typeId: 'BoxCollider2D' })
class BoxCollider2DComponent extends Collider2DBase { }
@Serializable({ version: 1, typeId: 'CircleCollider2D' })
class CircleCollider2DComponent extends Collider2DBase { }
// ⚠️ Not recommended: Relying on class name as typeId
// Class names may change after code minification, causing deserialization failure
@Serializable({ version: 1 })
class BoxCollider2DComponent extends Collider2DBase { }
```
### Subclass Overriding Parent Fields
Subclasses can redeclare parent fields to modify their serialization options:
```typescript
@ECSComponent('SpecialCollider')
@Serializable({ version: 1, typeId: 'SpecialCollider' })
class SpecialColliderComponent extends Collider2DBase {
// Override parent field with different alias
@Serialize({ alias: 'fric' })
public override friction: number = 0.8;
@Serialize()
public specialProperty: string = '';
}
```
### Ignoring Inherited Fields
Use `@IgnoreSerialization()` to ignore fields inherited from parent in subclass:
```typescript
@ECSComponent('TriggerOnly')
@Serializable({ version: 1, typeId: 'TriggerOnly' })
class TriggerOnlyCollider extends Collider2DBase {
// Ignore parent's friction and restitution fields
// Because Trigger doesn't need physics material properties
@IgnoreSerialization()
public override friction: number = 0;
@IgnoreSerialization()
public override restitution: number = 0;
}
```
## Decorator Reference
| Decorator | Description |
|-----------|-------------|
| `@Serializable({ version, typeId })` | Mark component as serializable |
| `@Serialize()` | Mark field as serializable |
| `@Serialize({ alias })` | Serialize field with alias |
| `@Serialize({ serializer, deserializer })` | Custom serialization logic |
| `@SerializeAsMap()` | Serialize Map type |
| `@SerializeAsSet()` | Serialize Set type |
| `@IgnoreSerialization()` | Ignore field serialization |

View File

@@ -0,0 +1,228 @@
---
title: "Incremental Serialization"
description: "Only serialize changes in the scene"
---
Incremental serialization only saves the changed parts of a scene, suitable for network synchronization, undo/redo, time rewinding, and other scenarios requiring frequent state saving.
## Basic Usage
### 1. Create Base Snapshot
```typescript
// Create base snapshot before starting to record changes
scene.createIncrementalSnapshot();
```
### 2. Modify Scene
```typescript
// Add entity
const enemy = scene.createEntity('Enemy');
enemy.addComponent(new PositionComponent(100, 200));
enemy.addComponent(new HealthComponent(50));
// Modify component
const player = scene.findEntity('Player');
const pos = player.getComponent(PositionComponent);
pos.x = 300;
pos.y = 400;
// Remove component
player.removeComponentByType(BuffComponent);
// Delete entity
const oldEntity = scene.findEntity('ToDelete');
oldEntity.destroy();
// Modify scene data
scene.sceneData.set('score', 1000);
```
### 3. Get Incremental Changes
```typescript
// Get all changes relative to base snapshot
const incremental = scene.serializeIncremental();
// View change statistics
const stats = IncrementalSerializer.getIncrementalStats(incremental);
console.log('Total changes:', stats.totalChanges);
console.log('Added entities:', stats.addedEntities);
console.log('Removed entities:', stats.removedEntities);
console.log('Added components:', stats.addedComponents);
console.log('Updated components:', stats.updatedComponents);
```
### 4. Serialize Incremental Data
```typescript
// JSON format (default)
const jsonData = IncrementalSerializer.serializeIncremental(incremental, {
format: 'json'
});
// Binary format (smaller size, higher performance)
const binaryData = IncrementalSerializer.serializeIncremental(incremental, {
format: 'binary'
});
// Pretty print JSON output (for debugging)
const prettyJson = IncrementalSerializer.serializeIncremental(incremental, {
format: 'json',
pretty: true
});
// Send or save
socket.send(binaryData); // Use binary for network transmission
localStorage.setItem('changes', jsonData); // JSON for local storage
```
### 5. Apply Incremental Changes
```typescript
// Apply changes to another scene
const otherScene = new Scene();
// Directly apply incremental object
otherScene.applyIncremental(incremental);
// Apply from JSON string
const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
otherScene.applyIncremental(jsonData);
// Apply from binary Uint8Array
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
otherScene.applyIncremental(binaryData);
```
## Incremental Snapshot Management
### Update Snapshot Base
After applying incremental changes, you can update the snapshot base:
```typescript
// Create initial snapshot
scene.createIncrementalSnapshot();
// First modification
entity.addComponent(new VelocityComponent(5, 0));
const incremental1 = scene.serializeIncremental();
// Update base (set current state as new base)
scene.updateIncrementalSnapshot();
// Second modification (incremental will be based on updated base)
entity.getComponent(VelocityComponent).dx = 10;
const incremental2 = scene.serializeIncremental();
```
### Clear Snapshot
```typescript
// Release memory used by snapshot
scene.clearIncrementalSnapshot();
// Check if snapshot exists
if (scene.hasIncrementalSnapshot()) {
console.log('Incremental snapshot exists');
}
```
## Incremental Serialization Options
```typescript
interface IncrementalSerializationOptions {
// Whether to perform deep comparison of component data
// Default true, set to false to improve performance but may miss internal field changes
deepComponentComparison?: boolean;
// Whether to track scene data changes
// Default true
trackSceneData?: boolean;
// Whether to compress snapshot (using JSON serialization)
// Default false
compressSnapshot?: boolean;
// Serialization format
// 'json': JSON format (readable, convenient for debugging)
// 'binary': MessagePack binary format (smaller, higher performance)
// Default 'json'
format?: 'json' | 'binary';
// Whether to pretty print JSON output (only effective when format='json')
// Default false
pretty?: boolean;
}
// Using options
scene.createIncrementalSnapshot({
deepComponentComparison: true,
trackSceneData: true
});
```
## Incremental Data Structure
Incremental snapshots contain the following change types:
```typescript
interface IncrementalSnapshot {
version: number; // Snapshot version number
timestamp: number; // Timestamp
sceneName: string; // Scene name
baseVersion: number; // Base version number
entityChanges: EntityChange[]; // Entity changes
componentChanges: ComponentChange[]; // Component changes
sceneDataChanges: SceneDataChange[]; // Scene data changes
}
// Change operation types
enum ChangeOperation {
EntityAdded = 'entity_added',
EntityRemoved = 'entity_removed',
EntityUpdated = 'entity_updated',
ComponentAdded = 'component_added',
ComponentRemoved = 'component_removed',
ComponentUpdated = 'component_updated',
SceneDataUpdated = 'scene_data_updated'
}
```
## Performance Optimization
### For High-Frequency Sync
```typescript
// Disable deep comparison to improve performance
scene.createIncrementalSnapshot({
deepComponentComparison: false // Only detect component addition/removal
});
```
### Batch Operations
```typescript
// Batch modify then serialize
scene.entities.buffer.forEach(entity => {
// Batch modifications
});
// Serialize all changes at once
const incremental = scene.serializeIncremental();
```
## API Reference
| Method | Description |
|--------|-------------|
| `scene.createIncrementalSnapshot(options?)` | Create base snapshot |
| `scene.serializeIncremental()` | Get incremental changes |
| `scene.applyIncremental(data)` | Apply incremental changes |
| `scene.updateIncrementalSnapshot()` | Update snapshot base |
| `scene.clearIncrementalSnapshot()` | Clear snapshot |
| `scene.hasIncrementalSnapshot()` | Check if snapshot exists |
| `IncrementalSerializer.getIncrementalStats(snapshot)` | Get change statistics |
| `IncrementalSerializer.serializeIncremental(snapshot, options)` | Serialize incremental data |

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