Compare commits

...

777 Commits

Author SHA1 Message Date
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
yhh
1da43ee822 fix: 修复 release-editor workflow 构建问题 2025-11-23 15:26:17 +08:00
yhh
f4c7563763 chore: 移除 network 相关包并修复 CI 问题 2025-11-23 15:13:51 +08:00
YHH
a3f7cc38b1 Feature/render pipeline (#232)
* refactor(engine): 重构2D渲染管线坐标系统

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: 移除 network 相关包

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* refactor(editor): UI组件拆分

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

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

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

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

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

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

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

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

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

* chore: 删除未使用的导入

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

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

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

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

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

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

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

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

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

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

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

* chore: 移除 Cocos Creator 扩展目录

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* 拆分日志类型与实现

* 新增 setLoggerFactory 方法

* tweak

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

* 更新日志说明文档

* 增加测试

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

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

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

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

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

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

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-10-16 14:45:32 +00:00
YHH
241acc9050 更新文档 2025-10-16 22:45:08 +08:00
YHH
8fa921930c Merge pull request #126 from esengine/issue-125-编辑器热更新
热更新配置
2025-10-16 22:31:26 +08:00
YHH
011e43811a 热更新配置 2025-10-16 22:26:50 +08:00
YHH
9f16debd75 新增rust编译缓存和ts构建缓存 2025-10-16 20:54:33 +08:00
YHH
92c56c439b 移除过时的工作流 2025-10-16 20:50:32 +08:00
YHH
7de6a5af0f v2.2.5 2025-10-16 20:37:16 +08:00
YHH
173a063781 Merge pull request #124 from esengine/issue-122-格式化的标准配置
所有源代码文件使用 LF (Unix 风格)
2025-10-16 20:29:21 +08:00
YHH
e04ac7c909 所有源代码文件使用 LF (Unix 风格)
prettier格式化
eslint代码质量检查
2025-10-16 20:24:45 +08:00
YHH
a6e49e1d47 修复publish-release中release_id错误问题 2025-10-16 20:07:56 +08:00
YHH
f0046c7dc2 新增icons作为编辑器图标 2025-10-16 19:52:17 +08:00
YHH
2a17c47c25 修复ts警告 2025-10-16 18:20:31 +08:00
YHH
8d741bf1b9 Merge pull request #123 from esengine/issue-119-插件化编辑器
Issue 119 插件化编辑器
2025-10-16 17:50:35 +08:00
YHH
c676006632 构建配置 2025-10-16 17:44:57 +08:00
YHH
5bcfd597b9 Merge remote-tracking branch 'remotes/origin/master' into issue-119-插件化编辑器 2025-10-16 17:37:24 +08:00
YHH
3cda3c2238 显示客户端链接的ip:port 2025-10-16 17:33:43 +08:00
YHH
43bdd7e43b 远程读取日志 2025-10-16 17:10:22 +08:00
YHH
1ec7892338 设置界面 2025-10-16 13:07:19 +08:00
YHH
6bcfd48a2f 清理调试日志 2025-10-16 12:21:18 +08:00
YHH
345ef70972 支持color类型 2025-10-16 12:00:17 +08:00
YHH
c876edca0c 调试实体和组件属性 2025-10-16 11:55:41 +08:00
YHH
fcf3def284 收集远端数据再profiler dockpanel上 2025-10-15 23:24:13 +08:00
YHH
6f1a2896dd 性能分析器及端口管理器 2025-10-15 22:30:49 +08:00
YHH
62381f4160 记录上一次操作的面板的size大小持久化 2025-10-15 20:26:40 +08:00
YHH
171805debf 禁用默认右键 2025-10-15 20:23:55 +08:00
YHH
619abcbfbc 插件管理器 2025-10-15 20:10:52 +08:00
YHH
03909924c2 app loading 2025-10-15 18:29:48 +08:00
YHH
f4ea077114 菜单栏 2025-10-15 18:24:13 +08:00
YHH
956ccf9195 2D/3D视口 2025-10-15 18:08:55 +08:00
YHH
e880925e3f 视口视图 2025-10-15 17:28:45 +08:00
YHH
0a860920ad 日志面板 2025-10-15 17:21:59 +08:00
YHH
fb7a1b1282 可动态识别属性 2025-10-15 17:15:05 +08:00
YHH
59970ef7c3 Merge pull request #121 from foxling/fix/parent-child-deserialization-bug
fix: 修复场景反序列化时子实体丢失的问题
2025-10-15 15:55:26 +08:00
LING YE
a7750c2894 fix: 修复场景反序列化时子实体丢失的问题
在场景反序列化过程中,子实体虽然保持了父子引用关系,
但未被添加到 Scene 的实体集合和查询系统中,导致查询时子实体"丢失"。
2025-10-15 15:48:54 +08:00
YHH
b69b81f63a 支持树形资源管理器 2025-10-15 10:08:15 +08:00
YHH
00fc6dfd67 Dock系统,支持Tab和拖放 2025-10-15 09:58:45 +08:00
YHH
82451e9fd3 可拖动调整大小的面板 2025-10-15 09:43:48 +08:00
YHH
d0fcc0e447 项目启动流程和资产浏览器功能 2025-10-15 09:34:44 +08:00
YHH
285279629e 优化加载脚本逻辑 2025-10-15 09:19:30 +08:00
YHH
cbfe09b5e9 组件发现和动态加载系统 2025-10-15 00:40:27 +08:00
YHH
b757c1d06c 项目打开功能 2025-10-15 00:23:19 +08:00
YHH
4550a6146a 组件属性编辑器 2025-10-15 00:15:12 +08:00
YHH
3224bb9696 国际化系统 2025-10-14 23:56:54 +08:00
YHH
3a5e73266e 组件注册与添加 2025-10-14 23:42:06 +08:00
YHH
1cf5641c4c 实体存储和管理服务 2025-10-14 23:31:09 +08:00
YHH
85dad41e60 Tauri 窗口,显示编辑器界面 2025-10-14 23:15:07 +08:00
YHH
bd839cf431 Tauri 编辑器应用框架 2025-10-14 22:53:26 +08:00
YHH
b20b2ae4ce 编辑器核心框架 2025-10-14 22:33:55 +08:00
YHH
cac6aedf78 调试插件 DebugPlugin 2025-10-14 22:12:35 +08:00
YHH
a572c80967 v2.2.4 2025-10-14 21:22:07 +08:00
YHH
a09e8261db Merge pull request #118 from esengine/issue-117-统一组件注册系统,移除ComponentTypeManager重复实现
移除了功能重复的ComponentTypeManager
2025-10-14 18:24:17 +08:00
YHH
62e8ebe926 移除了功能重复的ComponentTypeManager 2025-10-14 18:19:08 +08:00
YHH
96e0a9126f Merge pull request #116 from esengine/issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query
使用ComponentRegistry来确保与Entity.componentMask使用相同的bitIndex
2025-10-14 17:56:26 +08:00
YHH
4afb195814 Merge remote-tracking branch 'origin/master' into issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query 2025-10-14 17:51:59 +08:00
YHH
7da5366bca 修复querysystem循环依赖的问题 2025-10-14 17:50:32 +08:00
YHH
d979c38615 使用ComponentRegistry来确保与Entity.componentMask使用相同的bitIndex 2025-10-14 17:34:15 +08:00
YHH
7def06126b Merge pull request #115 from esengine/issue-114-reactive-query_文档
新增实体查询系统文档
2025-10-14 13:39:23 +08:00
YHH
9f600f88b0 新增实体查询系统文档 2025-10-14 13:33:12 +08:00
YHH
e3c4d5f0c0 Merge pull request #105 from esengine/issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query
响应式查询
2025-10-14 12:29:54 +08:00
YHH
b97f3a8431 移除了 EntitySystem.update() 中的冗余 invalidate() 调用 2025-10-14 12:08:08 +08:00
YHH
3b917a06af 修复响应式查询缓存失效和测试隔离问题 2025-10-14 11:48:04 +08:00
YHH
360106fb92 优化ReactiveQuery: 添加公共API、修复内存泄漏、提升通知性能 2025-10-13 23:55:43 +08:00
YHH
507ed5005f Merge remote-tracking branch 'origin/master' into issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query 2025-10-13 23:36:12 +08:00
YHH
8bea5d5e68 Merge remote-tracking branch 'origin/master' into issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query 2025-10-13 23:29:56 +08:00
YHH
99076adb72 Merge pull request #113 from esengine/issue-112-优化_DebugManager_使用_DI_避免依赖问题
使用DI避免依赖问题
2025-10-13 23:21:10 +08:00
YHH
d1a6230b23 新增调试配置服务用于DI 2025-10-13 23:17:10 +08:00
YHH
cfe3916934 使用DI避免依赖问题 2025-10-13 22:56:49 +08:00
YHH
d798995876 Merge pull request #111 from esengine/issue-110-_Inject_装饰器在_Cocos_Creator_环境中不执行
扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器
2025-10-12 23:44:22 +08:00
YHH
43e6b7bf88 扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器 2025-10-12 23:39:32 +08:00
YHH
9253686de1 v2.2.3 2025-10-12 21:41:09 +08:00
YHH
7e7eae2d1a Merge pull request #109 from esengine/issue-108-world和scene进行了多次更新
Revert "Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰"
2025-10-12 21:39:35 +08:00
YHH
1924d979d6 Revert "Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰"
This reverts commit f2b9c5cc5a, reversing
changes made to 5f507532ed.
2025-10-12 21:38:53 +08:00
YHH
ed84394301 更新序列化文档 2025-10-12 19:02:17 +08:00
YHH
bb99cf5389 v2.2.2 2025-10-12 18:56:39 +08:00
YHH
2d0700f441 Merge pull request #107 from esengine/issue-106-类型定义声明返回_Buffer导致浏览器兼容问题
修复buffer再浏览器环境不兼容的问题
2025-10-12 18:53:53 +08:00
YHH
e3ead8a695 修复buffer再浏览器环境不兼容的问题 2025-10-12 18:49:20 +08:00
YHH
942043f0b0 报告响应式查询的数量而不是传统缓存 2025-10-11 18:44:55 +08:00
YHH
23d81bca35 响应式查询 2025-10-11 18:31:20 +08:00
YHH
701f538e57 Merge pull request #104 from esengine/issue-103-WorldManager统一管理所有World实例_含默认World
Issue 103 world manager统一管理所有world实例 含默认world
2025-10-11 15:26:27 +08:00
YHH
bb3017ffc2 Merge remote-tracking branch 'origin/master' into issue-103-WorldManager统一管理所有World实例_含默认World
# Conflicts:
#	packages/core/src/Core.ts
#	packages/core/src/ECS/SceneManager.ts
2025-10-11 15:21:43 +08:00
YHH
f2b9c5cc5a Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰
统一World与Scene架构,SceneManager内部使用DefaultWorld
2025-10-11 15:16:52 +08:00
YHH
532a52acfc 统一的World管理路径 2025-10-11 15:14:37 +08:00
YHH
c19b5ae9a7 统一World与Scene架构,SceneManager内部使用DefaultWorld 2025-10-11 14:44:21 +08:00
YHH
5f507532ed 统一World与Scene架构,SceneManager内部使用DefaultWorld 2025-10-11 14:27:09 +08:00
YHH
6e48f22540 更新v2.2.1文档 2025-10-11 11:33:07 +08:00
YHH
66aa9f4f20 更新文档 2025-10-11 10:48:24 +08:00
YHH
62f895efe0 v2.2.1 2025-10-11 10:46:46 +08:00
YHH
4a060e1ce3 WorldManager 现在由 ServiceContainer 统一管理 2025-10-11 10:40:10 +08:00
YHH
a0177c9163 从 tslib 导入辅助函数 2025-10-11 10:36:59 +08:00
YHH
f45af34614 更新v2.2.0文档 2025-10-11 10:16:52 +08:00
YHH
14a8d755f0 PoolManager 现在由 ServiceContainer 统一管理 2025-10-11 09:38:16 +08:00
YHH
b67ab80c75 Merge pull request #93 from esengine/issue-80-插件系统
插件系统
2025-10-11 09:32:12 +08:00
YHH
ae71af856b 插件系统 2025-10-11 09:26:36 +08:00
YHH
279c1d9bc9 Merge pull request #92 from esengine/issue-82-组件引用完整性
组件引用完整性,升级到es2021使用weakref
2025-10-11 00:30:36 +08:00
YHH
9068a109b0 降级es2020,实现 WeakRef Polyfill 2025-10-11 00:25:10 +08:00
YHH
7850fc610c 组件引用完整性,升级到es2021使用weakref 2025-10-10 23:38:48 +08:00
YHH
536871d09b Merge pull request #88 from esengine/issue-76-依赖注入
依赖注入引入DI容器
2025-10-10 22:15:24 +08:00
YHH
1af2cf5f99 Scene 构造函数注入 PerformanceMonitor 2025-10-10 22:08:10 +08:00
YHH
b13132b259 依赖注入引入DI容器 2025-10-10 21:52:43 +08:00
YHH
a1a6970ea4 Merge pull request #87 from esengine/issue-73-Core类职责过重需要进行拆分
新增ServiceContainer服务容器, 所有服务统一实现 IService 接口
2025-10-10 18:17:55 +08:00
YHH
41bbe23404 新增ServiceContainer服务容器, 所有服务统一实现 IService 接口 2025-10-10 18:13:28 +08:00
YHH
1d2a3e283e Merge pull request #86 from esengine/issue-75-组件存储策略不统一
组件存储策略不统一
2025-10-10 16:38:06 +08:00
YHH
62d7521384 移除 Entity._localComponents/强制Entity必须属于Scene/简化组件操作逻辑 2025-10-10 16:31:43 +08:00
YHH
bf14b59a28 空查询应该返回所有实体 2025-10-10 11:49:06 +08:00
YHH
0a0f64510f 更新测试用例 2025-10-10 10:58:52 +08:00
YHH
9445c735c3 对象池内存管理优化 2025-10-10 10:16:44 +08:00
YHH
7339e7ecec 新增scenemanager,重构core类减少多世界造成的性能压力 2025-10-09 23:33:11 +08:00
YHH
79f7c89e23 修复再不同环境下buffer兼容性问题 2025-10-09 17:44:15 +08:00
YHH
e724e5a1ba 更新demo 2025-10-09 17:43:46 +08:00
YHH
fdaa94a61d v2.1.52 2025-10-09 17:26:07 +08:00
YHH
6af0074c36 导航增加序列化章节 2025-10-09 17:18:14 +08:00
YHH
97a69fed09 增量序列化支持二进制 2025-10-09 17:14:18 +08:00
YHH
959879440d 更新序列化文档 2025-10-09 14:18:43 +08:00
YHH
fd1bbb0e00 新增增量序列化 2025-10-09 12:30:04 +08:00
YHH
072e68cf43 修复序列化ci测试 2025-10-08 20:58:07 +08:00
YHH
610232e6b0 core库demo更新 2025-10-08 20:52:31 +08:00
YHH
69c46f32eb 支持二进制序列化 2025-10-08 20:42:55 +08:00
YHH
06b3f92007 场景自定义序列化支持 2025-10-08 18:34:15 +08:00
YHH
c631290049 对query/entity进行安全类型扩展 2025-10-08 13:13:23 +08:00
YHH
f41c1a3ca3 冗余测试合并 2025-10-08 12:04:13 +08:00
YHH
bd6ba84087 Merge pull request #72 from 0MirageTank0/master
优化掩码数据结构,新增BitMaskHashMap类型,支持无限数量原型
2025-10-05 09:13:30 +08:00
MirageTank
1512409eb3 优化位掩码工具的输出格式
- 十六进制不再输出无意义的前导0符号
- 修正部分测试单元检测逻辑
2025-10-04 13:16:51 +08:00
MirageTank
bcb5feeb1c 实现高性能 BitMaskHashMap 并优化ArchetypeSystem
- 引入 BitMaskHashMap 类,使用双层 MurmurHash3 哈希算法提升查找性能
- 替换 ArchetypeSystem 中原有的嵌套 Map 结构为 BitMaskHashMap,支持任意数量的原型
- 验证在十万级连续键值下无哈希冲突,确保生产环境可用性
2025-10-04 10:26:19 +08:00
MirageTank
da8b7cf601 重构位掩码数据结构,修复部分方法未考虑扩展位的问题
- 所有操作均考虑扩展位、扩展长度不一致的使用场景,无感扩容掩码位
- 使用定长数组存储高低位,遍历友好,为高效哈希计算提供结构支持
- 补充相应单元测试,覆盖所有方法及分支
2025-10-03 16:55:07 +08:00
YHH
316527c459 更新实体文档(components为只读属性) 2025-10-01 00:15:19 +08:00
YHH
da70818b22 v2.1.51 2025-10-01 00:11:14 +08:00
YHH
5ea3b72b2b 使用BitMask64Data.segments扩展ComponentRegistry 2025-09-30 23:58:52 +08:00
YHH
632864b361 单一数据源 + 懒加载缓存 2025-09-30 23:37:47 +08:00
YHH
952247def0 重构 Component基类违反ECS纯粹性问题 2025-09-30 22:26:44 +08:00
YHH
51debede52 移除废弃的文件 2025-09-30 20:44:08 +08:00
YHH
ce7b731bcf Merge pull request #71 from esengine/fix/issue-70-component-mask-string
Fix/issue 70 component mask string
2025-09-30 18:19:11 +08:00
YHH
86e2dc8fdb 完全删除 ComponentIndexManager 2025-09-30 18:12:49 +08:00
YHH
78047134c2 完全移除 byComponentType 2025-09-30 18:03:03 +08:00
YHH
125a1686ab 完全删除 byComponentType 2025-09-30 17:54:22 +08:00
YHH
d542ac48b8 重构byComponentType为lazy cache模式 2025-09-30 17:07:13 +08:00
YHH
1ac0227c90 移除bymask 2025-09-30 17:01:49 +08:00
YHH
a5e70bcd99 修复QuerySystem组件掩码索引使用toString()返回[object Object]的问题 #70 2025-09-30 16:38:32 +08:00
YHH
38763de7f6 BitMask64Segment 独立类型 2025-09-30 15:56:28 +08:00
YHH
db73b077c5 支持分层 BitMask 自动扩容,避免用户超过组件后报错问题 2025-09-30 15:38:50 +08:00
YHH
0969d09da1 优化ArchetypeSystem的AND指令 2025-09-30 15:08:33 +08:00
YHH
a07108a431 修复soa测试用例 2025-09-30 13:43:12 +08:00
YHH
6693b56ab8 Merge pull request #69 from 0MirageTank0/master
优化ArchetypeSystem性能
2025-09-30 13:31:11 +08:00
MirageTank
a7349bd360 补充TypeDecorators文件对ComponentType参数类型的修改 2025-09-30 12:53:23 +08:00
MirageTank
e92c0040b5 重构原型系统以提升性能
- 将原型ID类型从字符串改为BitMask64Data,避免拼接字符串的性能损耗,降低内存占用.
- 原型ID生成不再依赖组件名称.
2025-09-30 11:57:31 +08:00
MirageTank
f448fa48c4 重构组件类型管理器相关函数参数以使用ComponentType,而不是Component. 2025-09-30 11:42:01 +08:00
YHH
aa33cad4fa 扩展typedarray存储系统,允许自动类型推断@AutoTyped 2025-09-30 11:00:05 +08:00
YHH
d0cb7d5359 v2.1.50 2025-09-30 09:59:46 +08:00
YHH
90153b98fe 更新 2025-09-30 09:51:02 +08:00
YHH
8c4e8d523e 更新微信小游戏worker文档 2025-09-30 09:37:30 +08:00
YHH
90ad4b3ec4 抽象worker接口,避免污染项目 2025-09-29 18:15:47 +08:00
YHH
62bc6b547e 支持wx/browser的worker(由于wx限制默认不开启worker) 2025-09-29 13:21:08 +08:00
YHH
be11060674 archetypesystem只负责原型管理,querysytem负责查询和缓存 2025-09-29 11:01:39 +08:00
YHH
d62bf9f7f9 entitysytem的logger返回类型更改,需要重写实现getLoggerName 2025-09-29 09:53:13 +08:00
YHH
61fcd52c65 移除eventhandler装饰器 2025-09-29 09:35:02 +08:00
YHH
2947ddeb64 不限制ci的超时时间 2025-09-29 09:04:53 +08:00
YHH
d9b752c180 修复workerentitysysten和entitysytem的logger重复问题 2025-09-29 00:01:59 +08:00
YHH
b82891caee 装饰器事件自动清理 2025-09-28 23:58:43 +08:00
YHH
05f04ef37e 允许用户自定义核心数量 2025-09-28 23:35:25 +08:00
YHH
66dc9780b9 使用coi-serviceworker用于无法控制标头的情况 2025-09-28 21:11:17 +08:00
YHH
d48b22c656 更新demo界面 2025-09-28 21:00:28 +08:00
YHH
727b1864eb 更新demo 2025-09-28 20:49:00 +08:00
YHH
de3bfd7551 新增禁用sab功能 2025-09-28 20:41:23 +08:00
YHH
dedb91379f worker系统不支持sab回退到普通worker 2025-09-28 20:22:06 +08:00
YHH
1dfcd008aa 新增worker-system文档及源码示例 2025-09-28 20:03:29 +08:00
YHH
cf2dc91af6 更新worker-demo 2025-09-28 18:29:32 +08:00
YHH
a66f80a766 新增WorkerEntitySystem系统 2025-09-28 17:36:36 +08:00
YHH
f4e49c316e getWorldManager允许传入可选配置用于覆盖默认配置 2025-09-28 15:52:46 +08:00
YHH
d1cd72bbb2 修复QuerySystem中addEntities没有更新componentIndexManager/archetypeSystem索引 2025-09-28 15:32:54 +08:00
YHH
6178851def 修复QuerySystem/ArchetypeSystem未响应实体增删Component的问题 2025-09-28 15:23:59 +08:00
YHH
945f772c30 只在有package改动的时候触发ci流程 2025-09-28 12:32:35 +08:00
YHH
b546c9c712 vitepress修改为githubpage路径 2025-09-28 12:29:43 +08:00
YHH
413ce93b31 更新文档 2025-09-28 12:26:51 +08:00
YHH
cffe32911d v2.1.49 2025-09-28 10:36:01 +08:00
YHH
4f651eb42e 优化querysystem系统(减少数组拷贝)
移除dirtytracksystem
2025-09-28 09:40:36 +08:00
YHH
6da1585b6b entitysystem实用帧缓存和长期缓存策略 2025-09-26 18:30:49 +08:00
YHH
b988e81a1b 数学库新增Vector3 2025-09-26 17:45:52 +08:00
YHH
1a1c1087d2 标记组件不符合规范的废弃属性 2025-09-26 17:45:26 +08:00
YHH
1a1549230f 更新references库 2025-09-26 13:06:39 +08:00
YHH
64ea53eba1 系统添加缓存实体机制避免频繁开销 2025-09-26 10:50:31 +08:00
YHH
5e052a7e7d 默认不增强事件,避免事件性能开销 2025-09-26 10:28:00 +08:00
YHH
cf9ea495d0 移除过时类并标记组件和实体的update为过时方法 2025-09-26 10:09:23 +08:00
YHH
9603c6423b 系统避免在同一帧内进行多次query操作 2025-09-26 09:45:22 +08:00
YHH
457eef585e Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-09-26 09:38:57 +08:00
YHH
1ade449c4d 控制实体update默认不更新 2025-09-26 09:38:51 +08:00
YHH
aa9d73a810 Merge pull request #68 from snakenjq/master
场景移除时, 清理系统
2025-09-24 18:25:55 +08:00
SNDA\niujiaqun.nathan
cc266a7ba9 修改错误方法调用 2025-09-24 18:18:48 +08:00
SNDA\niujiaqun.nathan
d8ea324018 场景移除时, 清理系统 2025-09-24 18:14:22 +08:00
YHH
60566e8d78 v2.1.48 2025-09-24 16:05:22 +08:00
YHH
306d2994dc Merge pull request #67 from foxling/fix/global-log-level
修复 setGlobalLogLevel 不影响新创建 logger 的问题
2025-09-24 15:54:28 +08:00
YHH
e6a8791fc3 系统中提供更安全的事件监听器方法(避免内存泄露) 2025-09-24 11:03:37 +08:00
YHH
6cbbc06998 规范jsdoc注释 2025-09-24 10:45:33 +08:00
YHH
0b4244fd8e 修复循环依赖问题 2025-09-24 10:20:36 +08:00
LING YE
367ddfbf8a 修复 setGlobalLogLevel 方法,使其可以在后续新建的 Logger 实例中生效 2025-09-22 15:28:29 +08:00
YHH
168e028098 更新支持es5环境 2025-09-04 16:26:29 +08:00
YHH
042ded37d2 新增自定义log颜色ci测试 2025-09-04 16:25:40 +08:00
YHH
4137eb2bce 网络层完善消息队列 2025-09-04 16:25:18 +08:00
YHH
20a3f03e12 支持用户自定义log颜色 2025-09-04 16:24:38 +08:00
YHH
7792710694 避免splice开销问题,改为SwapPop + typeId → denseIndex 2025-09-03 10:56:33 +08:00
YHH
dbddbbdfb8 更新ci测试用例 2025-09-03 10:39:29 +08:00
YHH
4869f5741e bits多态改为POD+原地操作 2025-09-03 10:29:43 +08:00
YHH
bda547dd2e 把掩码从BigInt-like多态都改成Mask64 2025-09-03 00:39:00 +08:00
YHH
ef80b03a44 更改为固定64位掩码,没必要为任意精度付出性能代价 2025-09-03 00:12:59 +08:00
YHH
6e511ae949 改成 SparseSet+SwapRemove 的致密存储 2025-09-02 22:29:11 +08:00
YHH
94541d0abb 实体中的线性数组换为按组件类型ID直址的稀疏数组 2025-09-02 21:59:59 +08:00
YHH
586a0e5d14 降低ci测试覆盖率 2025-09-02 21:16:36 +08:00
YHH
814842dbaf 降低测试覆盖率导致的ci错误 2025-09-02 21:11:15 +08:00
YHH
70a993573f 修复ci报错 2025-09-02 18:04:55 +08:00
YHH
21659cbb13 交/并/差运算全部改用ID集合单次扫描 2025-09-02 17:17:07 +08:00
YHH
a44251cc55 新增world概念(多world管理多scene概念)现在支持多个world多个scene同时更新 2025-08-20 17:48:31 +08:00
YHH
69616bbddc 实现ServerRpc和ClientRpc装饰器 2025-08-20 10:32:56 +08:00
YHH
0a1d7ac083 实现SyncVar装饰器和组件同步 2025-08-20 10:16:54 +08:00
YHH
364bc4cdab update demo 2025-08-20 09:17:43 +08:00
YHH
2504eb24e1 经测试转换为wasm没有明显的效率提升,因为js和rust的交互反而有所下降
等待以后有密集计算再考虑使用,先移除了
2025-08-20 09:15:06 +08:00
YHH
bdbef0bd0d core核心rust实现 2025-08-18 20:46:47 +08:00
YHH
e4e38ee4e6 更新api文档 2025-08-15 13:47:11 +08:00
YHH
021e892e33 ci流程需要core库先编译后再测试network-shared 2025-08-15 13:32:36 +08:00
YHH
c27d5022fd 优化内部组件索引机制(更改为SparseSet索引)减少用户切换索引成本
修复内部系统初始化逻辑 - 不应该再onInitialize中初始内部entities,移动到initialize中
ci跳过cocos项目避免ci失败
soa开放更多安全类型接口
2025-08-15 12:58:55 +08:00
YHH
6730a5d625 传输层实现(客户端/服务端,链接管理和心跳机制,重连机制)
消息序列化(json序列化,消息压缩,消息ID和时间戳)
网络服务器核心(networkserver/基础room/链接状态同步)
网络客户端核心(networkclient/消息队列)
2025-08-14 23:59:00 +08:00
YHH
32092f992d 更新文档 2025-08-14 18:45:24 +08:00
YHH
a5f0c8f6b5 更新文档 2025-08-14 18:44:04 +08:00
YHH
85cd93e51a 文档更新 2025-08-14 18:38:09 +08:00
YHH
0b7e623748 新增组件/系统装饰器避免混淆
更改Set兼容web/小游戏
2025-08-14 18:35:03 +08:00
YHH
62f250b43c 重构network库(mvp版本)搭建基础设施和核心接口
定义ITransport/ISerializer/INetworkMessage接口
NetworkIdentity组件
基础事件定义
2025-08-13 13:07:40 +08:00
YHH
25136349ff 修复动态require导致的跨平台错误
新增emitter的dispose方法用于清理事件
启用composite增量编译
2025-08-13 12:18:40 +08:00
YHH
baeb047e27 支持可以任意参数 2025-08-12 11:47:18 +08:00
YHH
56dd18b983 废弃core.scene更改为setscene方法 2025-08-12 11:08:27 +08:00
YHH
86cb70a94f Merge branch 'master' of https://github.com/esengine/ecs-framework
# Conflicts:
#	packages/network-shared/package.json
2025-08-12 09:43:29 +08:00
YHH
9f76d37a82 更新network库及core库优化 2025-08-12 09:39:07 +08:00
YHH
a026ed9428 降级ws版本 2025-08-11 12:33:42 +08:00
YHH
c178e2fbcc 移除ci性能测试,github下不应该测试这些文件 2025-08-11 11:34:38 +08:00
YHH
b88bb1dc87 修复ci中的大小写问题
update gitsubmodule
2025-08-11 11:11:56 +08:00
YHH
3069e28224 add submodule electric-world 2025-08-11 11:06:28 +08:00
YHH
d69b3af99b 更新math版本 2025-08-11 10:39:14 +08:00
YHH
7398b7c6d0 修复math库tsconfig引用问题 2025-08-11 10:38:11 +08:00
YHH
5d57904d22 更新使用rollup打包
解决大小写冲突问题
2025-08-11 10:34:13 +08:00
YHH
7daf352a25 更新库rollup配置 2025-08-11 10:25:28 +08:00
YHH
6a49f6a534 npm包发布配置更改 2025-08-11 10:03:19 +08:00
YHH
5bce08683a update 2.1.30 2025-08-11 09:31:44 +08:00
YHH
edc60fc3d8 添加发布core命令 2025-08-11 09:15:14 +08:00
YHH
1361fd8a90 导出soa装饰器 2025-08-11 09:01:01 +08:00
YHH
d539bb3dd9 更新文档 2025-08-11 08:18:18 +08:00
YHH
3b9ae4f384 新增math库 2025-08-10 16:00:02 +08:00
YHH
2783448de5 重新整理网络架构,tsrpc/syncvar并行 2025-08-10 12:35:39 +08:00
YHH
6e21ff08d5 集成tsrpc代替protobuf 2025-08-09 18:56:19 +08:00
YHH
e56278e4a6 移除路径映射,只让Jest处理路径解析 2025-08-08 15:57:56 +08:00
YHH
fc9bf816dd 修复build失败问题 2025-08-08 15:47:48 +08:00
YHH
854fd7df3a 修复ci测试 2025-08-08 15:41:37 +08:00
YHH
87dd564a12 项目统一改用Logger控制管理
拆分pool类和FluentAPI
2025-08-08 11:16:00 +08:00
YHH
2d389308ea 新增syncvar高级特性,使用protobuf定义 2025-08-07 20:23:49 +08:00
YHH
ea8523be35 使用Lerna 和 monorepo管理项目结构 2025-08-07 13:29:12 +08:00
YHH
4479f0fab0 避免throw导致的中止运行,增加fallback回退json的序列化 2025-08-07 10:16:36 +08:00
YHH
7a000318a6 整合组件类型至统一的componentregistry中 2025-08-07 09:43:34 +08:00
YHH
9a08ae74b6 移除json序列化只保留protobuf 2025-08-06 17:42:12 +08:00
YHH
f3d2950df3 修复ci失败 2025-08-06 17:12:39 +08:00
YHH
8cfba4a166 新增protobuf依赖(为网络和序列化做准备)
更新readme
2025-08-06 17:04:02 +08:00
YHH
51e6bba2a7 2.1.29 2025-08-06 09:56:55 +08:00
YHH
ccbfa78070 修复了QuerySystem在销毁实体时的内存泄漏问题
实现了完整的onAdded/onRemoved回调系统
修复了override修饰符和类型兼容性问题
2025-08-06 09:39:08 +08:00
YHH
69655f1936 测试用例更新 2025-07-31 15:37:40 +08:00
YHH
6ea366cfed 优化matcher内部实现改为querysystem
完善type类型
更新文档
2025-07-31 11:56:04 +08:00
YHH
b7d17fb16d soa添加float32 2025-07-30 18:10:14 +08:00
YHH
f3dc8c6344 BigIntFactory 缓存优化
- 为 zero() 和 one() 方法添加缓存,避免重复创建对象
ComponentIndexManager 优化
  - 添加了空实体检查,跳过不必要的索引操作
  - 实现了 Set 对象池,重用 Set 实例以减少内存分配
  - 优化了自动优化检查频率,从每次操作变为每100次操作检查一次
EntityManager 优化
  - 对空实体跳过不必要的组件索引、原型系统和脏标记操作
  - 批量创建时同样应用空实体优化
2025-07-30 17:10:58 +08:00
YHH
69ec545854 优化createEntity的性能/新增批量创建实体api 2025-07-30 16:05:16 +08:00
YHH
65386ff731 优化EntitySystem初始化逻辑/防止多次初始化
增加matcher和entitysystem的测试
2025-07-30 15:42:19 +08:00
YHH
01fa33e122 新增soastorage存储器 2025-07-30 14:14:04 +08:00
YHH
0411aa9aef 2.1.28 2025-07-30 11:14:26 +08:00
YHH
4a5c890121 对bigint进行兼容处理(不支持的环境回退到兼容模式) 2025-07-30 11:11:46 +08:00
YHH
4c11fdc176 2.1.27 2025-07-29 16:10:09 +08:00
YHH
d99e7a45ea 新增更多覆盖测试 2025-07-29 15:56:40 +08:00
YHH
52528ff1b7 修复querysystem的rebuildindex方法
修复位掩码不一致问题
修复未注册组件的处理
2025-07-29 10:58:31 +08:00
YHH
4a9317f3f4 querysystem进行ci测试隔离 2025-07-29 10:39:48 +08:00
YHH
9450dd5869 修复ci报错 2025-07-29 10:25:12 +08:00
YHH
d5471e4828 修复ci报错问题 2025-07-29 09:29:29 +08:00
YHH
2f71785add 覆盖querysystem/eventbus/componentstorage测试 2025-07-29 09:08:31 +08:00
YHH
608f5030b2 对ecs目录进行更多的ci测试 2025-07-28 17:38:18 +08:00
YHH
dd8f3714ed 修复不应该jest console导致的ci失败 2025-07-28 17:20:28 +08:00
YHH
abec2b3648 querysystem内部框架维护(不需要用户手动调用事件派发)
新增test覆盖测试
2025-07-28 17:14:10 +08:00
YHH
ea06a9f07d Merge pull request #58 from esengine/develop_simple_entity
移除过度复杂的组件缓存系统 #57
2025-07-18 21:21:01 +08:00
YHH
9f54759cc5 添加-获取-移除循环CI条件放宽 #57 2025-07-18 18:29:17 +08:00
YHH
55dd5f9ed0 单元测试条件放宽 CI环境性能较低 #57 2025-07-18 18:26:47 +08:00
YHH
05455421fb 移除过度复杂的组件缓存系统 #57 2025-07-18 18:08:57 +08:00
YHH
af61067f08 修复ci导致的问题 2025-07-18 15:15:37 +08:00
YHH
19cda88248 移除子模块CI 2025-07-18 15:11:20 +08:00
YHH
0edb2738a1 优化IdentifierPool - 世代式ID池管理器 2025-07-18 14:59:00 +08:00
YHH
e1bc364525 新增实用ai编辑器 2025-07-13 22:53:30 +08:00
YHH
2925ee380d 新增mvvm示例 2025-07-08 20:23:19 +08:00
YHH
731edf5872 Merge branch 'master' of https://github.com/esengine/ecs-framework
# Conflicts:
#	.gitmodules
#	extensions/cocos/cocos-ecs/package-lock.json
#	extensions/cocos/cocos-ecs/package.json
2025-07-08 08:57:11 +08:00
YHH
7b85039b17 更新mvvm示例 2025-07-08 08:55:55 +08:00
YHH
2bc45fa574 新增子模块cocos-mvvm 2025-07-07 23:41:53 +08:00
YHH
d2b4455205 2.1.26 2025-07-07 11:49:57 +08:00
YHH
bce4a26197 构建cjs/mjs 2025-07-07 11:49:36 +08:00
YHH
1da5040d60 打包支持nodejs模块 2025-07-07 11:06:08 +08:00
YHH
afd33e053b 更新子模块及导入demo 2025-07-07 11:02:11 +08:00
YHH
171d03c006 新增snapshot快照功能 2025-07-07 09:45:36 +08:00
YHH
34d5237aaa Merge branch 'master' of https://github.com/esengine/ecs-framework
# Conflicts:
#	package-lock.json
#	package.json
2025-07-03 09:02:37 +08:00
YHH
037c3d6a05 2.1.25 2025-07-02 23:49:28 +08:00
YHH
5596ba634e 2.1.24 2025-07-02 23:49:02 +08:00
YHH
a5f69065f4 update 2025-07-02 23:48:51 +08:00
YHH
969ef249ea Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-07-02 23:47:41 +08:00
YHH
a37183851f 修复queryall缓存信息错误问题 2025-07-02 23:47:30 +08:00
YHH
4cf3e1a769 2.1.24 2025-07-02 11:25:00 +08:00
YHH
354e5a2761 update submodule 2025-07-02 11:23:50 +08:00
YHH
c9fd8cc2a7 优化组件递归调用导致的性能问题/新增实体所在的场景显示 2025-07-02 09:42:49 +08:00
YHH
bb19f752a1 优化性能结构/延迟加载
新增测试代码用于测试性能
2025-07-02 00:13:29 +08:00
YHH
6bd9c1055c 2.1.23 2025-07-01 11:48:41 +08:00
YHH
dff77097c6 git commit 2025-07-01 11:48:26 +08:00
YHH
b4dc1c5661 修复type大小写问题 2025-06-30 20:43:11 +08:00
YHH
992338d924 更新性能分析器及更改部分注释 2025-06-30 20:33:45 +08:00
YHH
f88a402b0c 新增程序化地形子模块 2025-06-27 16:51:19 +08:00
YHH
5938d36149 版本更新 2025-06-26 18:00:02 +08:00
YHH
78577db3f9 更新示例教程 2025-06-25 23:17:55 +08:00
YHH
0b4a6b77e2 采矿行为树示例 2025-06-25 17:50:40 +08:00
YHH
01084a8897 更新rts示例代码(矿工自动采矿) 2025-06-24 23:51:59 +08:00
YHH
0f18a1979e rts-demo 2025-06-24 19:34:37 +08:00
YHH
68a615bc7b 移除测试代码 2025-06-23 23:48:19 +08:00
YHH
add1068c1a Merge branch 'master' of https://github.com/esengine/ecs-framework 2025-06-23 16:08:40 +08:00
YHH
7a40df9965 修复过时文档问题 2025-06-23 16:08:31 +08:00
YHH
3e6a1aa59a 更新example 2025-06-22 21:21:46 +08:00
YHH
d3fe79cf39 example update 2025-06-20 18:21:50 +08:00
YHH
48fa547c8f 更改为submodule 2025-06-19 21:04:38 +08:00
YHH
80e2f7df71 remove cocos-extensions 2025-06-19 21:00:42 +08:00
YHH
0107f1f58a remove admin-dashboard 2025-06-19 20:49:09 +08:00
YHH
d29c9a96f4 新增热更新后台管理系统 2025-06-19 18:32:32 +08:00
YHH
37d75c3281 2.1.22 2025-06-19 15:43:48 +08:00
YHH
666ded7b89 初始化只在系统真正开始工作时执行 2025-06-19 15:43:22 +08:00
YHH
73a882f75e 2.1.21 2025-06-19 15:01:20 +08:00
YHH
310f5f2349 支持先加入实体后加入系统以让matcher进行实体匹配/优化行为树节点效果及逻辑 2025-06-19 15:00:14 +08:00
YHH
8c86d6b696 更新文档及优化行为树编辑器 2025-06-19 10:38:31 +08:00
YHH
82cd163adc 清理冗余代码及频繁日志输出 2025-06-18 23:37:47 +08:00
YHH
802ee25621 新增Blackboard 2025-06-18 23:31:53 +08:00
YHH
f48ebb65ba 修复插件生成代码报错问题 2025-06-18 20:22:17 +08:00
YHH
aaa2a8ed2c 新增装饰节点选中功能 2025-06-18 18:31:29 +08:00
YHH
5a06f5420b 条件装饰节点和条件装饰器结合 2025-06-18 18:21:55 +08:00
YHH
343f5a44f2 新增根节点 2025-06-18 15:53:48 +08:00
YHH
92125aee3a 删除连线操作 2025-06-18 15:43:17 +08:00
YHH
96f651b7ca 新增cocos右键打开和保存行为树功能 2025-06-18 15:20:07 +08:00
YHH
06ea01e928 拖拽逻辑更新 2025-06-17 23:59:30 +08:00
YHH
577f1e429a 新增行为树编辑器 2025-06-17 18:28:57 +08:00
YHH
7808f64fe5 更新生成代码工具 2025-06-17 10:46:47 +08:00
YHH
e6789e49e4 2.1.20 2025-06-17 07:35:46 +08:00
YHH
797619aece 更新池利用率 2025-06-17 07:35:23 +08:00
YHH
1b5363611d 新增cocos-debug-profiler 2025-06-17 00:32:16 +08:00
YHH
103f773286 添加Cocos Creator ECS编辑器插件:完整的框架管理和模板生成功能 2025-06-16 18:32:44 +08:00
YHH
d9ef0b587e 2.1.19 2025-06-16 09:36:59 +08:00
YHH
d5b98256f0 优化bits性能及移除组件上限 2025-06-16 09:36:36 +08:00
YHH
efcceaa898 2.1.18 2025-06-12 09:47:44 +08:00
YHH
e4aad11965 update readme 2025-06-12 09:47:25 +08:00
YHH
47207fad52 2.1.17 2025-06-12 09:44:05 +08:00
YHH
202bf82896 更新场景切换状态 2025-06-12 09:43:57 +08:00
YHH
0e3274a743 移除coreevents事件派发机制 2025-06-12 09:42:35 +08:00
YHH
b06174926d 更新快速入门指南 2025-06-10 13:22:28 +08:00
YHH
abb23a3c02 2.1.16 2025-06-10 13:12:22 +08:00
YHH
0c8f232282 文档及教程更新 2025-06-10 13:12:14 +08:00
YHH
ef023d27bf 2.1.15 2025-06-10 09:50:05 +08:00
YHH
7a591825eb 先移除wasm后续再通过其他方式接入 2025-06-10 09:49:55 +08:00
YHH
e71c49d596 移除额外的打包流程 2025-06-09 18:27:20 +08:00
YHH
e6ce8995ba 2.1.14 2025-06-09 18:01:34 +08:00
YHH
f6250b6d5b 规范为cococs wasm 2025-06-09 18:00:50 +08:00
YHH
757eff2937 2.1.13 2025-06-09 15:55:26 +08:00
YHH
996a7f3ddf 重构WASM架构:移除npm包中的WASM文件,改为独立发布 - 移除自动WASM加载逻辑 - 添加手动initializeWasm API - 创建专门的WASM发布包构建脚本 - 更新Cocos Creator使用指南 2025-06-09 15:54:34 +08:00
YHH
94c050bacb 2.1.12 2025-06-09 15:47:10 +08:00
YHH
3f4aa59a29 完善Cocos Creator支持:添加手动WASM初始化API和使用指南 2025-06-09 15:46:14 +08:00
YHH
bee7cf4278 2.1.11 2025-06-09 15:32:11 +08:00
YHH
b9db6f0b40 2.1.10 2025-06-09 15:16:31 +08:00
YHH
8967cba3c7 修复WASM路径解析问题,支持Cocos Creator环境 - 改进多路径WASM加载策略 - 添加Core.disableWasm()方法 - 简化WASM初始化逻辑以避免环境兼容性问题 2025-06-09 15:15:41 +08:00
YHH
d04ad2eea9 Update build-rollup.js 2025-06-09 14:53:15 +08:00
YHH
f2d3880a06 重构项目结构:整理gitignore,移动source目录到根目录,统一依赖管理 2025-06-09 14:51:26 +08:00
YHH
ec5f70ecfc fix: 修复npm包构建和发布问题 - 将esbuild target从es2017升级到es2020以支持BigInt - 修复浏览器环境下Node.js模块依赖问题 - 添加define配置处理require等Node.js全局变量 - 成功发布@esengine/ecs-framework@2.1.7到npm 2025-06-09 13:34:46 +08:00
YHH
40b3fe7165 docs: 更新项目文档 - 添加EntityManager、事件系统、性能优化使用示例和说明 2025-06-09 13:25:10 +08:00
YHH
4095f1e946 refactor: 规范化代码注释和更新核心模块 - 移除冗余JSDoc注释,统一代码风格 2025-06-09 13:24:54 +08:00
YHH
e219fc47ba feat: 添加ECS核心功能模块 - EntityManager实体管理器、EventBus事件总线、性能优化系统 2025-06-09 13:24:24 +08:00
YHH
6e2e7a4af5 refactor: 拆分WasmCore大文件为模块化结构 - 将23KB的WasmCore.ts拆分为types/loader/fallback/core/instance五个模块 2025-06-09 13:23:46 +08:00
YHH
2e7f764d6c refactor: 细化ECS/Core目录结构 - 按功能拆分为Events/Query/Performance/Storage四个子模块 2025-06-09 13:23:29 +08:00
YHH
ce64de5b3d 更改为es2020模块适应Cocos/laya引擎 2025-06-09 10:42:19 +08:00
YHH
35ca1dd7ea 将dist目录添加到.gitignore中 - dist目录是npm包构建的输出目录,不应该提交到版本控制 2025-06-09 10:39:37 +08:00
YHH
8d0ad6b871 新增wasm以优化实体update速度 2025-06-08 21:50:50 +08:00
YHH
0aa4791cf7 更新文档并预留wasm接口 2025-06-08 10:20:51 +08:00
YHH
082c2b46d0 移除数学库 2025-06-07 21:45:11 +08:00
YHH
50420f9052 增加性能测试 2025-06-07 21:28:31 +08:00
YHH
499cbf8a60 Delete node.js.yml 2025-06-07 20:34:55 +08:00
YHH
2e38284d6e 升级项目框架,移除大部分无用的物理和tween系统 2025-06-07 20:32:43 +08:00
YHH
00cc3a11c6 修复安全漏洞 2025-06-07 12:37:16 +08:00
yhh
9909a7f7b0 补全注释 2023-03-14 17:33:05 +08:00
yhh
3363fca160 更改示例及新增注释 2023-03-14 14:03:41 +08:00
yhh
78e0b09c7a 文档更新 2023-03-14 11:22:09 +08:00
yhh
caa3ffc8f5 1.新增es.TimeUtils
2.优化并给部分类添加注释
3.移除fasterDictionary
2023-03-13 23:32:24 +08:00
yhh
323fb6a5fe list注释优化 2023-03-13 22:17:11 +08:00
yhh
1adc5f1729 对ECS系统进行注释、移除JobSystem 2023-03-13 17:46:16 +08:00
yhh
78079252c9 修复rectangle中rayinterest方法返回错误信息 2023-03-13 15:23:02 +08:00
yhh
72fdabd099 mathHelper优化及注释。修复pointCircle错误实现 2023-03-13 14:51:38 +08:00
yhh
64bd6aa055 优化mathHelper代码 2023-03-13 12:31:59 +08:00
yhh
6329200b84 升级package包,更新readme 2023-03-13 12:10:17 +08:00
yhh
3f3fd16110 新增扇形collider 2023-02-24 19:16:20 +08:00
YHH
34f0c4ac2d 新增allTweensWithTargetEntity与clearAllCoroutines方法 2022-07-09 11:24:32 +08:00
YHH
dde04d514e 新增场景过渡效果 2022-03-12 23:49:14 +08:00
YHH
ffddadd798 新增Out类 2022-03-12 10:23:33 +08:00
YHH
5dca337b92 新增MathHelper.toInt 2022-03-12 09:13:06 +08:00
YHH
131df181e6 pool池优化性能 2022-03-07 22:52:51 +08:00
YHH
e207952786 性能优化 2022-03-07 16:00:48 +08:00
YHH
3f7ef284fc transform.position更改不需要修改dirty 2022-03-06 18:40:42 +08:00
YHH
1a41533d15 修复mover.motion修正错误问题 2022-03-06 13:56:34 +08:00
YHH
85f7bbbf1a Merge branch 'master' of https://github.com/esengine/ecs-framework
# Conflicts:
#	source/bin/framework.min.js
2022-03-05 10:24:33 +08:00
YHH
ccc603b59f 移除渲染模块,提供更纯净的ecs 2022-03-05 10:23:49 +08:00
YHH
8135f99616 防止小游戏函数丢失console.assert 2022-02-24 09:38:07 +08:00
YHH
713f4ae18b Merge branch 'master' of https://github.com/esengine/ecs-framework 2021-10-21 10:09:02 +08:00
YHH
f3f5d0bbd1 修复重复多余的定义 2021-10-04 16:53:00 +08:00
YHH
4a9e11c480 更新注释 2021-09-14 18:24:57 +08:00
yhh
cd94326aad 新增系统时序控制 2021-08-28 21:26:44 +08:00
yhh
a4b971bba0 新增StringUtils.isNullOrEmpty方法 2021-08-26 13:58:09 +08:00
yhh
5d1609111c 补充泛型参数 2021-08-20 19:10:20 +08:00
yhh
d54ccaf629 提供component的快捷操作 2021-08-20 19:05:40 +08:00
yhh
6adea240e2 修复componentlist中渲染组件add错误 2021-08-13 19:27:15 +08:00
yhh
3486d403d2 修复ITriggerListener失效问题 2021-08-09 19:08:27 +08:00
yhh
f533186c8d listpool根据type划分池 2021-08-06 11:10:45 +08:00
yhh
0beadf8e5a 修复Time不传入dt获取deltaTime错误问题 2021-08-05 11:47:47 +08:00
yhh
8bc06f0476 Merge branch 'master' of https://github.com/esengine/ecs-framework
# Conflicts:
#	extensions/ecs-tween/lib/framework.d.ts
2021-08-04 12:54:18 +08:00
yhh
8fd8f74b27 update physics 2021-08-04 11:28:55 +08:00
yhh
9fc9b60de5 Merge branch 'master' of https://github.com/esengine/ecs-framework into master 2021-08-03 00:13:25 +08:00
yhh
2a026726db 修复tween中start报错 2021-08-03 00:09:43 +08:00
YHH
f9a99f8b09 Update physics.md 2021-08-02 09:00:27 +08:00
yhh
95b8dc765d add collision readme 2021-07-27 14:03:57 +08:00
yhh
052d6e25e7 新增emitter与physics文档 2021-07-27 10:05:18 +08:00
yhh
fd82486bbc Merge branch 'master' of https://github.com/esengine/ecs-framework into master 2021-07-24 13:48:35 +08:00
yhh
ef07f7555f add time/coroutine 使用文档 2021-07-23 14:09:53 +08:00
yhh
67859b7be0 update readme 2021-07-22 10:48:35 +08:00
yhh
1e5ddadd00 Merge branch 'master' of https://github.com/esengine/ecs-framework into master 2021-07-16 07:09:54 +08:00
yhh
7bab76d765 fix 2021-07-16 07:03:14 +08:00
yhh
416f243bda 新增cloth与ragdoll 2021-07-04 23:53:38 +08:00
yhh
77ad112f67 新增tire。修改transform.Component为es.ComponentTransform 2021-07-04 21:03:30 +08:00
yhh
dc3d639824 内置tween管理器 2021-07-03 12:27:21 +08:00
yhh
13a001c258 修复verlet报错 2021-07-02 20:59:44 +08:00
yhh
85bdd97d48 新增verlet物理引擎(实验性) 2021-07-02 18:25:30 +08:00
yhh
3d9c8699e7 框架优化 2021-07-02 10:11:09 +08:00
yhh
ea482dab48 修复normalized 2021-06-29 18:40:34 +08:00
yhh
96a41eb2cc 移除collider的enable属性 2021-06-22 14:42:30 +08:00
yhh
e5dfb20aa2 新增角色控制器 2021-06-22 14:41:21 +08:00
yhh
fe308d35f2 完善Color 2021-06-22 13:50:31 +08:00
yhh
219b90fc5d 感谢NEZ库提供的思路 2021-06-11 16:20:01 +08:00
yhh
44e2ca07e5 修复无graphics报错问题 2021-05-28 17:00:33 +08:00
yhh
79d684caae overlapCircle添加检测圆重合 2021-05-28 16:16:58 +08:00
yhh
4db8734a34 添加检查圆重叠的盒子 2021-05-28 16:13:10 +08:00
yhh
8b7baf7f86 新增physics.debugDraw默认绘制 2021-05-28 15:45:45 +08:00
yhh
28145e876f 修复mover返回的collisionResult无数据问题 2021-05-28 15:05:15 +08:00
yhh
53625cf87b 修复mover碰撞失效问题 2021-05-28 14:19:53 +08:00
yhh
88f9779dd0 修复ArcadeRigidbody不触发OnEntityTransform事件 2021-05-28 08:41:38 +08:00
yhh
561a44b26b Merge branch 'master' of https://github.com/esengine/egret-framework 2021-05-27 18:37:17 +08:00
yhh
26068aaf6f 新增渲染接口 2021-05-27 18:32:38 +08:00
YHH
fb0f9dc608 Update README.md 2021-05-25 12:10:23 +08:00
yhh
6c44d38c10 对像素进行取整保证在不同分辨率下保持清晰 2021-05-25 11:16:49 +08:00
yhh
da3ab02a8d 优化vector2构造函数 2021-05-24 17:20:27 +08:00
yhh
9c7703eb2d 优化框架性能,Time支持传入引擎dt 2021-05-13 16:58:24 +08:00
yhh
84922794fb updateComponent更新 2021-05-12 14:53:29 +08:00
yhh
1dcce99d87 Merge branch 'master' of https://github.com/esengine/egret-framework 2021-05-12 13:09:02 +08:00
yhh
fd62aa469a 优化实体更新效率 2021-05-12 13:08:47 +08:00
yhh
63d307b95a 新增identifierPool用于实体id复用 2021-05-11 17:23:04 +08:00
YHH
9f12971eda Update README.md 2021-05-08 10:56:48 +08:00
YHH
47edd4f583 Update README.md 2021-05-08 10:54:22 +08:00
yhh
e94b7b23e4 update readme 2021-05-08 10:53:13 +08:00
yhh
5c77e6c56d publish 2021-05-07 16:25:01 +08:00
yhh
522cbe0e6e Merge branch 'master' of https://github.com/esengine/egret-framework
# Conflicts:
#	source/bin/framework.min.js
2021-05-07 16:24:25 +08:00
yhh
f7061f360d 移除Bitset更改为Bits 2021-05-07 16:23:15 +08:00
yhh
16cdfa0426 完善mathhelper类与vector2 2021-05-03 08:17:48 +08:00
yhh
74bd0c161f 完善MathHelper/RectangleExt/Vector2Ext 2021-05-02 20:03:56 +08:00
YHH
da595e1eb9 Update README.md 2021-04-30 20:55:34 +08:00
yhh
5277c0de7d Time.deltaTime更正
ArcadeRigidbody优化
2021-04-30 20:35:10 +08:00
yhh
b3c85e00f9 完善mathHelper类 2021-04-30 15:10:11 +08:00
yhh
0748652a8d 修复getcomponents获取为空问题 2021-04-29 11:00:15 +08:00
yhh
9fa0442b20 shapeCollisions拆分 2021-04-28 14:43:48 +08:00
2573 changed files with 530137 additions and 39954 deletions

62
.all-contributorsrc Normal file
View File

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

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"
]
}

