Compare commits

..

5 Commits

Author SHA1 Message Date
yhh
566e1977fd refactor: 改用 Imgur 图床上传图片 2025-12-04 09:56:10 +08:00
yhh
17f6259f43 chore: 删除测试图片 2025-12-04 09:54:46 +08:00
yhh
5d3483fc65 chore: 更新 pnpm-lock.yaml 2025-12-04 09:47:12 +08:00
yhh
d07a5d81fc Merge branch 'master' into feat/github-forum 2025-12-04 09:46:22 +08:00
yhh
6a4e6fbc04 feat(editor): 添加 GitHub Discussions 社区论坛功能 2025-12-04 09:45:16 +08:00
2680 changed files with 70104 additions and 293408 deletions

View File

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

View File

@@ -1,56 +0,0 @@
{
"$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/sdk",
"@esengine/worker-generator",
"@esengine/engine"
]
}

2
.github/FUNDING.yml vendored
View File

@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://github.com/esengine/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']
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']

View File

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

View File

@@ -8,12 +8,12 @@ body:
value: |
💡 提示:如果是简单问题,可以先查看:
- [📚 文档](https://esengine.github.io/ecs-framework/)
- [📖 AI 文档助手](https://deepwiki.com/esengine/esengine)
- [📖 AI 文档助手](https://deepwiki.com/esengine/ecs-framework)
- [💬 QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6)
💡 Tip: For simple questions, please check first:
- [📚 Documentation](https://esengine.github.io/ecs-framework/)
- [📖 AI Documentation](https://deepwiki.com/esengine/esengine)
- [📖 AI Documentation](https://deepwiki.com/esengine/ecs-framework)
- type: textarea
id: question

View File

@@ -6,8 +6,3 @@ paths-ignore:
- "**/node_modules"
- "**/dist"
- "**/bin"
- "**/tests"
- "**/*.test.ts"
- "**/*.spec.ts"
- "**/test"
- "**/__tests__"

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

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

View File

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

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

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

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

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

View File

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

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

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

View File

@@ -13,42 +13,27 @@ on:
- '.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
paths:
- 'packages/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'tsconfig.json'
- 'turbo.json'
- 'jest.config.*'
- '.github/workflows/ci.yml'
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
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -56,36 +41,67 @@ jobs:
node-version: '20.x'
cache: 'pnpm'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
# 缓存 Rust 编译结果
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: packages/engine
cache-on-failure: true
# 缓存 wasm-pack
- name: Cache wasm-pack
uses: actions/cache@v4
with:
path: ~/.cargo/bin/wasm-pack
key: wasm-pack-${{ runner.os }}
- name: Install wasm-pack
run: |
if ! command -v wasm-pack &> /dev/null; then
cargo install wasm-pack
fi
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
# 构建 framework 包 (可独立发布的通用库,无外部依赖)
- name: Build framework packages
# 缓存 Turbo
- name: Cache Turbo
uses: actions/cache@v4
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
turbo-${{ runner.os }}-
# 构建所有包
- name: Build all packages
run: pnpm run build
- name: Copy WASM files to ecs-engine-bindgen
run: |
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
mkdir -p packages/ecs-engine-bindgen/src/wasm
cp packages/engine/pkg/es_engine.js packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine.d.ts packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
# 类型检查 (仅 framework 包)
- name: Type check (framework packages)
run: pnpm run type-check:framework
# 类型检查
- name: Type check
run: pnpm run type-check
# Lint 检查 (仅 framework 包)
- name: Lint check (framework packages)
run: pnpm run lint:framework
# Lint 检查
- name: Lint check
run: pnpm run lint
# 测试 (仅 framework 包)
# 测试
- name: Run tests with coverage
run: pnpm run test:ci:framework
run: pnpm run test:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
@@ -96,11 +112,9 @@ jobs:
name: codecov-umbrella
fail_ci_if_error: false
# 构建 npm 包 (core 和 math)
# 构建 npm 包
- name: Build npm packages
run: |
pnpm run build:npm:core
pnpm run build:npm:math
run: pnpm run build:npm
# 上传构建产物
- name: Upload build artifacts
@@ -108,6 +122,6 @@ jobs:
with:
name: build-artifacts
path: |
packages/framework/**/dist/
packages/framework/**/bin/
packages/*/dist/
packages/*/bin/
retention-days: 7

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

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

@@ -34,7 +34,9 @@ jobs:
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -50,7 +52,7 @@ jobs:
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: packages/editor/editor-app/src-tauri
workspaces: packages/editor-app/src-tauri
cache-on-failure: true
- name: Install dependencies (Ubuntu)
@@ -65,7 +67,7 @@ jobs:
- name: Update version in config files (for manual trigger)
if: github.event_name == 'workflow_dispatch'
run: |
cd packages/editor/editor-app
cd packages/editor-app
node -e "const pkg=require('./package.json'); pkg.version='${{ github.event.inputs.version }}'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2)+'\n')"
node scripts/sync-version.js
@@ -80,134 +82,38 @@ jobs:
- 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/
mkdir -p packages/ecs-engine-bindgen/src/wasm
cp packages/engine/pkg/es_engine.js packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine.d.ts packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm packages/ecs-engine-bindgen/src/wasm/
cp packages/engine/pkg/es_engine_bg.wasm.d.ts packages/ecs-engine-bindgen/src/wasm/
- name: Bundle runtime files for Tauri
run: |
cd packages/editor/editor-app
cd packages/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
projectPath: packages/editor-app
tagName: ${{ github.event_name == 'workflow_dispatch' && format('editor-v{0}', github.event.inputs.version) || github.ref_name }}
releaseName: 'ECS Editor v${{ github.event.inputs.version || github.ref_name }}'
releaseBody: 'See the assets to download this version and install.'
releaseDraft: true
releaseDraft: false
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()
needs: build-tauri
if: github.event_name == 'workflow_dispatch' && success()
runs-on: ubuntu-latest
steps:
@@ -221,7 +127,7 @@ jobs:
- name: Update version files
run: |
cd packages/editor/editor-app
cd packages/editor-app
node -e "const pkg=require('./package.json'); pkg.version='${{ github.event.inputs.version }}'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2)+'\n')"
node scripts/sync-version.js
@@ -239,8 +145,8 @@ jobs:
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 }}`
- Updated `packages/editor-app/package.json` → `${{ github.event.inputs.version }}`
- Updated `packages/editor-app/src-tauri/tauri.conf.json` → `${{ github.event.inputs.version }}`
### Release
- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/editor-v${{ github.event.inputs.version }})

View File

@@ -1,24 +1,10 @@
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'
description: '选择要发布的包'
required: true
type: choice
options:
@@ -29,15 +15,19 @@ on:
- blueprint
- tilemap
- physics-rapier2d
- worker-generator
version_type:
description: 'Version bump type'
description: '版本更新类型'
required: true
type: choice
options:
- patch
- minor
- major
- custom
custom_version:
description: '自定义版本号 (仅当选择 custom 时使用,例如: 2.2.9)'
required: false
type: string
permissions:
contents: write
@@ -46,7 +36,7 @@ permissions:
jobs:
release-package:
name: Release Package
name: Release ${{ github.event.inputs.package }} Package
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -55,42 +45,10 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Parse tag or input
id: parse
run: |
if [ "${{ github.event_name }}" = "push" ]; then
# Parse package and version from tag
TAG="${GITHUB_REF#refs/tags/}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
# Parse format: v1.0.0 or package-v1.0.0
if [[ "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then
PACKAGE="core"
VERSION="${BASH_REMATCH[1]}"
elif [[ "$TAG" =~ ^([a-z-]+)-v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then
PACKAGE="${BASH_REMATCH[1]}"
VERSION="${BASH_REMATCH[2]}"
else
echo "::error::Invalid tag format: $TAG"
echo "Expected: v1.0.0 or package-v1.0.0"
exit 1
fi
echo "package=$PACKAGE" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "mode=tag" >> $GITHUB_OUTPUT
echo "Package: $PACKAGE"
echo "Version: $VERSION"
else
# Manual trigger: read from package.json and bump version
PACKAGE="${{ github.event.inputs.package }}"
echo "package=$PACKAGE" >> $GITHUB_OUTPUT
echo "mode=manual" >> $GITHUB_OUTPUT
echo "version_type=${{ github.event.inputs.version_type }}" >> $GITHUB_OUTPUT
fi
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v2
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -102,124 +60,76 @@ jobs:
- 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' }}
if: ${{ github.event.inputs.package != 'core' && github.event.inputs.package != 'node-editor' }}
run: |
cd packages/framework/core
cd packages/core
pnpm run build
- name: Build node-editor package (if needed for blueprint)
if: ${{ steps.parse.outputs.package == 'blueprint' }}
if: ${{ github.event.inputs.package == 'blueprint' }}
run: |
cd packages/node-editor
pnpm run build
# - name: Run tests
# run: |
# cd packages/${{ github.event.inputs.package }}
# npm run test:ci
- name: Update version
id: version
run: |
cd packages/${{ github.event.inputs.package }}
if [ "${{ github.event.inputs.version_type }}" = "custom" ]; then
NEW_VERSION=${{ github.event.inputs.custom_version }}
else
# Get current version and bump it
CURRENT=$(node -p "require('./package.json').version")
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
case "${{ github.event.inputs.version_type }}" in
major) NEW_VERSION="$((MAJOR+1)).0.0" ;;
minor) NEW_VERSION="$MAJOR.$((MINOR+1)).0" ;;
patch) NEW_VERSION="$MAJOR.$MINOR.$((PATCH+1))" ;;
esac
fi
# Update package.json using node
node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json')); pkg.version='$NEW_VERSION'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2)+'\n')"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "发布版本: $NEW_VERSION"
- name: Build package
run: |
cd packages/${{ steps.parse.outputs.package }}
cd packages/${{ github.event.inputs.package }}
pnpm run build:npm
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
cd packages/${{ steps.parse.outputs.package }}/dist
cd packages/${{ github.event.inputs.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'
- name: Create Pull Request
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 }}
commit-message: "chore(${{ github.event.inputs.package }}): release v${{ steps.version.outputs.new_version }}"
branch: release/${{ github.event.inputs.package }}-v${{ steps.version.outputs.new_version }}
delete-branch: true
title: "chore(${{ steps.parse.outputs.package }}): Release v${{ steps.version.outputs.value }}"
title: "chore(${{ github.event.inputs.package }}): Release v${{ steps.version.outputs.new_version }}"
body: |
## Release v${{ steps.version.outputs.value }}
## 🚀 Release v${{ steps.version.outputs.new_version }}
This PR updates `@esengine/${{ steps.parse.outputs.package }}` package version.
此 PR 更新 `@esengine/${{ github.event.inputs.package }}` 包的版本号
### 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 }}`
### 变更
- ✅ 已发布到 npm: [@esengine/${{ github.event.inputs.package }}@${{ steps.version.outputs.new_version }}](https://www.npmjs.com/package/@esengine/${{ github.event.inputs.package }}/v/${{ steps.version.outputs.new_version }})
- ✅ 更新 `packages/${{ github.event.inputs.package }}/package.json` `${{ steps.version.outputs.new_version }}`
---
*This PR was automatically created by the release workflow*
*此 PR 由发布工作流自动创建*
labels: |
release
${{ steps.parse.outputs.package }}
${{ github.event.inputs.package }}
automated pr

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

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

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

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

12
.gitignore vendored
View File

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

View File

@@ -119,9 +119,9 @@ npm run format
## 问题反馈 / Issue Reporting
如果你发现了 bug 或有新功能建议,请[创建 Issue](https://github.com/esengine/esengine/issues/new)。
如果你发现了 bug 或有新功能建议,请[创建 Issue](https://github.com/esengine/ecs-framework/issues/new)。
If you find a bug or have a feature request, please [create an issue](https://github.com/esengine/esengine/issues/new).
If you find a bug or have a feature request, please [create an issue](https://github.com/esengine/ecs-framework/issues/new).
## 许可证 / License

View File

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

341
README.md
View File

@@ -1,85 +1,49 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/esengine/esengine/master/docs/public/logo.svg" alt="ESEngine" width="180">
<br>
ESEngine
</h1>
# ESEngine
<p align="center">
<strong>Modular Game Framework for TypeScript</strong>
</p>
**English** | [中文](./README_CN.md)
<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>
**[Documentation](https://esengine.github.io/ecs-framework/) | [API Reference](https://esengine.github.io/ecs-framework/api/) | [Examples](./examples/)**
<p align="center">
<b>English</b> | <a href="./README_CN.md">中文</a>
</p>
ESEngine is a cross-platform 2D game engine for creating games from a unified interface. It provides a comprehensive set of common tools so that developers can focus on making games without having to reinvent the wheel.
<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>
Games can be exported to multiple platforms including Web browsers, WeChat Mini Games, and other mini-game platforms.
---
## Free and Open Source
## What is ESEngine?
ESEngine is completely free and open source under the MIT license. No strings attached, no royalties. Your games are yours.
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.
## Features
The core is a high-performance **ECS (Entity-Component-System)** framework, accompanied by optional modules for AI, networking, physics, and more.
- **Data-Driven Architecture**: Built on Entity-Component-System (ECS) pattern for flexible and performant game logic
- **High-Performance Rendering**: Rust/WebAssembly 2D renderer with sprite batching and WebGL 2.0 backend
- **Visual Editor**: Cross-platform desktop editor with scene management, asset browser, and visual tools
- **Modular Design**: Use only what you need. Each feature is a separate module that can be included independently
- **Multi-Platform**: Deploy to Web, WeChat Mini Games, and more from a single codebase
## Getting the Engine
### Using npm
```bash
npm install @esengine/ecs-framework
```
## Features
### Building from Source
| Module | Description | Engine Required |
|--------|-------------|:---------------:|
| **ECS Core** | Entity-Component-System framework with reactive queries | No |
| **Behavior Tree** | AI behavior trees with visual editor support | No |
| **Blueprint** | Visual scripting system | No |
| **FSM** | Finite state machine | No |
| **Timer** | Timer and cooldown systems | No |
| **Spatial** | Spatial indexing and queries (QuadTree, Grid) | No |
| **Pathfinding** | A* and navigation mesh pathfinding | No |
| **Procgen** | Procedural generation (noise, random, sampling) | No |
| **RPC** | High-performance RPC communication framework | No |
| **Server** | Game server framework with rooms, auth, rate limiting | No |
| **Network** | Client networking with prediction, AOI, delta compression | No |
| **Transaction** | Game transaction system with Redis/Memory storage | No |
| **World Streaming** | Open world chunk loading and streaming | No |
See [Building from Source](#building-from-source) for detailed instructions.
> All framework modules can be used standalone with any rendering engine.
### Editor Download
Pre-built editor binaries are available on the [Releases](https://github.com/esengine/ecs-framework/releases) page for Windows and macOS.
## Quick Start
### Using CLI (Recommended)
The easiest way to add ECS to your existing project:
```bash
# In your project directory
npx @esengine/cli init
```
The CLI automatically detects your project type (Cocos Creator 2.x/3.x, LayaAir 3.x, or Node.js) and generates the necessary integration code.
### Manual Setup
```typescript
import {
Core, Scene, Entity, Component, EntitySystem,
Matcher, Time, ECSComponent, ECSSystem
} from '@esengine/ecs-framework';
// Define components (data only)
@ECSComponent('Position')
class Position extends Component {
x = 0;
@@ -92,7 +56,6 @@ class Velocity extends Component {
dy = 0;
}
// Define system (logic)
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
@@ -109,7 +72,6 @@ class MovementSystem extends EntitySystem {
}
}
// Initialize
Core.create();
const scene = new Scene();
scene.addSystem(new MovementSystem());
@@ -120,193 +82,164 @@ player.addComponent(new Velocity());
Core.setScene(scene);
// Integrate with your game loop
// Game loop
let lastTime = 0;
function gameLoop(currentTime: number) {
Core.update(currentTime / 1000);
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
Core.update(deltaTime);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
## Using with Other Engines
## Modules
ESEngine's framework modules are designed to work alongside your preferred rendering engine:
ESEngine is organized into modular packages. Each feature has a runtime module and an optional editor extension.
### With Cocos Creator
### Core
```typescript
import { Component as CCComponent, _decorator } from 'cc';
import { Core, Scene, Matcher, EntitySystem } from '@esengine/ecs-framework';
import { BehaviorTreeExecutionSystem } from '@esengine/behavior-tree';
| Package | Description |
|---------|-------------|
| `@esengine/ecs-framework` | Core ECS framework with entity management, component system, and queries |
| `@esengine/math` | Vector, matrix, and mathematical utilities |
| `@esengine/engine` | Rust/WASM 2D renderer |
| `@esengine/engine-core` | Engine module system and lifecycle management |
const { ccclass } = _decorator;
### Runtime Modules
@ccclass('GameManager')
export class GameManager extends CCComponent {
private ecsScene!: Scene;
| Package | Description |
|---------|-------------|
| `@esengine/sprite` | 2D sprite rendering and animation |
| `@esengine/tilemap` | Tile-based map rendering with animation support |
| `@esengine/physics-rapier2d` | 2D physics simulation powered by Rapier |
| `@esengine/behavior-tree` | Behavior tree AI system |
| `@esengine/blueprint` | Visual scripting runtime |
| `@esengine/camera` | Camera control and management |
| `@esengine/audio` | Audio playback |
| `@esengine/ui` | UI components |
| `@esengine/material-system` | Material and shader system |
| `@esengine/asset-system` | Asset loading and management |
start() {
Core.create();
this.ecsScene = new Scene();
### Editor Extensions
// Add ECS systems
this.ecsScene.addSystem(new BehaviorTreeExecutionSystem());
this.ecsScene.addSystem(new MyGameSystem());
| Package | Description |
|---------|-------------|
| `@esengine/sprite-editor` | Sprite inspector and tools |
| `@esengine/tilemap-editor` | Visual tilemap editor with brush tools |
| `@esengine/physics-rapier2d-editor` | Physics collider visualization and editing |
| `@esengine/behavior-tree-editor` | Visual behavior tree editor |
| `@esengine/blueprint-editor` | Visual scripting editor |
| `@esengine/material-editor` | Material and shader editor |
| `@esengine/shader-editor` | Shader code editor |
Core.setScene(this.ecsScene);
}
### Platform
update(dt: number) {
Core.update(dt);
}
}
```
| Package | Description |
|---------|-------------|
| `@esengine/platform-common` | Platform abstraction interfaces |
| `@esengine/platform-web` | Web browser runtime |
| `@esengine/platform-wechat` | WeChat Mini Game runtime |
### With Laya 3.x
## Editor
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { FSMSystem } from '@esengine/fsm';
ESEngine Editor is a cross-platform desktop application built with Tauri and React.
const { regClass } = Laya;
### Features
@regClass()
export class ECSManager extends Laya.Script {
private ecsScene = new Scene();
- Scene hierarchy and entity management
- Component inspector with custom editors
- Asset browser with drag-and-drop support
- Tilemap editor with paint, fill, and selection tools
- Behavior tree visual editor
- Blueprint visual scripting
- Material and shader editing
- Built-in performance profiler
- Localization support (English, Chinese)
onAwake(): void {
Core.create();
this.ecsScene.addSystem(new FSMSystem());
Core.setScene(this.ecsScene);
}
### Screenshot
onUpdate(): void {
Core.update(Laya.timer.delta / 1000);
}
![ESEngine Editor](screenshots/main_screetshot.png)
onDestroy(): void {
Core.destroy();
}
}
```
## Supported Platforms
## Packages
### Framework (Engine-Agnostic)
These packages have **zero rendering dependencies** and work with any engine:
```bash
npm install @esengine/ecs-framework # Core ECS
npm install @esengine/behavior-tree # AI behavior trees
npm install @esengine/blueprint # Visual scripting
npm install @esengine/fsm # State machines
npm install @esengine/timer # Timers & cooldowns
npm install @esengine/spatial # Spatial indexing
npm install @esengine/pathfinding # Pathfinding
npm install @esengine/procgen # Procedural generation
npm install @esengine/rpc # RPC framework
npm install @esengine/server # Game server
npm install @esengine/network # Client networking
npm install @esengine/transaction # Transaction system
npm install @esengine/world-streaming # World streaming
```
### ESEngine Runtime (Optional)
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)
- [Build from source](./packages/editor/editor-app/README.md)
- 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
│ │ ├── rpc/ # RPC framework
│ │ ├── server/ # Game server
│ │ ├── network/ # Client networking
│ │ ├── transaction/ # Transaction system
│ │ └── world-streaming/ # World streaming
│ │
│ ├── engine/ # ESEngine runtime
│ ├── rendering/ # Rendering modules
│ ├── physics/ # Physics modules
│ ├── editor/ # Visual editor
│ └── rust/ # WASM renderer
├── docs/ # Documentation
└── examples/ # Examples
```
| Platform | Runtime | Editor |
|----------|---------|--------|
| Web Browser | Yes | - |
| Windows | - | Yes |
| macOS | - | Yes |
| WeChat Mini Game | In Progress | - |
| Playable Ads | Planned | - |
| Android | Planned | - |
| iOS | Planned | - |
| Windows Native | Planned | - |
| Other Platforms | Planned | - |
## Building from Source
```bash
git clone https://github.com/esengine/esengine.git
cd esengine
### Prerequisites
- Node.js 18 or later
- pnpm 10 or later
- Rust toolchain (for WASM renderer)
- wasm-pack
### Setup
```bash
# Clone repository
git clone https://github.com/esengine/ecs-framework.git
cd ecs-framework
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Type check framework packages
pnpm type-check:framework
# Build WASM renderer (optional)
pnpm build:wasm
```
# Run tests
pnpm test
### Running the Editor
```bash
cd packages/editor-app
pnpm tauri:dev
```
### Project Structure
```
ecs-framework/
├── packages/ Engine packages (runtime, editor, platform)
├── docs/ Documentation source
├── examples/ Example projects
├── scripts/ Build utilities
└── thirdparty/ Third-party dependencies
```
## Documentation
- [ECS Framework Guide](./packages/framework/core/README.md)
- [Behavior Tree Guide](./packages/framework/behavior-tree/README.md)
- [Editor Setup Guide](./packages/editor/editor-app/README.md) ([中文](./packages/editor/editor-app/README_CN.md))
- [API Reference](https://esengine.cn/api/README)
- [Getting Started](https://esengine.github.io/ecs-framework/guide/getting-started.html)
- [Architecture Guide](https://esengine.github.io/ecs-framework/guide/)
- [API Reference](https://esengine.github.io/ecs-framework/api/)
## Community
- [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
- [GitHub Issues](https://github.com/esengine/ecs-framework/issues) - Bug reports and feature requests
- [GitHub Discussions](https://github.com/esengine/ecs-framework/discussions) - Questions and ideas
## Contributing
Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
Contributions are welcome. Please read the contributing guidelines before submitting a pull request.
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
2. Create a feature branch
3. Make changes with tests
4. Submit a pull request
## License
ESEngine is licensed under the [MIT License](LICENSE). Free for personal and commercial use.
---
<p align="center">
Made with care by the ESEngine community
</p>
ESEngine is licensed under the [MIT License](LICENSE).

View File

@@ -1,85 +1,49 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/esengine/esengine/master/docs/public/logo.svg" alt="ESEngine" width="180">
<br>
ESEngine
</h1>
# ESEngine
<p align="center">
<strong>TypeScript 模块化游戏框架</strong>
</p>
[English](./README.md) | **中文**
<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>
**[文档](https://esengine.github.io/ecs-framework/) | [API 参考](https://esengine.github.io/ecs-framework/api/) | [示例](./examples/)**
<p align="center">
<a href="./README.md">English</a> | <b>中文</b>
</p>
ESEngine 是一个跨平台 2D 游戏引擎,提供统一的开发界面。它包含完整的常用工具集,让开发者专注于游戏创作本身。
<p align="center">
<a href="https://esengine.cn/">文档</a> ·
<a href="https://esengine.cn/api/README">API 参考</a> ·
<a href="./examples/">示例</a>
</p>
游戏可以导出到多个平台,包括 Web 浏览器、微信小游戏等小游戏平台。
---
## 免费开源
## ESEngine 是什么?
ESEngine 基于 MIT 协议完全免费开源。无附加条件,无版税。你的游戏完全属于你。
ESEngine 是一套**引擎无关的游戏开发模块**,可与 Cocos Creator、Laya、Phaser、PixiJS 等任何 JavaScript 游戏引擎配合使用。
## 特性
核心是一个高性能的 **ECS实体-组件-系统)** 框架,配套 AI、网络、物理等可选模块。
- **数据驱动架构**:基于 ECS实体-组件-系统)模式构建,提供灵活高效的游戏逻辑
- **高性能渲染**Rust/WebAssembly 2D 渲染器,支持精灵批处理和 WebGL 2.0
- **可视化编辑器**:跨平台桌面编辑器,包含场景管理、资源浏览器和可视化工具
- **模块化设计**:按需使用,每个功能都是独立模块,可单独引入
- **多平台支持**:一套代码部署到 Web、微信小游戏等多个平台
## 获取引擎
### 通过 npm 安装
```bash
npm install @esengine/ecs-framework
```
## 功能模块
### 从源码构建
| 模块 | 描述 | 需要渲染引擎 |
|------|------|:----------:|
| **ECS 核心** | 实体-组件-系统框架,支持响应式查询 | 否 |
| **行为树** | AI 行为树,支持可视化编辑 | 否 |
| **蓝图** | 可视化脚本系统 | 否 |
| **状态机** | 有限状态机 | 否 |
| **定时器** | 定时器和冷却系统 | 否 |
| **空间索引** | 空间查询(四叉树、网格) | 否 |
| **寻路** | A* 和导航网格寻路 | 否 |
| **程序化生成** | 噪声、随机、采样等生成算法 | 否 |
| **RPC** | 高性能 RPC 通信框架 | 否 |
| **服务端** | 游戏服务器框架,支持房间、认证、速率限制 | 否 |
| **网络** | 客户端网络支持预测、AOI、增量压缩 | 否 |
| **事务系统** | 游戏事务系统,支持 Redis/内存存储 | 否 |
| **世界流送** | 开放世界分块加载和流送 | 否 |
详见 [从源码构建](#从源码构建) 章节。
> 所有框架模块都可以独立使用,无需依赖特定渲染引擎。
### 编辑器下载
预编译的编辑器可在 [Releases](https://github.com/esengine/ecs-framework/releases) 页面下载,支持 Windows 和 macOS。
## 快速开始
### 使用 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;
@@ -92,7 +56,6 @@ class Velocity extends Component {
dy = 0;
}
// 定义系统(逻辑)
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
@@ -109,7 +72,6 @@ class MovementSystem extends EntitySystem {
}
}
// 初始化
Core.create();
const scene = new Scene();
scene.addSystem(new MovementSystem());
@@ -120,194 +82,165 @@ player.addComponent(new Velocity());
Core.setScene(scene);
// 集成到你的游戏循环
// 游戏循环
let lastTime = 0;
function gameLoop(currentTime: number) {
Core.update(currentTime / 1000);
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
Core.update(deltaTime);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
## 与其他引擎配合使用
## 模块
ESEngine 的框架模块设计为可与你喜欢的渲染引擎配合使用:
ESEngine 采用模块化组织。每个功能都有运行时模块和可选的编辑器扩展。
### 与 Cocos Creator 配合
### 核心
```typescript
import { Component as CCComponent, _decorator } from 'cc';
import { Core, Scene, Matcher, EntitySystem } from '@esengine/ecs-framework';
import { BehaviorTreeExecutionSystem } from '@esengine/behavior-tree';
const { ccclass } = _decorator;
@ccclass('GameManager')
export class GameManager extends CCComponent {
private ecsScene!: Scene;
start() {
Core.create();
this.ecsScene = new Scene();
// 添加 ECS 系统
this.ecsScene.addSystem(new BehaviorTreeExecutionSystem());
this.ecsScene.addSystem(new MyGameSystem());
Core.setScene(this.ecsScene);
}
update(dt: number) {
Core.update(dt);
}
}
```
### 与 Laya 3.x 配合
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { FSMSystem } from '@esengine/fsm';
const { regClass } = Laya;
@regClass()
export class ECSManager extends Laya.Script {
private ecsScene = new Scene();
onAwake(): void {
Core.create();
this.ecsScene.addSystem(new FSMSystem());
Core.setScene(this.ecsScene);
}
onUpdate(): void {
Core.update(Laya.timer.delta / 1000);
}
onDestroy(): void {
Core.destroy();
}
}
```
## 包列表
### 框架包(引擎无关)
这些包**零渲染依赖**,可与任何引擎配合使用:
```bash
npm install @esengine/ecs-framework # ECS 核心
npm install @esengine/behavior-tree # AI 行为树
npm install @esengine/blueprint # 可视化脚本
npm install @esengine/fsm # 状态机
npm install @esengine/timer # 定时器和冷却
npm install @esengine/spatial # 空间索引
npm install @esengine/pathfinding # 寻路
npm install @esengine/procgen # 程序化生成
npm install @esengine/rpc # RPC 框架
npm install @esengine/server # 游戏服务器
npm install @esengine/network # 客户端网络
npm install @esengine/transaction # 事务系统
npm install @esengine/world-streaming # 世界流送
```
### ESEngine 运行时(可选)
如果你需要完整的引擎解决方案:
| 分类 | 包名 |
| 包名 | 描述 |
|------|------|
| **核心** | `engine-core`, `asset-system`, `material-system` |
| **渲染** | `sprite`, `tilemap`, `particle`, `camera`, `mesh-3d` |
| **物理** | `physics-rapier2d` |
| **平台** | `platform-web`, `platform-wechat` |
| `@esengine/ecs-framework` | ECS 框架核心,包含实体管理、组件系统和查询 |
| `@esengine/math` | 向量、矩阵和数学工具 |
| `@esengine/engine` | Rust/WASM 2D 渲染器 |
| `@esengine/engine-core` | 引擎模块系统和生命周期管理 |
### 编辑器(可选)
### 运行时模块
基于 Tauri 构建的可视化编辑器:
| 包名 | 描述 |
|------|------|
| `@esengine/sprite` | 2D 精灵渲染和动画 |
| `@esengine/tilemap` | Tilemap 渲染,支持动画 |
| `@esengine/physics-rapier2d` | 基于 Rapier 的 2D 物理模拟 |
| `@esengine/behavior-tree` | 行为树 AI 系统 |
| `@esengine/blueprint` | 可视化脚本运行时 |
| `@esengine/camera` | 相机控制和管理 |
| `@esengine/audio` | 音频播放 |
| `@esengine/ui` | UI 组件 |
| `@esengine/material-system` | 材质和着色器系统 |
| `@esengine/asset-system` | 资源加载和管理 |
- 从 [Releases](https://github.com/esengine/esengine/releases) 下载
- [从源码构建](./packages/editor/editor-app/README.md)
- 支持行为树编辑、Tilemap 绘制、可视化脚本
### 编辑器扩展
## 项目结构
| 包名 | 描述 |
|------|------|
| `@esengine/sprite-editor` | 精灵检视器和工具 |
| `@esengine/tilemap-editor` | 可视化 Tilemap 编辑器,支持笔刷工具 |
| `@esengine/physics-rapier2d-editor` | 物理碰撞体可视化和编辑 |
| `@esengine/behavior-tree-editor` | 可视化行为树编辑器 |
| `@esengine/blueprint-editor` | 可视化脚本编辑器 |
| `@esengine/material-editor` | 材质和着色器编辑器 |
| `@esengine/shader-editor` | 着色器代码编辑器 |
```
esengine/
├── packages/
│ ├── framework/ # 引擎无关模块(可发布到 NPM
│ │ ├── core/ # ECS 框架
│ │ ├── math/ # 数学工具
│ │ ├── behavior-tree/ # AI 行为树
│ │ ├── blueprint/ # 可视化脚本
│ │ ├── fsm/ # 有限状态机
│ │ ├── timer/ # 定时器系统
│ │ ├── spatial/ # 空间查询
│ │ ├── pathfinding/ # 寻路
│ │ ├── procgen/ # 程序化生成
│ │ ├── rpc/ # RPC 框架
│ │ ├── server/ # 游戏服务器
│ │ ├── network/ # 客户端网络
│ │ ├── transaction/ # 事务系统
│ │ └── world-streaming/ # 世界流送
│ │
│ ├── engine/ # ESEngine 运行时
│ ├── rendering/ # 渲染模块
│ ├── physics/ # 物理模块
│ ├── editor/ # 可视化编辑器
│ └── rust/ # WASM 渲染器
├── docs/ # 文档
└── examples/ # 示例
```
### 平台
| 包名 | 描述 |
|------|------|
| `@esengine/platform-common` | 平台抽象接口 |
| `@esengine/platform-web` | Web 浏览器运行时 |
| `@esengine/platform-wechat` | 微信小游戏运行时 |
## 编辑器
ESEngine 编辑器是基于 Tauri 和 React 构建的跨平台桌面应用。
### 功能
- 场景层级和实体管理
- 组件检视器,支持自定义编辑器
- 资源浏览器,支持拖放
- Tilemap 编辑器,支持绘制、填充、选择工具
- 行为树可视化编辑器
- 蓝图可视化脚本
- 材质和着色器编辑
- 内置性能分析器
- 多语言支持(英文、中文)
### 截图
![ESEngine Editor](screenshots/main_screetshot.png)
## 支持的平台
| 平台 | 运行时 | 编辑器 |
|------|--------|--------|
| Web 浏览器 | 支持 | - |
| Windows | - | 支持 |
| macOS | - | 支持 |
| 微信小游戏 | 开发中 | - |
| Playable 可玩广告 | 计划中 | - |
| Android | 计划中 | - |
| iOS | 计划中 | - |
| Windows 原生 | 计划中 | - |
| 其他平台 | 计划中 | - |
## 从源码构建
```bash
git clone https://github.com/esengine/esengine.git
cd esengine
### 前置要求
- Node.js 18 或更高版本
- pnpm 10 或更高版本
- Rust 工具链(用于 WASM 渲染器)
- wasm-pack
### 安装
```bash
# 克隆仓库
git clone https://github.com/esengine/ecs-framework.git
cd ecs-framework
# 安装依赖
pnpm install
# 构建所有包
pnpm build
# 框架包类型检查
pnpm type-check:framework
# 构建 WASM 渲染器(可选)
pnpm build:wasm
```
# 运行测试
pnpm test
### 运行编辑器
```bash
cd packages/editor-app
pnpm tauri:dev
```
### 项目结构
```
ecs-framework/
├── packages/ 引擎包(运行时、编辑器、平台)
├── docs/ 文档源码
├── examples/ 示例项目
├── scripts/ 构建工具
└── thirdparty/ 第三方依赖
```
## 文档
- [ECS 框架指南](./packages/framework/core/README.md)
- [行为树指南](./packages/framework/behavior-tree/README.md)
- [编辑器启动指南](./packages/editor/editor-app/README_CN.md) ([English](./packages/editor/editor-app/README.md))
- [API 参考](https://esengine.cn/api/README)
- [快速入门](https://esengine.github.io/ecs-framework/guide/getting-started.html)
- [架构指南](https://esengine.github.io/ecs-framework/guide/)
- [API 参考](https://esengine.github.io/ecs-framework/api/)
## 社区
- [GitHub Issues](https://github.com/esengine/ecs-framework/issues) - Bug 反馈和功能建议
- [GitHub Discussions](https://github.com/esengine/ecs-framework/discussions) - 问题和想法
- [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - 中文社区
- [Discord](https://discord.gg/gCAgzXFW) - 国际社区
- [GitHub Issues](https://github.com/esengine/esengine/issues) - Bug 反馈和功能建议
- [GitHub Discussions](https://github.com/esengine/esengine/discussions) - 问题和想法
## 贡献
欢迎贡献代码提交 PR 前请阅读贡献指南。
欢迎贡献代码提交 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
2. 创建功能分支
3. 修改代码并测试
4. 提交 PR
## 许可证
ESEngine 基于 [MIT 协议](LICENSE) 开源,个人和商业使用均免费
---
<p align="center">
由 ESEngine 社区用心打造
</p>
ESEngine 基于 [MIT 协议](LICENSE) 开源。

View File

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

21
docs/.gitignore vendored
View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,10 @@
---
title: "Quick Start"
---
# 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:
### NPM Installation
```bash
# Using npm
@@ -352,39 +333,27 @@ function gameLoop(deltaTime: number) {
## Game Engine Integration
### Laya 3.x Engine Integration
Using `Laya.Script` component to manage ECS lifecycle is recommended:
### Laya Engine Integration
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { Stage } from "laya/display/Stage";
import { Laya } from "Laya";
import { Core } from '@esengine/ecs-framework';
const { regClass } = Laya;
// Initialize Laya
Laya.init(800, 600).then(() => {
// Initialize ECS
Core.create(true);
Core.setScene(new GameScene());
@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();
}
}
// Start game loop
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime); // Auto-updates global services and scene
});
});
```
In Laya IDE, attach the `ECSManager` script to a node in your scene.
### Cocos Creator Integration
```typescript

View File

@@ -1,6 +1,4 @@
---
title: "Guide"
---
# Guide
Welcome to the ECS Framework Guide. This guide covers the core concepts and usage of the framework.

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

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

View File

@@ -1,6 +1,4 @@
---
title: "示例"
---
# 示例
这里展示了ECS Framework的各种使用示例通过实际的演示帮助您理解框架的功能和最佳实践。

View File

@@ -1,6 +1,4 @@
---
title: "Worker系统演示"
---
# Worker系统演示
这是一个展示ECS框架Worker系统功能的交互式演示。

View File

@@ -1,6 +1,4 @@
---
title: "高级用法"
---
# 高级用法
本文介绍行为树系统的高级功能和使用技巧。
@@ -305,11 +303,11 @@ const tree = BehaviorTreeBuilder.create('Timeout')
### Cocos Creator集成
参见[Cocos Creator集成指南](./cocos-integration/)
参见[Cocos Creator集成指南](./cocos-integration.md)
### LayaAir集成
参见[LayaAir集成指南](./laya-integration/)
参见[LayaAir集成指南](./laya-integration.md)
## 最佳实践
@@ -389,6 +387,6 @@ const tree = BehaviorTreeBuilder.create('AI')
## 下一步
- 查看[自定义节点执行器](./custom-actions/)学习如何创建自定义节点
- 阅读[最佳实践](./best-practices/)了解行为树设计技巧
- 参考[编辑器使用指南](./editor-guide/)学习可视化编辑
- 查看[自定义节点执行器](./custom-actions.md)学习如何创建自定义节点
- 阅读[最佳实践](./best-practices.md)了解行为树设计技巧
- 参考[编辑器使用指南](./editor-guide.md)学习可视化编辑

View File

@@ -1,6 +1,4 @@
---
title: "资产管理"
---
# 资产管理
本文介绍如何加载、管理和复用行为树资产。
@@ -503,6 +501,6 @@ console.log(json);
## 下一步
- 学习[Cocos Creator 集成](./cocos-integration/)了解如何在游戏引擎中加载资源
- 查看[自定义节点执行器](./custom-actions/)创建自定义行为
- 阅读[最佳实践](./best-practices/)优化你的行为树设计
- 学习[Cocos Creator 集成](./cocos-integration.md)了解如何在游戏引擎中加载资源
- 查看[自定义节点执行器](./custom-actions.md)创建自定义行为
- 阅读[最佳实践](./best-practices.md)优化你的行为树设计

View File

@@ -1,6 +1,4 @@
---
title: "最佳实践"
---
# 最佳实践
本文介绍行为树设计和使用的最佳实践,帮助你构建高效、可维护的AI系统。
@@ -26,7 +24,7 @@ Root Selector
### 2. 单一职责原则
每个节点应该只做一件事。要实现复杂动作,创建自定义执行器,参见[自定义节点执行器](./custom-actions/)。
每个节点应该只做一件事。要实现复杂动作,创建自定义执行器,参见[自定义节点执行器](./custom-actions.md)。
```typescript
// 好的设计 - 使用内置节点
@@ -465,6 +463,6 @@ export class SmartUpdate implements INodeExecutor {
## 下一步
- 学习[自定义节点执行器](./custom-actions/)扩展行为树功能
- 探索[高级用法](./advanced-usage/)了解更多技巧
- 参考[核心概念](./core-concepts/)深入理解原理
- 学习[自定义节点执行器](./custom-actions.md)扩展行为树功能
- 探索[高级用法](./advanced-usage.md)了解更多技巧
- 参考[核心概念](./core-concepts.md)深入理解原理

View File

@@ -1,6 +1,4 @@
---
title: "Cocos Creator 集成"
---
# Cocos Creator 集成
本教程将引导你在 Cocos Creator 项目中集成和使用行为树系统。
@@ -8,7 +6,7 @@ title: "Cocos Creator 集成"
- Cocos Creator 3.x 或更高版本
- 基本的 TypeScript 知识
- 已完成[快速开始](./getting-started/)教程
- 已完成[快速开始](./getting-started.md)教程
## 安装
@@ -679,7 +677,7 @@ const updateInterval = sys.isNative ? 0.016 : 0.05;
## 下一步
- 查看[资产管理](./asset-management/)了解如何加载和管理行为树资产、使用子树
- 学习[高级用法](./advanced-usage/)了解性能优化和调试技巧
- 阅读[最佳实践](./best-practices/)优化你的 AI
- 学习[自定义节点执行器](./custom-actions/)创建自定义行为
- 查看[资产管理](./asset-management.md)了解如何加载和管理行为树资产、使用子树
- 学习[高级用法](./advanced-usage.md)了解性能优化和调试技巧
- 阅读[最佳实践](./best-practices.md)优化你的 AI
- 学习[自定义节点执行器](./custom-actions.md)创建自定义行为

View File

@@ -1,6 +1,4 @@
---
title: "核心概念"
---
# 核心概念
本文介绍行为树系统的核心概念和工作原理。
@@ -192,7 +190,7 @@ const tree = BehaviorTreeBuilder.create('Actions')
.build();
```
要实现自定义动作,需要创建自定义执行器,参见[自定义节点执行器](./custom-actions/)。
要实现自定义动作,需要创建自定义执行器,参见[自定义节点执行器](./custom-actions.md)。
#### Condition(条件)
@@ -487,7 +485,7 @@ NodeRuntimeState
现在你已经理解了行为树的核心概念,接下来可以:
- 查看[快速开始](./getting-started/)创建第一个行为树
- 学习[自定义节点执行器](./custom-actions/)创建自定义节点
- 探索[高级用法](./advanced-usage/)了解更多功能
- 阅读[最佳实践](./best-practices/)学习设计模式
- 查看[快速开始](./getting-started.md)创建第一个行为树
- 学习[自定义节点执行器](./custom-actions.md)创建自定义节点
- 探索[高级用法](./advanced-usage.md)了解更多功能
- 阅读[最佳实践](./best-practices.md)学习设计模式

View File

@@ -1,6 +1,4 @@
---
title: "自定义节点执行器"
---
# 自定义节点执行器
本教程介绍如何为项目创建专用的节点执行器,供策划在编辑器中使用。
@@ -606,107 +604,6 @@ export class RetryDecorator implements INodeExecutor {
}
```
## 在代码中使用自定义执行器
定义了自定义执行器后,可以通过 `BehaviorTreeBuilder``.action()``.condition()` 方法在代码中使用:
### 使用 action() 方法
```typescript
import { BehaviorTreeBuilder, BehaviorTreeStarter } from '@esengine/behavior-tree';
// 使用自定义执行器构建行为树
const tree = BehaviorTreeBuilder.create('CombatAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.selector('Root')
.sequence('AttackSequence')
// 使用自定义动作 - implementationType 匹配装饰器中的定义
.action('AttackAction', 'Attack', { damage: 25 })
.action('MoveToPosition', 'Chase', { speed: 10 })
.end()
.action('DelayAction', 'Idle', { duration: 1.0 })
.end()
.build();
// 启动行为树
const entity = scene.createEntity('Enemy');
BehaviorTreeStarter.start(entity, tree);
```
### 使用 condition() 方法
```typescript
const tree = BehaviorTreeBuilder.create('AI')
.selector('Root')
.sequence('AttackBranch')
// 使用自定义条件
.condition('CheckHealth', 'IsHealthy', { threshold: 50, operator: 'greater' })
.action('AttackAction', 'Attack')
.end()
.end()
.build();
```
### Builder 方法对照表
| 方法 | 说明 | 使用场景 |
|------|------|----------|
| `.action(type, name?, config?)` | 使用自定义动作执行器 | 自定义 Action 类 |
| `.condition(type, name?, config?)` | 使用自定义条件执行器 | 自定义 Condition 类 |
| `.executeAction(name)` | 调用黑板函数 `action_{name}` | 简单逻辑、快速原型 |
| `.executeCondition(name)` | 调用黑板函数 `condition_{name}` | 简单条件判断 |
### 完整示例
```typescript
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
NodeExecutorMetadata,
INodeExecutor,
NodeExecutionContext,
TaskStatus,
NodeType,
BindingHelper
} from '@esengine/behavior-tree';
// 1. 定义自定义执行器
@NodeExecutorMetadata({
implementationType: 'AttackAction',
nodeType: NodeType.Action,
displayName: '攻击',
category: 'Combat',
configSchema: {
damage: { type: 'number', default: 10, supportBinding: true }
}
})
class AttackAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const damage = BindingHelper.getValue<number>(context, 'damage', 10);
console.log(`执行攻击,造成 ${damage} 点伤害!`);
return TaskStatus.Success;
}
}
// 2. 构建行为树
const enemyAI = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.selector('MainBehavior')
.sequence('AttackBranch')
.condition('CheckHealth', 'HasEnoughHealth', { threshold: 20, operator: 'greater' })
.action('AttackAction', 'Attack', { damage: 50 })
.end()
.log('逃跑', 'Flee')
.end()
.build();
// 3. 启动行为树
const entity = scene.createEntity('Enemy');
BehaviorTreeStarter.start(entity, enemyAI);
```
## 注册执行器
### 自动注册
@@ -1123,6 +1020,6 @@ execute(context: NodeExecutionContext): TaskStatus {
## 下一步
- 学习[编辑器工作流](./editor-workflow/)了解如何在编辑器中使用自定义节点
- 阅读[最佳实践](./best-practices/)学习行为树设计模式
- 查看[高级用法](./advanced-usage/)了解更多功能
- 学习[编辑器工作流](./editor-workflow.md)了解如何在编辑器中使用自定义节点
- 阅读[最佳实践](./best-practices.md)学习行为树设计模式
- 查看[高级用法](./advanced-usage.md)了解更多功能

View File

@@ -1,6 +1,4 @@
---
title: "行为树编辑器使用指南"
---
# 行为树编辑器使用指南
行为树编辑器提供了可视化的方式来创建和编辑行为树。
@@ -117,5 +115,5 @@ BehaviorTreeStarter.start(entity, tree);
## 下一步
- 查看[编辑器工作流](./editor-workflow/)了解完整的开发流程
- 查看[自定义节点执行器](./custom-actions/)学习如何扩展节点
- 查看[编辑器工作流](./editor-workflow.md)了解完整的开发流程
- 查看[自定义节点执行器](./custom-actions.md)学习如何扩展节点

View File

@@ -1,6 +1,4 @@
---
title: "编辑器工作流"
---
# 编辑器工作流
本教程介绍如何使用行为树编辑器创建AI并在游戏中加载使用。
@@ -112,7 +110,7 @@ setInterval(() => {
## 实现自定义执行器
要扩展行为树的功能,需要创建自定义执行器(详见[自定义节点执行器](./custom-actions/)
要扩展行为树的功能,需要创建自定义执行器(详见[自定义节点执行器](./custom-actions.md)
```typescript
import {
@@ -250,6 +248,6 @@ setInterval(() => {
## 下一步
- 查看[自定义节点执行器](./custom-actions/)学习如何创建自定义节点
- 查看[高级用法](./advanced-usage/)了解性能优化等高级特性
- 查看[最佳实践](./best-practices/)优化你的AI设计
- 查看[自定义节点执行器](./custom-actions.md)学习如何创建自定义节点
- 查看[高级用法](./advanced-usage.md)了解性能优化等高级特性
- 查看[最佳实践](./best-practices.md)优化你的AI设计

View File

@@ -1,6 +1,4 @@
---
title: "快速开始"
---
# 快速开始
本教程将引导你在5分钟内创建第一个行为树。
@@ -333,11 +331,11 @@ BehaviorTreeStarter.restart(entity);
现在你已经创建了第一个行为树,接下来可以:
1. 学习[核心概念](./core-concepts/)深入理解行为树原理
2. 学习[资产管理](./asset-management/)了解如何加载和复用行为树、使用子树
3. 查看[自定义节点执行器](./custom-actions/)学习如何创建自定义节点
4. 根据你的场景查看集成教程:[Cocos Creator](./cocos-integration/) 或 [Node.js](./nodejs-usage.md)
5. 查看[高级用法](./advanced-usage/)了解更多功能
1. 学习[核心概念](./core-concepts.md)深入理解行为树原理
2. 学习[资产管理](./asset-management.md)了解如何加载和复用行为树、使用子树
3. 查看[自定义节点执行器](./custom-actions.md)学习如何创建自定义节点
4. 根据你的场景查看集成教程:[Cocos Creator](./cocos-integration.md) 或 [Node.js](./nodejs-usage.md)
5. 查看[高级用法](./advanced-usage.md)了解更多功能
## 常见问题
@@ -384,4 +382,4 @@ console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));
内置的`executeAction``executeCondition`节点只是占位符。要实现真正的自定义逻辑,你需要创建自定义执行器:
参见[自定义节点执行器](./custom-actions/)学习如何创建。
参见[自定义节点执行器](./custom-actions.md)学习如何创建。

View File

@@ -1,6 +1,4 @@
---
title: "行为树系统"
---
# 行为树系统
行为树(Behavior Tree)是一种用于游戏AI和自动化控制的强大工具。本框架提供了基于Runtime执行器架构的行为树系统,具有高性能、类型安全、易于扩展的特点。
@@ -43,29 +41,29 @@ title: "行为树系统"
### 入门教程
- **[快速开始](./getting-started/)** - 5分钟上手行为树
- **[核心概念](./core-concepts/)** - 理解行为树的基本原理
- **[快速开始](./getting-started.md)** - 5分钟上手行为树
- **[核心概念](./core-concepts.md)** - 理解行为树的基本原理
### 编辑器使用
- **[编辑器使用指南](./editor-guide/)** - 可视化创建行为树
- **[编辑器工作流](./editor-workflow/)** - 完整的开发流程
- **[编辑器使用指南](./editor-guide.md)** - 可视化创建行为树
- **[编辑器工作流](./editor-workflow.md)** - 完整的开发流程
### 资源管理
- **[资产管理](./asset-management/)** - 加载、管理和复用行为树资产、使用子树
- **[资产管理](./asset-management.md)** - 加载、管理和复用行为树资产、使用子树
### 引擎集成
- **[Cocos Creator 集成](./cocos-integration/)** - 在 Cocos Creator 中使用行为树
- **[Laya 引擎集成](./laya-integration/)** - 在 Laya 中使用行为树
- **[Node.js 服务端使用](./nodejs-usage/)** - 在服务器、聊天机器人等场景中使用行为树
- **[Cocos Creator 集成](./cocos-integration.md)** - 在 Cocos Creator 中使用行为树
- **[Laya 引擎集成](./laya-integration.md)** - 在 Laya 中使用行为树
- **[Node.js 服务端使用](./nodejs-usage.md)** - 在服务器、聊天机器人等场景中使用行为树
### 高级主题
- **[高级用法](./advanced-usage/)** - 性能优化、调试技巧
- **[自定义节点执行器](./custom-actions/)** - 创建自定义行为节点
- **[最佳实践](./best-practices/)** - 行为树设计模式和技巧
- **[高级用法](./advanced-usage.md)** - 性能优化、调试技巧
- **[自定义节点执行器](./custom-actions.md)** - 创建自定义行为节点
- **[最佳实践](./best-practices.md)** - 行为树设计模式和技巧
## 快速示例
@@ -177,23 +175,23 @@ export class AttackAction implements INodeExecutor {
}
```
详细说明请参见[自定义节点执行器](./custom-actions/)。
详细说明请参见[自定义节点执行器](./custom-actions.md)。
## 下一步
建议按照以下顺序学习:
1. 阅读[快速开始](./getting-started/)了解基础用法
2. 学习[核心概念](./core-concepts/)理解行为树原理
3. 学习[资产管理](./asset-management/)了解如何加载和复用行为树、使用子树
1. 阅读[快速开始](./getting-started.md)了解基础用法
2. 学习[核心概念](./core-concepts.md)理解行为树原理
3. 学习[资产管理](./asset-management.md)了解如何加载和复用行为树、使用子树
4. 根据你的场景查看集成教程:
- 客户端游戏:[Cocos Creator](./cocos-integration/) 或 [Laya](./laya-integration.md)
- 服务端应用:[Node.js 服务端使用](./nodejs-usage/)
5. 尝试[编辑器使用指南](./editor-guide/)可视化创建行为树
6. 探索[高级用法](./advanced-usage/)和[自定义节点执行器](./custom-actions.md)提升技能
- 客户端游戏:[Cocos Creator](./cocos-integration.md) 或 [Laya](./laya-integration.md)
- 服务端应用:[Node.js 服务端使用](./nodejs-usage.md)
5. 尝试[编辑器使用指南](./editor-guide.md)可视化创建行为树
6. 探索[高级用法](./advanced-usage.md)和[自定义节点执行器](./custom-actions.md)提升技能
## 获取帮助
- 提交 [Issue](https://github.com/esengine/esengine/issues)
- 提交 [Issue](https://github.com/esengine/ecs-framework/issues)
- 加入社区讨论
- 参考文档中的完整代码示例

View File

@@ -1,6 +1,4 @@
---
title: "Laya 引擎集成"
---
# Laya 引擎集成
本教程将引导你在 Laya 引擎项目中集成和使用行为树系统。
@@ -8,7 +6,7 @@ title: "Laya 引擎集成"
- LayaAir 3.x 或更高版本
- 基本的 TypeScript 知识
- 已完成[快速开始](./getting-started/)教程
- 已完成[快速开始](./getting-started.md)教程
## 安装
@@ -311,5 +309,5 @@ class AIManager {
## 下一步
- 查看[高级用法](./advanced-usage/)
- 学习[最佳实践](./best-practices/)
- 查看[高级用法](./advanced-usage.md)
- 学习[最佳实践](./best-practices.md)

View File

@@ -1,6 +1,4 @@
---
title: "Node.js 服务端使用"
---
# Node.js 服务端使用
本文介绍如何在 Node.js 服务端环境(如游戏服务器、机器人、自动化工具)中使用行为树系统。
@@ -577,6 +575,6 @@ function loadAIState(entity: Entity, savedState: any) {
## 下一步
- 查看[资产管理](./asset-management/)了解资源加载和子树
- 学习[自定义节点执行器](./custom-actions/)创建自定义行为
- 阅读[最佳实践](./best-practices/)优化你的服务端AI
- 查看[资产管理](./asset-management.md)了解资源加载和子树
- 学习[自定义节点执行器](./custom-actions.md)创建自定义行为
- 阅读[最佳实践](./best-practices.md)优化你的服务端AI

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

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

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

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

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

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

View File

@@ -1,6 +1,4 @@
---
title: "事件系统"
---
# 事件系统
ECS 框架内置了强大的类型安全事件系统,支持同步/异步事件、优先级、批处理等高级功能。事件系统是实现组件间通信、系统间协作的核心机制。

View File

@@ -1,30 +1,10 @@
---
title: 快速开始
description: 5 分钟上手 ESEngine ECS 框架
---
# 快速开始
本指南将帮助你快速上手 ECS Framework从安装到创建第一个 ECS 应用。
## 安装
### 使用 CLI推荐
在现有项目中添加 ECS 的最简单方式:
```bash
# 在项目目录中运行
npx @esengine/cli init
```
CLI 会自动检测项目类型Cocos Creator 2.x/3.x、LayaAir 3.x 或 Node.js并生成相应的集成代码包括
- `ECSManager` 组件/脚本 - 负责 ECS 生命周期管理
- 示例组件和系统 - 帮助快速上手
- 自动安装依赖
### NPM 手动安装
如果你更喜欢手动配置,可以直接安装:
### NPM 安装
```bash
# 使用 npm
@@ -353,39 +333,27 @@ function gameLoop(deltaTime: number) {
## 与游戏引擎集成
### Laya 3.x 引擎集成
推荐使用 `Laya.Script` 组件来管理 ECS 生命周期:
### Laya 引擎集成
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { Stage } from "laya/display/Stage";
import { Laya } from "Laya";
import { Core } from '@esengine/ecs-framework';
const { regClass } = Laya;
// 初始化 Laya
Laya.init(800, 600).then(() => {
// 初始化 ECS
Core.create(true);
Core.setScene(new GameScene());
@regClass()
export class ECSManager extends Laya.Script {
private ecsScene = new GameScene();
onAwake(): void {
// 初始化 ECS
Core.create({ debug: true });
Core.setScene(this.ecsScene);
}
onUpdate(): void {
// 自动更新全局服务和场景
Core.update(Laya.timer.delta / 1000);
}
onDestroy(): void {
// 清理资源
Core.destroy();
}
}
// 启动游戏循环
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime); // 自动更新全局服务和场景
});
});
```
在 Laya IDE 中,将 `ECSManager` 脚本挂载到场景中的节点上即可。
### Cocos Creator 集成
```typescript

View File

@@ -1,6 +1,4 @@
---
title: "层级系统"
---
# 层级系统
在游戏开发中实体间的父子层级关系是常见需求。ECS Framework 采用组件化方式管理层级关系,通过 `HierarchyComponent``HierarchySystem` 实现,完全遵循 ECS 组合原则。
@@ -8,7 +6,7 @@ title: "层级系统"
### 为什么不在 Entity 中内置层级?
传统的游戏对象模型将层级关系内置于实体中。ECS Framework 选择组件化方案的原因:
传统的游戏对象模型(如 Unity 的 GameObject将层级关系内置于实体中。ECS Framework 选择组件化方案的原因:
1. **ECS 组合原则**:层级是一种"功能",应该通过组件添加,而非所有实体都具备
2. **按需使用**:只有需要层级关系的实体才添加 `HierarchyComponent`
@@ -434,6 +432,6 @@ const found = hierarchySystem.findChild(parent, "Child");
## 下一步
- 了解 [实体类](./entity/) 的其他功能
- 了解 [场景管理](./scene/) 如何组织实体和系统
- 了解 [组件系统](./component/) 如何定义和使用组件
- 了解 [实体类](./entity.md) 的其他功能
- 了解 [场景管理](./scene.md) 如何组织实体和系统
- 了解 [组件系统](./component.md) 如何定义和使用组件

View File

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

View File

@@ -1,6 +1,4 @@
---
title: "日志系统"
---
# 日志系统
ECS 框架提供了功能强大的分级日志系统,支持多种日志级别、颜色输出、自定义前缀和灵活的配置选项。日志系统可以帮助开发者调试代码和监控应用运行状态。

View File

@@ -1,6 +1,4 @@
---
title: "平台适配器"
---
# 平台适配器
## 概述
@@ -16,7 +14,7 @@ ECS框架提供了平台适配器接口允许用户为不同的运行环境
## 支持的平台
### 🌐 [浏览器适配器](./platform-adapter/browser/)
### 🌐 [浏览器适配器](./platform-adapter/browser.md)
支持所有现代浏览器环境,包括 Chrome、Firefox、Safari、Edge 等。
@@ -30,7 +28,7 @@ ECS框架提供了平台适配器接口允许用户为不同的运行环境
---
### 📱 [微信小游戏适配器](./platform-adapter/wechat-minigame/)
### 📱 [微信小游戏适配器](./platform-adapter/wechat-minigame.md)
专为微信小游戏环境设计处理微信小游戏的特殊限制和API。
@@ -44,7 +42,7 @@ ECS框架提供了平台适配器接口允许用户为不同的运行环境
---
### 🖥️ [Node.js适配器](./platform-adapter/nodejs/)
### 🖥️ [Node.js适配器](./platform-adapter/nodejs.md)
为 Node.js 服务器环境提供支持,适用于游戏服务器和计算服务器。

View File

@@ -1,6 +1,4 @@
---
title: "浏览器适配器"
---
# 浏览器适配器
## 概述

View File

@@ -1,6 +1,4 @@
---
title: "Node.js 适配器"
---
# Node.js 适配器
## 概述

View File

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

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

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

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

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

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

@@ -0,0 +1,661 @@
# 场景管理
在 ECS 架构中场景Scene是游戏世界的容器负责管理实体、系统和组件的生命周期。场景提供了完整的 ECS 运行环境。
## 基本概念
场景是 ECS 框架的核心容器,提供:
- 实体的创建、管理和销毁
- 系统的注册和执行调度
- 组件的存储和查询
- 事件系统支持
- 性能监控和调试信息
## 场景管理方式
ECS Framework 提供了两种场景管理方式:
1. **[SceneManager](./scene-manager.md)** - 适用于 95% 的游戏应用
- 单人游戏、简单多人游戏、移动游戏
- 轻量级,简单直观的 API
- 支持场景切换
2. **[WorldManager](./world-manager.md)** - 适用于高级多世界隔离场景
- MMO 游戏服务器、游戏房间系统
- 多 World 管理,每个 World 可包含多个场景
- 完全隔离的独立环境
本文档重点介绍 Scene 类本身的使用方法。关于场景管理器的详细信息,请查看对应的文档。
## 创建场景
### 继承 Scene 类
**推荐做法:继承 Scene 类来创建自定义场景**
```typescript
import { Scene, EntitySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
// 设置场景名称
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
this.addSystem(new PhysicsSystem());
// 创建初始实体
this.createInitialEntities();
}
private createInitialEntities(): void {
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
player.addComponent(new PlayerController());
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
enemy.addComponent(new Health(50));
enemy.addComponent(new EnemyAI());
}
}
public onStart(): void {
console.log("游戏场景已启动");
// 场景启动时的逻辑
}
public unload(): void {
console.log("游戏场景已卸载");
// 场景卸载时的清理逻辑
}
}
```
### 使用场景配置
```typescript
import { ISceneConfig } from '@esengine/ecs-framework';
const config: ISceneConfig = {
name: "MainGame",
enableEntityDirectUpdate: false
};
class ConfiguredScene extends Scene {
constructor() {
super(config);
}
}
```
## 场景生命周期
场景提供了完整的生命周期管理:
```typescript
class ExampleScene extends Scene {
protected initialize(): void {
// 场景初始化:设置系统和初始实体
console.log("场景初始化");
}
public onStart(): void {
// 场景开始运行:游戏逻辑开始执行
console.log("场景开始运行");
}
public unload(): void {
// 场景卸载:清理资源
console.log("场景卸载");
}
}
// 使用场景(由框架自动管理生命周期)
const scene = new ExampleScene();
// 场景的 initialize(), begin(), update(), end() 由框架自动调用
```
**生命周期方法**
1. `initialize()` - 场景初始化,设置系统和初始实体
2. `begin()` / `onStart()` - 场景开始运行
3. `update()` - 每帧更新(由场景管理器调用)
4. `end()` / `unload()` - 场景卸载,清理资源
## 实体管理
### 创建实体
```typescript
class EntityScene extends Scene {
createGameEntities(): void {
// 创建单个实体
const player = this.createEntity("Player");
// 批量创建实体(高性能)
const bullets = this.createEntities(100, "Bullet");
// 为批量创建的实体添加组件
bullets.forEach((bullet, index) => {
bullet.addComponent(new Position(index * 10, 100));
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
});
}
}
```
### 查找实体
```typescript
class SearchScene extends Scene {
findEntities(): void {
// 按名称查找
const player = this.findEntity("Player");
const player2 = this.getEntityByName("Player"); // 别名方法
// 按 ID 查找
const entity = this.findEntityById(123);
// 按标签查找
const enemies = this.findEntitiesByTag(2);
const enemies2 = this.getEntitiesByTag(2); // 别名方法
if (player) {
console.log(`找到玩家: ${player.name}`);
}
console.log(`找到 ${enemies.length} 个敌人`);
}
}
```
### 销毁实体
```typescript
class DestroyScene extends Scene {
cleanupEntities(): void {
// 销毁所有实体
this.destroyAllEntities();
// 单个实体的销毁通过实体本身
const enemy = this.findEntity("Enemy_1");
if (enemy) {
enemy.destroy(); // 实体会自动从场景中移除
}
}
}
```
## 系统管理
### 添加和移除系统
```typescript
class SystemScene extends Scene {
protected initialize(): void {
// 添加系统
const movementSystem = new MovementSystem();
this.addSystem(movementSystem);
// 设置系统更新顺序
movementSystem.updateOrder = 1;
// 添加更多系统
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
}
public removeUnnecessarySystems(): void {
// 获取系统
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
// 移除系统
if (physicsSystem) {
this.removeSystem(physicsSystem);
}
}
}
```
### 系统访问
```typescript
class SystemAccessScene extends Scene {
public pausePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = false;
}
}
public getAllSystems(): EntitySystem[] {
return this.systems; // 获取所有系统
}
}
```
## 事件系统
场景内置了类型安全的事件系统:
```typescript
class EventScene extends Scene {
protected initialize(): void {
// 监听事件
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this));
}
private onPlayerDied(data: any): void {
console.log('玩家死亡事件');
// 处理玩家死亡
}
private onEnemySpawned(data: any): void {
console.log('敌人生成事件');
// 处理敌人生成
}
private onLevelComplete(data: any): void {
console.log('关卡完成事件');
// 处理关卡完成
}
public triggerGameEvent(): void {
// 发送事件(同步)
this.eventSystem.emitSync('custom_event', {
message: "这是自定义事件",
timestamp: Date.now()
});
// 发送事件(异步)
this.eventSystem.emit('async_event', {
data: "异步事件数据"
});
}
}
```
### 事件系统 API
```typescript
// 监听事件
this.eventSystem.on('event_name', callback);
// 监听一次(自动取消订阅)
this.eventSystem.once('event_name', callback);
// 取消监听
this.eventSystem.off('event_name', callback);
// 同步发送事件
this.eventSystem.emitSync('event_name', data);
// 异步发送事件
this.eventSystem.emit('event_name', data);
// 清除所有事件监听
this.eventSystem.clear();
```
## 场景统计和调试
### 获取场景统计
```typescript
class StatsScene extends Scene {
public showStats(): void {
const stats = this.getStats();
console.log(`实体数量: ${stats.entityCount}`);
console.log(`系统数量: ${stats.processorCount}`);
console.log('组件存储统计:', stats.componentStorageStats);
}
public showDebugInfo(): void {
const debugInfo = this.getDebugInfo();
console.log('场景调试信息:', debugInfo);
// 显示所有实体信息
debugInfo.entities.forEach(entity => {
console.log(`实体 ${entity.name}(${entity.id}): ${entity.componentCount} 个组件`);
console.log('组件类型:', entity.componentTypes);
});
// 显示所有系统信息
debugInfo.processors.forEach(processor => {
console.log(`系统 ${processor.name}: 处理 ${processor.entityCount} 个实体`);
});
}
}
```
## 组件查询
Scene 提供了强大的组件查询系统:
```typescript
class QueryScene extends Scene {
protected initialize(): void {
// 创建一些实体
for (let i = 0; i < 10; i++) {
const entity = this.createEntity(`Entity_${i}`);
entity.addComponent(new Transform(i * 10, 0));
entity.addComponent(new Velocity(1, 0));
if (i % 2 === 0) {
entity.addComponent(new Renderer());
}
}
}
public queryEntities(): void {
// 通过 QuerySystem 查询
const entities = this.querySystem.query([Transform, Velocity]);
console.log(`找到 ${entities.length} 个有 Transform 和 Velocity 的实体`);
// 使用 ECS 流式 API如果通过 SceneManager
// const api = sceneManager.api;
// const entities = api?.find(Transform, Velocity);
}
}
```
## 性能监控
Scene 内置了性能监控功能:
```typescript
class PerformanceScene extends Scene {
public showPerformance(): void {
// 获取性能数据
const perfData = this.performanceMonitor?.getPerformanceData();
if (perfData) {
console.log('FPS:', perfData.fps);
console.log('帧时间:', perfData.frameTime);
console.log('实体更新时间:', perfData.entityUpdateTime);
console.log('系统更新时间:', perfData.systemUpdateTime);
}
// 获取性能报告
const report = this.performanceMonitor?.generateReport();
if (report) {
console.log('性能报告:', report);
}
}
}
```
## 最佳实践
### 1. 场景职责分离
```typescript
// 好的场景设计 - 职责清晰
class MenuScene extends Scene {
// 只处理菜单相关逻辑
}
class GameScene extends Scene {
// 只处理游戏玩法逻辑
}
class InventoryScene extends Scene {
// 只处理物品栏逻辑
}
// 避免的场景设计 - 职责混乱
class MegaScene extends Scene {
// 包含菜单、游戏、物品栏等所有逻辑
}
```
### 2. 合理的系统组织
```typescript
class OrganizedScene extends Scene {
protected initialize(): void {
// 按功能和依赖关系添加系统
this.addInputSystems();
this.addLogicSystems();
this.addRenderSystems();
}
private addInputSystems(): void {
this.addSystem(new InputSystem());
}
private addLogicSystems(): void {
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new CollisionSystem());
}
private addRenderSystems(): void {
this.addSystem(new RenderSystem());
this.addSystem(new UISystem());
}
}
```
### 3. 资源管理
```typescript
class ResourceScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
// 加载场景所需资源
this.textures.set('player', this.loadTexture('player.png'));
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
}
public unload(): void {
// 清理资源
this.textures.clear();
this.sounds.clear();
console.log('场景资源已清理');
}
private loadTexture(path: string): any {
// 加载纹理
return null;
}
private loadSound(path: string): any {
// 加载音效
return null;
}
}
```
### 4. 事件处理规范
```typescript
class EventHandlingScene extends Scene {
protected initialize(): void {
// 集中管理事件监听
this.setupEventListeners();
}
private setupEventListeners(): void {
this.eventSystem.on('game_pause', this.onGamePause.bind(this));
this.eventSystem.on('game_resume', this.onGameResume.bind(this));
this.eventSystem.on('player_input', this.onPlayerInput.bind(this));
}
private onGamePause(): void {
// 暂停游戏逻辑
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = false;
}
});
}
private onGameResume(): void {
// 恢复游戏逻辑
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = true;
}
});
}
private onPlayerInput(data: any): void {
// 处理玩家输入
}
public unload(): void {
// 清理事件监听
this.eventSystem.clear();
}
}
```
### 5. 初始化顺序
```typescript
class ProperInitScene extends Scene {
protected initialize(): void {
// 1. 首先设置场景配置
this.name = "GameScene";
// 2. 然后添加系统(按依赖顺序)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
// 3. 最后创建实体
this.createEntities();
// 4. 设置事件监听
this.setupEvents();
}
private createEntities(): void {
// 创建实体
}
private setupEvents(): void {
// 设置事件监听
}
}
```
## 完整示例
```typescript
import { Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// 定义组件
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// 定义系统
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// 定义场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
// 设置事件监听
this.eventSystem.on('player_died', () => {
console.log('玩家死亡!');
});
}
public onStart(): void {
console.log('游戏场景启动');
}
public unload(): void {
console.log('游戏场景卸载');
this.eventSystem.clear();
}
}
// 使用场景
// 方式1通过 SceneManager推荐
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
// 方式2通过 WorldManager高级用例
import { WorldManager } from '@esengine/ecs-framework';
const worldManager = Core.services.resolve(WorldManager);
const world = worldManager.createWorld('game');
world.createScene('main', new GameScene());
world.setSceneActive('main', true);
```
## 下一步
- 了解 [SceneManager](./scene-manager.md) - 适用于大多数游戏的简单场景管理
- 了解 [WorldManager](./world-manager.md) - 适用于需要多世界隔离的高级场景
场景是 ECS 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。

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

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

View File

@@ -0,0 +1,828 @@
# 服务容器
服务容器ServiceContainer是 ECS Framework 的依赖注入容器,负责管理框架中所有服务的注册、解析和生命周期。通过服务容器,你可以实现松耦合的架构设计,提高代码的可测试性和可维护性。
## 概述
### 什么是服务容器
服务容器是一个轻量级的依赖注入DI容器它提供了
- **服务注册**: 将服务类型注册到容器中
- **服务解析**: 从容器中获取服务实例
- **生命周期管理**: 自动管理服务实例的创建和销毁
- **依赖注入**: 自动解析服务之间的依赖关系
### 核心概念
#### 服务Service
服务是实现了 `IService` 接口的类,必须提供 `dispose()` 方法用于资源清理:
```typescript
import { IService } from '@esengine/ecs-framework';
class MyService implements IService {
constructor() {
// 初始化逻辑
}
dispose(): void {
// 清理资源
}
}
```
#### 服务标识符ServiceIdentifier
服务标识符用于在容器中唯一标识一个服务,支持两种类型:
- **类构造函数**: 直接使用服务类作为标识符,适用于具体实现类
- **Symbol**: 使用 Symbol 作为标识符,适用于接口抽象(推荐用于插件和跨包场景)
```typescript
// 方式1: 使用类作为标识符
Core.services.registerSingleton(DataService);
const data = Core.services.resolve(DataService);
// 方式2: 使用 Symbol 作为标识符(推荐用于接口)
const IFileSystem = Symbol.for('IFileSystem');
Core.services.registerInstance(IFileSystem, new TauriFileSystem());
const fs = Core.services.resolve<IFileSystem>(IFileSystem);
```
> **提示**: 使用 `Symbol.for()` 而非 `Symbol()` 可确保跨包/跨模块共享同一个标识符。详见[高级用法 - 接口与 Symbol 标识符模式](#接口与-symbol-标识符模式)。
#### 生命周期
服务容器支持两种生命周期:
- **Singleton单例**: 整个应用程序生命周期内只有一个实例,所有解析请求返回同一个实例
- **Transient瞬时**: 每次解析都创建新的实例
## 基础使用
### 访问服务容器
ECS Framework 提供了三级服务容器:
> **版本说明**World 服务容器功能在 v2.2.13+ 版本中可用
#### Core 级别服务容器
应用程序全局服务容器,可以通过 `Core.services` 访问:
```typescript
import { Core } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 访问全局服务容器
const container = Core.services;
```
#### World 级别服务容器
每个 World 拥有独立的服务容器,用于管理 World 范围内的服务:
```typescript
import { World } from '@esengine/ecs-framework';
// 创建 World
const world = new World({ name: 'GameWorld' });
// 访问 World 级别的服务容器
const worldContainer = world.services;
// 注册 World 级别的服务
world.services.registerSingleton(RoomManager);
```
#### Scene 级别服务容器
每个 Scene 拥有独立的服务容器,用于管理 Scene 范围内的服务:
```typescript
// 访问 Scene 级别的服务容器
const sceneContainer = scene.services;
// 注册 Scene 级别的服务
scene.services.registerSingleton(PhysicsSystem);
```
#### 服务容器层级
```
Core.services (应用程序全局)
└─ World.services (World 级别)
└─ Scene.services (Scene 级别)
```
不同级别的服务容器是独立的,服务不会自动向上或向下查找。选择合适的容器级别:
- **Core.services**: 应用程序级别的全局服务(配置、插件管理器等)
- **World.services**: World 级别的服务(房间管理器、多人游戏状态等)
- **Scene.services**: Scene 级别的服务ECS 系统、场景特定逻辑等)
### 注册服务
#### 注册单例服务
单例服务在首次解析时创建,之后所有解析请求都返回同一个实例:
```typescript
class DataService implements IService {
private data: Map<string, any> = new Map();
getData(key: string) {
return this.data.get(key);
}
setData(key: string, value: any) {
this.data.set(key, value);
}
dispose(): void {
this.data.clear();
}
}
// 注册单例服务
Core.services.registerSingleton(DataService);
```
#### 注册瞬时服务
瞬时服务每次解析都创建新实例,适用于无状态或短生命周期的服务:
```typescript
class CommandService implements IService {
execute(command: string) {
console.log(`Executing: ${command}`);
}
dispose(): void {
// 清理资源
}
}
// 注册瞬时服务
Core.services.registerTransient(CommandService);
```
#### 注册服务实例
直接注册已创建的实例,自动视为单例:
```typescript
const config = new ConfigService();
config.load('./config.json');
// 注册实例
Core.services.registerInstance(ConfigService, config);
```
#### 使用工厂函数注册
工厂函数允许你在创建服务时执行自定义逻辑:
```typescript
Core.services.registerSingleton(LoggerService, (container) => {
const logger = new LoggerService();
logger.setLevel('debug');
return logger;
});
```
### 解析服务
#### resolve 方法
解析服务实例,如果服务未注册会抛出异常:
```typescript
// 解析服务
const dataService = Core.services.resolve(DataService);
dataService.setData('player', { name: 'Alice', score: 100 });
// 单例服务,多次解析返回同一个实例
const same = Core.services.resolve(DataService);
console.log(same === dataService); // true
```
#### tryResolve 方法
尝试解析服务,如果未注册返回 null 而不抛出异常:
```typescript
const optional = Core.services.tryResolve(OptionalService);
if (optional) {
optional.doSomething();
}
```
#### isRegistered 方法
检查服务是否已注册:
```typescript
if (Core.services.isRegistered(DataService)) {
const service = Core.services.resolve(DataService);
}
```
## 内置服务
Core 在初始化时自动注册了以下内置服务:
### TimerManager
定时器管理器,负责管理所有游戏定时器:
```typescript
const timerManager = Core.services.resolve(TimerManager);
// 创建定时器
timerManager.schedule(1.0, false, null, (timer) => {
console.log('1秒后执行');
});
```
### PerformanceMonitor
性能监控器,监控游戏性能并提供优化建议:
```typescript
const monitor = Core.services.resolve(PerformanceMonitor);
// 启用性能监控
monitor.enable();
// 获取性能数据
const fps = monitor.getFPS();
```
### SceneManager
场景管理器,管理单场景应用的场景生命周期:
```typescript
const sceneManager = Core.services.resolve(SceneManager);
// 设置当前场景
sceneManager.setScene(new GameScene());
// 获取当前场景
const currentScene = sceneManager.currentScene;
// 延迟切换场景
sceneManager.loadScene(new MenuScene());
// 更新场景
sceneManager.update();
```
### WorldManager
世界管理器,管理多个独立的 World 实例(高级用例):
```typescript
const worldManager = Core.services.resolve(WorldManager);
// 创建独立的游戏世界
const gameWorld = worldManager.createWorld('game_room_001', {
name: 'GameRoom',
maxScenes: 5
});
// 在World中创建场景
const scene = gameWorld.createScene('battle', new BattleScene());
gameWorld.setSceneActive('battle', true);
// 更新所有World
worldManager.updateAll();
```
**适用场景**:
- SceneManager: 适用于 95% 的游戏(单人游戏、简单多人游戏)
- WorldManager: 适用于 MMO 服务器、游戏房间系统等需要完全隔离的多世界应用
### PoolManager
对象池管理器,管理所有对象池:
```typescript
const poolManager = Core.services.resolve(PoolManager);
// 创建对象池
const bulletPool = poolManager.createPool('bullets', () => new Bullet(), 100);
```
### PluginManager
插件管理器,管理插件的安装和卸载:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// 获取所有已安装的插件
const plugins = pluginManager.getAllPlugins();
```
## 依赖注入
ECS Framework 提供了装饰器来简化依赖注入。
### @Injectable 装饰器
标记类为可注入的服务:
```typescript
import { Injectable, IService } from '@esengine/ecs-framework';
@Injectable()
class GameService implements IService {
constructor() {
console.log('GameService created');
}
dispose(): void {
console.log('GameService disposed');
}
}
```
### @InjectProperty 装饰器
通过属性装饰器注入依赖。注入时机是在构造函数执行后、`onInitialize()` 调用前完成:
```typescript
import { Injectable, InjectProperty, IService } from '@esengine/ecs-framework';
@Injectable()
class PlayerService implements IService {
@InjectProperty(DataService)
private data!: DataService;
@InjectProperty(GameService)
private game!: GameService;
dispose(): void {
// 清理资源
}
}
```
在 EntitySystem 中使用属性注入:
```typescript
@Injectable()
class CombatSystem extends EntitySystem {
@InjectProperty(TimeService)
private timeService!: TimeService;
@InjectProperty(AudioService)
private audio!: AudioService;
constructor() {
super(Matcher.all(Health, Attack));
}
onInitialize(): void {
// 此时属性已注入完成,可以安全使用
console.log('Delta time:', this.timeService.getDeltaTime());
}
processEntity(entity: Entity): void {
// 使用注入的服务
this.audio.playSound('attack');
}
}
```
> **注意**: 属性声明时使用 `!` 断言(如 `private data!: DataService`),表示该属性会在使用前被注入。
### 注册可注入服务
使用 `registerInjectable` 自动处理依赖注入:
```typescript
import { registerInjectable } from '@esengine/ecs-framework';
// 注册服务(会自动解析 @InjectProperty 依赖)
registerInjectable(Core.services, PlayerService);
// 解析时会自动注入属性依赖
const player = Core.services.resolve(PlayerService);
```
### @Updatable 装饰器
标记服务为可更新的,使其在每帧自动被调用:
```typescript
import { Injectable, Updatable, IService, IUpdatable } from '@esengine/ecs-framework';
@Injectable()
@Updatable() // 默认优先级为0
class PhysicsService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧更新物理模拟
}
dispose(): void {
// 清理资源
}
}
// 指定更新优先级(数值越小越先执行)
@Injectable()
@Updatable(10)
class RenderService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧渲染
}
dispose(): void {
// 清理资源
}
}
```
使用 `@Updatable` 装饰器的服务会被 Core 自动调用,无需手动管理:
```typescript
// Core.update() 会自动调用所有@Updatable服务的update方法
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有可更新服务
}
```
## 自定义服务
### 创建自定义服务
实现 `IService` 接口并注册到容器:
```typescript
import { IService } from '@esengine/ecs-framework';
class AudioService implements IService {
private sounds: Map<string, HTMLAudioElement> = new Map();
play(soundId: string) {
const sound = this.sounds.get(soundId);
if (sound) {
sound.play();
}
}
load(soundId: string, url: string) {
const audio = new Audio(url);
this.sounds.set(soundId, audio);
}
dispose(): void {
// 停止所有音效并清理
for (const sound of this.sounds.values()) {
sound.pause();
sound.src = '';
}
this.sounds.clear();
}
}
// 注册自定义服务
Core.services.registerSingleton(AudioService);
// 使用服务
const audio = Core.services.resolve(AudioService);
audio.load('jump', '/sounds/jump.mp3');
audio.play('jump');
```
### 服务间依赖
服务可以依赖其他服务:
```typescript
@Injectable()
class ConfigService implements IService {
private config: any = {};
get(key: string) {
return this.config[key];
}
dispose(): void {
this.config = {};
}
}
@Injectable()
class NetworkService implements IService {
constructor(
@Inject(ConfigService) private config: ConfigService
) {
// 使用配置服务
const apiUrl = this.config.get('apiUrl');
}
dispose(): void {
// 清理网络连接
}
}
// 注册服务(按依赖顺序)
registerInjectable(Core.services, ConfigService);
registerInjectable(Core.services, NetworkService);
```
## 高级用法
### 接口与 Symbol 标识符模式
在大型项目或需要跨平台适配的游戏中,推荐使用"接口 + Symbol.for 标识符"模式。这种模式实现了真正的依赖倒置,让代码依赖于抽象而非具体实现。
#### 为什么使用 Symbol.for
- **跨包共享**: `Symbol.for('key')` 在全局 Symbol 注册表中创建/获取 Symbol确保不同包中使用相同的标识符
- **接口解耦**: 消费者只依赖接口定义,不依赖具体实现类
- **可替换实现**: 可以在运行时注入不同的实现(如测试 Mock、不同平台适配
#### 定义接口和标识符
以音频服务为例游戏需要在不同平台Web、微信小游戏、原生App使用不同的音频实现
```typescript
// IAudioService.ts - 定义接口和标识符
export interface IAudioService {
dispose(): void;
playSound(id: string): void;
playMusic(id: string, loop?: boolean): void;
stopMusic(): void;
setVolume(volume: number): void;
preload(id: string, url: string): Promise<void>;
}
// 使用 Symbol.for 确保跨包共享同一个 Symbol
export const IAudioService = Symbol.for('IAudioService');
```
#### 实现接口
```typescript
// WebAudioService.ts - Web 平台实现
import { IAudioService } from './IAudioService';
export class WebAudioService implements IAudioService {
private audioContext: AudioContext;
private sounds: Map<string, AudioBuffer> = new Map();
constructor() {
this.audioContext = new AudioContext();
}
playSound(id: string): void {
const buffer = this.sounds.get(id);
if (buffer) {
const source = this.audioContext.createBufferSource();
source.buffer = buffer;
source.connect(this.audioContext.destination);
source.start();
}
}
async preload(id: string, url: string): Promise<void> {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
this.sounds.set(id, audioBuffer);
}
// ... 其他方法实现
dispose(): void {
this.audioContext.close();
this.sounds.clear();
}
}
```
```typescript
// WechatAudioService.ts - 微信小游戏平台实现
export class WechatAudioService implements IAudioService {
private innerAudioContexts: Map<string, WechatMinigame.InnerAudioContext> = new Map();
playSound(id: string): void {
const ctx = this.innerAudioContexts.get(id);
if (ctx) {
ctx.play();
}
}
async preload(id: string, url: string): Promise<void> {
const ctx = wx.createInnerAudioContext();
ctx.src = url;
this.innerAudioContexts.set(id, ctx);
}
// ... 其他方法实现
dispose(): void {
for (const ctx of this.innerAudioContexts.values()) {
ctx.destroy();
}
this.innerAudioContexts.clear();
}
}
```
#### 注册和使用
```typescript
import { IAudioService } from './IAudioService';
import { WebAudioService } from './WebAudioService';
import { WechatAudioService } from './WechatAudioService';
// 根据平台注册不同实现
if (typeof wx !== 'undefined') {
Core.services.registerInstance(IAudioService, new WechatAudioService());
} else {
Core.services.registerInstance(IAudioService, new WebAudioService());
}
// 业务代码中使用 - 不关心具体实现
const audio = Core.services.resolve<IAudioService>(IAudioService);
await audio.preload('explosion', '/sounds/explosion.mp3');
audio.playSound('explosion');
```
#### 跨模块使用
```typescript
// 在游戏系统中使用
import { IAudioService } from '@mygame/core';
class CombatSystem extends EntitySystem {
private audio: IAudioService;
initialize(): void {
// 获取音频服务,不需要知道具体实现
this.audio = this.scene.services.resolve<IAudioService>(IAudioService);
}
onEntityDeath(entity: Entity): void {
this.audio.playSound('death');
}
}
```
#### Symbol vs Symbol.for
```typescript
// Symbol() - 每次创建唯一的 Symbol
const sym1 = Symbol('test');
const sym2 = Symbol('test');
console.log(sym1 === sym2); // false - 不同的 Symbol
// Symbol.for() - 在全局注册表中共享
const sym3 = Symbol.for('test');
const sym4 = Symbol.for('test');
console.log(sym3 === sym4); // true - 同一个 Symbol
// 跨包场景
// package-a/index.ts
export const IMyService = Symbol.for('IMyService');
// package-b/index.ts (不同的包)
const IMyService = Symbol.for('IMyService');
// 与 package-a 中的是同一个 Symbol
```
### 循环依赖检测
服务容器会自动检测循环依赖:
```typescript
// A 依赖 BB 依赖 A
@Injectable()
class ServiceA implements IService {
constructor(@Inject(ServiceB) b: ServiceB) {}
dispose(): void {}
}
@Injectable()
class ServiceB implements IService {
constructor(@Inject(ServiceA) a: ServiceA) {}
dispose(): void {}
}
// 解析时会抛出错误: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
```
### 获取所有服务
```typescript
// 获取所有已注册的服务类型
const types = Core.services.getRegisteredServices();
// 获取所有已实例化的服务实例
const instances = Core.services.getAll();
```
### 服务清理
```typescript
// 注销单个服务
Core.services.unregister(MyService);
// 清空所有服务会调用每个服务的dispose方法
Core.services.clear();
```
## 最佳实践
### 服务命名
服务类名应该以 `Service``Manager` 结尾,清晰表达其职责:
```typescript
class PlayerService implements IService {}
class AudioManager implements IService {}
class NetworkService implements IService {}
```
### 资源清理
始终在 `dispose()` 方法中清理资源:
```typescript
class ResourceService implements IService {
private resources: Map<string, Resource> = new Map();
dispose(): void {
// 释放所有资源
for (const resource of this.resources.values()) {
resource.release();
}
this.resources.clear();
}
}
```
### 避免过度使用
不要把所有类都注册为服务,服务应该是:
- 全局单例或需要共享状态
- 需要在多处使用
- 生命周期需要管理
- 需要依赖注入
对于简单的工具类或数据类,直接创建实例即可。
### 依赖方向
保持清晰的依赖方向,避免循环依赖:
```
高层服务 -> 中层服务 -> 底层服务
GameLogic -> DataService -> ConfigService
```
## 常见问题
### 服务未注册错误
**问题**: `Error: Service MyService is not registered`
**解决**:
```typescript
// 确保服务已注册
Core.services.registerSingleton(MyService);
// 或者使用tryResolve
const service = Core.services.tryResolve(MyService);
if (!service) {
console.log('Service not found');
}
```
### 循环依赖错误
**问题**: `Circular dependency detected`
**解决**: 重新设计服务依赖关系,引入中间服务或使用事件系统解耦。
### 何时使用单例 vs 瞬时
- **单例**: 管理器类、配置、缓存、状态管理
- **瞬时**: 命令对象、请求处理器、临时任务
## 相关链接
- [插件系统](./plugin-system.md) - 使用服务容器注册插件服务
- [快速开始](./getting-started.md) - Core 初始化和基础使用
- [系统架构](./system.md) - 在系统中使用服务

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

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

View File

@@ -1,6 +1,4 @@
---
title: "时间和定时器系统"
---
# 时间和定时器系统
ECS 框架提供了完整的时间管理和定时器系统,包括时间缩放、帧时间计算和灵活的定时器调度功能。
@@ -32,64 +30,6 @@ class GameSystem extends EntitySystem {
}
```
### 游戏暂停
框架提供两种暂停方式,适用于不同场景:
#### Core.paused推荐
`Core.paused` 是**真正的暂停**,设置后整个游戏循环停止:
```typescript
import { Core } from '@esengine/ecs-framework';
class PauseMenuSystem extends EntitySystem {
public pauseGame(): void {
// 真正暂停 - 所有系统停止执行
Core.paused = true;
console.log('游戏已暂停');
}
public resumeGame(): void {
// 恢复游戏
Core.paused = false;
console.log('游戏已恢复');
}
public togglePause(): void {
Core.paused = !Core.paused;
console.log(Core.paused ? '游戏已暂停' : '游戏已恢复');
}
}
```
#### Time.timeScale = 0
`Time.timeScale = 0` 只是让 `deltaTime` 变为 0**系统仍然在执行**
```typescript
class SlowMotionSystem extends EntitySystem {
public freezeTime(): void {
// 时间冻结 - 系统仍在执行,只是 deltaTime = 0
Time.timeScale = 0;
}
}
```
#### 两种方式对比
| 特性 | `Core.paused = true` | `Time.timeScale = 0` |
|------|---------------------|---------------------|
| 系统执行 | ❌ 完全停止 | ✅ 仍在执行 |
| CPU 开销 | 零 | 正常开销 |
| Time 更新 | ❌ 停止 | ✅ 继续deltaTime=0 |
| 定时器 | ❌ 停止 | ✅ 继续(但时间不走) |
| 适用场景 | 暂停菜单、游戏暂停 | 慢动作、时间冻结特效 |
**推荐**
- 暂停菜单、真正的游戏暂停 → 使用 `Core.paused = true`
- 慢动作、子弹时间等特效 → 使用 `Time.timeScale`
### 时间缩放
Time 类支持时间缩放功能,可以实现慢动作、快进等效果:
@@ -108,10 +48,10 @@ class TimeControlSystem extends EntitySystem {
console.log('快进模式启用');
}
public enableBulletTime(): void {
// 子弹时间效果10%速度
Time.timeScale = 0.1;
console.log('子弹时间启用');
public pauseGame(): void {
// 暂停游戏(时间静止
Time.timeScale = 0;
console.log('游戏暂停');
}
public resumeNormalSpeed(): void {

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

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

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

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

317
docs/index.md Normal file
View File

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

View File

@@ -1,22 +0,0 @@
{
"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"
}
}

View File

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

Before

Width:  |  Height:  |  Size: 696 B

View File

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

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

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