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 构建错误并优化构建性能
This commit is contained in:
107
.github/workflows/ci.yml
vendored
107
.github/workflows/ci.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
- 'package.json'
|
- 'package.json'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'tsconfig.json'
|
- 'tsconfig.json'
|
||||||
|
- 'turbo.json'
|
||||||
- 'jest.config.*'
|
- 'jest.config.*'
|
||||||
- '.github/workflows/ci.yml'
|
- '.github/workflows/ci.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -17,11 +18,12 @@ on:
|
|||||||
- 'package.json'
|
- 'package.json'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'tsconfig.json'
|
- 'tsconfig.json'
|
||||||
|
- 'turbo.json'
|
||||||
- 'jest.config.*'
|
- 'jest.config.*'
|
||||||
- '.github/workflows/ci.yml'
|
- '.github/workflows/ci.yml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
ci:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -39,22 +41,47 @@ jobs:
|
|||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install Rust stable
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: wasm32-unknown-unknown
|
||||||
|
|
||||||
|
# 缓存 Rust 编译结果
|
||||||
|
- name: Cache Rust dependencies
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: packages/engine
|
||||||
|
cache-on-failure: true
|
||||||
|
|
||||||
|
# 缓存 wasm-pack
|
||||||
|
- name: Cache wasm-pack
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cargo/bin/wasm-pack
|
||||||
|
key: wasm-pack-${{ runner.os }}
|
||||||
|
|
||||||
|
- name: Install wasm-pack
|
||||||
|
run: |
|
||||||
|
if ! command -v wasm-pack &> /dev/null; then
|
||||||
|
cargo install wasm-pack
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Install Rust stable
|
# 缓存 Turbo
|
||||||
uses: dtolnay/rust-toolchain@stable
|
- name: Cache Turbo
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .turbo
|
||||||
|
key: turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
|
||||||
|
turbo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install wasm-pack
|
# 构建所有包
|
||||||
run: cargo install wasm-pack
|
- name: Build all packages
|
||||||
|
run: pnpm run build
|
||||||
- name: Build core package first
|
|
||||||
run: pnpm run build:core
|
|
||||||
|
|
||||||
- name: Build engine WASM package
|
|
||||||
run: |
|
|
||||||
cd packages/engine
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Copy WASM files to ecs-engine-bindgen
|
- name: Copy WASM files to ecs-engine-bindgen
|
||||||
run: |
|
run: |
|
||||||
@@ -64,30 +91,15 @@ jobs:
|
|||||||
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
|
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
|
||||||
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
|
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
|
||||||
|
|
||||||
- name: Build dependent packages for type declarations
|
# 类型检查
|
||||||
run: |
|
|
||||||
cd packages/platform-common && pnpm run build
|
|
||||||
cd ../asset-system && pnpm run build
|
|
||||||
cd ../components && pnpm run build
|
|
||||||
cd ../editor-core && pnpm run build
|
|
||||||
cd ../ui && pnpm run build
|
|
||||||
cd ../editor-runtime && pnpm run build
|
|
||||||
cd ../behavior-tree && pnpm run build
|
|
||||||
cd ../tilemap && pnpm run build
|
|
||||||
cd ../physics-rapier2d && pnpm run build
|
|
||||||
cd ../node-editor && pnpm run build
|
|
||||||
cd ../blueprint && pnpm run build
|
|
||||||
|
|
||||||
- name: Build ecs-engine-bindgen
|
|
||||||
run: |
|
|
||||||
cd packages/ecs-engine-bindgen && pnpm run build
|
|
||||||
|
|
||||||
- name: Type check
|
- name: Type check
|
||||||
run: pnpm run type-check
|
run: pnpm run type-check
|
||||||
|
|
||||||
|
# Lint 检查
|
||||||
- name: Lint check
|
- name: Lint check
|
||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
|
|
||||||
|
# 测试
|
||||||
- name: Run tests with coverage
|
- name: Run tests with coverage
|
||||||
run: pnpm run test:ci
|
run: pnpm run test:ci
|
||||||
|
|
||||||
@@ -100,39 +112,16 @@ jobs:
|
|||||||
name: codecov-umbrella
|
name: codecov-umbrella
|
||||||
fail_ci_if_error: false
|
fail_ci_if_error: false
|
||||||
|
|
||||||
build:
|
# 构建 npm 包
|
||||||
runs-on: ubuntu-latest
|
- name: Build npm packages
|
||||||
needs: test
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v2
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20.x'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
run: pnpm run build
|
|
||||||
|
|
||||||
- name: Build npm package
|
|
||||||
run: pnpm run build:npm
|
run: pnpm run build:npm
|
||||||
|
|
||||||
|
# 上传构建产物
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
path: |
|
path: |
|
||||||
bin/
|
packages/*/dist/
|
||||||
dist/
|
packages/*/bin/
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|||||||
96
.github/workflows/release-editor.yml
vendored
96
.github/workflows/release-editor.yml
vendored
@@ -71,39 +71,13 @@ jobs:
|
|||||||
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 -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
|
node scripts/sync-version.js
|
||||||
|
|
||||||
# ===== 第一层:基础包(无依赖) =====
|
|
||||||
- name: Build core package
|
|
||||||
run: pnpm run build:core
|
|
||||||
|
|
||||||
- name: Build math package
|
|
||||||
run: |
|
|
||||||
cd packages/math
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Build platform-common package
|
|
||||||
run: |
|
|
||||||
cd packages/platform-common
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
# ===== 第二层:依赖 core 的包 =====
|
|
||||||
- name: Build asset-system package
|
|
||||||
run: |
|
|
||||||
cd packages/asset-system
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Build components package
|
|
||||||
run: |
|
|
||||||
cd packages/components
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
# ===== 第三层:Rust WASM 引擎 =====
|
|
||||||
- name: Install wasm-pack
|
- name: Install wasm-pack
|
||||||
run: cargo install wasm-pack
|
run: cargo install wasm-pack
|
||||||
|
|
||||||
- name: Build engine package (Rust WASM)
|
# 使用 Turborepo 自动按依赖顺序构建所有包
|
||||||
run: |
|
# 这会自动处理:core -> asset-system -> editor-core -> ui -> 等等
|
||||||
cd packages/engine
|
- name: Build all packages with Turborepo
|
||||||
pnpm run build
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Copy WASM files to ecs-engine-bindgen
|
- name: Copy WASM files to ecs-engine-bindgen
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -114,60 +88,6 @@ jobs:
|
|||||||
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
|
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
|
||||||
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
|
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
|
||||||
|
|
||||||
- name: Build ecs-engine-bindgen package
|
|
||||||
run: |
|
|
||||||
cd packages/ecs-engine-bindgen
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
# ===== 第四层:依赖 ecs-engine-bindgen/asset-system 的包 =====
|
|
||||||
- name: Build editor-core package
|
|
||||||
run: |
|
|
||||||
cd packages/editor-core
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
# ===== 第五层:依赖 editor-core 的包 =====
|
|
||||||
- name: Build UI package
|
|
||||||
run: |
|
|
||||||
cd packages/ui
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Build tilemap package
|
|
||||||
run: |
|
|
||||||
cd packages/tilemap
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Build editor-runtime package
|
|
||||||
run: |
|
|
||||||
cd packages/editor-runtime
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Build node-editor package
|
|
||||||
run: |
|
|
||||||
cd packages/node-editor
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
# ===== 第六层:依赖 editor-runtime 的包 =====
|
|
||||||
- name: Build behavior-tree package
|
|
||||||
run: |
|
|
||||||
cd packages/behavior-tree
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Build physics-rapier2d package
|
|
||||||
run: |
|
|
||||||
cd packages/physics-rapier2d
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Build blueprint package
|
|
||||||
run: |
|
|
||||||
cd packages/blueprint
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
# ===== 第七层:平台包(依赖 ui, tilemap, behavior-tree, physics-rapier2d) =====
|
|
||||||
- name: Build platform-web package
|
|
||||||
run: |
|
|
||||||
cd packages/platform-web
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
- name: Bundle runtime files for Tauri
|
- name: Bundle runtime files for Tauri
|
||||||
run: |
|
run: |
|
||||||
cd packages/editor-app
|
cd packages/editor-app
|
||||||
@@ -220,16 +140,16 @@ jobs:
|
|||||||
delete-branch: true
|
delete-branch: true
|
||||||
title: "chore(editor): Release v${{ github.event.inputs.version }}"
|
title: "chore(editor): Release v${{ github.event.inputs.version }}"
|
||||||
body: |
|
body: |
|
||||||
## 🚀 Release v${{ github.event.inputs.version }}
|
## Release v${{ github.event.inputs.version }}
|
||||||
|
|
||||||
This PR updates the editor version after successful release build.
|
This PR updates the editor version after successful release build.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- ✅ Updated `packages/editor-app/package.json` → `${{ github.event.inputs.version }}`
|
- Updated `packages/editor-app/package.json` → `${{ github.event.inputs.version }}`
|
||||||
- ✅ Updated `packages/editor-app/src-tauri/tauri.conf.json` → `${{ github.event.inputs.version }}`
|
- Updated `packages/editor-app/src-tauri/tauri.conf.json` → `${{ github.event.inputs.version }}`
|
||||||
|
|
||||||
### Release
|
### Release
|
||||||
- 📦 [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/editor-v${{ github.event.inputs.version }})
|
- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/editor-v${{ github.event.inputs.version }})
|
||||||
|
|
||||||
---
|
---
|
||||||
*This PR was automatically created by the release workflow.*
|
*This PR was automatically created by the release workflow.*
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,6 +18,9 @@ dist/
|
|||||||
.cache/
|
.cache/
|
||||||
.build-cache/
|
.build-cache/
|
||||||
|
|
||||||
|
# Turborepo
|
||||||
|
.turbo/
|
||||||
|
|
||||||
# IDE 配置
|
# IDE 配置
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export default defineConfig({
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
items: [
|
items: [
|
||||||
{ text: '实体类 (Entity)', link: '/guide/entity' },
|
{ text: '实体类 (Entity)', link: '/guide/entity' },
|
||||||
|
{ text: '层级系统 (Hierarchy)', link: '/guide/hierarchy' },
|
||||||
{ text: '组件系统 (Component)', link: '/guide/component' },
|
{ text: '组件系统 (Component)', link: '/guide/component' },
|
||||||
{ text: '实体查询系统', link: '/guide/entity-query' },
|
{ text: '实体查询系统', link: '/guide/entity-query' },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,12 @@
|
|||||||
- 提供唯一标识(ID)
|
- 提供唯一标识(ID)
|
||||||
- 管理组件的生命周期
|
- 管理组件的生命周期
|
||||||
|
|
||||||
|
::: tip 关于父子层级关系
|
||||||
|
实体间的父子层级关系通过 `HierarchyComponent` 和 `HierarchySystem` 管理,而非 Entity 内置属性。这种设计遵循 ECS 组合原则 —— 只有需要层级关系的实体才添加此组件。
|
||||||
|
|
||||||
|
详见 [层级系统](./hierarchy.md) 文档。
|
||||||
|
:::
|
||||||
|
|
||||||
## 创建实体
|
## 创建实体
|
||||||
|
|
||||||
**重要提示:实体必须通过场景创建,不支持手动创建!**
|
**重要提示:实体必须通过场景创建,不支持手动创建!**
|
||||||
@@ -285,4 +291,10 @@ entity.components.forEach(component => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
实体是 ECS 架构的核心概念之一,理解如何正确使用实体将帮助你构建高效、可维护的游戏代码。
|
实体是 ECS 架构的核心概念之一,理解如何正确使用实体将帮助你构建高效、可维护的游戏代码。
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- 了解 [层级系统](./hierarchy.md) 建立实体间的父子关系
|
||||||
|
- 了解 [组件系统](./component.md) 为实体添加功能
|
||||||
|
- 了解 [场景管理](./scene.md) 组织和管理实体
|
||||||
437
docs/guide/hierarchy.md
Normal file
437
docs/guide/hierarchy.md
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
# 层级系统
|
||||||
|
|
||||||
|
在游戏开发中,实体间的父子层级关系是常见需求。ECS Framework 采用组件化方式管理层级关系,通过 `HierarchyComponent` 和 `HierarchySystem` 实现,完全遵循 ECS 组合原则。
|
||||||
|
|
||||||
|
## 设计理念
|
||||||
|
|
||||||
|
### 为什么不在 Entity 中内置层级?
|
||||||
|
|
||||||
|
传统的游戏对象模型(如 Unity 的 GameObject)将层级关系内置于实体中。ECS Framework 选择组件化方案的原因:
|
||||||
|
|
||||||
|
1. **ECS 组合原则**:层级是一种"功能",应该通过组件添加,而非所有实体都具备
|
||||||
|
2. **按需使用**:只有需要层级关系的实体才添加 `HierarchyComponent`
|
||||||
|
3. **数据与逻辑分离**:`HierarchyComponent` 存储数据,`HierarchySystem` 处理逻辑
|
||||||
|
4. **序列化友好**:层级关系作为组件数据可以轻松序列化和反序列化
|
||||||
|
|
||||||
|
## 基本概念
|
||||||
|
|
||||||
|
### HierarchyComponent
|
||||||
|
|
||||||
|
存储层级关系数据的组件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { HierarchyComponent } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// HierarchyComponent 的核心属性
|
||||||
|
interface HierarchyComponent {
|
||||||
|
parentId: number | null; // 父实体 ID,null 表示根实体
|
||||||
|
childIds: number[]; // 子实体 ID 列表
|
||||||
|
depth: number; // 在层级中的深度(由系统维护)
|
||||||
|
bActiveInHierarchy: boolean; // 在层级中是否激活(由系统维护)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HierarchySystem
|
||||||
|
|
||||||
|
处理层级逻辑的系统,提供所有层级操作的 API:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { HierarchySystem } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
// 获取系统
|
||||||
|
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 添加系统到场景
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Scene, HierarchySystem } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameScene extends Scene {
|
||||||
|
protected initialize(): void {
|
||||||
|
// 添加层级系统
|
||||||
|
this.addSystem(new HierarchySystem());
|
||||||
|
|
||||||
|
// 添加其他系统...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 建立父子关系
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 创建实体
|
||||||
|
const parent = scene.createEntity("Parent");
|
||||||
|
const child1 = scene.createEntity("Child1");
|
||||||
|
const child2 = scene.createEntity("Child2");
|
||||||
|
|
||||||
|
// 获取层级系统
|
||||||
|
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
|
||||||
|
|
||||||
|
// 设置父子关系(自动添加 HierarchyComponent)
|
||||||
|
hierarchySystem.setParent(child1, parent);
|
||||||
|
hierarchySystem.setParent(child2, parent);
|
||||||
|
|
||||||
|
// 现在 parent 有两个子实体
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询层级
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取父实体
|
||||||
|
const parentEntity = hierarchySystem.getParent(child1);
|
||||||
|
|
||||||
|
// 获取所有子实体
|
||||||
|
const children = hierarchySystem.getChildren(parent);
|
||||||
|
|
||||||
|
// 获取子实体数量
|
||||||
|
const count = hierarchySystem.getChildCount(parent);
|
||||||
|
|
||||||
|
// 检查是否有子实体
|
||||||
|
const hasKids = hierarchySystem.hasChildren(parent);
|
||||||
|
|
||||||
|
// 获取在层级中的深度
|
||||||
|
const depth = hierarchySystem.getDepth(child1); // 返回 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 参考
|
||||||
|
|
||||||
|
### 父子关系操作
|
||||||
|
|
||||||
|
#### setParent
|
||||||
|
|
||||||
|
设置实体的父级:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 设置父级
|
||||||
|
hierarchySystem.setParent(child, parent);
|
||||||
|
|
||||||
|
// 移动到根级(无父级)
|
||||||
|
hierarchySystem.setParent(child, null);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### insertChildAt
|
||||||
|
|
||||||
|
在指定位置插入子实体:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在第一个位置插入
|
||||||
|
hierarchySystem.insertChildAt(parent, child, 0);
|
||||||
|
|
||||||
|
// 追加到末尾
|
||||||
|
hierarchySystem.insertChildAt(parent, child, -1);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### removeChild
|
||||||
|
|
||||||
|
从父级移除子实体(子实体变为根级):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const success = hierarchySystem.removeChild(parent, child);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### removeAllChildren
|
||||||
|
|
||||||
|
移除所有子实体:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
hierarchySystem.removeAllChildren(parent);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 层级查询
|
||||||
|
|
||||||
|
#### getParent / getChildren
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const parent = hierarchySystem.getParent(entity);
|
||||||
|
const children = hierarchySystem.getChildren(entity);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### getRoot
|
||||||
|
|
||||||
|
获取实体的根节点:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const root = hierarchySystem.getRoot(deepChild);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### getRootEntities
|
||||||
|
|
||||||
|
获取所有根实体(没有父级的实体):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const roots = hierarchySystem.getRootEntities();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### isAncestorOf / isDescendantOf
|
||||||
|
|
||||||
|
检查祖先/后代关系:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// grandparent -> parent -> child
|
||||||
|
const isAncestor = hierarchySystem.isAncestorOf(grandparent, child); // true
|
||||||
|
const isDescendant = hierarchySystem.isDescendantOf(child, grandparent); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 层级遍历
|
||||||
|
|
||||||
|
#### findChild
|
||||||
|
|
||||||
|
根据名称查找子实体:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 直接子级中查找
|
||||||
|
const child = hierarchySystem.findChild(parent, "ChildName");
|
||||||
|
|
||||||
|
// 递归查找所有后代
|
||||||
|
const deepChild = hierarchySystem.findChild(parent, "DeepChild", true);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### findChildrenByTag
|
||||||
|
|
||||||
|
根据标签查找子实体:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 查找直接子级
|
||||||
|
const tagged = hierarchySystem.findChildrenByTag(parent, TAG_ENEMY);
|
||||||
|
|
||||||
|
// 递归查找
|
||||||
|
const allTagged = hierarchySystem.findChildrenByTag(parent, TAG_ENEMY, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### forEachChild
|
||||||
|
|
||||||
|
遍历子实体:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 遍历直接子级
|
||||||
|
hierarchySystem.forEachChild(parent, (child) => {
|
||||||
|
console.log(child.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 递归遍历所有后代
|
||||||
|
hierarchySystem.forEachChild(parent, (child) => {
|
||||||
|
console.log(child.name);
|
||||||
|
}, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 层级状态
|
||||||
|
|
||||||
|
#### isActiveInHierarchy
|
||||||
|
|
||||||
|
检查实体在层级中是否激活(考虑所有祖先的激活状态):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 如果 parent.active = false,即使 child.active = true
|
||||||
|
// isActiveInHierarchy(child) 也会返回 false
|
||||||
|
const activeInHierarchy = hierarchySystem.isActiveInHierarchy(child);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### getDepth
|
||||||
|
|
||||||
|
获取实体在层级中的深度(根实体深度为 0):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const depth = hierarchySystem.getDepth(entity);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 扁平化层级(用于 UI 渲染)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 用于实现可展开/折叠的层级树视图
|
||||||
|
const expandedIds = new Set([parent.id]);
|
||||||
|
|
||||||
|
const flatNodes = hierarchySystem.flattenHierarchy(expandedIds);
|
||||||
|
// 返回 [{ entity, depth, bHasChildren, bIsExpanded }, ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
### 创建游戏角色层级
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
Scene,
|
||||||
|
HierarchySystem,
|
||||||
|
HierarchyComponent
|
||||||
|
} from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class GameScene extends Scene {
|
||||||
|
private hierarchySystem!: HierarchySystem;
|
||||||
|
|
||||||
|
protected initialize(): void {
|
||||||
|
// 添加层级系统
|
||||||
|
this.hierarchySystem = new HierarchySystem();
|
||||||
|
this.addSystem(this.hierarchySystem);
|
||||||
|
|
||||||
|
// 创建角色层级
|
||||||
|
this.createPlayerHierarchy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPlayerHierarchy(): void {
|
||||||
|
// 根实体
|
||||||
|
const player = this.createEntity("Player");
|
||||||
|
player.addComponent(new Transform(0, 0));
|
||||||
|
|
||||||
|
// 身体部件
|
||||||
|
const body = this.createEntity("Body");
|
||||||
|
body.addComponent(new Sprite("body.png"));
|
||||||
|
this.hierarchySystem.setParent(body, player);
|
||||||
|
|
||||||
|
// 武器(挂载在身体上)
|
||||||
|
const weapon = this.createEntity("Weapon");
|
||||||
|
weapon.addComponent(new Sprite("sword.png"));
|
||||||
|
this.hierarchySystem.setParent(weapon, body);
|
||||||
|
|
||||||
|
// 特效(挂载在武器上)
|
||||||
|
const effect = this.createEntity("WeaponEffect");
|
||||||
|
effect.addComponent(new ParticleEmitter());
|
||||||
|
this.hierarchySystem.setParent(effect, weapon);
|
||||||
|
|
||||||
|
// 查询层级信息
|
||||||
|
console.log(`Player 层级深度: ${this.hierarchySystem.getDepth(player)}`); // 0
|
||||||
|
console.log(`Weapon 层级深度: ${this.hierarchySystem.getDepth(weapon)}`); // 2
|
||||||
|
console.log(`Effect 层级深度: ${this.hierarchySystem.getDepth(effect)}`); // 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public equipNewWeapon(weaponName: string): void {
|
||||||
|
const body = this.findEntity("Body");
|
||||||
|
const oldWeapon = this.hierarchySystem.findChild(body!, "Weapon");
|
||||||
|
|
||||||
|
if (oldWeapon) {
|
||||||
|
// 移除旧武器的所有子实体
|
||||||
|
this.hierarchySystem.removeAllChildren(oldWeapon);
|
||||||
|
oldWeapon.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新武器
|
||||||
|
const newWeapon = this.createEntity("Weapon");
|
||||||
|
newWeapon.addComponent(new Sprite(`${weaponName}.png`));
|
||||||
|
this.hierarchySystem.setParent(newWeapon, body!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 层级变换系统
|
||||||
|
|
||||||
|
结合 Transform 组件实现层级变换:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { EntitySystem, Matcher, HierarchySystem, HierarchyComponent } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
class HierarchyTransformSystem extends EntitySystem {
|
||||||
|
private hierarchySystem!: HierarchySystem;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(Transform, HierarchyComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public onAddedToScene(): void {
|
||||||
|
// 获取层级系统引用
|
||||||
|
this.hierarchySystem = this.scene!.getEntityProcessor(HierarchySystem)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: readonly Entity[]): void {
|
||||||
|
// 按深度排序,确保父级先更新
|
||||||
|
const sorted = [...entities].sort((a, b) => {
|
||||||
|
return this.hierarchySystem.getDepth(a) - this.hierarchySystem.getDepth(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const entity of sorted) {
|
||||||
|
const transform = entity.getComponent(Transform)!;
|
||||||
|
const parent = this.hierarchySystem.getParent(entity);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
const parentTransform = parent.getComponent(Transform);
|
||||||
|
if (parentTransform) {
|
||||||
|
// 计算世界坐标
|
||||||
|
transform.worldX = parentTransform.worldX + transform.localX;
|
||||||
|
transform.worldY = parentTransform.worldY + transform.localY;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 根实体,本地坐标即世界坐标
|
||||||
|
transform.worldX = transform.localX;
|
||||||
|
transform.worldY = transform.localY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
### 缓存机制
|
||||||
|
|
||||||
|
`HierarchySystem` 内置了缓存机制:
|
||||||
|
|
||||||
|
- `depth` 和 `bActiveInHierarchy` 由系统自动维护
|
||||||
|
- 使用 `bCacheDirty` 标记优化更新
|
||||||
|
- 层级变化时自动标记所有子级缓存为脏
|
||||||
|
|
||||||
|
### 最佳实践
|
||||||
|
|
||||||
|
1. **避免深层嵌套**:系统限制最大深度为 32 层
|
||||||
|
2. **批量操作**:构建复杂层级时,尽量一次性设置好所有父子关系
|
||||||
|
3. **按需添加**:只有真正需要层级关系的实体才添加 `HierarchyComponent`
|
||||||
|
4. **缓存系统引用**:避免每次调用都获取 `HierarchySystem`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 好的做法
|
||||||
|
class MySystem extends EntitySystem {
|
||||||
|
private hierarchySystem!: HierarchySystem;
|
||||||
|
|
||||||
|
onAddedToScene() {
|
||||||
|
this.hierarchySystem = this.scene!.getEntityProcessor(HierarchySystem)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
process() {
|
||||||
|
// 使用缓存的引用
|
||||||
|
const parent = this.hierarchySystem.getParent(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免的做法
|
||||||
|
process() {
|
||||||
|
// 每次都获取,性能较差
|
||||||
|
const system = this.scene!.getEntityProcessor(HierarchySystem);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 迁移指南
|
||||||
|
|
||||||
|
如果你之前使用的是旧版 Entity 内置的层级 API,请参考以下迁移指南:
|
||||||
|
|
||||||
|
| 旧 API (已移除) | 新 API |
|
||||||
|
|----------------|--------|
|
||||||
|
| `entity.parent` | `hierarchySystem.getParent(entity)` |
|
||||||
|
| `entity.children` | `hierarchySystem.getChildren(entity)` |
|
||||||
|
| `entity.addChild(child)` | `hierarchySystem.setParent(child, entity)` |
|
||||||
|
| `entity.removeChild(child)` | `hierarchySystem.removeChild(entity, child)` |
|
||||||
|
| `entity.findChild(name)` | `hierarchySystem.findChild(entity, name)` |
|
||||||
|
| `entity.activeInHierarchy` | `hierarchySystem.isActiveInHierarchy(entity)` |
|
||||||
|
|
||||||
|
### 迁移示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 旧代码
|
||||||
|
const parent = scene.createEntity("Parent");
|
||||||
|
const child = scene.createEntity("Child");
|
||||||
|
parent.addChild(child);
|
||||||
|
const found = parent.findChild("Child");
|
||||||
|
|
||||||
|
// 新代码
|
||||||
|
const hierarchySystem = scene.getEntityProcessor(HierarchySystem);
|
||||||
|
|
||||||
|
const parent = scene.createEntity("Parent");
|
||||||
|
const child = scene.createEntity("Child");
|
||||||
|
hierarchySystem.setParent(child, parent);
|
||||||
|
const found = hierarchySystem.findChild(parent, "Child");
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- 了解 [实体类](./entity.md) 的其他功能
|
||||||
|
- 了解 [场景管理](./scene.md) 如何组织实体和系统
|
||||||
|
- 探索 [变换系统](./transform.md) 实现空间层级变换
|
||||||
26
package.json
26
package.json
@@ -3,6 +3,7 @@
|
|||||||
"version": "2.1.29",
|
"version": "2.1.29",
|
||||||
"description": "ECS Framework Monorepo - 高性能ECS框架及其网络插件",
|
"description": "ECS Framework Monorepo - 高性能ECS框架及其网络插件",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"packageManager": "pnpm@10.22.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
@@ -17,16 +18,18 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"clean": "lerna run clean",
|
"clean": "turbo run clean",
|
||||||
"build": "npm run build:core && npm run build:math",
|
"build": "turbo run build",
|
||||||
"build:core": "cd packages/core && npm run build",
|
"build:filter": "turbo run build --filter",
|
||||||
"build:math": "cd packages/math && npm run build",
|
"build:core": "turbo run build --filter=@esengine/ecs-framework",
|
||||||
"build:npm": "npm run build:npm:core && npm run build:npm:math",
|
"build:math": "turbo run build --filter=@esengine/ecs-framework-math",
|
||||||
|
"build:editor": "turbo run build --filter=@esengine/editor-app...",
|
||||||
|
"build:npm": "turbo run build:npm",
|
||||||
"build:npm:core": "cd packages/core && npm run build:npm",
|
"build:npm:core": "cd packages/core && npm run build:npm",
|
||||||
"build:npm:math": "cd packages/math && npm run build:npm",
|
"build:npm:math": "cd packages/math && npm run build:npm",
|
||||||
"test": "lerna run test",
|
"test": "turbo run test",
|
||||||
"test:coverage": "lerna run test:coverage",
|
"test:coverage": "turbo run test:coverage",
|
||||||
"test:ci": "lerna run test:ci",
|
"test:ci": "turbo run test:ci",
|
||||||
"prepare:publish": "npm run build:npm && node scripts/pre-publish-check.cjs",
|
"prepare:publish": "npm run build:npm && node scripts/pre-publish-check.cjs",
|
||||||
"sync:versions": "node scripts/sync-versions.cjs",
|
"sync:versions": "node scripts/sync-versions.cjs",
|
||||||
"publish:all": "npm run prepare:publish && npm run publish:all:dist",
|
"publish:all": "npm run prepare:publish && npm run publish:all:dist",
|
||||||
@@ -51,9 +54,9 @@
|
|||||||
"copy:worker-demo": "node scripts/update-worker-demo.js",
|
"copy:worker-demo": "node scripts/update-worker-demo.js",
|
||||||
"format": "prettier --write \"packages/**/src/**/*.{ts,tsx,js,jsx}\"",
|
"format": "prettier --write \"packages/**/src/**/*.{ts,tsx,js,jsx}\"",
|
||||||
"format:check": "prettier --check \"packages/**/src/**/*.{ts,tsx,js,jsx}\"",
|
"format:check": "prettier --check \"packages/**/src/**/*.{ts,tsx,js,jsx}\"",
|
||||||
"type-check": "lerna run type-check",
|
"type-check": "turbo run type-check",
|
||||||
"lint": "eslint \"packages/**/src/**/*.{ts,tsx,js,jsx}\"",
|
"lint": "turbo run lint",
|
||||||
"lint:fix": "eslint \"packages/**/src/**/*.{ts,tsx,js,jsx}\" --fix",
|
"lint:fix": "turbo run lint:fix",
|
||||||
"build:wasm": "cd packages/engine && wasm-pack build --dev --out-dir pkg",
|
"build:wasm": "cd packages/engine && wasm-pack build --dev --out-dir pkg",
|
||||||
"build:wasm:release": "cd packages/engine && wasm-pack build --release --out-dir pkg"
|
"build:wasm:release": "cd packages/engine && wasm-pack build --release --out-dir pkg"
|
||||||
},
|
},
|
||||||
@@ -92,6 +95,7 @@
|
|||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"size-limit": "^11.0.2",
|
"size-limit": "^11.0.2",
|
||||||
"ts-jest": "^29.4.0",
|
"ts-jest": "^29.4.0",
|
||||||
|
"turbo": "^2.6.1",
|
||||||
"typedoc": "^0.28.13",
|
"typedoc": "^0.28.13",
|
||||||
"typedoc-plugin-markdown": "^4.9.0",
|
"typedoc-plugin-markdown": "^4.9.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
|
|||||||
@@ -2,24 +2,24 @@
|
|||||||
"name": "@esengine/asset-system",
|
"name": "@esengine/asset-system",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Asset management system for ES Engine",
|
"description": "Asset management system for ES Engine",
|
||||||
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.mjs",
|
"module": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./dist/index.mjs",
|
"types": "./dist/index.d.ts",
|
||||||
"require": "./dist/index.js",
|
"import": "./dist/index.js"
|
||||||
"types": "./dist/index.d.ts"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "tsup",
|
||||||
"build:npm": "npm run build",
|
"build:watch": "tsup --watch",
|
||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
"type-check": "npx tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ecs",
|
"ecs",
|
||||||
@@ -29,16 +29,11 @@
|
|||||||
],
|
],
|
||||||
"author": "yhh",
|
"author": "yhh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
|
||||||
"@esengine/ecs-framework": "^2.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^28.0.3",
|
"@esengine/ecs-framework": "workspace:*",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@esengine/build-config": "workspace:*",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
|
||||||
"rimraf": "^5.0.0",
|
"rimraf": "^5.0.0",
|
||||||
"rollup": "^4.42.0",
|
"tsup": "^8.0.0",
|
||||||
"rollup-plugin-dts": "^6.2.1",
|
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import typescript from '@rollup/plugin-typescript';
|
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
|
||||||
import dts from 'rollup-plugin-dts';
|
|
||||||
|
|
||||||
const external = ['@esengine/ecs-framework'];
|
|
||||||
|
|
||||||
export default [
|
|
||||||
// ESM and CJS builds
|
|
||||||
{
|
|
||||||
input: 'src/index.ts',
|
|
||||||
output: [
|
|
||||||
{
|
|
||||||
file: 'dist/index.js',
|
|
||||||
format: 'cjs',
|
|
||||||
sourcemap: true,
|
|
||||||
exports: 'named'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: 'dist/index.mjs',
|
|
||||||
format: 'es',
|
|
||||||
sourcemap: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
external,
|
|
||||||
plugins: [
|
|
||||||
resolve({
|
|
||||||
preferBuiltins: false,
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
typescript({
|
|
||||||
tsconfig: './tsconfig.json',
|
|
||||||
declaration: false,
|
|
||||||
declarationMap: false
|
|
||||||
})
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// Type definitions
|
|
||||||
{
|
|
||||||
input: 'src/index.ts',
|
|
||||||
output: {
|
|
||||||
file: 'dist/index.d.ts',
|
|
||||||
format: 'es'
|
|
||||||
},
|
|
||||||
external,
|
|
||||||
plugins: [dts()]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
22
packages/asset-system/tsconfig.build.json
Normal file
22
packages/asset-system/tsconfig.build.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ES2020",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020", "DOM"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
7
packages/asset-system/tsup.config.ts
Normal file
7
packages/asset-system/tsup.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
import { runtimeOnlyPreset } from '../build-config/src/presets/plugin-tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...runtimeOnlyPreset(),
|
||||||
|
tsconfig: 'tsconfig.build.json'
|
||||||
|
});
|
||||||
46
packages/audio/package.json
Normal file
46
packages/audio/package.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "@esengine/audio",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "ECS-based audio system",
|
||||||
|
"esengine": {
|
||||||
|
"plugin": true,
|
||||||
|
"pluginExport": "AudioPlugin",
|
||||||
|
"category": "audio",
|
||||||
|
"isEnginePlugin": true
|
||||||
|
},
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"build:watch": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rimraf dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@esengine/ecs-framework": "workspace:*",
|
||||||
|
"@esengine/engine-core": "workspace:*",
|
||||||
|
"@esengine/build-config": "workspace:*",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"tsup": "^8.0.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ecs",
|
||||||
|
"audio",
|
||||||
|
"sound",
|
||||||
|
"music"
|
||||||
|
],
|
||||||
|
"author": "yhh",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
24
packages/audio/src/AudioPlugin.ts
Normal file
24
packages/audio/src/AudioPlugin.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { ComponentRegistry as ComponentRegistryType } from '@esengine/ecs-framework';
|
||||||
|
import type { IRuntimeModule, IPlugin, PluginDescriptor } from '@esengine/engine-core';
|
||||||
|
import { AudioSourceComponent } from './AudioSourceComponent';
|
||||||
|
|
||||||
|
class AudioRuntimeModule implements IRuntimeModule {
|
||||||
|
registerComponents(registry: typeof ComponentRegistryType): void {
|
||||||
|
registry.register(AudioSourceComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const descriptor: PluginDescriptor = {
|
||||||
|
id: '@esengine/audio',
|
||||||
|
name: 'Audio',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: '音频组件',
|
||||||
|
category: 'audio',
|
||||||
|
enabledByDefault: true,
|
||||||
|
isEnginePlugin: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AudioPlugin: IPlugin = {
|
||||||
|
descriptor,
|
||||||
|
runtimeModule: new AudioRuntimeModule()
|
||||||
|
};
|
||||||
43
packages/audio/src/AudioSourceComponent.ts
Normal file
43
packages/audio/src/AudioSourceComponent.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Component, ECSComponent, Serializable, Serialize, Property } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
@ECSComponent('AudioSource')
|
||||||
|
@Serializable({ version: 1, typeId: 'AudioSource' })
|
||||||
|
export class AudioSourceComponent extends Component {
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'asset', label: 'Audio Clip', assetType: 'audio' })
|
||||||
|
clip: string = '';
|
||||||
|
|
||||||
|
/** 范围 [0, 1] */
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'number', label: 'Volume', min: 0, max: 1, step: 0.01 })
|
||||||
|
volume: number = 1;
|
||||||
|
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'number', label: 'Pitch', min: 0.1, max: 3, step: 0.1 })
|
||||||
|
pitch: number = 1;
|
||||||
|
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'boolean', label: 'Loop' })
|
||||||
|
loop: boolean = false;
|
||||||
|
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'boolean', label: 'Play On Awake' })
|
||||||
|
playOnAwake: boolean = false;
|
||||||
|
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'boolean', label: 'Mute' })
|
||||||
|
mute: boolean = false;
|
||||||
|
|
||||||
|
/** 0 = 2D, 1 = 3D */
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'number', label: 'Spatial Blend', min: 0, max: 1, step: 0.1 })
|
||||||
|
spatialBlend: number = 0;
|
||||||
|
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'number', label: 'Min Distance' })
|
||||||
|
minDistance: number = 1;
|
||||||
|
|
||||||
|
@Serialize()
|
||||||
|
@Property({ type: 'number', label: 'Max Distance' })
|
||||||
|
maxDistance: number = 500;
|
||||||
|
}
|
||||||
2
packages/audio/src/index.ts
Normal file
2
packages/audio/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { AudioSourceComponent } from './AudioSourceComponent';
|
||||||
|
export { AudioPlugin } from './AudioPlugin';
|
||||||
12
packages/audio/tsconfig.build.json
Normal file
12
packages/audio/tsconfig.build.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": false,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||||
|
}
|
||||||
13
packages/audio/tsconfig.json
Normal file
13
packages/audio/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../core" }
|
||||||
|
]
|
||||||
|
}
|
||||||
7
packages/audio/tsup.config.ts
Normal file
7
packages/audio/tsup.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
import { runtimeOnlyPreset } from '../build-config/src/presets/plugin-tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...runtimeOnlyPreset(),
|
||||||
|
tsconfig: 'tsconfig.build.json'
|
||||||
|
});
|
||||||
49
packages/behavior-tree-editor/package.json
Normal file
49
packages/behavior-tree-editor/package.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "@esengine/behavior-tree-editor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Editor support for @esengine/behavior-tree - visual editor, inspectors, and tools",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"build:watch": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rimraf dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@esengine/behavior-tree": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@esengine/ecs-framework": "workspace:*",
|
||||||
|
"@esengine/engine-core": "workspace:*",
|
||||||
|
"@esengine/editor-core": "workspace:*",
|
||||||
|
"@esengine/editor-runtime": "workspace:*",
|
||||||
|
"@esengine/node-editor": "workspace:*",
|
||||||
|
"@esengine/build-config": "workspace:*",
|
||||||
|
"lucide-react": "^0.545.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"zustand": "^5.0.8",
|
||||||
|
"@types/react": "^18.3.12",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"tsup": "^8.0.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ecs",
|
||||||
|
"behavior-tree",
|
||||||
|
"editor"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
@@ -21,18 +21,16 @@ export const descriptor: PluginDescriptor = {
|
|||||||
{
|
{
|
||||||
name: 'BehaviorTreeRuntime',
|
name: 'BehaviorTreeRuntime',
|
||||||
type: 'runtime',
|
type: 'runtime',
|
||||||
loadingPhase: 'default',
|
loadingPhase: 'default'
|
||||||
entry: './src/index.ts'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'BehaviorTreeEditor',
|
name: 'BehaviorTreeEditor',
|
||||||
type: 'editor',
|
type: 'editor',
|
||||||
loadingPhase: 'default',
|
loadingPhase: 'default'
|
||||||
entry: './src/editor/index.ts'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{ id: '@esengine/core', version: '>=1.0.0' }
|
{ id: '@esengine/engine-core', version: '>=1.0.0', optional: true }
|
||||||
],
|
],
|
||||||
icon: 'GitBranch'
|
icon: 'GitBranch'
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GlobalBlackboardConfig, BlackboardValueType, BlackboardVariable } from '../../..';
|
import { type GlobalBlackboardConfig, BlackboardValueType, type BlackboardVariable } from '@esengine/behavior-tree';
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
const logger = createLogger('GlobalBlackboardService');
|
const logger = createLogger('GlobalBlackboardService');
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createStore } from '@esengine/editor-runtime';
|
import { createStore } from '@esengine/editor-runtime';
|
||||||
|
|
||||||
const create = createStore;
|
const create = createStore;
|
||||||
import { NodeTemplates, NodeTemplate } from '../../..';
|
import { NodeTemplates, type NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { BehaviorTree } from '../../domain/models/BehaviorTree';
|
import { BehaviorTree } from '../../domain/models/BehaviorTree';
|
||||||
import { Node } from '../../domain/models/Node';
|
import { Node } from '../../domain/models/Node';
|
||||||
import { Connection, ConnectionType } from '../../domain/models/Connection';
|
import { Connection, ConnectionType } from '../../domain/models/Connection';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NodeTemplate } from '../../..';
|
import type { NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { Node } from '../../domain/models/Node';
|
import { Node } from '../../domain/models/Node';
|
||||||
import { Position } from '../../domain/value-objects/Position';
|
import { Position } from '../../domain/value-objects/Position';
|
||||||
import { INodeFactory } from '../../domain/interfaces/INodeFactory';
|
import { INodeFactory } from '../../domain/interfaces/INodeFactory';
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
createLogger,
|
createLogger,
|
||||||
} from '@esengine/editor-runtime';
|
} from '@esengine/editor-runtime';
|
||||||
import { GlobalBlackboardTypeGenerator } from '../generators/GlobalBlackboardTypeGenerator';
|
import { GlobalBlackboardTypeGenerator } from '../generators/GlobalBlackboardTypeGenerator';
|
||||||
import { EditorFormatConverter, BehaviorTreeAssetSerializer } from '../..';
|
import { EditorFormatConverter, BehaviorTreeAssetSerializer } from '@esengine/behavior-tree';
|
||||||
import { useBehaviorTreeDataStore } from '../application/state/BehaviorTreeDataStore';
|
import { useBehaviorTreeDataStore } from '../application/state/BehaviorTreeDataStore';
|
||||||
|
|
||||||
const { File, FolderTree, FolderOpen } = Icons;
|
const { File, FolderTree, FolderOpen } = Icons;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { React, useEffect, useMemo, useRef, useState, useCallback } from '@esengine/editor-runtime';
|
import { React, useEffect, useMemo, useRef, useState, useCallback } from '@esengine/editor-runtime';
|
||||||
import { NodeTemplate, BlackboardValueType } from '../..';
|
import { BlackboardValueType, type NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { useBehaviorTreeDataStore, BehaviorTreeNode, ROOT_NODE_ID } from '../stores';
|
import { useBehaviorTreeDataStore, BehaviorTreeNode, ROOT_NODE_ID } from '../stores';
|
||||||
import { useUIStore } from '../stores';
|
import { useUIStore } from '../stores';
|
||||||
import { showToast as notificationShowToast } from '../services/NotificationService';
|
import { showToast as notificationShowToast } from '../services/NotificationService';
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { React, useRef, useEffect, useState, useMemo, Icons } from '@esengine/editor-runtime';
|
import { React, useRef, useEffect, useState, useMemo, Icons } from '@esengine/editor-runtime';
|
||||||
import type { LucideIcon } from '@esengine/editor-runtime';
|
import type { LucideIcon } from '@esengine/editor-runtime';
|
||||||
import { NodeTemplate } from '../../..';
|
import type { NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { NodeFactory } from '../../infrastructure/factories/NodeFactory';
|
import { NodeFactory } from '../../infrastructure/factories/NodeFactory';
|
||||||
|
|
||||||
const { Search, X, ChevronDown, ChevronRight } = Icons;
|
const { Search, X, ChevronDown, ChevronRight } = Icons;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { React, Icons } from '@esengine/editor-runtime';
|
import { React, Icons } from '@esengine/editor-runtime';
|
||||||
import type { LucideIcon } from '@esengine/editor-runtime';
|
import type { LucideIcon } from '@esengine/editor-runtime';
|
||||||
import { PropertyDefinition } from '../../..';
|
import type { PropertyDefinition } from '@esengine/behavior-tree';
|
||||||
|
|
||||||
import { Node as BehaviorTreeNodeType } from '../../domain/models/Node';
|
import { Node as BehaviorTreeNodeType } from '../../domain/models/Node';
|
||||||
import { Connection } from '../../domain/models/Connection';
|
import { Connection } from '../../domain/models/Connection';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NodeTemplate, NodeType } from '../..';
|
import { NodeType, type NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { Icons } from '@esengine/editor-runtime';
|
import { Icons } from '@esengine/editor-runtime';
|
||||||
import type { LucideIcon } from '@esengine/editor-runtime';
|
import type { LucideIcon } from '@esengine/editor-runtime';
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Node } from '../models/Node';
|
import { Node } from '../models/Node';
|
||||||
import { Position } from '../value-objects/Position';
|
import { Position } from '../value-objects/Position';
|
||||||
import { NodeTemplate } from '../../..';
|
import type { NodeTemplate } from '@esengine/behavior-tree';
|
||||||
|
|
||||||
export const ROOT_NODE_ID = 'root-node';
|
export const ROOT_NODE_ID = 'root-node';
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NodeTemplate } from '../../..';
|
import type { NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { Node } from '../models/Node';
|
import { Node } from '../models/Node';
|
||||||
import { Position } from '../value-objects';
|
import { Position } from '../value-objects';
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NodeTemplate } from '../../..';
|
import type { NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { Position, NodeType } from '../value-objects';
|
import { Position, NodeType } from '../value-objects';
|
||||||
import { ValidationError } from '../errors';
|
import { ValidationError } from '../errors';
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GlobalBlackboardConfig, BlackboardValueType } from '../..';
|
import { BlackboardValueType, type GlobalBlackboardConfig } from '@esengine/behavior-tree';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类型生成配置选项
|
* 类型生成配置选项
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, type RefObject, React, createLogger } from '@esengine/editor-runtime';
|
import { useState, type RefObject, React, createLogger } from '@esengine/editor-runtime';
|
||||||
import { NodeTemplate, NodeType } from '../..';
|
import { NodeType, type NodeTemplate } from '@esengine/behavior-tree';
|
||||||
import { Position } from '../domain/value-objects/Position';
|
import { Position } from '../domain/value-objects/Position';
|
||||||
import { useNodeOperations } from './useNodeOperations';
|
import { useNodeOperations } from './useNodeOperations';
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user