36
.coderabbit.yaml Normal file
View File

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

29
.commitlintrc.json Normal file
View File

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

35
.editorconfig Normal file
View File

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

4
.fleet/settings.json Normal file
View File

@@ -0,0 +1,4 @@
{
"toolchains": [],
"backend.maxHeapSizeMb": 896
}

44
.gitattributes vendored Normal file
View File

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

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']

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

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

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

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

View File

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

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

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

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__"

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

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

113
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
name: CI
on:
push:
branches: [ master, main, develop ]
paths:
- 'packages/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'turbo.json'
- 'jest.config.*'
- '.github/workflows/ci.yml'
pull_request:
branches: [ master, main, develop ]
# Run on all PRs to satisfy branch protection, but skip build if no code changes
jobs:
# 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@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
# 构建 framework 包 (可独立发布的通用库,无外部依赖)
- name: Build framework packages
run: |
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
# 类型检查 (仅 framework 包)
- name: Type check (framework packages)
run: pnpm run type-check:framework
# Lint 检查 (仅 framework 包)
- name: Lint check (framework packages)
run: pnpm run lint:framework
# 测试 (仅 framework 包)
- name: Run tests with coverage
run: pnpm run test:ci:framework
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
continue-on-error: true
with:
file: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
# 构建 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: |
packages/framework/**/dist/
packages/framework/**/bin/
retention-days: 7

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

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

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

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

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

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

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

@@ -0,0 +1,70 @@
name: Deploy Documentation
on:
push:
branches: [master]
paths:
- 'docs/**'
- 'packages/**'
- 'typedoc.json'
- 'package.json'
- '.github/workflows/docs.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'pnpm'
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Install dependencies
run: pnpm install
- name: Build core package
run: pnpm run build:core
- name: Generate API documentation
run: pnpm run docs:api
- name: Build documentation
run: pnpm run docs:build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,34 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
node-version: [8.x]
defaults:
run:
shell: cmd
working-directory: ./source
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
run: npm install
- run: npm run build --if-present
- run: gulp build

View File

@@ -0,0 +1,71 @@
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/cli" 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 }}

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

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

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

@@ -0,0 +1,225 @@
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: '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: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release-package:
name: Release Package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
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@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Verify version (tag mode)
if: steps.parse.outputs.mode == 'tag'
run: |
PACKAGE="${{ steps.parse.outputs.package }}"
EXPECTED_VERSION="${{ steps.parse.outputs.version }}"
# Get version from package.json
ACTUAL_VERSION=$(node -p "require('./packages/$PACKAGE/package.json').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: |
if [ "${{ steps.parse.outputs.mode }}" = "tag" ]; then
echo "value=${{ steps.parse.outputs.version }}" >> $GITHUB_OUTPUT
else
echo "value=${{ steps.bump.outputs.version }}" >> $GITHUB_OUTPUT
fi
- 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/${{ steps.parse.outputs.package }}
pnpm run build:npm
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
cd packages/${{ steps.parse.outputs.package }}/dist
pnpm publish --access public --no-git-checks
- 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(${{ 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(${{ steps.parse.outputs.package }}): Release v${{ steps.version.outputs.value }}"
body: |
## Release v${{ steps.version.outputs.value }}
This PR updates `@esengine/${{ steps.parse.outputs.package }}` package 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 }}`
---
*This PR was automatically created by the release workflow*
labels: |
release
${{ steps.parse.outputs.package }}
automated pr

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

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

106
.gitignore vendored
View File

@@ -1,10 +1,96 @@
/source/node_modules
/demo/bin-debug
/demo/bin-release
/.idea
/.vscode
/demo_wxgame
/demo/.wing
/demo/.idea
/demo/.vscode
/source/docs
# 依赖目录
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 构建输出
bin/
dist/
*.tgz
# TypeScript
*.tsbuildinfo
# 临时文件
*.tmp
*.temp
.cache/
.build-cache/
# Turborepo
.turbo/
# IDE 配置
.idea/
.vscode/
*.swp
*.swo
*~
# 操作系统文件
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# 日志文件
logs/
*.log
# 环境配置
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# 代码签名证书(敏感文件)
certs/
*.pfx
*.p12
*.cer
*.pem
*.key
# 测试覆盖率
coverage/
*.lcov
# 包管理器锁文件忽略yarn保留pnpm
yarn.lock
package-lock.json
# 文档生成
docs/api/
docs/build/
docs/.vitepress/cache/
docs/.vitepress/dist/
# 备份文件
*.bak
*.backup
# 演示项目构建产物
/demo/bin-debug/
/demo/bin-release/
/demo/.wing/
/demo/.idea/
/demo/.vscode/
/demo_wxgame/
# Tauri 构建产物
**/src-tauri/target/
**/src-tauri/WixTools/
**/src-tauri/gen/
# Tauri 捆绑输出
**/src-tauri/target/release/bundle/
**/src-tauri/target/debug/bundle/
# Rust 构建产物
**/engine-shared/target/
external/

33
.gitmodules vendored
View File

@@ -1,15 +1,18 @@
[submodule "demo/egret_demo"]
path = demo/egret_demo
url = https://github.com/esengine/ecs-egret-demo
[submodule "demo/laya_demo"]
path = demo/laya_demo
url = https://github.com/esengine/ecs-laya-demo.git
[submodule "extensions/ecs-star"]
path = extensions/ecs-star
url = https://github.com/esengine/ecs-astar
[submodule "extensions/behaviourTree-ai"]
path = extensions/behaviourTree-ai
url = https://github.com/esengine/BehaviourTree-ai
[submodule "extensions/ecs-tween"]
path = extensions/ecs-tween
url = https://github.com/esengine/ecs-tween
[submodule "thirdparty/BehaviourTree-ai"]
path = thirdparty/BehaviourTree-ai
url = https://github.com/esengine/BehaviourTree-ai.git
[submodule "thirdparty/admin-backend"]
path = thirdparty/admin-backend
url = https://github.com/esengine/admin-backend.git
[submodule "thirdparty/mvvm-ui-framework"]
path = thirdparty/mvvm-ui-framework
url = https://github.com/esengine/mvvm-ui-framework.git
[submodule "thirdparty/cocos-nexus"]
path = thirdparty/cocos-nexus
url = https://github.com/esengine/cocos-nexus.git
[submodule "thirdparty/ecs-astar"]
path = thirdparty/ecs-astar
url = https://github.com/esengine/ecs-astar.git
[submodule "examples/lawn-mower-demo"]
path = examples/lawn-mower-demo
url = https://github.com/esengine/lawn-mower-demo.git

40
.npmignore Normal file
View File

@@ -0,0 +1,40 @@
# 源代码文件
src/
tsconfig*.json
*.ts
!bin/**/*.d.ts
# 开发文件
dev-bin/
scripts/
.vscode/
.git/
.gitignore
# 测试文件
**/*.test.*
**/*.spec.*
**/test/
**/tests/
# 构建缓存
node_modules/
*.log
*.tmp
*.temp
# 文档草稿
docs/draft/
*.draft.md
# 编辑器文件
.DS_Store
Thumbs.db
*.swp
*.swo
*~
# 环境文件
.env
.env.local
.env.*.local

2
.npmrc Normal file
View File

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

2
.nxignore Normal file
View File

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

49
.prettierignore Normal file
View File

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

14
.prettierrc Normal file
View File

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

25
.size-limit.json Normal file
View File

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

130
CONTRIBUTING.md Normal file
View File

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

214
LICENSE
View File

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

435
README.md
View File

@@ -1,231 +1,296 @@
这是一套ecs游戏框架里面包含ECS框架用于管理场景实体一些常用2D碰撞检测及游戏中常用的工具
<h1 align="center">
<img src="https://raw.githubusercontent.com/esengine/esengine/master/docs/public/logo.svg" alt="ESEngine" width="180">
<br>
ESEngine
</h1>
# 项目规划及讨论
- [点击参与及查看](https://github.com/esengine/ecs-framework/discussions/36)
<p align="center">
<strong>Modular Game Framework for TypeScript</strong>
</p>
## 交流群
点击链接加入群聊【ecs游戏框架交流】https://jq.qq.com/?_wv=1027&k=29w1Nud6
<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>
## 快速开始
- 初始化框架
```typescript
es.Core.create();
```
- 派发帧事件
```typescript
es.Core.emitter.emit(es.CoreEvents.frameUpdated);
```
完成以上对框架的初始化,您还需要一个默认场景并激活它,新建一个场景类并继承`es.Scene`
<p align="center">
<b>English</b> | <a href="./README_CN.md">中文</a>
</p>
```typescript
class MainScene extends es.Scene { }
<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>
---
## What is ESEngine?
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.
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
```
- 激活场景
```typescript
es.Core.scene = new MainScene();
## 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 |
| **Network** | Client/server networking with TSRPC | 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.
## ECS框架的基本使用
#### 创建实体
创建实体一般由场景控制。您需要在场景内进行创建或添加实体(`createEntity` / `addEntity`
> 注意:创建实体需要在场景`onStart`方法进行创建,而不是直接在`initialize`方法内创建。`initialize` 只是在构造函数完成后进行调用,此时该场景并未被激活。
### Manual Setup
```typescript
class MainScene extends es.Scene {
onStart() {
// 创建一个名为player的实体
const player = this.createEntity("player");
import {
Core, Scene, Entity, Component, EntitySystem,
Matcher, Time, ECSComponent, ECSSystem
} from '@esengine/ecs-framework';
// Define components (data only)
@ECSComponent('Position')
class Position extends Component {
x = 0;
y = 0;
}
@ECSComponent('Velocity')
class Velocity extends Component {
dx = 0;
dy = 0;
}
// Define system (logic)
@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;
}
}
}
// Initialize
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);
// 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);
}
}
```
#### 添加组件
每个实体都会有多个组件,如果我们需要实体能够拥有一个新的能力,则需要给它添加相对应的组件,组件必须继承 `es.Component` 来标志为组件。
> 注意:组件一般没有具体逻辑,一般只存放一些属性,逻辑应该由系统进行调用
### With Laya 3.x
```typescript
class MovementComponent extends es.Component {
// 定义该实体的移动速度
public moveSpeed: number = 100;
}
```
import { Core, Scene } from '@esengine/ecs-framework';
import { FSMSystem } from '@esengine/fsm';
- 将组件添加至Player实体上(`addComponent`)
const { regClass } = Laya;
```typescript
class MainScene extends es.Scene {
onStart() {
// 创建一个名为player的实体createEntity方法会将创建的实体返回给你
const player = this.createEntity("player");
// 为实体添加一个移动组件addComponent会将新建的移动组件返回给你
const moveComponent = player.addComponent(new MovementComponent())
@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`类帮助过滤它所感兴趣的实体并对其进行更新。我们一般定义系统会与对应的组件相匹配,刚我们创建了一个移动组件,则相对应的就会有一个移动系统来处理该类型的组件。
### Framework (Engine-Agnostic)
这里我们使用`es.EntityProcessingSystem`,因为我们要对带有移动组件的实体进行更新。
These packages have **zero rendering dependencies** and work with any engine:
> 系统也分多种类型,分为管理实体的系统(`es.EntityProcessingSystem`)/管理系统的系统(`es.ProcessingSystem`)
```typescript
class MoveSystem extends es.EntityProcessingSystem {
// 必须实现
processEntity(entity: Entity) {
// 这里会传入带有MoveComponent的实体
// 为什么会传入只带有MoveComponent的实体?
// 因为在构造函数中有一个Matcher匹配器在你初始化MoveSystem的时候需要你传入匹配的对象. 见下方如何定义匹配带MoveComponent的匹配器
// 该方法每帧都会执行,请确保您的操作尽可能的小或者在使用大数据时采用缓存的方法进行获取操作
const moveComponent = entity.getComponent(MovementComponent);
if (!moveComponent.enabled)
return;
// 根据moveComponent的数据执行移动的逻辑
}
}
```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/network # Networking
```
##### 定义Matcher匹配器匹配所有带MoveComponent的实体
### ESEngine Runtime (Optional)
```typescript
// 这里你可以传入多个组件类型
const matcher = Matcher.empty().all(MovementComponent);
If you want a complete engine solution with rendering:
| Category | Packages |
|----------|----------|
| **Core** | `engine-core`, `asset-system`, `material-system` |
| **Rendering** | `sprite`, `tilemap`, `particle`, `camera`, `mesh-3d` |
| **Physics** | `physics-rapier2d` |
| **Platform** | `platform-web`, `platform-wechat` |
### Editor (Optional)
A visual editor built with Tauri for scene management:
- Download from [Releases](https://github.com/esengine/esengine/releases)
- Supports behavior tree editing, tilemap painting, visual scripting
## 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
│ │ └── network/ # Networking
│ │
│ ├── engine/ # ESEngine runtime
│ ├── rendering/ # Rendering modules
│ ├── physics/ # Physics modules
│ ├── editor/ # Visual editor
│ └── rust/ # WASM renderer
├── docs/ # Documentation
└── examples/ # Examples
```
#### 激活系统
## Building from Source
使用`addEntityProcessor`方法进行添加系统
```bash
git clone https://github.com/esengine/esengine.git
cd esengine
```typescript
class MainScene extends es.Scene {
onStart() {
const matcher = Matcher.empty().all(MovementComponent);
// 将所有带MoveComponent的实体送到该系统处理
this.addEntityProcessor(new MoveSystem(matcher));
pnpm install
pnpm build
// 创建一个名为player的实体createEntity方法会将创建的实体返回给你
const player = this.createEntity("player");
// 为实体添加一个移动组件addComponent会将新建的移动组件返回给你
const moveComponent = player.addComponent(new MovementComponent());
}
}
# Type check framework packages
pnpm type-check:framework
# Run tests
pnpm test
```
至此所有的快速开始的学习到此为止,如果上述运行过程您已看懂,则框架的基本知识你已经掌握,框架内还含有部分内置组件和更高级的用法,您刚所学习的只是框架的冰山一角,如果您需要学习更多的话可以查看源码或查阅更多资料进行学习。
## Documentation
## 关于 Physics/Collision
框架中的物理不是一个真实的物理模拟。它只提供了游戏物理。您可以执行一些操作,如检测碰撞器、重叠检查、碰撞检查、扫描测试等。不是一个完整的刚体模拟。
- [ECS Framework Guide](./packages/framework/core/README.md)
- [Behavior Tree Guide](./packages/framework/behavior-tree/README.md)
- [API Reference](https://esengine.cn/api/README)
### Colliders 物理系统的根本
没有Collider在物理系统中什么也不会发生。 碰撞器存在于实体类中有几种类型BoxColliderCircleCollider和PolygonCollider。 您可以像这样添加Collider`entity.addComponent(new BoxCollider())`. 将碰撞器添加到Entity时它们会自动添加到SpatialHash中。
## Community
### SpatialHash你永远不会用到它但它仍然对你很重要
SpatialHash类是一个隐藏类该类为您的游戏全局管理 `collider`。静态物理类是SpatialHash的公共包装器。 SpatialHash没有设置大小限制用于快速进行碰撞/线投射/重叠检查。例如如果你有一个英雄在世界各地移动而不必检查每个对撞机可能是数百个是否发生碰撞则只需向SpatialHash询问英雄附近的所有collider即可。这大大缩小了您的碰撞检查范围。
- [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
SpatialHash有一个可配置的方面它可以极大地影响其性能单元大小。 SpatialHash将空间分成一个网格选择适当的网格大小可以将可能发生的碰撞查询减到最少。默认情况下网格大小为100像素。您可以通过在创建场景之前设置`Physics.SpatialHashCellSize`来更改此设置。选择比您的平均玩家/敌人人数稍大的尺寸通常效果最佳。
## Contributing
### Physics 类
物理类是物理的核心类。 您可以设置一些属性例如前面提到的spatialHashCellSizeraycastsHitTriggers和raycastsStartInColliders。
- linecast从开始到结束投射一条线并返回与layerMask相匹配的碰撞器的第一次命中
- overlapRectangle检查是否有任何collider在矩形区域内
- overlapCircle检查是否有任何collider在圆形区域内
- boxcastBroadphase返回边界与collider.bounds相交的所有碰撞器。 请注意这是一个broadphase检查因此它只检查边界不执行单个碰撞器到碰撞器的检查
Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
会注意到上面提到的layerMask。 layerMask允许您确定与哪些碰撞器碰撞。 每个collider都可以设置其物理层以便在查询物理系统时可以选择仅检索与传入的layerMask匹配的对撞机。 所有物理方法都接受默认为所有图层的图层蒙版参数。 使用此选项可以过滤冲突检查,并通过不执行不必要的冲突检查来使性能保持最佳状态。
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
### 使用物理系统
射线检测对于检查敌人的视线、探测实体的空间环境、快速移动的子弹等各种事情都非常有用。下面是一个从头到尾投射线条的示例,如果击中某个物体,它只会记录数据:
```ts
const hit = Physics.linecast( start, end );
if( hit.Collider != null )
console.log( `ray hit ${hit}, entity: {hit.collider.entity}`);
```
## License
我们使用了一些更先进的碰撞/重叠检查方法如Minkowski和、分离轴定理和古老的三角法。这些都被包装在Collider类上的简单易用的方法中。让我们看一些例子。
ESEngine is licensed under the [MIT License](LICENSE). Free for personal and commercial use.
第一个例子是处理碰撞的最简单方法。deltaMovement是您希望移动实体的量通常是velocity*Time.deltaTime. collidesWithAny方法将检查所有碰撞并调整deltaMovement以解决任何碰撞。
```ts
// 碰撞结果将包含一些非常有用的信息例如被撞的collider表面命中的法线和最小平移矢量MTV。 MTV可用于将碰撞实体直接移动到命中的碰撞器附近。
let collisionResult = null;
// 进行检查以查看entity.getComponent<Collider>(实体上的第一个碰撞器)是否与场景中的任何其他碰撞器发生碰撞。请注意,如果您有多个碰撞器,则可以获取并遍历它们,而不是仅检查第一个碰撞器。
if( entity.getComponent<Collider>().collidesWithAny( deltaMovement, collisionResult ) )
{
// 记录CollisionResult。 您可能需要使用它来添加一些粒子效果或与您的游戏相关的任何其他内容。
console.log( `collision result: ${collisionResult}` );
}
// 将实体移到新位置。 已经调整了deltaMovement为我们解决冲突。
entity.position = Vector2.add(entity.position, deltaMovement);
```
如果您需要对碰撞发生时的情况进行更多控制则也可以手动检查是否与其他collider发生碰撞。 请注意执行此操作时deltaMovement不会为您调整。 解决冲突时,您需要考虑最小平移矢量。
```ts
let collisionResult = null;
// 进行检查以查看entity.getComponent<Collider>是否与一些其他Collider发生碰撞
if( entity.getComponent<Collider>().collidesWith( someOtherCollider, deltaMovement, collisionResult ) )
{
// 将实体移动到与命中Collider相邻的位置然后记录CollisionResult
entity.position = Vector2.add(entity.position, Vector2.substract(deltaMovement, collisionResult.minimumTranslationVector));
console.log( `collision result: ${collisionResult}` );
}
```
我们可以使用前面提到的Physics.boxcastBroadphase方法或更具体地讲将自身排除在查询之外的版本使上述示例更进一步。 该方法将为我们提供场景中所有在我们附近的collider然后我们可以使用这些对撞机进行实际的碰撞检查。
```ts
// 在我们自身以外的位置获取可能与之重叠的任何东西
let neighborColliders = Physics.boxcastBroadphaseExcludingSelf( entity.getComponent<Collider>() );
// 遍历并检查每个对撞机是否重叠
for( let collider of neighborColliders )
{
if( entity.getComponent<Collider>().overlaps( collider ) )
console.log( `我们正在重叠一个collider : ${collider}` );
}
```
### 示例地址
#### [laya-demo](https://github.com/esengine/ecs-laya-demo)
#### [egret-demo](https://github.com/esengine/ecs-egret-demo)
## 扩展库
#### [基于ecs-framework开发的astar/BreadthFirst/Dijkstra/GOAP目标导向计划 路径寻找库](https://github.com/esengine/ecs-astar)
#### [基于ecs-framework开发的AIBehaviourTree、UtilityAI系统](https://github.com/esengine/BehaviourTree-ai))
## 关于用ecs框架typescript/javascript
ecs 是功能强大的实体组件系统。typescript与其他语言不同因此我对ecs的设计尽可能的支持typescript特性。虽然ecs拥有标准实体组件系统但在细节上有很大不同。创建标准ecs通常处于原始速度、缓存位置和其他性能原因。使用typescript我们没有struct因为没有必要匹配标准实体组件系统的设计方式因为我们对内存布局没有那种控制。
ecs更灵活可以更好的集中、组织、排序和过滤游戏中的对象。ecs让您拥有轻量级实体和组件这些组件可以由系统批量处理。
例如,您在制作一个射手,您可能会有几十到几百个子弹,这些作为轻量级实体由系统批量处理。
所以ecs在设计当中拥有四种重要类型世界Scene过滤器Matcher系统System和实体(Entity)
## [世界Scene](https://github.com/esengine/egret-framework/wiki/%E5%9C%BA%E6%99%AF-Scene)
Scene是ecs包含系统和实体最外面的容器。
## 实体Entity
实体只由系统处理。
## 组件(Component)
组件应该只包含数据而没有逻辑代码。对数据进行逻辑是系统的工作。
## 系统System
ecs中的系统会不断的更新实体。系统使用过滤器选择某些实体然后仅更新那些选择的实体。
---
<p align="center">
Made with care by the ESEngine community
</p>

297
README_CN.md Normal file
View File

@@ -0,0 +1,297 @@
<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* 和导航网格寻路 | 否 |
| **网络** | 客户端/服务端网络通信 (TSRPC) | 否 |
> 所有框架模块都可以独立使用,无需依赖特定渲染引擎。
## 快速开始
### 使用 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/network # 网络
```
### 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) 下载
- 支持行为树编辑、Tilemap 绘制、可视化脚本
## 项目结构
```
esengine/
├── packages/
│ ├── framework/ # 引擎无关模块(可发布到 NPM
│ │ ├── core/ # ECS 框架
│ │ ├── math/ # 数学工具
│ │ ├── behavior-tree/ # AI 行为树
│ │ ├── blueprint/ # 可视化脚本
│ │ ├── fsm/ # 有限状态机
│ │ ├── timer/ # 定时器系统
│ │ ├── spatial/ # 空间查询
│ │ ├── pathfinding/ # 寻路
│ │ ├── procgen/ # 程序化生成
│ │ └── network/ # 网络
│ │
│ ├── 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)
- [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,21 +1,113 @@
# Security Policy
# Security Policy / 安全政策
**English** | [中文](#安全政策-1)
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
We provide security updates for the following versions:
| Version | Supported |
| ------- | ------------------ |
| 5.1.x | :white_check_mark: |
| 5.0.x | :x: |
| 4.0.x | :white_check_mark: |
| < 4.0 | :x: |
| 2.x.x | :white_check_mark: |
| 1.x.x | :x: |
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
If you discover a security vulnerability, please report it through the following channels:
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.
### 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.x.x | :white_check_mark: |
| 1.x.x | :x: |
## 报告漏洞
如果您发现了安全漏洞,请通过以下方式报告:
### 报告渠道
- **GitHub 安全公告**: [报告漏洞](https://github.com/esengine/esengine/security/advisories/new)(推荐)
- **邮箱**: security@esengine.dev
### 报告指南
1. **不要**在公开的 issue 中报告安全漏洞
2. 提供详细的漏洞描述,包括:
- 受影响的版本
- 复现步骤
- 潜在的影响范围
- 如果可能,提供修复建议
### 响应时间
- **确认收到**: 72小时内
- **初步评估**: 1周内
- **修复发布**: 根据严重程度通常在2-4周内
### 处理流程
1. 我们会确认漏洞的存在和严重程度
2. 开发修复方案并进行测试
3. 发布安全更新
4. 在修复发布后,会在相关渠道公布漏洞详情
## 安全最佳实践
使用 ESEngine 时,请遵循以下安全建议:
- 始终使用最新的稳定版本
- 定期更新依赖项
- 在生产环境中禁用调试模式
- 验证所有外部输入数据
- 不要在客户端存储敏感信息
感谢您帮助保持 ESEngine 的安全性!
Thank you for helping keep ESEngine secure!

53
codecov.yml Normal file
View File

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

Submodule demo/egret_demo deleted from 7fd9460053

Submodule demo/laya_demo deleted from e4273ff0f5

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

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).

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

@@ -0,0 +1,313 @@
// @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: '网络同步',
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/sync', translations: { en: 'State Sync' } },
{ label: 'API 参考', slug: 'modules/network/api', translations: { en: 'API Reference' } },
],
},
],
},
{
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/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()],
},
});

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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 |

View File

@@ -0,0 +1,170 @@
---
title: "Serialization System"
description: "ECS serialization system overview and full serialization"
---
The serialization system provides a complete solution for persisting scene, entity, and component data. It supports both full serialization and incremental serialization modes, suitable for game saves, network synchronization, scene editors, time rewinding, and more.
## Basic Concepts
The serialization system has two layers:
- **Full Serialization**: Serializes the complete scene state, including all entities, components, and scene data
- **Incremental Serialization**: Only serializes changes relative to a base snapshot, greatly reducing data size
### Supported Data Formats
- **JSON Format**: Human-readable, convenient for debugging and editing
- **Binary Format**: Uses MessagePack, smaller size and better performance
> **v2.2.2 Important Change**
>
> Starting from v2.2.2, binary serialization returns `Uint8Array` instead of Node.js `Buffer` to ensure browser compatibility:
> - `serialize({ format: 'binary' })` returns `string | Uint8Array` (was `string | Buffer`)
> - `deserialize(data)` accepts `string | Uint8Array` (was `string | Buffer`)
> - `applyIncremental(data)` accepts `IncrementalSnapshot | string | Uint8Array` (was including `Buffer`)
>
> **Migration Impact**:
> - **Runtime Compatible**: Node.js `Buffer` inherits from `Uint8Array`, existing code works directly
> - **Type Checking**: If your TypeScript code explicitly uses `Buffer` type, change to `Uint8Array`
> - **Browser Support**: `Uint8Array` is a standard JavaScript type supported by all modern browsers
## Full Serialization
### Basic Usage
#### 1. Mark Serializable Components
Use `@Serializable` and `@Serialize` decorators to mark components and fields for serialization:
```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 };
// Fields without @Serialize() won't be serialized
private tempData: any = null;
}
```
#### 2. Serialize Scene
```typescript
// JSON format serialization
const jsonData = scene.serialize({
format: 'json',
pretty: true // Pretty print output
});
// Save to local storage
localStorage.setItem('gameSave', jsonData);
// Binary format serialization (smaller size)
const binaryData = scene.serialize({
format: 'binary'
});
// Save to file (Node.js environment)
// Note: binaryData is Uint8Array type, Node.js fs can write it directly
fs.writeFileSync('save.bin', binaryData);
```
#### 3. Deserialize Scene
```typescript
// Restore from JSON
const saveData = localStorage.getItem('gameSave');
if (saveData) {
scene.deserialize(saveData, {
strategy: 'replace' // Replace current scene content
});
}
// Restore from Binary
const binaryData = fs.readFileSync('save.bin');
scene.deserialize(binaryData, {
strategy: 'merge' // Merge into existing scene
});
```
### Serialization Options
#### SerializationOptions
```typescript
interface SceneSerializationOptions {
// Component types to serialize (optional)
components?: ComponentType[];
// Serialization format: 'json' or 'binary'
format?: 'json' | 'binary';
// Pretty print JSON output
pretty?: boolean;
// Include metadata
includeMetadata?: boolean;
}
```
Example:
```typescript
// Only serialize specific component types
const saveData = scene.serialize({
format: 'json',
components: [PlayerComponent, InventoryComponent],
pretty: true,
includeMetadata: true
});
```
#### DeserializationOptions
```typescript
interface SceneDeserializationOptions {
// Deserialization strategy
strategy?: 'merge' | 'replace';
// Component type registry (optional, uses global registry by default)
componentRegistry?: Map<string, ComponentType>;
}
```
### Scene Custom Data
Besides entities and components, you can also serialize scene-level configuration data:
```typescript
// Set scene data
scene.sceneData.set('weather', 'rainy');
scene.sceneData.set('difficulty', 'hard');
scene.sceneData.set('checkpoint', { x: 100, y: 200 });
// Scene data is automatically included when serializing
const saveData = scene.serialize({ format: 'json' });
// Scene data is restored after deserialization
scene.deserialize(saveData);
console.log(scene.sceneData.get('weather')); // 'rainy'
```
## More Topics
- [Decorators & Inheritance](/en/guide/serialization/decorators) - Advanced decorator usage and component inheritance
- [Incremental Serialization](/en/guide/serialization/incremental) - Only serialize changes
- [Version Migration](/en/guide/serialization/migration) - Handle data structure changes
- [Use Cases](/en/guide/serialization/use-cases) - Save system, network sync, undo/redo examples

View File

@@ -0,0 +1,151 @@
---
title: "Version Migration"
description: "Handle component and scene data structure changes"
---
When component structure changes, the version migration system can automatically upgrade old version save data.
## Register Migration Function
```typescript
import { VersionMigrationManager } from '@esengine/ecs-framework';
// Assume PlayerComponent v1 has hp field
// v2 changes to health and maxHealth fields
// Register migration from version 1 to version 2
VersionMigrationManager.registerComponentMigration(
'Player',
1, // From version
2, // To version
(data) => {
// Migration logic
const newData = {
...data,
health: data.hp,
maxHealth: data.hp,
};
delete newData.hp;
return newData;
}
);
```
## Using Migration Builder
```typescript
import { MigrationBuilder } from '@esengine/ecs-framework';
new MigrationBuilder()
.forComponent('Player')
.fromVersionToVersion(2, 3)
.migrate((data) => {
// Migrate from version 2 to version 3
data.experience = data.exp || 0;
delete data.exp;
return data;
});
```
## Scene-Level Migration
```typescript
// Register scene-level migration
VersionMigrationManager.registerSceneMigration(
1, // From version
2, // To version
(scene) => {
// Migrate scene structure
scene.metadata = {
...scene.metadata,
migratedFrom: 1
};
return scene;
}
);
```
## Check Migration Path
```typescript
// Check if migration is possible
const canMigrate = VersionMigrationManager.canMigrateComponent(
'Player',
1, // From version
3 // To version
);
if (canMigrate) {
// Safe to migrate
scene.deserialize(oldSaveData);
}
// Get migration path
const path = VersionMigrationManager.getComponentMigrationPath('Player');
console.log('Available migration versions:', path); // [1, 2, 3]
```
## Migration Chain Example
When migrating across multiple versions, the system automatically chains migrations:
```typescript
// Register v1 -> v2
VersionMigrationManager.registerComponentMigration('Player', 1, 2, (data) => {
data.health = data.hp;
delete data.hp;
return data;
});
// Register v2 -> v3
VersionMigrationManager.registerComponentMigration('Player', 2, 3, (data) => {
data.stats = { health: data.health, mana: 100 };
delete data.health;
return data;
});
// When loading v1 data, it will automatically execute v1 -> v2 -> v3
```
## Best Practices
### 1. Always Maintain Backward Compatibility
```typescript
// Document data structure changes for each version
// v1: { hp: number }
// v2: { health: number, maxHealth: number }
// v3: { stats: { health: number, mana: number } }
```
### 2. Test Migration Paths
```typescript
// Test all possible migration paths
const testData = { hp: 100 };
const migrated = VersionMigrationManager.migrateComponent('Player', testData, 1, 3);
expect(migrated.stats.health).toBe(100);
```
### 3. Keep Backup of Original Data
```typescript
// Backup before migration
const backup = JSON.parse(JSON.stringify(saveData));
try {
scene.deserialize(saveData);
} catch (e) {
// Restore on migration failure
console.error('Migration failed:', e);
}
```
## API Reference
| Method | Description |
|--------|-------------|
| `registerComponentMigration(type, from, to, fn)` | Register component migration function |
| `registerSceneMigration(from, to, fn)` | Register scene migration function |
| `canMigrateComponent(type, from, to)` | Check if migration is possible |
| `getComponentMigrationPath(type)` | Get component's migration version path |
| `migrateComponent(type, data, from, to)` | Execute component data migration |

View File

@@ -0,0 +1,306 @@
---
title: "Use Cases"
description: "Practical examples of the serialization system"
---
## Game Save System
```typescript
class SaveSystem {
private static SAVE_KEY = 'game_save';
// Save game
public static saveGame(scene: Scene): void {
const saveData = scene.serialize({
format: 'json',
pretty: false
});
localStorage.setItem(this.SAVE_KEY, saveData);
console.log('Game saved');
}
// Load game
public static loadGame(scene: Scene): boolean {
const saveData = localStorage.getItem(this.SAVE_KEY);
if (saveData) {
scene.deserialize(saveData, {
strategy: 'replace'
});
console.log('Game loaded');
return true;
}
return false;
}
// Check if save exists
public static hasSave(): boolean {
return localStorage.getItem(this.SAVE_KEY) !== null;
}
}
```
## Network Synchronization
```typescript
class NetworkSync {
private baseSnapshot?: any;
private syncInterval: number = 100; // Sync every 100ms
constructor(private scene: Scene, private socket: WebSocket) {
this.setupSync();
}
private setupSync(): void {
// Create base snapshot
this.scene.createIncrementalSnapshot();
// Periodically send incremental updates
setInterval(() => {
this.sendIncremental();
}, this.syncInterval);
// Receive remote incremental updates
this.socket.onmessage = (event) => {
this.receiveIncremental(event.data);
};
}
private sendIncremental(): void {
const incremental = this.scene.serializeIncremental();
const stats = IncrementalSerializer.getIncrementalStats(incremental);
// Only send when there are changes
if (stats.totalChanges > 0) {
// Use binary format to reduce network transmission
const binaryData = IncrementalSerializer.serializeIncremental(incremental, {
format: 'binary'
});
this.socket.send(binaryData);
// Update base
this.scene.updateIncrementalSnapshot();
}
}
private receiveIncremental(data: ArrayBuffer): void {
// Directly apply binary data (ArrayBuffer to Uint8Array)
const uint8Array = new Uint8Array(data);
this.scene.applyIncremental(uint8Array);
}
}
```
## Undo/Redo System
```typescript
class UndoRedoSystem {
private history: IncrementalSnapshot[] = [];
private currentIndex: number = -1;
private maxHistory: number = 50;
constructor(private scene: Scene) {
// Create initial snapshot
this.scene.createIncrementalSnapshot();
this.saveState('Initial');
}
// Save current state
public saveState(label: string): void {
const incremental = this.scene.serializeIncremental();
// Remove history after current position
this.history = this.history.slice(0, this.currentIndex + 1);
// Add new state
this.history.push(incremental);
this.currentIndex++;
// Limit history count
if (this.history.length > this.maxHistory) {
this.history.shift();
this.currentIndex--;
}
// Update snapshot base
this.scene.updateIncrementalSnapshot();
}
// Undo
public undo(): boolean {
if (this.currentIndex > 0) {
this.currentIndex--;
const incremental = this.history[this.currentIndex];
this.scene.applyIncremental(incremental);
return true;
}
return false;
}
// Redo
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;
}
}
```
## Level Editor
```typescript
class LevelEditor {
// Export level
public exportLevel(scene: Scene, filename: string): void {
const levelData = scene.serialize({
format: 'json',
pretty: true,
includeMetadata: true
});
// Browser environment
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);
}
// Import level
public importLevel(scene: Scene, fileContent: string): void {
scene.deserialize(fileContent, {
strategy: 'replace'
});
}
// Validate level data
public validateLevel(saveData: string): boolean {
const validation = SceneSerializer.validate(saveData);
if (!validation.valid) {
console.error('Level data invalid:', validation.errors);
return false;
}
return true;
}
// Get level info (without full deserialization)
public getLevelInfo(saveData: string): any {
const info = SceneSerializer.getInfo(saveData);
return info;
}
}
```
## Performance Tips
### 1. Choose the Right Format
- **Development**: Use JSON format for easy debugging and inspection
- **Production**: Use Binary format to reduce 30-50% data size
### 2. Serialize On-Demand
```typescript
// Only serialize components that need persistence
const saveData = scene.serialize({
format: 'binary',
components: [PlayerComponent, InventoryComponent, QuestComponent]
});
```
### 3. Optimize Incremental Serialization
```typescript
// For high-frequency sync, disable deep comparison for better performance
scene.createIncrementalSnapshot({
deepComponentComparison: false // Only detect component addition/removal
});
```
## Best Practices
### 1. Explicitly Mark Serialized Fields
```typescript
// Clearly mark fields that need serialization
@ECSComponent('Player')
@Serializable({ version: 1 })
class PlayerComponent extends Component {
@Serialize()
public name: string = '';
@Serialize()
public level: number = 1;
// Runtime data not serialized
private _cachedSprite: any = null;
}
```
### 2. Use Version Control
```typescript
// Specify version for components
@Serializable({ version: 2 })
class PlayerComponent extends Component {
// Version 2 fields
}
// Register migration function to ensure compatibility
VersionMigrationManager.registerComponentMigration('Player', 1, 2, migrateV1ToV2);
```
### 3. Avoid Circular References
```typescript
// Don't directly reference other entities in components
@ECSComponent('Follower')
@Serializable({ version: 1 })
class FollowerComponent extends Component {
// Store entity ID instead of entity reference
@Serialize()
public targetId: number = 0;
// Find target entity through scene
public getTarget(scene: Scene): Entity | null {
return scene.entities.findEntityById(this.targetId);
}
}
```
### 4. Compress Large Data
```typescript
// For large data structures, use custom serialization
@ECSComponent('LargeData')
@Serializable({ version: 1 })
class LargeDataComponent extends Component {
@Serialize({
serializer: (data: LargeObject) => compressData(data),
deserializer: (data: CompressedData) => decompressData(data)
})
public data: LargeObject;
}
```
## Performance Comparison
| Scenario | JSON Format | Binary Format | Savings |
|----------|-------------|---------------|---------|
| Small save (100 entities) | 50KB | 35KB | 30% |
| Medium save (1000 entities) | 500KB | 300KB | 40% |
| Large save (10000 entities) | 5MB | 2.5MB | 50% |

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