Compare commits

...

38 Commits

Author SHA1 Message Date
YHH
ddc7a7750e Chore/lint fixes (#212)
* fix(eslint): 修复装饰器缩进配置

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

* chore: 删除未使用的导入

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

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

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

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

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

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

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

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

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

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

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

* chore: 移除 Cocos Creator 扩展目录

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* 拆分日志类型与实现

* 新增 setLoggerFactory 方法

* tweak

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

* 更新日志说明文档

* 增加测试

* 使用 getSystemInstanceTypeName,避免压缩导致获取类名不一致
2025-10-26 11:53:46 +08:00
446 changed files with 37354 additions and 27348 deletions

View File

@@ -1,45 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "single", { "avoidEscape": true }],
"indent": ["error", 4, { "SwitchCase": 1 }],
"no-trailing-spaces": "error",
"eol-last": ["error", "always"],
"comma-dangle": ["error", "none"],
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": ["error", "never"],
"arrow-parens": ["error", "always"],
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1 }],
"no-console": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-non-null-assertion": "off"
},
"ignorePatterns": [
"node_modules/",
"dist/",
"bin/",
"build/",
"coverage/",
"thirdparty/",
"examples/lawn-mower-demo/",
"extensions/",
"*.min.js",
"*.d.ts"
]
}

View File

@@ -1,90 +0,0 @@
version: 2
updates:
# 核心包依赖
- package-ecosystem: "npm"
directory: "/packages/core"
schedule:
interval: "monthly" # 改为每月更新,减少干扰
open-pull-requests-limit: 3 # 减少同时打开的 PR 数量
labels:
- "dependencies"
- "core"
commit-message:
prefix: "chore(deps)"
include: "scope"
groups:
# 将开发依赖打包在一起
dev-dependencies:
dependency-type: "development"
# 将生产依赖的 patch 更新打包在一起
production-dependencies:
dependency-type: "production"
update-types:
- "patch"
- "minor"
# 忽略频繁更新但不重要的包
ignore:
- dependency-name: "@types/*"
update-types: ["version-update:semver-patch"]
# 编辑器应用依赖
- package-ecosystem: "npm"
directory: "/packages/editor-app"
schedule:
interval: "monthly"
open-pull-requests-limit: 3
labels:
- "dependencies"
- "editor"
commit-message:
prefix: "chore(deps)"
include: "scope"
groups:
dev-dependencies:
dependency-type: "development"
production-dependencies:
dependency-type: "production"
update-types:
- "patch"
- "minor"
ignore:
- dependency-name: "@types/*"
update-types: ["version-update:semver-patch"]
# 根目录依赖
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 3
labels:
- "dependencies"
commit-message:
prefix: "chore(deps)"
groups:
dev-dependencies:
dependency-type: "development"
production-dependencies:
dependency-type: "production"
update-types:
- "patch"
- "minor"
ignore:
- dependency-name: "@types/*"
update-types: ["version-update:semver-patch"]
# GitHub Actions - 保持更新以获取安全修复
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 2
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "chore(deps)"
groups:
github-actions:
patterns:
- "*"

View File

@@ -37,6 +37,12 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run type-check
- name: Lint check
run: npm run lint
- name: Build core package first
run: npm run build:core

View File

@@ -73,7 +73,8 @@ jobs:
path: |
packages/core/bin
packages/editor-core/dist
key: ${{ runner.os }}-ts-build-${{ hashFiles('packages/core/src/**', 'packages/editor-core/src/**') }}
packages/behavior-tree/bin
key: ${{ runner.os }}-ts-build-${{ hashFiles('packages/core/src/**', 'packages/editor-core/src/**', 'packages/behavior-tree/src/**') }}
restore-keys: |
${{ runner.os }}-ts-build-
@@ -85,6 +86,11 @@ jobs:
cd packages/editor-core
npm run build
- name: Build behavior-tree package
run: |
cd packages/behavior-tree
npm run build
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0.5
env:

View File

@@ -1,30 +1,44 @@
name: Release
name: Release NPM Packages
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (仅预览,不实际发布)'
type: boolean
default: false
package:
description: '选择要发布的包'
required: true
type: choice
options:
- core
- behavior-tree
- editor-core
version_type:
description: '版本更新类型'
required: true
type: choice
options:
- patch
- minor
- major
- custom
custom_version:
description: '自定义版本号 (仅当选择 custom 时使用,例如: 2.2.9)'
required: false
type: string
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
jobs:
release-core:
name: Release Core Package
release-package:
name: Release ${{ github.event.inputs.package }} Package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
@@ -37,32 +51,62 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Run tests
- name: Build core package (if needed)
if: ${{ github.event.inputs.package == 'behavior-tree' || github.event.inputs.package == 'editor-core' }}
run: |
cd packages/core
npm run test:ci
npm 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
npm version ${{ github.event.inputs.custom_version }} --no-git-tag-version --allow-same-version
else
npm version ${{ github.event.inputs.version_type }} --no-git-tag-version
fi
NEW_VERSION=$(node -p "require('./package.json').version")
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "发布版本: $NEW_VERSION"
- name: Build package
run: |
cd packages/core
cd packages/${{ github.event.inputs.package }}
npm run build:npm
- name: Release (Dry Run)
if: ${{ github.event.inputs.dry_run == 'true' }}
- name: Publish to npm
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
cd packages/core
npx semantic-release --dry-run
cd packages/${{ github.event.inputs.package }}/dist
npm publish --access public
- name: Release
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
cd packages/core
npx semantic-release
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore(${{ github.event.inputs.package }}): release v${{ steps.version.outputs.new_version }}"
branch: release/${{ github.event.inputs.package }}-v${{ steps.version.outputs.new_version }}
delete-branch: true
title: "chore(${{ github.event.inputs.package }}): Release v${{ steps.version.outputs.new_version }}"
body: |
## 🚀 Release v${{ steps.version.outputs.new_version }}
此 PR 更新 `@esengine/${{ github.event.inputs.package }}` 包的版本号
### 变更
- ✅ 已发布到 npm: [@esengine/${{ github.event.inputs.package }}@${{ steps.version.outputs.new_version }}](https://www.npmjs.com/package/@esengine/${{ github.event.inputs.package }}/v/${{ steps.version.outputs.new_version }})
- ✅ 更新 `packages/${{ github.event.inputs.package }}/package.json` → `${{ steps.version.outputs.new_version }}`
---
*此 PR 由发布工作流自动创建*
labels: |
release
${{ github.event.inputs.package }}
automated pr

15
.gitmodules vendored
View File

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

View File

@@ -1,58 +0,0 @@
{
"branches": ["master", "main"],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "angular",
"releaseRules": [
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "type": "revert", "release": "patch" },
{ "type": "docs", "release": false },
{ "type": "chore", "release": false },
{ "type": "refactor", "release": "patch" },
{ "type": "test", "release": false },
{ "type": "build", "release": false },
{ "type": "ci", "release": false }
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "angular",
"writerOpts": {
"commitsSort": ["subject", "scope"]
}
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file."
}
],
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
"@semantic-release/github",
{
"assets": []
}
]
]
}

53
codecov.yml Normal file
View File

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

View File

@@ -82,6 +82,21 @@ export default defineConfig({
{ text: 'WorldManager', link: '/guide/world-manager' }
]
},
{
text: '行为树系统 (Behavior Tree)',
link: '/guide/behavior-tree/',
items: [
{ text: '快速开始', link: '/guide/behavior-tree/getting-started' },
{ text: '核心概念', link: '/guide/behavior-tree/core-concepts' },
{ text: '编辑器指南', link: '/guide/behavior-tree/editor-guide' },
{ text: '编辑器工作流', link: '/guide/behavior-tree/editor-workflow' },
{ text: '自定义动作组件', link: '/guide/behavior-tree/custom-actions' },
{ text: 'Cocos Creator集成', link: '/guide/behavior-tree/cocos-integration' },
{ text: 'Laya引擎集成', link: '/guide/behavior-tree/laya-integration' },
{ text: '高级用法', link: '/guide/behavior-tree/advanced-usage' },
{ text: '最佳实践', link: '/guide/behavior-tree/best-practices' }
]
},
{ text: '序列化系统 (Serialization)', link: '/guide/serialization' },
{ text: '事件系统 (Event)', link: '/guide/event-system' },
{ text: '时间和定时器 (Time)', link: '/guide/time-and-timers' },

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

View File

@@ -238,6 +238,50 @@ class HierarchicalLoggingExample {
}
```
### 集成第三方日志库
通过 `setLoggerFactory` 可以将业务代码中的日志器替换为第三方日志库(如 winston、pino、nestjs Logger 等)。
**说明**: 目前框架内部日志仍使用 ConsoleLogger自定义日志器仅影响业务代码如 EntitySystem
#### 基本用法
```typescript
import { setLoggerFactory } from '@esengine/ecs-framework';
setLoggerFactory((name?: string) => {
// 返回实现 ILogger 接口的日志器实例
return yourLogger;
});
```
#### 使用示例
```typescript
// 集成 Winston
setLoggerFactory((name?: string) => winston.createLogger({ /* ... */ }));
// 集成 Pino
setLoggerFactory((name?: string) => pino({ name }));
// 集成 NestJS Logger
setLoggerFactory((name?: string) => new Logger(name));
```
#### EntitySystem 中的使用
EntitySystem 会自动使用类名创建日志器:
```typescript
class PlayerMovementSystem extends EntitySystem {
// this.logger 自动使用 'PlayerMovementSystem' 作为名称
protected process(entities: readonly Entity[]): void {
this.logger.info(`处理 ${entities.length} 个玩家实体`);
}
}
```
### 自定义输出
```typescript
@@ -547,4 +591,4 @@ class LoggingConfiguration {
LoggingConfiguration.setupLogging();
```
日志系统是调试和监控应用的重要工具,正确使用日志系统能大大提高开发效率和问题排查能力。
日志系统是调试和监控应用的重要工具,正确使用日志系统能大大提高开发效率和问题排查能力。

71
eslint.config.mjs Normal file
View File

@@ -0,0 +1,71 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
eslint.configs.recommended,
...tseslint.configs.recommended,
{
files: ['packages/**/src/**/*.{ts,tsx,js,jsx}'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: true,
tsconfigRootDir: import.meta.dirname
}
},
rules: {
'semi': ['warn', 'always'],
'quotes': ['warn', 'single', { avoidEscape: true }],
'indent': ['warn', 4, {
SwitchCase: 1,
ignoredNodes: [
'PropertyDefinition[decorators.length > 0]',
'TSTypeParameterInstantiation'
]
}],
'no-trailing-spaces': 'warn',
'eol-last': ['warn', 'always'],
'comma-dangle': ['warn', 'never'],
'object-curly-spacing': ['warn', 'always'],
'array-bracket-spacing': ['warn', 'never'],
'arrow-parens': ['warn', 'always'],
'no-multiple-empty-lines': ['warn', { max: 2, maxEOF: 1 }],
'no-console': 'off',
'no-empty': 'warn',
'no-case-declarations': 'warn',
'no-useless-catch': 'warn',
'no-prototype-builtins': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/no-unsafe-function-type': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-non-null-assertion': 'off'
}
},
{
ignores: [
'node_modules/**',
'**/node_modules/**',
'dist/**',
'**/dist/**',
'bin/**',
'**/bin/**',
'build/**',
'**/build/**',
'coverage/**',
'**/coverage/**',
'thirdparty/**',
'examples/lawn-mower-demo/**',
'extensions/**',
'**/*.min.js',
'**/*.d.ts'
]
}
];

View File

@@ -1,2 +0,0 @@
[InternetShortcut]
URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template

View File

@@ -1,24 +0,0 @@
#///////////////////////////
# Cocos Creator 3D Project
#///////////////////////////
library/
temp/
local/
build/
profiles/
native
#//////////////////////////
# NPM
#//////////////////////////
node_modules/
#//////////////////////////
# VSCode
#//////////////////////////
.vscode/
#//////////////////////////
# WebStorm
#//////////////////////////
.idea/

View File

@@ -1,14 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "2a691dda-d56d-4a72-9fef-111a999415db",
"files": [],
"subMetas": {},
"userData": {
"isBundle": true,
"bundleConfigID": "default",
"bundleName": "resources",
"priority": 8
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "8c25761f-50d6-498b-a95f-d863bf1fbff1",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "3a66cbbc-6612-4408-838b-875d0bb2e9a3",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,317 +0,0 @@
{
"nodes": [
{
"id": "node_15iffhg4p",
"type": "root",
"name": "根节点",
"description": "行为树的根节点,每棵树只能有一个根节点",
"children": [
"node_o6tsnrxyg"
]
},
{
"id": "node_o6tsnrxyg",
"type": "selector",
"name": "选择器",
"description": "按顺序执行子节点,任一成功则整体成功",
"properties": {
"abortType": "LowerPriority"
},
"children": [
"node_tljchzbno",
"node_txhx0hau5",
"node_r9kvcwv8u",
"node_520hedw22"
]
},
{
"id": "node_tljchzbno",
"type": "conditional-decorator",
"name": "休息条件装饰器",
"description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)",
"properties": {
"conditionType": "blackboardCompare",
"executeWhenTrue": true,
"abortType": "LowerPriority",
"shouldReevaluate": true,
"variableName": "{{isLowStamina}}",
"operator": "equal",
"compareValue": "true"
},
"children": [
"node_ulp8qx68h"
],
"condition": {
"type": "blackboard-value-comparison",
"properties": {}
}
},
{
"id": "node_txhx0hau5",
"type": "conditional-decorator",
"name": "存储条件装饰器",
"description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)",
"properties": {
"conditionType": "blackboardCompare",
"executeWhenTrue": true,
"abortType": "LowerPriority",
"shouldReevaluate": true,
"variableName": "{{hasOre}}",
"operator": "equal",
"compareValue": "true"
},
"children": [
"node_dhsz8rgl1"
],
"condition": {
"type": "blackboard-value-comparison",
"properties": {}
}
},
{
"id": "node_r9kvcwv8u",
"type": "conditional-decorator",
"name": "挖矿条件装饰器",
"description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)",
"properties": {
"conditionType": "blackboardCompare",
"executeWhenTrue": true,
"abortType": "LowerPriority",
"shouldReevaluate": true,
"variableName": "{{isLowStamina}}",
"operator": "equal",
"compareValue": "false"
},
"children": [
"node_zguxml6u7"
],
"condition": {
"type": "blackboard-value-comparison",
"properties": {}
}
},
{
"id": "node_ulp8qx68h",
"type": "sequence",
"name": "序列器",
"description": "按顺序执行子节点,任一失败则整体失败",
"properties": {
"abortType": "None"
},
"children": [
"node_0fgq85ovw",
"node_9v13vpqyr"
]
},
{
"id": "node_0fgq85ovw",
"type": "event-action",
"name": "回家休息",
"description": "执行已注册的事件处理函数(推荐)",
"properties": {
"eventName": "go-home-rest",
"parameters": "{}"
}
},
{
"id": "node_9v13vpqyr",
"type": "event-action",
"name": "恢复体力",
"description": "执行已注册的事件处理函数(推荐)",
"properties": {
"eventName": "recover-stamina",
"parameters": "{}"
}
},
{
"id": "node_ui4ja9mlj",
"type": "event-action",
"name": "前往仓库存储",
"description": "执行已注册的事件处理函数(推荐)",
"properties": {
"eventName": "store-ore",
"parameters": "{}"
}
},
{
"id": "node_969njccy2",
"type": "event-action",
"name": "挖掘金矿",
"description": "执行已注册的事件处理函数(推荐)",
"properties": {
"eventName": "mine-gold-ore",
"parameters": "{}"
}
},
{
"id": "node_520hedw22",
"type": "event-action",
"name": "默认待机",
"description": "执行已注册的事件处理函数(推荐)",
"properties": {
"eventName": "idle-behavior",
"parameters": "{}"
}
},
{
"id": "node_o5c7hv5wx",
"type": "set-blackboard-value",
"name": "设置黑板变量",
"description": "设置黑板变量的值",
"properties": {
"variableName": "{{hasOre}}",
"value": "false"
}
},
{
"id": "node_zf0sgkqev",
"type": "set-blackboard-value",
"name": "设置黑板变量",
"description": "设置黑板变量的值",
"properties": {
"variableName": "{{hasOre}}",
"value": "true"
}
},
{
"id": "node_dhsz8rgl1",
"type": "sequence",
"name": "序列器",
"description": "按顺序执行子节点,任一失败则整体失败",
"properties": {
"abortType": "None"
},
"children": [
"node_ui4ja9mlj",
"node_o5c7hv5wx"
]
},
{
"id": "node_zguxml6u7",
"type": "sequence",
"name": "序列器",
"description": "按顺序执行子节点,任一失败则整体失败",
"properties": {
"abortType": "None"
},
"children": [
"node_969njccy2",
"node_zf0sgkqev"
]
}
],
"blackboard": [
{
"name": "unitType",
"type": "string",
"value": "miner",
"description": "单位类型",
"group": "基础属性"
},
{
"name": "currentHealth",
"type": "number",
"value": 100,
"description": "当前生命值",
"group": "基础属性"
},
{
"name": "maxHealth",
"type": "number",
"value": 100,
"description": "最大生命值",
"group": "基础属性"
},
{
"name": "stamina",
"type": "number",
"value": 100,
"description": "当前体力值 - 挖矿会消耗体力",
"group": "体力系统"
},
{
"name": "maxStamina",
"type": "number",
"value": 100,
"description": "最大体力值",
"group": "体力系统"
},
{
"name": "staminaPercentage",
"type": "number",
"value": 1,
"description": "体力百分比",
"group": "体力系统"
},
{
"name": "isLowStamina",
"type": "boolean",
"value": false,
"description": "是否低体力 - 体力低于20%时为true",
"group": "体力系统"
},
{
"name": "isResting",
"type": "boolean",
"value": false,
"description": "是否正在休息",
"group": "体力系统"
},
{
"name": "homePosition",
"type": "vector3",
"value": {
"x": 0,
"y": 0,
"z": 0
},
"description": "家的位置 - 矿工休息的地方",
"group": "体力系统"
},
{
"name": "hasOre",
"type": "boolean",
"value": false,
"description": "是否携带矿石",
"group": "工作状态"
},
{
"name": "currentCommand",
"type": "string",
"value": "mine",
"description": "当前命令",
"group": "工作状态"
},
{
"name": "hasTarget",
"type": "boolean",
"value": false,
"description": "是否有目标",
"group": "工作状态"
},
{
"name": "targetPosition",
"type": "vector3",
"value": {
"x": 0,
"y": 0,
"z": 0
},
"description": "目标位置",
"group": "移动属性"
},
{
"name": "isMoving",
"type": "boolean",
"value": false,
"description": "是否正在移动",
"group": "移动属性"
}
],
"metadata": {
"name": "behavior-tree",
"created": "2025-06-25T14:06:55.596Z",
"version": "1.0",
"exportType": "clean"
}
}

View File

@@ -1,11 +0,0 @@
{
"ver": "2.0.1",
"importer": "json",
"imported": true,
"uuid": "598e1450-8c7a-46c7-9540-398f9809d627",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -1,12 +0,0 @@
{
"ver": "1.0.0",
"importer": "*",
"imported": true,
"uuid": "24c6e7e6-4ff0-4e7b-b470-9468bfa66b5d",
"files": [
".btree",
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "2bf3ded8-4054-4d8f-a367-c76b21eaf538",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,13 +0,0 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "51a6e245-2983-4258-be9f-9e21378f7f9f",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "Panel_Node"
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "4fd895f7-6b0f-4357-aa3a-7c2e88ffac9a",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "829183be-61a1-4494-bf64-3df359c0e8e7",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "240e4a78-e55f-47a8-84de-39220bba1321",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,757 +0,0 @@
[
{
"__type__": "cc.SceneAsset",
"_name": "behaviour-example-scene",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"scene": {
"__id__": 1
}
},
{
"__type__": "cc.Scene",
"_name": "behaviour-example-scene",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
},
{
"__id__": 5
},
{
"__id__": 7
},
{
"__id__": 8
},
{
"__id__": 14
}
],
"_active": true,
"_components": [],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"autoReleaseAssets": false,
"_globals": {
"__id__": 16
},
"_id": "ff354f0b-c2f5-4dea-8ffb-0152d175d11c"
},
{
"__type__": "cc.Node",
"_name": "Main Light",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.06397656665577071,
"y": -0.44608233363525845,
"z": -0.8239028751062036,
"w": -0.3436591377065261
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -117.894,
"y": -194.909,
"z": 38.562
},
"_id": "c0y6F5f+pAvI805TdmxIjx"
},
{
"__type__": "cc.DirectionalLight",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 250,
"b": 240,
"a": 255
},
"_useColorTemperature": false,
"_colorTemperature": 6550,
"_staticSettings": {
"__id__": 4
},
"_visibility": -325058561,
"_illuminanceHDR": 65000,
"_illuminance": 65000,
"_illuminanceLDR": 1.6927083333333335,
"_shadowEnabled": false,
"_shadowPcf": 0,
"_shadowBias": 0.00001,
"_shadowNormalBias": 0,
"_shadowSaturation": 1,
"_shadowDistance": 50,
"_shadowInvisibleOcclusionRange": 200,
"_csmLevel": 4,
"_csmLayerLambda": 0.75,
"_csmOptimizationMode": 2,
"_csmAdvancedOptions": false,
"_csmLayersTransition": false,
"_csmTransitionRange": 0.05,
"_shadowFixedArea": false,
"_shadowNear": 0.1,
"_shadowFar": 10,
"_shadowOrthoSize": 5,
"_id": "597uMYCbhEtJQc0ffJlcgA"
},
{
"__type__": "cc.StaticLightSettings",
"_baked": false,
"_editorOnly": false,
"_castShadow": false
},
{
"__type__": "cc.Node",
"_name": "Main Camera",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 6
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": -10,
"y": 10,
"z": 10
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.27781593346944056,
"y": -0.36497167621709875,
"z": -0.11507512748638377,
"w": 0.8811195706053617
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -35,
"y": -45,
"z": 0
},
"_id": "c9DMICJLFO5IeO07EPon7U"
},
{
"__type__": "cc.Camera",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 5
},
"_enabled": true,
"__prefab": null,
"_projection": 1,
"_priority": 0,
"_fov": 45,
"_fovAxis": 0,
"_orthoHeight": 10,
"_near": 1,
"_far": 1000,
"_color": {
"__type__": "cc.Color",
"r": 51,
"g": 51,
"b": 51,
"a": 255
},
"_depth": 1,
"_stencil": 0,
"_clearFlags": 14,
"_rect": {
"__type__": "cc.Rect",
"x": 0,
"y": 0,
"width": 1,
"height": 1
},
"_aperture": 19,
"_shutter": 7,
"_iso": 0,
"_screenScale": 1,
"_visibility": 1822425087,
"_targetTexture": null,
"_postProcess": null,
"_usePostProcess": false,
"_cameraType": -1,
"_trackingType": 0,
"_id": "7dWQTpwS5LrIHnc1zAPUtf"
},
{
"__type__": "cc.Node",
"_name": "GameWorld",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "8b9QorrGZIl64tVv0Z0vRQ"
},
{
"__type__": "cc.Node",
"_name": "Canvas",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 9
}
],
"_active": true,
"_components": [
{
"__id__": 11
},
{
"__id__": 12
},
{
"__id__": 13
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 640,
"y": 360,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "4edRVPFLtIz5pR5edsryvx"
},
{
"__type__": "cc.Node",
"_name": "Camera",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 8
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 10
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 1000
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "dfyZdh0bxJop4PyQrmHEP6"
},
{
"__type__": "cc.Camera",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 9
},
"_enabled": true,
"__prefab": null,
"_projection": 0,
"_priority": 1073741824,
"_fov": 45,
"_fovAxis": 0,
"_orthoHeight": 360,
"_near": 1,
"_far": 2000,
"_color": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_depth": 1,
"_stencil": 0,
"_clearFlags": 6,
"_rect": {
"__type__": "cc.Rect",
"x": 0,
"y": 0,
"width": 1,
"height": 1
},
"_aperture": 19,
"_shutter": 7,
"_iso": 0,
"_screenScale": 1,
"_visibility": 41943040,
"_targetTexture": null,
"_postProcess": null,
"_usePostProcess": false,
"_cameraType": -1,
"_trackingType": 0,
"_id": "48lLOhLY5Onqokj70aNP+E"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 1280,
"height": 720
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "c3qBrLTLNImoltQDlZ6coz"
},
{
"__type__": "cc.Canvas",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": null,
"_cameraComponent": {
"__id__": 10
},
"_alignCanvasWithScreen": true,
"_id": "9d3SdE3ORAOZ6AG/imW6NO"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": null,
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": "4a8iJypC1J8pMml467hQ6c"
},
{
"__type__": "cc.Node",
"_name": "RTSDemo",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 15
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "89cmsd2gNNsq155xC7mob8"
},
{
"__type__": "c33869Km+9Bb7dw/OyRztvE",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 14
},
"_enabled": true,
"__prefab": null,
"minerCount": 1,
"goldMineCount": 3,
"_id": "86AIY7iYlMNqJsDC/+LIMU"
},
{
"__type__": "cc.SceneGlobals",
"ambient": {
"__id__": 17
},
"shadows": {
"__id__": 18
},
"_skybox": {
"__id__": 19
},
"fog": {
"__id__": 20
},
"octree": {
"__id__": 21
},
"skin": {
"__id__": 22
},
"lightProbeInfo": {
"__id__": 23
},
"postSettings": {
"__id__": 24
},
"bakedWithStationaryMainLight": false,
"bakedWithHighpLightmap": false
},
{
"__type__": "cc.AmbientInfo",
"_skyColorHDR": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.5,
"z": 0.8,
"w": 0.520833125
},
"_skyColor": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.5,
"z": 0.8,
"w": 0.520833125
},
"_skyIllumHDR": 20000,
"_skyIllum": 20000,
"_groundAlbedoHDR": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.2,
"z": 0.2,
"w": 1
},
"_groundAlbedo": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.2,
"z": 0.2,
"w": 1
},
"_skyColorLDR": {
"__type__": "cc.Vec4",
"x": 0.452588,
"y": 0.607642,
"z": 0.755699,
"w": 0
},
"_skyIllumLDR": 0.8,
"_groundAlbedoLDR": {
"__type__": "cc.Vec4",
"x": 0.618555,
"y": 0.577848,
"z": 0.544564,
"w": 0
}
},
{
"__type__": "cc.ShadowsInfo",
"_enabled": false,
"_type": 0,
"_normal": {
"__type__": "cc.Vec3",
"x": 0,
"y": 1,
"z": 0
},
"_distance": 0,
"_planeBias": 1,
"_shadowColor": {
"__type__": "cc.Color",
"r": 76,
"g": 76,
"b": 76,
"a": 255
},
"_maxReceived": 4,
"_size": {
"__type__": "cc.Vec2",
"x": 1024,
"y": 1024
}
},
{
"__type__": "cc.SkyboxInfo",
"_envLightingType": 0,
"_envmapHDR": {
"__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0",
"__expectedType__": "cc.TextureCube"
},
"_envmap": {
"__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0",
"__expectedType__": "cc.TextureCube"
},
"_envmapLDR": {
"__uuid__": "6f01cf7f-81bf-4a7e-bd5d-0afc19696480@b47c0",
"__expectedType__": "cc.TextureCube"
},
"_diffuseMapHDR": null,
"_diffuseMapLDR": null,
"_enabled": true,
"_useHDR": true,
"_editableMaterial": null,
"_reflectionHDR": null,
"_reflectionLDR": null,
"_rotationAngle": 0
},
{
"__type__": "cc.FogInfo",
"_type": 0,
"_fogColor": {
"__type__": "cc.Color",
"r": 200,
"g": 200,
"b": 200,
"a": 255
},
"_enabled": false,
"_fogDensity": 0.3,
"_fogStart": 0.5,
"_fogEnd": 300,
"_fogAtten": 5,
"_fogTop": 1.5,
"_fogRange": 1.2,
"_accurate": false
},
{
"__type__": "cc.OctreeInfo",
"_enabled": false,
"_minPos": {
"__type__": "cc.Vec3",
"x": -1024,
"y": -1024,
"z": -1024
},
"_maxPos": {
"__type__": "cc.Vec3",
"x": 1024,
"y": 1024,
"z": 1024
},
"_depth": 8
},
{
"__type__": "cc.SkinInfo",
"_enabled": true,
"_blurRadius": 0.01,
"_sssIntensity": 3
},
{
"__type__": "cc.LightProbeInfo",
"_giScale": 1,
"_giSamples": 1024,
"_bounces": 2,
"_reduceRinging": 0,
"_showProbe": true,
"_showWireframe": true,
"_showConvex": false,
"_data": null,
"_lightProbeSphereVolume": 1
},
{
"__type__": "cc.PostSettingsInfo",
"_toneMappingType": 0
}
]

View File

@@ -1,11 +0,0 @@
{
"ver": "1.1.50",
"importer": "scene",
"imported": true,
"uuid": "ff354f0b-c2f5-4dea-8ffb-0152d175d11c",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
{
"ver": "1.1.50",
"importer": "scene",
"imported": true,
"uuid": "fcbf2917-6d43-4528-8829-7ee089594879",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "1556cd72-9618-4f9f-b9e7-28152a33bde9",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,130 +0,0 @@
import { _decorator, Component, Node, Vec3, Color } from 'cc';
import { SimplePrefabFactory } from './components/SimplePrefabFactory';
import { BehaviorTreeComponent } from './components/BehaviorTreeComponent';
import { StatusUIManager } from './components/StatusUIManager';
const { ccclass, property } = _decorator;
/**
* 矿工AI演示场景
*/
@ccclass('SimpleMinerDemo')
export class SimpleMinerDemo extends Component {
@property
minerCount: number = 1;
@property
goldMineCount: number = 3;
private miners: Node[] = [];
private goldMines: Node[] = [];
private warehouse: Node | null = null;
private ground: Node | null = null;
private totalOresCollected: number = 0;
private warehouseUI: any = null;
start() {
this.createWorld();
this.createWarehouse();
this.createGoldMines();
this.createMiners();
}
private createWorld() {
this.ground = SimplePrefabFactory.createGround(new Vec3(20, 0.2, 20));
this.node.addChild(this.ground);
this.ground.setWorldPosition(new Vec3(0, 0, 0));
}
private createWarehouse() {
this.warehouse = SimplePrefabFactory.createBuilding('Warehouse', new Vec3(2, 2, 2), Color.GRAY);
this.node.addChild(this.warehouse);
this.warehouse.setWorldPosition(new Vec3(0, 1, 0));
this.createWarehouseUI();
}
private createGoldMines() {
for (let i = 0; i < this.goldMineCount; i++) {
const angle = (i / this.goldMineCount) * Math.PI * 2;
const radius = 6 + Math.random() * 2;
const position = new Vec3(
Math.cos(angle) * radius,
0.8,
Math.sin(angle) * radius
);
const goldMine = SimplePrefabFactory.createResource(`GoldMine_${i + 1}`, Color.YELLOW);
this.node.addChild(goldMine);
goldMine.setWorldPosition(position);
goldMine.setScale(new Vec3(1.2, 1.2, 1.2));
this.goldMines.push(goldMine);
}
}
private createMiners() {
for (let i = 0; i < this.minerCount; i++) {
const angle = (i / this.minerCount) * Math.PI * 2;
const radius = 3;
const position = new Vec3(
Math.cos(angle) * radius,
1,
Math.sin(angle) * radius
);
const miner = SimplePrefabFactory.createUnit(`Miner_${i + 1}`, Color.BLUE);
this.node.addChild(miner);
miner.setWorldPosition(position);
const behaviorTree = miner.addComponent(BehaviorTreeComponent);
behaviorTree.behaviorTreeFile = 'miner-stamina-ai.bt';
behaviorTree.debugMode = true;
this.scheduleOnce(() => {
const blackboard = behaviorTree.getBlackboard();
if (blackboard) {
blackboard.setValue('homePosition', position.clone());
}
}, 0.5);
this.miners.push(miner);
}
}
public getAllGoldMines(): Node[] {
return this.goldMines.filter(mine => mine && mine.isValid);
}
public getWarehouse(): Node | null {
return this.warehouse;
}
public mineGoldOre(miner: Node): boolean {
this.totalOresCollected++;
this.updateWarehouseUI();
return true;
}
public getTotalOresCollected(): number {
return this.totalOresCollected;
}
private createWarehouseUI() {
if (!this.warehouse) return;
this.warehouseUI = StatusUIManager.createWarehouseUI(this.warehouse);
if (this.warehouseUI) {
this.updateWarehouseUI();
}
}
private updateWarehouseUI() {
if (this.warehouseUI && this.warehouseUI.warehouseCountLabel) {
this.warehouseUI.warehouseCountLabel.string = `🏭 总存储: ${this.totalOresCollected}`;
}
}
onDestroy() {
this.unscheduleAllCallbacks();
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "c3386f4a-9bef-416f-b770-fcec91cedbc4",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "d07d95ad-f180-4b6e-9d0a-7248e75ec795",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,518 +0,0 @@
import { Node, resources, JsonAsset, Component, _decorator, Vec3, tween, instantiate, Prefab } from 'cc';
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, TaskStatus, BehaviorTreeJSONConfig, EventRegistry, IBehaviorTreeContext, ActionResult } from '@esengine/ai';
import { MinerStatusUI } from './MinerStatusUI';
import { StatusUIManager } from './StatusUIManager';
const { ccclass, property } = _decorator;
@ccclass('BehaviorTreeComponent')
export class BehaviorTreeComponent extends Component {
@property
behaviorTreeFile: string = '';
@property
autoStart: boolean = true;
@property
debugMode: boolean = false;
@property
showStatusUI: boolean = true;
@property(Prefab)
statusUIPrefab: Prefab | null = null;
private behaviorTree: BehaviorTree<any> | null = null;
private statusUI: MinerStatusUI | null = null;
private blackboard: Blackboard | null = null;
private context: any = null;
private eventRegistry: EventRegistry | null = null;
private isLoaded: boolean = false;
private isRunning: boolean = false;
private actionStates: Map<string, {
isExecuting: boolean;
startTime: number;
duration: number;
}> = new Map();
start() {
if (this.autoStart && this.behaviorTreeFile) {
this.initialize();
}
if (this.showStatusUI) {
this.createStatusUI();
}
}
async initialize() {
if (!this.behaviorTreeFile) {
return;
}
try {
await this.loadBehaviorTree();
this.isLoaded = true;
this.isRunning = true;
} catch (error) {
// 静默处理
}
}
private async loadBehaviorTree(): Promise<void> {
return new Promise((resolve, reject) => {
let jsonPath = this.behaviorTreeFile;
resources.load(jsonPath, JsonAsset, (err, asset) => {
if (err) {
reject(err);
return;
}
try {
const treeData = asset.json as BehaviorTreeJSONConfig;
this.buildBehaviorTree(treeData);
resolve();
} catch (buildError) {
reject(buildError);
}
});
});
}
private buildBehaviorTree(treeData: BehaviorTreeJSONConfig) {
this.eventRegistry = new EventRegistry();
this.setupEventHandlers();
const baseContext = {
node: this.node,
component: this,
eventRegistry: this.eventRegistry
};
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(treeData, baseContext);
this.behaviorTree = result.tree;
this.blackboard = result.blackboard;
this.context = result.context;
this.initializeBlackboard();
}
private setupEventHandlers() {
if (!this.eventRegistry) return;
this.eventRegistry.registerAction('go-home-rest', (context, params) => {
return this.handleGoHomeRest(context, params);
});
this.eventRegistry.registerAction('recover-stamina', (context, params) => {
return this.handleRecoverStamina(context, params);
});
this.eventRegistry.registerAction('store-ore', (context, params) => {
return this.handleStoreOre(context, params);
});
this.eventRegistry.registerAction('mine-gold-ore', (context, params) => {
return this.handleMineGoldOre(context, params);
});
this.eventRegistry.registerAction('idle-behavior', (context, params) => {
return this.handleIdleBehavior(context, params);
});
}
private initializeBlackboard() {
if (!this.blackboard) return;
this.blackboard.setValue('stamina', 100);
this.blackboard.setValue('staminaPercentage', 1.0);
this.blackboard.setValue('isLowStamina', false);
this.blackboard.setValue('hasOre', false);
this.blackboard.setValue('isResting', false);
this.blackboard.setValue('homePosition', this.node.worldPosition);
}
private createStatusUI() {
if (!this.statusUIPrefab) {
this.createSimpleStatusUI();
return;
}
const uiNode = instantiate(this.statusUIPrefab);
const canvas = this.node.scene?.getChildByName('Canvas');
if (canvas) {
canvas.addChild(uiNode);
this.statusUI = uiNode.getComponent(MinerStatusUI);
if (this.statusUI) {
this.statusUI.setFollowTarget(this.node);
}
}
}
private createSimpleStatusUI() {
this.statusUI = StatusUIManager.createStatusUIForMiner(this.node);
}
private updateStatusUI() {
if (!this.statusUI || !this.blackboard) return;
const stamina = this.blackboard.getValue('stamina') || 0;
const maxStamina = this.blackboard.getValue('maxStamina') || 100;
const hasOre = this.blackboard.getValue('hasOre') || false;
const isResting = this.blackboard.getValue('isResting') || false;
// 更新体力
this.statusUI.updateStamina(stamina, maxStamina);
// 更新状态文本
let status = '';
if (isResting) {
status = '😴休息中';
} else if (hasOre) {
status = '🚚运输中';
} else {
status = '⛏️挖矿中';
}
this.statusUI.updateStatus(status);
// 获取仓库矿石总数
const gameManager = this.node.parent?.getComponent('SimpleMinerDemo');
const warehouseTotal = (gameManager as any)?.getTotalOresCollected() || 0;
// 更新矿石数量显示
this.statusUI.updateOreCount(hasOre, warehouseTotal);
// 更新动作进度
this.updateActionProgressUI();
}
private updateActionProgressUI() {
if (!this.statusUI) return;
let actionName = '';
let progress = 0;
// 检查当前正在执行的动作
for (const [key, state] of this.actionStates.entries()) {
if (state.isExecuting) {
const elapsed = Date.now() - state.startTime;
progress = Math.min(elapsed / state.duration, 1.0);
switch (key) {
case 'mine-gold-ore':
actionName = '⛏️ 挖掘中';
break;
case 'store-ore':
actionName = '📦 存储中';
break;
case 'recover-stamina':
actionName = '💤 恢复体力';
break;
default:
actionName = key;
}
break; // 只显示第一个正在执行的动作
}
}
// 如果没有正在执行的动作,清空进度显示
this.statusUI.updateActionProgress(actionName, progress);
}
// ==================== 行为树事件处理器 ====================
/**
* 清理动作状态 - 当动作被中止时调用
*/
private clearActionState(actionKey: string) {
if (this.actionStates.has(actionKey)) {
this.actionStates.delete(actionKey);
}
}
/**
* 回家休息 - 包含体力恢复逻辑
*/
private handleGoHomeRest(context: any, params: any): ActionResult {
const blackboard = this.blackboard;
if (!blackboard) return 'failure';
// 检查是否已经在家了
const homePos = blackboard.getValue('homePosition') || this.node.worldPosition;
const distance = Vec3.distance(this.node.worldPosition, homePos);
if (distance > 1.0) {
// 还没到家,继续移动
this.moveToPosition(homePos, 2.0);
return 'running';
} else {
this.clearActionState('mine-gold-ore');
this.clearActionState('store-ore');
blackboard.setValue('isResting', true);
const actionKey = 'go-home-rest';
const currentTime = Date.now();
// 初始化休息状态
if (!this.actionStates.has(actionKey)) {
this.actionStates.set(actionKey, {
isExecuting: true,
startTime: currentTime,
duration: 2000 // 2秒恢复一次
});
return 'running';
}
const actionState = this.actionStates.get(actionKey)!;
const elapsed = currentTime - actionState.startTime;
if (elapsed >= actionState.duration) {
const currentStamina = blackboard.getValue('stamina');
const newStamina = Math.min(100, currentStamina + 10);
blackboard.setValue('stamina', newStamina);
blackboard.setValue('staminaPercentage', newStamina / 100);
if (newStamina >= 80) {
blackboard.setValue('isResting', false);
blackboard.setValue('isLowStamina', false);
this.actionStates.delete(actionKey);
return 'success';
}
actionState.startTime = currentTime;
}
return 'running';
}
}
private handleRecoverStamina(context: any, params: any): ActionResult {
return 'success';
}
private handleMineGoldOre(context: any, params: any): ActionResult {
const blackboard = this.blackboard;
if (!blackboard) return 'failure';
const hasOre = blackboard.getValue('hasOre');
const isLowStamina = blackboard.getValue('isLowStamina');
const isResting = blackboard.getValue('isResting');
if (hasOre || isLowStamina || isResting) {
return 'failure';
}
const gameManager = this.node.parent?.getComponent('SimpleMinerDemo');
const goldMines = (gameManager as any)?.getAllGoldMines();
if (!goldMines?.length) return 'failure';
let nearestMine = goldMines[0];
let minDistance = Vec3.distance(this.node.worldPosition, nearestMine.worldPosition);
for (const mine of goldMines) {
const distance = Vec3.distance(this.node.worldPosition, mine.worldPosition);
if (distance < minDistance) {
minDistance = distance;
nearestMine = mine;
}
}
if (minDistance > 2.0) {
this.moveToPosition(nearestMine.worldPosition, 2.0);
return 'running';
} else {
const actionKey = 'mine-gold-ore';
const currentTime = Date.now();
if (!this.actionStates.has(actionKey)) {
this.actionStates.set(actionKey, {
isExecuting: true,
startTime: currentTime,
duration: 3000
});
return 'running';
}
const actionState = this.actionStates.get(actionKey)!;
const elapsed = currentTime - actionState.startTime;
if (elapsed >= actionState.duration) {
const currentStamina = blackboard.getValue('stamina');
const newStamina = Math.max(0, currentStamina - 15);
blackboard.setValue('stamina', newStamina);
blackboard.setValue('staminaPercentage', newStamina / 100);
blackboard.setValue('hasOre', true);
blackboard.setValue('isLowStamina', newStamina < 20);
this.actionStates.delete(actionKey);
return 'failure';
}
return 'running';
}
}
private handleStoreOre(context: any, params: any): ActionResult {
const blackboard = this.blackboard;
if (!blackboard) return 'failure';
const hasOre = blackboard.getValue('hasOre');
if (!hasOre) {
return 'failure';
}
const isLowStamina = blackboard.getValue('isLowStamina');
if (isLowStamina) {
return 'failure';
}
this.clearActionState('mine-gold-ore');
const gameManager = this.node.parent?.getComponent('SimpleMinerDemo');
const warehouse = (gameManager as any)?.getWarehouse();
if (!warehouse) return 'failure';
const distance = Vec3.distance(this.node.worldPosition, warehouse.worldPosition);
if (distance > 2.0) {
this.moveToPosition(warehouse.worldPosition, 2.0);
return 'running';
} else {
const actionKey = 'store-ore';
const currentTime = Date.now();
if (!this.actionStates.has(actionKey)) {
this.actionStates.set(actionKey, {
isExecuting: true,
startTime: currentTime,
duration: 1500
});
return 'running';
}
const actionState = this.actionStates.get(actionKey)!;
const elapsed = currentTime - actionState.startTime;
if (elapsed >= actionState.duration) {
blackboard.setValue('hasOre', false);
(gameManager as any).mineGoldOre(this.node);
this.actionStates.delete(actionKey);
return 'success';
}
return 'running';
}
}
private handleIdleBehavior(context: any, params: any): ActionResult {
return 'success';
}
private moveToPosition(targetPos: Vec3, duration: number) {
tween(this.node).stop();
tween(this.node).to(duration, { worldPosition: targetPos }).start();
}
update(deltaTime: number) {
if (this.behaviorTree && this.isRunning) {
this.behaviorTree.tick(deltaTime);
}
if (this.showStatusUI) {
this.updateStatusUI();
}
}
/**
* 获取黑板
*/
getBlackboard(): Blackboard | null {
return this.blackboard;
}
/**
* 获取行为树
*/
getBehaviorTree(): BehaviorTree<any> | null {
return this.behaviorTree;
}
/**
* 暂停行为树
*/
pause() {
this.isRunning = false;
if (this.debugMode) {
}
}
/**
* 恢复行为树
*/
resume() {
if (this.isLoaded) {
this.isRunning = true;
if (this.debugMode) {
}
}
}
/**
* 停止行为树
*/
stop() {
this.isRunning = false;
if (this.behaviorTree) {
this.behaviorTree.reset();
}
if (this.debugMode) {
}
}
/**
* 重新加载行为树
*/
async reload() {
this.stop();
await this.initialize();
}
/**
* 重置行为树状态
*/
reset() {
if (this.behaviorTree) {
this.behaviorTree.reset();
}
if (this.debugMode) {
}
}
onDestroy() {
this.stop();
if (this.eventRegistry) {
this.eventRegistry.clear();
}
// 清理UI
if (this.statusUI) {
this.statusUI.node.destroy();
this.statusUI = null;
}
this.behaviorTree = null;
this.blackboard = null;
this.context = null;
this.eventRegistry = null;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "8efd182b-9891-4903-bef2-eb07b5184263",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,299 +0,0 @@
import { _decorator, Component, resources, JsonAsset, Vec3 } from 'cc';
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, BehaviorTreeJSONConfig, ExecutionContext, EventRegistry, ActionResult } from '@esengine/ai';
import { UnitController } from './UnitController';
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
const { ccclass, property } = _decorator;
/**
* 游戏执行上下文接口
* 继承框架的ExecutionContext添加游戏特定的属性
*/
interface GameExecutionContext extends ExecutionContext {
unitController: UnitController;
gameObject: any;
eventRegistry?: EventRegistry;
// 确保继承索引签名
[key: string]: unknown;
}
/**
* 行为树管理器 - 使用@esengine/ai包管理行为树
*/
@ccclass('BehaviorTreeManager')
export class BehaviorTreeManager extends Component {
@property
debugMode: boolean = true;
@property
tickInterval: number = 0.1; // 行为树更新间隔(秒)- 10fps更新频率平衡性能和响应性
private behaviorTree: BehaviorTree<GameExecutionContext> | null = null;
private blackboard: Blackboard | null = null;
private context: GameExecutionContext | null = null;
private eventRegistry: EventRegistry | null = null;
private isLoaded: boolean = false;
private isRunning: boolean = false;
private lastTickTime: number = 0;
private unitController: UnitController | null = null;
private currentBehaviorTreeName: string = '';
private behaviorHandler: RTSBehaviorHandler | null = null;
/**
* 初始化行为树
*/
async initializeBehaviorTree(behaviorTreeName: string, unitController: UnitController) {
this.currentBehaviorTreeName = behaviorTreeName;
this.unitController = unitController;
// 获取RTSBehaviorHandler组件
this.behaviorHandler = this.getComponent(RTSBehaviorHandler);
if (!this.behaviorHandler) {
console.error(`BehaviorTreeManager: 未找到RTSBehaviorHandler组件 - ${this.node.name}`);
return;
}
try {
await this.loadBehaviorTree(behaviorTreeName);
this.setupBlackboard();
this.isLoaded = true;
this.isRunning = true;
} catch (error) {
console.error(`行为树初始化失败: ${behaviorTreeName}`, error);
}
}
/**
* 加载行为树文件
*/
private async loadBehaviorTree(behaviorTreeName: string): Promise<void> {
return new Promise((resolve, reject) => {
const jsonPath = `${behaviorTreeName}.bt`;
resources.load(jsonPath, JsonAsset, (err, asset) => {
if (err) {
console.error(`加载行为树文件失败: ${jsonPath}`, err);
reject(err);
return;
}
try {
const behaviorTreeData = asset.json as BehaviorTreeJSONConfig;
// 创建执行上下文
this.blackboard = new Blackboard();
this.eventRegistry = this.createEventRegistry();
this.context = {
blackboard: this.blackboard,
unitController: this.unitController!,
gameObject: this.node,
eventRegistry: this.eventRegistry
} as GameExecutionContext;
// 从JSON数据创建行为树
const buildResult = BehaviorTreeBuilder.fromBehaviorTreeConfig<GameExecutionContext>(behaviorTreeData, this.context);
this.behaviorTree = buildResult.tree;
this.blackboard = buildResult.blackboard;
resolve();
} catch (parseError) {
console.error(`创建行为树失败: ${jsonPath}`, parseError);
reject(parseError);
}
});
});
}
/**
* 创建事件注册表
*/
private createEventRegistry(): EventRegistry {
const registry = new EventRegistry();
// 注册体力系统矿工行为事件处理器
const eventHandlers = {
// 矿工体力系统核心行为
'mine-gold-ore': (context: any, params?: any) => this.callBehaviorHandler('onMineGoldOre', params),
'store-ore': (context: any, params?: any) => this.callBehaviorHandler('onStoreOre', params),
'go-home-rest': (context: any, params?: any) => this.callBehaviorHandler('onGoHomeRest', params),
'recover-stamina': (context: any, params?: any) => this.callBehaviorHandler('onRecoverStamina', params),
'idle-behavior': (context: any, params?: any) => this.callBehaviorHandler('onIdleBehavior', params)
};
// 将事件处理器注册到EventRegistry
Object.entries(eventHandlers).forEach(([eventName, handler]) => {
registry.registerAction(eventName, handler);
});
return registry;
}
/**
* 调用行为处理器的方法
*/
private callBehaviorHandler(methodName: string, params: any = {}): ActionResult {
if (!this.behaviorHandler) {
console.error(`BehaviorTreeManager: RTSBehaviorHandler未初始化 - ${this.node.name}`);
return 'failure';
}
try {
// 直接调用RTSBehaviorHandler的方法
const method = (this.behaviorHandler as any)[methodName];
if (typeof method === 'function') {
const result = method.call(this.behaviorHandler, params);
return result || 'success'; // 确保有返回值
} else {
console.error(`BehaviorTreeManager: 方法不存在: ${methodName}`);
return 'failure';
}
} catch (error) {
console.error(`BehaviorTreeManager: 调用方法失败: ${methodName}`, error);
return 'failure';
}
}
/**
* 设置黑板基础信息
*/
private setupBlackboard() {
if (!this.unitController || !this.blackboard) return;
// 设置矿工基础信息
this.blackboard.setValue('unitType', this.unitController.unitType);
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
this.blackboard.setValue('maxHealth', this.unitController.maxHealth);
this.blackboard.setValue('currentCommand', 'mine');
this.blackboard.setValue('hasOre', false);
this.blackboard.setValue('hasTarget', false);
this.blackboard.setValue('targetPosition', null);
this.blackboard.setValue('isMoving', false);
// 设置体力系统信息
this.blackboard.setValue('stamina', this.unitController.currentStamina);
this.blackboard.setValue('maxStamina', this.unitController.maxStamina);
this.blackboard.setValue('staminaPercentage', this.unitController.currentStamina / this.unitController.maxStamina);
this.blackboard.setValue('isLowStamina', this.unitController.currentStamina < this.unitController.maxStamina * 0.2);
this.blackboard.setValue('isResting', false);
this.blackboard.setValue('homePosition', this.unitController.homePosition);
}
/**
* 更新黑板值
*/
updateBlackboardValue(key: string, value: any) {
if (this.blackboard) {
this.blackboard.setValue(key, value);
}
}
/**
* 获取黑板值
*/
getBlackboardValue(key: string): any {
return this.blackboard?.getValue(key);
}
/**
* 获取黑板
*/
getBlackboard(): Blackboard | null {
return this.blackboard;
}
/**
* 获取行为树
*/
getBehaviorTree(): BehaviorTree<GameExecutionContext> | null {
return this.behaviorTree;
}
/**
* 更新行为树
*/
update(deltaTime: number) {
if (!this.isLoaded || !this.isRunning || !this.behaviorTree || !this.blackboard) return;
// 控制更新频率
this.lastTickTime += deltaTime;
if (this.lastTickTime < this.tickInterval) return;
this.lastTickTime = 0;
// 更新矿工状态信息
if (this.unitController) {
// 基础属性
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
this.blackboard.setValue('currentCommand', this.unitController.currentCommand);
this.blackboard.setValue('hasTarget', this.unitController.targetPosition && !this.unitController.targetPosition.equals(Vec3.ZERO));
this.blackboard.setValue('targetPosition', this.unitController.targetPosition);
this.blackboard.setValue('isMoving', this.unitController.targetPosition && !this.unitController.targetPosition.equals(Vec3.ZERO));
// 体力系统状态
this.blackboard.setValue('stamina', this.unitController.currentStamina);
this.blackboard.setValue('maxStamina', this.unitController.maxStamina);
this.blackboard.setValue('staminaPercentage', this.unitController.currentStamina / this.unitController.maxStamina);
this.blackboard.setValue('isLowStamina', this.unitController.currentStamina < this.unitController.maxStamina * 0.2);
this.blackboard.setValue('homePosition', this.unitController.homePosition);
}
// 执行行为树
try {
this.behaviorTree.tick(deltaTime);
} catch (error) {
console.error(`行为树执行错误: ${this.node.name}`, error);
}
}
/**
* 暂停行为树
*/
pause() {
this.isRunning = false;
}
/**
* 恢复行为树
*/
resume() {
if (this.isLoaded) {
this.isRunning = true;
}
}
/**
* 停止行为树
*/
stop() {
this.isRunning = false;
}
/**
* 重新加载行为树
*/
async reloadBehaviorTree() {
if (this.currentBehaviorTreeName && this.unitController) {
this.stop();
await this.initializeBehaviorTree(this.currentBehaviorTreeName, this.unitController);
}
}
/**
* 重置行为树
*/
reset() {
if (this.behaviorTree) {
this.behaviorTree.reset();
}
}
onDestroy() {
this.stop();
this.behaviorTree = null;
this.blackboard = null;
this.context = null;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "891c88fc-282d-4791-a961-8d85244bfee7",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,130 +0,0 @@
import { Component, _decorator, Label, ProgressBar, Node, UITransform, Canvas, find, Camera, Vec3, director, Color, Layers, Graphics } from 'cc';
const { ccclass, property } = _decorator;
/**
* 矿工状态UI组件
*/
@ccclass('MinerStatusUI')
export class MinerStatusUI extends Component {
nameLabel: Label | null = null;
statusLabel: Label | null = null;
staminaBar: ProgressBar | null = null;
actionProgressBar: ProgressBar | null = null;
actionLabel: Label | null = null;
oreCountLabel: Label | null = null;
warehouseCountLabel: Label | null = null;
@property
followTarget: Node | null = null;
@property
yOffset: number = 100;
private camera: Camera | null = null;
private canvas: Canvas | null = null;
start() {
this.node.layer = Layers.Enum.UI_2D;
this.camera = find('Main Camera')?.getComponent(Camera) || director.getScene()?.getComponentInChildren(Camera);
this.canvas = find('Canvas')?.getComponent(Canvas) || director.getScene()?.getComponentInChildren(Canvas);
if (this.nameLabel && this.followTarget) {
this.nameLabel.string = this.followTarget.name;
}
this.updateStamina(100, 100);
this.updateStatus('待机中');
this.updateActionProgress('', 0);
}
update() {
if (this.followTarget && this.camera && this.canvas) {
this.updateUIPosition();
}
}
private updateUIPosition() {
if (!this.followTarget || !this.camera || !this.canvas) return;
const targetWorldPos = this.followTarget.worldPosition.clone();
// 根据目标类型设置不同的Y偏移
if (this.followTarget.name.includes('Warehouse')) {
targetWorldPos.y += 3.0; // 仓库偏移更高
} else {
targetWorldPos.y += 2.0; // 矿工偏移
}
// 将世界坐标直接转换为UI坐标
const uiPos = new Vec3();
this.camera.convertToUINode(targetWorldPos, this.canvas.node, uiPos);
this.node.setPosition(uiPos);
}
setFollowTarget(target: Node) {
this.followTarget = target;
if (this.nameLabel) {
this.nameLabel.string = target.name;
}
}
updateStamina(current: number, max: number) {
if (this.staminaBar) {
this.staminaBar.progress = current / max;
}
if (this.staminaBar) {
const percentage = current / max;
const fillNode = this.staminaBar.node.getChildByName('Bar');
if (fillNode) {
const graphics = fillNode.getComponent(Graphics);
if (graphics) {
let color: Color;
if (percentage > 0.6) {
color = new Color(0, 255, 0, 255);
} else if (percentage > 0.3) {
color = new Color(255, 255, 0, 255);
} else {
color = new Color(255, 0, 0, 255);
}
graphics.clear();
graphics.fillColor = color;
graphics.rect(-75, -4, 150 * percentage, 8);
graphics.fill();
}
}
}
}
updateStatus(status: string) {
if (this.statusLabel) {
this.statusLabel.string = status;
}
}
updateActionProgress(actionName: string, progress: number) {
if (this.actionLabel) {
this.actionLabel.string = actionName;
}
if (this.actionProgressBar) {
this.actionProgressBar.progress = Math.max(0, Math.min(1, progress));
this.actionProgressBar.node.active = actionName !== '' && progress > 0;
}
}
setVisible(visible: boolean) {
this.node.active = visible;
}
updateOreCount(hasOre: boolean, warehouseTotal: number) {
if (this.oreCountLabel) {
this.oreCountLabel.string = hasOre ? '💎1' : '💎0';
}
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "5f877c25-5c26-49c6-bbb5-7ff36323e0a1",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,334 +0,0 @@
import { _decorator, Component, Vec3, Node } from 'cc';
import { UnitController } from './UnitController';
const { ccclass } = _decorator;
/**
* 矿工体力系统行为处理器 - 处理挖矿、休息、存储的完整循环
* 展示体力驱动的工作-休息循环系统
*/
@ccclass('RTSBehaviorHandler')
export class RTSBehaviorHandler extends Component {
private unitController: UnitController | null = null;
private minerDemo: any = null; // MinerDemo组件引用
private lastActionTime: number = 0;
private actionCooldown: number = 0.5; // 动作冷却时间,避免频繁切换
private minerIndex: number = -1; // 矿工索引,用于找到对应的家
start() {
this.unitController = this.getComponent(UnitController);
// 获取场景中的MinerDemo组件
this.minerDemo = this.node.parent?.getComponent('MinerDemo');
if (!this.unitController) {
console.error('RTSBehaviorHandler: 未找到UnitController组件');
}
if (!this.minerDemo) {
console.error('RTSBehaviorHandler: 未找到MinerDemo组件');
}
// 从节点名称中提取矿工索引
const match = this.node.name.match(/Miner_(\d+)/);
if (match) {
this.minerIndex = parseInt(match[1]) - 1; // 转换为0基索引
}
this.lastActionTime = Date.now();
}
/**
* 检查动作冷却
*/
private isActionOnCooldown(): boolean {
return (Date.now() - this.lastActionTime) < (this.actionCooldown * 1000);
}
/**
* 更新动作时间
*/
private updateActionTime() {
this.lastActionTime = Date.now();
}
/**
* 挖掘金矿(永不枯竭)
* @param params 事件参数,包含黑板变量值
*/
onMineGoldOre(params: any = {}): string {
if (!this.unitController || !this.minerDemo) {
return 'failure';
}
// 检查体力是否充足
if (this.unitController.currentStamina < this.unitController.staminaCostPerMining) {
return 'failure';
}
// 检查是否已经携带矿石
const hasOre = this.unitController.getBlackboardValue('hasOre');
if (hasOre) {
return 'failure';
}
// 动作冷却检查
if (this.isActionOnCooldown()) {
return 'running';
}
// 获取所有金矿
const goldMines = this.minerDemo.getAllGoldMines();
if (goldMines.length === 0) {
return 'failure';
}
// 寻找最近的金矿
const currentPos = this.node.worldPosition;
let nearestMine: Node | null = null;
let minDistance = Infinity;
for (const mine of goldMines) {
if (!mine || !mine.isValid) continue;
const distance = Vec3.distance(currentPos, mine.worldPosition);
if (distance < minDistance) {
minDistance = distance;
nearestMine = mine;
}
}
if (!nearestMine) {
return 'failure';
}
// 检查是否已经到达金矿位置
if (minDistance < 2.0) {
// 检查是否正在移动
const isMoving = this.unitController.getBlackboardValue('isMoving');
if (isMoving) {
return 'running';
}
// 消耗体力
this.unitController.currentStamina = Math.max(0, this.unitController.currentStamina - this.unitController.staminaCostPerMining);
// 设置携带矿石状态
this.unitController.setBlackboardValue('hasOre', true);
// 通知演示管理器
this.minerDemo.mineGoldOre(this.node);
// 清除移动目标
this.unitController.clearTarget();
this.unitController.setBlackboardValue('isMoving', false);
this.updateActionTime();
return 'success';
} else {
// 设置移动目标
this.unitController.setTarget(nearestMine.worldPosition);
return 'running';
}
}
/**
* 前往仓库存储矿石
* @param params 事件参数,包含黑板变量值
*/
onStoreOre(params: any = {}): string {
if (!this.unitController || !this.minerDemo) {
return 'failure';
}
// 检查是否携带矿石
const hasOre = this.unitController.getBlackboardValue('hasOre');
if (!hasOre) {
return 'failure';
}
// 动作冷却检查
if (this.isActionOnCooldown()) {
return 'running';
}
const warehouse = this.minerDemo.getWarehouse();
if (!warehouse || !warehouse.isValid) {
return 'failure';
}
// 计算到仓库的距离
const currentPos = this.node.worldPosition;
const warehousePos = warehouse.worldPosition;
const distance = Vec3.distance(currentPos, warehousePos);
// 检查是否已经到达仓库
if (distance < 2.5) {
// 检查是否正在移动
const isMoving = this.unitController.getBlackboardValue('isMoving');
if (isMoving) {
return 'running';
}
// 清除携带矿石状态
this.unitController.setBlackboardValue('hasOre', false);
// 清除移动目标
this.unitController.clearTarget();
this.unitController.setBlackboardValue('isMoving', false);
this.updateActionTime();
return 'success';
} else {
// 设置移动目标
this.unitController.setTarget(warehousePos);
return 'running';
}
}
/**
* 回家休息
* @param params 事件参数,包含黑板变量值
*/
onGoHomeRest(params: any = {}): string {
if (!this.unitController || !this.minerDemo) {
return 'failure';
}
// 动作冷却检查
if (this.isActionOnCooldown()) {
return 'running';
}
// 获取矿工的家
const home = this.minerDemo.getMinerHome(this.minerIndex);
if (!home || !home.isValid) {
return 'failure';
}
// 计算到家的距离
const currentPos = this.node.worldPosition;
const homePos = home.worldPosition;
const distance = Vec3.distance(currentPos, homePos);
// 检查是否已经到达家
if (distance < 2.0) {
// 检查是否正在移动
const isMoving = this.unitController.getBlackboardValue('isMoving');
if (isMoving) {
return 'running';
}
// 设置休息状态
this.unitController.setBlackboardValue('isResting', true);
// 清除移动目标
this.unitController.clearTarget();
this.unitController.setBlackboardValue('isMoving', false);
this.updateActionTime();
return 'success';
} else {
// 设置移动目标
this.unitController.setTarget(homePos);
return 'running';
}
}
/**
* 恢复体力
* @param params 事件参数,包含黑板变量值
*/
onRecoverStamina(params: any = {}): string {
if (!this.unitController) {
return 'failure';
}
// 检查是否在家中
const isResting = this.unitController.getBlackboardValue('isResting');
if (!isResting) {
return 'failure';
}
// 恢复体力
const oldStamina = this.unitController.currentStamina;
this.unitController.currentStamina = Math.min(this.unitController.maxStamina,
this.unitController.currentStamina + this.unitController.staminaRecoveryRate * 0.1); // 每次恢复2点体力
const isFullyRested = this.unitController.currentStamina >= this.unitController.maxStamina;
if (isFullyRested) {
// 清除休息状态
this.unitController.setBlackboardValue('isResting', false);
// 通知演示管理器
this.minerDemo.completeRestCycle();
this.updateActionTime();
return 'success';
} else {
// 体力还在恢复中
return 'running';
}
}
/**
* 待机行为
* @param params 事件参数,包含黑板变量值
*/
onIdleBehavior(params: any = {}): string {
if (!this.unitController) {
return 'failure';
}
// 清除移动目标,确保停止移动
this.unitController.clearTarget();
this.unitController.setBlackboardValue('isMoving', false);
return 'success';
}
/**
* 获取矿工状态摘要
*/
getMinerStatus(): string {
if (!this.unitController) return 'Unknown';
const hasOre = this.unitController.getBlackboardValue('hasOre');
const isMoving = this.unitController.getBlackboardValue('isMoving');
const isResting = this.unitController.getBlackboardValue('isResting');
const stamina = this.unitController.currentStamina;
const maxStamina = this.unitController.maxStamina;
let status = '';
if (isResting) {
status = '😴休息中';
} else if (hasOre) {
status = isMoving ? '🚚运输中' : '📦携带矿石';
} else {
status = isMoving ? '🚶移动中' : '⛏️挖矿';
}
return `${status} (体力:${stamina.toFixed(0)}/${maxStamina})`;
}
/**
* 调试信息
*/
getDebugInfo(): any {
if (!this.unitController) return {};
return {
name: this.node.name,
hasOre: this.unitController.getBlackboardValue('hasOre'),
isMoving: this.unitController.getBlackboardValue('isMoving'),
isResting: this.unitController.getBlackboardValue('isResting'),
stamina: this.unitController.currentStamina,
maxStamina: this.unitController.maxStamina,
staminaPercentage: this.unitController.currentStamina / this.unitController.maxStamina,
isLowStamina: this.unitController.currentStamina < this.unitController.maxStamina * 0.2,
status: this.getMinerStatus()
};
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "739ff9ee-42d5-4542-bb5b-3e7611c729e2",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,126 +0,0 @@
import { _decorator, Component, Node, Vec3, MeshRenderer, BoxCollider, RigidBody, Material, Color, primitives, utils } from 'cc';
const { ccclass, property } = _decorator;
/**
* 简单预制体工厂
*/
@ccclass('SimplePrefabFactory')
export class SimplePrefabFactory extends Component {
/**
* 创建单位节点
*/
static createUnit(name: string, color: Color = Color.WHITE): Node {
const unit = new Node(name);
// 添加网格渲染器
const meshRenderer = unit.addComponent(MeshRenderer);
// 创建立方体网格
const mesh = utils.createMesh(primitives.box({ width: 1, height: 1, length: 1 }));
meshRenderer.mesh = mesh;
// 创建材质
const material = new Material();
material.initialize({ effectName: 'builtin-unlit' });
material.setProperty('mainColor', color);
meshRenderer.material = material;
// 添加碰撞器
const collider = unit.addComponent(BoxCollider);
collider.size = new Vec3(1, 1, 1);
// 添加刚体
const rigidBody = unit.addComponent(RigidBody);
rigidBody.type = RigidBody.Type.KINEMATIC;
return unit;
}
/**
* 创建建筑节点
*/
static createBuilding(name: string, size: Vec3 = new Vec3(2, 2, 2), color: Color = Color.GRAY): Node {
const building = new Node(name);
// 添加网格渲染器
const meshRenderer = building.addComponent(MeshRenderer);
// 创建立方体网格
const mesh = utils.createMesh(primitives.box({
width: size.x,
height: size.y,
length: size.z
}));
meshRenderer.mesh = mesh;
// 创建材质
const material = new Material();
material.initialize({ effectName: 'builtin-unlit' });
material.setProperty('mainColor', color);
meshRenderer.material = material;
// 添加碰撞器
const collider = building.addComponent(BoxCollider);
collider.size = size;
return building;
}
/**
* 创建资源节点
*/
static createResource(name: string, color: Color = Color.YELLOW): Node {
const resource = new Node(name);
// 添加网格渲染器
const meshRenderer = resource.addComponent(MeshRenderer);
// 创建球体网格
const mesh = utils.createMesh(primitives.sphere(0.5));
meshRenderer.mesh = mesh;
// 创建材质
const material = new Material();
material.initialize({ effectName: 'builtin-unlit' });
material.setProperty('mainColor', color);
meshRenderer.material = material;
// 添加碰撞器
const collider = resource.addComponent(BoxCollider);
collider.size = new Vec3(1, 1, 1);
return resource;
}
/**
* 创建地面节点
*/
static createGround(size: Vec3 = new Vec3(50, 0.1, 50), color: Color = new Color(100, 150, 100, 255)): Node {
const ground = new Node('Ground');
// 添加网格渲染器
const meshRenderer = ground.addComponent(MeshRenderer);
// 创建平面网格
const mesh = utils.createMesh(primitives.box({
width: size.x,
height: size.y,
length: size.z
}));
meshRenderer.mesh = mesh;
// 创建材质
const material = new Material();
material.initialize({ effectName: 'builtin-unlit' });
material.setProperty('mainColor', color);
meshRenderer.material = material;
// 添加碰撞器
const collider = ground.addComponent(BoxCollider);
collider.size = size;
return ground;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ac45cfc7-cf47-4315-bdf0-ba002b45b4b6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,282 +0,0 @@
import { Component, _decorator, Node, Label, ProgressBar, UITransform, Widget, Canvas, find, director, Color, Sprite, Layers, Graphics } from 'cc';
import { MinerStatusUI } from './MinerStatusUI';
const { ccclass, property } = _decorator;
/**
* 状态UI管理器
* 负责创建和管理游戏对象的状态显示界面
*/
@ccclass('StatusUIManager')
export class StatusUIManager extends Component {
/**
* 为矿工创建状态显示UI
*/
static createStatusUIForMiner(miner: Node): MinerStatusUI | null {
const canvas = find('Canvas') || director.getScene()?.getChildByName('Canvas');
if (!canvas) {
return null;
}
const uiRoot = new Node(`${miner.name}_StatusUI`);
canvas.addChild(uiRoot);
const uiTransform = uiRoot.addComponent(UITransform);
uiTransform.setContentSize(200, 100);
const borderNode = new Node('Border');
uiRoot.addChild(borderNode);
const borderTransform = borderNode.addComponent(UITransform);
borderTransform.setContentSize(202, 102);
const borderGraphics = borderNode.addComponent(Graphics);
borderGraphics.fillColor = new Color(100, 100, 100, 120);
borderGraphics.rect(-101, -51, 202, 102);
borderGraphics.fill();
const borderWidget = borderNode.addComponent(Widget);
borderWidget.isAlignTop = true;
borderWidget.isAlignBottom = true;
borderWidget.isAlignLeft = true;
borderWidget.isAlignRight = true;
borderWidget.top = -1;
borderWidget.bottom = -1;
borderWidget.left = -1;
borderWidget.right = -1;
borderWidget.updateAlignment();
const backgroundNode = new Node('Background');
uiRoot.addChild(backgroundNode);
const backgroundTransform = backgroundNode.addComponent(UITransform);
backgroundTransform.setContentSize(200, 100);
const backgroundGraphics = backgroundNode.addComponent(Graphics);
backgroundGraphics.fillColor = new Color(0, 0, 0, 100);
backgroundGraphics.rect(-100, -50, 200, 100);
backgroundGraphics.fill();
const statusUI = uiRoot.addComponent(MinerStatusUI);
statusUI.setFollowTarget(miner);
const nameNode = new Node('NameLabel');
uiRoot.addChild(nameNode);
const nameTransform = nameNode.addComponent(UITransform);
nameTransform.setContentSize(200, 25);
const nameLabel = nameNode.addComponent(Label);
nameLabel.string = miner.name;
nameLabel.fontSize = 16;
nameLabel.color = new Color(255, 255, 255, 255);
const nameWidget = nameNode.addComponent(Widget);
nameWidget.isAlignTop = true;
nameWidget.top = 0;
nameWidget.isAlignHorizontalCenter = true;
nameWidget.updateAlignment();
// 创建状态标签
const statusNode = new Node('StatusLabel');
uiRoot.addChild(statusNode);
const statusTransform = statusNode.addComponent(UITransform);
statusTransform.setContentSize(200, 20);
const statusLabel = statusNode.addComponent(Label);
statusLabel.string = '待机中';
statusLabel.fontSize = 14;
statusLabel.color = new Color(200, 200, 200, 255);
// 设置状态标签位置
const statusWidget = statusNode.addComponent(Widget);
statusWidget.isAlignTop = true;
statusWidget.top = 25;
statusWidget.isAlignHorizontalCenter = true;
statusWidget.updateAlignment();
// 创建体力进度条
const staminaBarNode = new Node('StaminaBar');
uiRoot.addChild(staminaBarNode);
const staminaBarTransform = staminaBarNode.addComponent(UITransform);
staminaBarTransform.setContentSize(150, 8);
const staminaBar = staminaBarNode.addComponent(ProgressBar);
staminaBar.progress = 1.0;
// 创建体力进度条背景
const staminaBgNode = new Node('Background');
staminaBarNode.addChild(staminaBgNode);
const staminaBgTransform = staminaBgNode.addComponent(UITransform);
staminaBgTransform.setContentSize(150, 8);
const staminaBgGraphics = staminaBgNode.addComponent(Graphics);
staminaBgGraphics.fillColor = new Color(50, 50, 50, 255);
staminaBgGraphics.rect(-75, -4, 150, 8);
staminaBgGraphics.fill();
// 创建体力进度条填充
const staminaFillNode = new Node('Bar');
staminaBarNode.addChild(staminaFillNode);
const staminaFillTransform = staminaFillNode.addComponent(UITransform);
staminaFillTransform.setContentSize(150, 8);
const staminaFillGraphics = staminaFillNode.addComponent(Graphics);
staminaFillGraphics.fillColor = new Color(0, 255, 0, 255);
staminaFillGraphics.rect(-75, -4, 150, 8);
staminaFillGraphics.fill();
// 设置体力进度条位置
const staminaWidget = staminaBarNode.addComponent(Widget);
staminaWidget.isAlignTop = true;
staminaWidget.top = 45;
staminaWidget.isAlignHorizontalCenter = true;
staminaWidget.updateAlignment();
// 创建动作进度条
const actionBarNode = new Node('ActionProgressBar');
uiRoot.addChild(actionBarNode);
const actionBarTransform = actionBarNode.addComponent(UITransform);
actionBarTransform.setContentSize(150, 6);
const actionBar = actionBarNode.addComponent(ProgressBar);
actionBar.progress = 0;
actionBarNode.active = false; // 初始隐藏
// 创建动作进度条背景
const actionBgNode = new Node('Background');
actionBarNode.addChild(actionBgNode);
const actionBgTransform = actionBgNode.addComponent(UITransform);
actionBgTransform.setContentSize(150, 6);
const actionBgGraphics = actionBgNode.addComponent(Graphics);
actionBgGraphics.fillColor = new Color(50, 50, 50, 255);
actionBgGraphics.rect(-75, -3, 150, 6);
actionBgGraphics.fill();
// 创建动作进度条填充
const actionFillNode = new Node('Bar');
actionBarNode.addChild(actionFillNode);
const actionFillTransform = actionFillNode.addComponent(UITransform);
actionFillTransform.setContentSize(150, 6);
const actionFillGraphics = actionFillNode.addComponent(Graphics);
actionFillGraphics.fillColor = new Color(255, 255, 0, 255);
actionFillGraphics.rect(-75, -3, 150, 6);
actionFillGraphics.fill();
// 设置动作进度条位置
const actionWidget = actionBarNode.addComponent(Widget);
actionWidget.isAlignTop = true;
actionWidget.top = 55;
actionWidget.isAlignHorizontalCenter = true;
actionWidget.updateAlignment();
// 创建动作标签
const actionLabelNode = new Node('ActionLabel');
uiRoot.addChild(actionLabelNode);
const actionLabelTransform = actionLabelNode.addComponent(UITransform);
actionLabelTransform.setContentSize(200, 15);
const actionLabel = actionLabelNode.addComponent(Label);
actionLabel.string = '';
actionLabel.fontSize = 12;
actionLabel.color = new Color(255, 255, 0, 255);
// 设置动作标签位置
const actionLabelWidget = actionLabelNode.addComponent(Widget);
actionLabelWidget.isAlignTop = true;
actionLabelWidget.top = 65;
actionLabelWidget.isAlignHorizontalCenter = true;
actionLabelWidget.updateAlignment();
// 创建矿石数量标签
const oreCountNode = new Node('OreCountLabel');
uiRoot.addChild(oreCountNode);
const oreCountTransform = oreCountNode.addComponent(UITransform);
oreCountTransform.setContentSize(100, 15);
const oreCountLabel = oreCountNode.addComponent(Label);
oreCountLabel.string = '💎0';
oreCountLabel.fontSize = 12;
oreCountLabel.color = new Color(255, 215, 0, 255); // 金色
// 设置矿石数量标签位置(居中显示)
const oreCountWidget = oreCountNode.addComponent(Widget);
oreCountWidget.isAlignTop = true;
oreCountWidget.top = 80;
oreCountWidget.isAlignHorizontalCenter = true;
oreCountWidget.updateAlignment();
statusUI.nameLabel = nameLabel;
statusUI.statusLabel = statusLabel;
statusUI.staminaBar = staminaBar;
statusUI.actionProgressBar = actionBar;
statusUI.actionLabel = actionLabel;
statusUI.oreCountLabel = oreCountLabel;
statusUI.warehouseCountLabel = null;
StatusUIManager.setNodeLayerRecursively(uiRoot, Layers.Enum.UI_2D);
return statusUI;
}
/**
* 递归设置节点及其子节点的层级
*/
private static setNodeLayerRecursively(node: Node, layer: number) {
node.layer = layer;
for (const child of node.children) {
StatusUIManager.setNodeLayerRecursively(child, layer);
}
}
/**
* 从矿工名字中提取索引号
*/
private static extractMinerIndex(minerName: string): number {
const match = minerName.match(/Miner_(\d+)/);
if (match) {
return parseInt(match[1]) - 1;
}
return 0;
}
/**
* 为仓库创建存储量显示UI
*/
static createWarehouseUI(warehouse: Node): MinerStatusUI | null {
const canvas = find('Canvas') || director.getScene()?.getChildByName('Canvas');
if (!canvas) {
return null;
}
const uiRoot = new Node(`${warehouse.name}_StorageUI`);
canvas.addChild(uiRoot);
const uiTransform = uiRoot.addComponent(UITransform);
uiTransform.setContentSize(120, 40);
const backgroundNode = new Node('Background');
uiRoot.addChild(backgroundNode);
const backgroundTransform = backgroundNode.addComponent(UITransform);
backgroundTransform.setContentSize(120, 40);
const backgroundGraphics = backgroundNode.addComponent(Graphics);
backgroundGraphics.fillColor = new Color(0, 0, 0, 120);
backgroundGraphics.rect(-60, -20, 120, 40);
backgroundGraphics.fill();
const storageNode = new Node('StorageLabel');
uiRoot.addChild(storageNode);
const storageTransform = storageNode.addComponent(UITransform);
storageTransform.setContentSize(120, 30);
const storageLabel = storageNode.addComponent(Label);
storageLabel.string = '🏭 总存储: 0';
storageLabel.fontSize = 14;
storageLabel.color = new Color(255, 255, 255, 255);
const storageWidget = storageNode.addComponent(Widget);
storageWidget.isAlignHorizontalCenter = true;
storageWidget.isAlignVerticalCenter = true;
storageWidget.updateAlignment();
const statusUI = uiRoot.addComponent(MinerStatusUI);
statusUI.setFollowTarget(warehouse);
statusUI.nameLabel = null;
statusUI.statusLabel = null;
statusUI.staminaBar = null;
statusUI.actionProgressBar = null;
statusUI.actionLabel = null;
statusUI.oreCountLabel = null;
statusUI.warehouseCountLabel = storageLabel;
StatusUIManager.setNodeLayerRecursively(uiRoot, Layers.Enum.UI_2D);
return statusUI;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "7478e794-dd80-4661-9421-8e147d33c51e",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,353 +0,0 @@
import { _decorator, Component, Node, Vec3, MeshRenderer, Color, tween } from 'cc';
import { BehaviorTreeManager } from './BehaviorTreeManager';
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
const { ccclass, property } = _decorator;
/**
* 单位配置接口
*/
export interface UnitConfig {
unitType: string;
behaviorTreeName: string;
maxHealth: number;
moveSpeed: number;
attackRange: number;
attackDamage: number;
color: string;
}
/**
* 单位控制器
*/
@ccclass('UnitController')
export class UnitController extends Component {
@property
showDebugInfo: boolean = true;
// 单位属性
public unitType: string = '';
public maxHealth: number = 100;
public currentHealth: number = 100;
public moveSpeed: number = 1.5;
public attackRange: number = 2;
public attackDamage: number = 25;
public isSelected: boolean = false;
public currentCommand: string = 'idle';
public targetPosition: Vec3 = Vec3.ZERO.clone();
public targetNode: Node | null = null;
public lastAttackTime: number = 0;
public attackCooldown: number = 1.5;
public color: string = 'white';
// 体力系统属性
public maxStamina: number = 100;
public currentStamina: number = 100;
public homePosition: Vec3 = Vec3.ZERO.clone();
public staminaRecoveryRate: number = 20; // 每秒恢复的体力
public staminaCostPerMining: number = 15; // 每次挖矿消耗的体力
// 移动状态管理
private isMoving: boolean = false;
private moveStartTime: number = 0;
private lastTargetUpdateTime: number = 0;
private behaviorTreeManager: BehaviorTreeManager | null = null;
private behaviorHandler: Component | null = null;
private meshRenderer: MeshRenderer | null = null;
onLoad() {
this.meshRenderer = this.getComponent(MeshRenderer);
// 创建行为树管理器
this.behaviorTreeManager = this.addComponent(BehaviorTreeManager);
// 添加RTS行为处理器
try {
// 添加RTSBehaviorHandler组件
this.behaviorHandler = this.addComponent(RTSBehaviorHandler);
} catch (error) {
console.warn('RTSBehaviorHandler组件添加失败', error);
}
}
/**
* 设置单位配置
*/
setup(config: UnitConfig) {
this.unitType = config.unitType;
this.maxHealth = config.maxHealth;
this.currentHealth = config.maxHealth;
this.moveSpeed = config.moveSpeed;
this.attackRange = config.attackRange;
this.attackDamage = config.attackDamage;
this.color = config.color;
// 设置材质颜色
this.setUnitColor(config.color);
// 设置节点名称显示单位类型
this.node.name = `${config.unitType.toUpperCase()}_${this.node.name}`;
// 初始化行为树
if (this.behaviorTreeManager) {
this.behaviorTreeManager.initializeBehaviorTree(config.behaviorTreeName, this);
}
}
/**
* 设置单位颜色
*/
private setUnitColor(colorName: string) {
if (!this.meshRenderer || !this.meshRenderer.material) return;
const colorMap: { [key: string]: Color } = {
'red': Color.RED,
'green': Color.GREEN,
'blue': Color.BLUE,
'yellow': Color.YELLOW,
'white': Color.WHITE,
'cyan': Color.CYAN,
'magenta': Color.MAGENTA
};
const color = colorMap[colorName] || Color.WHITE;
this.meshRenderer.material.setProperty('mainColor', color);
}
/**
* 设置选择状态
*/
setSelected(selected: boolean) {
this.isSelected = selected;
// 视觉效果
if (selected) {
this.showSelectionEffect();
} else {
this.hideSelectionEffect();
}
// 更新行为树黑板
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('isSelected', selected);
}
}
/**
* 显示选择效果
*/
private showSelectionEffect() {
// 添加选择圈效果
tween(this.node)
.to(0.3, { scale: new Vec3(1.1, 1.1, 1.1) })
.to(0.3, { scale: Vec3.ONE })
.union()
.repeatForever()
.start();
}
/**
* 隐藏选择效果
*/
private hideSelectionEffect() {
// 停止所有缩放动画
tween(this.node).stop();
this.node.setScale(Vec3.ONE);
}
/**
* 发布命令
*/
issueCommand(command: string, target?: Vec3 | Node) {
this.currentCommand = command;
// 设置目标
if (target instanceof Vec3) {
this.targetPosition = target.clone();
this.targetNode = null;
} else if (target instanceof Node) {
this.targetPosition = target.worldPosition.clone();
this.targetNode = target;
}
// 更新行为树黑板
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('currentCommand', command);
this.behaviorTreeManager.updateBlackboardValue('hasTarget', target !== undefined);
this.behaviorTreeManager.updateBlackboardValue('targetPosition', this.targetPosition);
if (target instanceof Node) {
this.behaviorTreeManager.updateBlackboardValue('targetType',
target.name.includes('Resource') ? 'resource' :
target.name.includes('Building') ? 'building' : 'unit');
}
}
}
/**
* 设置黑板变量值
*/
setBlackboardValue(key: string, value: any) {
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue(key, value);
}
}
/**
* 获取黑板变量值
*/
getBlackboardValue(key: string): any {
return this.behaviorTreeManager?.getBlackboardValue(key);
}
/**
* 设置移动目标
*/
setTarget(position: Vec3) {
this.targetPosition = position.clone();
this.isMoving = true;
this.moveStartTime = Date.now();
}
/**
* 清除移动目标
*/
clearTarget() {
this.targetPosition = Vec3.ZERO.clone();
this.isMoving = false;
}
/**
* 受到伤害
*/
takeDamage(damage: number) {
this.currentHealth = Math.max(0, this.currentHealth - damage);
// 更新行为树黑板
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('currentHealth', this.currentHealth);
this.behaviorTreeManager.updateBlackboardValue('healthPercentage', this.currentHealth / this.maxHealth);
this.behaviorTreeManager.updateBlackboardValue('isLowHealth', this.currentHealth < this.maxHealth * 0.3);
}
// 视觉效果
this.showDamageEffect();
if (this.currentHealth <= 0) {
this.die();
}
}
/**
* 显示受伤效果
*/
private showDamageEffect() {
if (!this.meshRenderer || !this.meshRenderer.material) return;
// 闪红效果
const originalColor = this.meshRenderer.material.getProperty('mainColor') as Color;
this.meshRenderer.material.setProperty('mainColor', Color.RED);
this.scheduleOnce(() => {
if (this.meshRenderer && this.meshRenderer.material) {
this.meshRenderer.material.setProperty('mainColor', originalColor);
}
}, 0.2);
}
/**
* 单位死亡
*/
private die() {
console.log(`单位 ${this.node.name} 死亡`);
// 播放死亡动画后销毁节点
tween(this.node)
.to(0.5, { scale: Vec3.ZERO })
.call(() => {
this.node.destroy();
})
.start();
}
/**
* 移动到目标位置只在水平面移动不改变Y轴
*/
moveToTarget(targetPos: Vec3, speed?: number, deltaTime?: number): boolean {
const currentPos = this.node.worldPosition;
const distance = Vec3.distance(currentPos, targetPos);
if (distance < 0.5) {
this.isMoving = false;
return true;
}
const actualSpeed = speed || this.moveSpeed;
const actualDeltaTime = deltaTime || 0.016;
const direction = new Vec3();
Vec3.subtract(direction, targetPos, currentPos);
direction.normalize();
const moveDistance = actualSpeed * actualDeltaTime;
const newPosition = new Vec3();
Vec3.scaleAndAdd(newPosition, currentPos, direction, moveDistance);
this.node.setWorldPosition(newPosition);
this.isMoving = true;
return false;
}
/**
* 攻击目标
*/
attackTarget(): boolean {
const currentTime = Date.now();
if (currentTime - this.lastAttackTime < this.attackCooldown * 1000) {
return false;
}
if (this.targetNode && this.targetNode.isValid) {
const distance = Vec3.distance(this.node.worldPosition, this.targetNode.worldPosition);
if (distance <= this.attackRange) {
this.lastAttackTime = currentTime;
return true;
}
}
return false;
}
update(deltaTime: number) {
if (this.behaviorTreeManager) {
this.behaviorTreeManager.update(deltaTime);
}
if (this.isMoving && !this.targetPosition.equals(Vec3.ZERO)) {
const reached = this.moveToTarget(this.targetPosition, this.moveSpeed, deltaTime);
if (reached) {
this.clearTarget();
}
}
// 调试信息显示
if (this.showDebugInfo) {
this.updateDebugInfo();
}
}
/**
* 更新调试信息
*/
private updateDebugInfo() {
// 可以在这里添加调试信息的显示逻辑
// 比如在单位上方显示状态文本等
}
onDestroy() {
// 停止所有动画
tween(this.node).stop();
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "4ac64480-2d09-4de6-a22c-add022790676",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "a1f43720-46e1-4d07-b56a-c9307e45726c",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,103 +0,0 @@
import { Core } from '@esengine/ecs-framework';
import { Component, _decorator } from 'cc';
import { GameScene } from './scenes/GameScene';
const { ccclass, property } = _decorator;
/**
* ECS管理器 - Cocos Creator组件
* 将此组件添加到场景中的任意节点上即可启动ECS框架
*
* 使用说明:
* 1. 在Cocos Creator场景中创建一个空节点
* 2. 将此ECSManager组件添加到该节点
* 3. 运行场景即可自动启动ECS框架
*/
@ccclass('ECSManager')
export class ECSManager extends Component {
@property({
tooltip: '是否启用调试模式(建议开发阶段开启)'
})
public debugMode: boolean = true;
private isInitialized: boolean = false;
/**
* 组件启动时初始化ECS
*/
start() {
this.initializeECS();
}
/**
* 初始化ECS框架
*/
private initializeECS(): void {
if (this.isInitialized) return;
// ECS框架初始化开始
try {
// 1. 创建Core实例启用调试功能
if (this.debugMode) {
Core.create({
debug: true,
enableEntitySystems: true,
debugConfig: {
enabled: true,
websocketUrl: 'ws://localhost:8080',
autoReconnect: true,
debugFrameRate: 30,
channels: {
entities: true,
systems: true,
performance: true,
components: true,
scenes: true
}
}
});
console.log('✅ ECS调试模式已启用');
} else {
Core.create({
debug: false,
enableEntitySystems: true
});
console.log(' ECS调试模式已禁用');
}
// 2. 创建游戏场景
const gameScene = new GameScene();
// 3. 设置为当前场景会自动调用scene.begin()
Core.setScene(gameScene);
this.isInitialized = true;
// ECS框架初始化完成
} catch (error) {
console.error('ECS框架初始化失败:', error);
}
}
/**
* 每帧更新ECS框架
*/
update(deltaTime: number) {
if (this.isInitialized) {
// 更新ECS核心系统
Core.update(deltaTime);
}
}
/**
* 组件销毁时清理ECS
*/
onDestroy() {
if (this.isInitialized) {
// ECS框架清理
this.isInitialized = false;
}
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b89656f0-6320-4b6d-81cd-447bf811230c",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,153 +0,0 @@
# ECS框架启动模板
欢迎使用ECS框架这是一个最基础的启动模板帮助您快速开始ECS项目开发。
## 📁 项目结构
```
ecs/
├── components/ # 组件目录(请在此添加您的组件)
├── systems/ # 系统目录(请在此添加您的系统)
├── scenes/ # 场景目录
│ └── GameScene.ts # 主游戏场景
├── ECSManager.ts # ECS管理器组件
└── README.md # 本文档
```
## 🚀 快速开始
### 1. 启动ECS框架
ECS框架已经配置完成您只需要
1. 在Cocos Creator中打开您的场景
2. 创建一个空节点(例如命名为"ECSManager"
3.`ECSManager` 组件添加到该节点
4. 运行场景ECS框架将自动启动
### 2. 查看控制台输出
如果一切正常,您将在控制台看到:
```
🎮 正在初始化ECS框架...
🔧 ECS调试模式已启用可在Cocos Creator扩展面板中查看调试信息
🎯 游戏场景已创建
✅ ECS框架初始化成功
🚀 游戏场景已启动
```
### 3. 使用调试面板
ECS框架已启用调试功能您可以
1. 在Cocos Creator编辑器菜单中选择 "扩展" → "ECS Framework" → "调试面板"
2. 调试面板将显示实时的ECS运行状态
- 实体数量和状态
- 系统执行信息
- 性能监控数据
- 组件统计信息
**注意**:调试功能会消耗一定性能,正式发布时建议关闭调试模式。
## 📚 下一步开发
### 创建您的第一个组件
`components/` 目录下创建组件:
```typescript
// components/PositionComponent.ts
import { Component } from '@esengine/ecs-framework';
import { Vec3 } from 'cc';
export class PositionComponent extends Component {
public position: Vec3 = new Vec3();
constructor(x: number = 0, y: number = 0, z: number = 0) {
super();
this.position.set(x, y, z);
}
}
```
### 创建您的第一个系统
`systems/` 目录下创建系统:
```typescript
// systems/MovementSystem.ts
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
import { PositionComponent } from '../components/PositionComponent';
export class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PositionComponent));
}
protected process(entities: Entity[]): void {
for (const entity of entities) {
const position = entity.getComponent(PositionComponent);
if (position) {
// TODO: 在这里编写移动逻辑
console.log(`实体 ${entity.name} 位置: ${position.position}`);
}
}
}
}
```
### 在场景中注册系统
`scenes/GameScene.ts``initialize()` 方法中添加:
```typescript
import { MovementSystem } from '../systems/MovementSystem';
public initialize(): void {
super.initialize();
this.name = "MainGameScene";
// 添加系统
this.addEntityProcessor(new MovementSystem());
// 创建测试实体
const testEntity = this.createEntity("TestEntity");
testEntity.addComponent(new PositionComponent(0, 0, 0));
}
```
## 🔗 学习资源
- [ECS框架完整文档](https://github.com/esengine/ecs-framework)
- [ECS概念详解](https://github.com/esengine/ecs-framework/blob/master/docs/concepts-explained.md)
- [新手教程](https://github.com/esengine/ecs-framework/blob/master/docs/beginner-tutorials.md)
- [组件设计指南](https://github.com/esengine/ecs-framework/blob/master/docs/component-design-guide.md)
- [系统开发指南](https://github.com/esengine/ecs-framework/blob/master/docs/system-guide.md)
## 💡 开发提示
1. **组件只存储数据**:避免在组件中编写复杂逻辑
2. **系统处理逻辑**:所有业务逻辑应该在系统中实现
3. **使用Matcher过滤实体**系统通过Matcher指定需要处理的实体类型
4. **性能优化**:大量实体时考虑使用位掩码查询和组件索引
## ❓ 常见问题
### Q: 如何创建实体?
A: 在场景中使用 `this.createEntity("实体名称")`
### Q: 如何给实体添加组件?
A: 使用 `entity.addComponent(new YourComponent())`
### Q: 如何获取实体的组件?
A: 使用 `entity.getComponent(YourComponent)`
### Q: 如何删除实体?
A: 使用 `entity.destroy()``this.destroyEntity(entity)`
---
🎮 **开始您的ECS开发之旅吧**
如有问题请查阅官方文档或提交Issue。

View File

@@ -1,11 +0,0 @@
{
"ver": "1.0.1",
"importer": "text",
"imported": true,
"uuid": "ca94b460-6c6a-4f72-9ec1-ab5fcd2e0e0a",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "3c7bd2b3-6781-482c-be41-21f3dde0e2ab",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,328 +0,0 @@
import { Component } from '@esengine/ecs-framework';
import { Vec3 } from 'cc';
/**
* AI组件 - 复杂的AI行为和状态管理
*/
export class AIComponent extends Component {
/** AI状态 */
public currentState: 'idle' | 'patrol' | 'chase' | 'attack' | 'flee' | 'dead' = 'idle';
/** 目标ID避免循环引用 */
public targetId: number | null = null;
/** AI伙伴ID列表避免循环引用 */
public allyIds: number[] = [];
/** 敌人ID列表 */
public enemyIds: number[] = [];
/** 复杂的AI配置 */
public config: {
personality: {
aggression: number; // 攻击性 0-1
curiosity: number; // 好奇心 0-1
loyalty: number; // 忠诚度 0-1
intelligence: number; // 智力 0-1
};
capabilities: {
sightRange: number;
hearingRange: number;
movementSpeed: number;
attackDamage: number;
health: number;
};
behaviorTree: {
rootNode: BehaviorNode;
blackboard: Map<string, any>;
executionHistory: BehaviorExecution[];
};
memory: {
lastSeenEnemyPosition: Vec3 | null;
lastSeenEnemyTime: number;
knownLocations: Array<{
position: Vec3;
type: 'safe' | 'danger' | 'resource' | 'patrol';
confidence: number;
lastVisited: number;
}>;
relationships: Map<number, {
entityId: number;
relationship: 'ally' | 'enemy' | 'neutral';
trustLevel: number;
lastInteraction: number;
interactionHistory: Array<{
type: 'friendly' | 'hostile' | 'neutral';
timestamp: number;
impact: number;
}>;
}>;
};
};
/** 状态机 */
public stateMachine: {
states: Map<string, AIState>;
transitions: Map<string, Array<{
targetState: string;
condition: () => boolean;
priority: number;
}>>;
stateHistory: Array<{
state: string;
enterTime: number;
exitTime: number;
data: any;
}>;
};
/** 感知系统 */
public perception: {
visibleEntities: Array<{
entityId: number;
position: Vec3;
distance: number;
angle: number;
lastSeen: number;
componentId?: number; // 使用组件ID避免循环引用
}>;
audibleSounds: Array<{
source: Vec3;
volume: number;
type: string;
timestamp: number;
}>;
tacticleInfo: Array<{
entityId: number;
contactPoint: Vec3;
force: number;
timestamp: number;
}>;
};
constructor() {
super();
// 初始化AI配置
this.config = {
personality: {
aggression: Math.random(),
curiosity: Math.random(),
loyalty: Math.random(),
intelligence: Math.random()
},
capabilities: {
sightRange: 100 + Math.random() * 100,
hearingRange: 50 + Math.random() * 50,
movementSpeed: 80 + Math.random() * 40,
attackDamage: 10 + Math.random() * 20,
health: 80 + Math.random() * 40
},
behaviorTree: {
rootNode: new BehaviorNode('root'),
blackboard: new Map(),
executionHistory: []
},
memory: {
lastSeenEnemyPosition: null,
lastSeenEnemyTime: 0,
knownLocations: [],
relationships: new Map()
}
};
// 初始化状态机
this.stateMachine = {
states: new Map(),
transitions: new Map(),
stateHistory: []
};
// 初始化感知系统
this.perception = {
visibleEntities: [],
audibleSounds: [],
tacticleInfo: []
};
this.initializeStateMachine();
this.initializeBehaviorTree();
}
/**
* 初始化状态机
*/
private initializeStateMachine(): void {
// 添加基本状态
this.stateMachine.states.set('idle', new AIState('idle', this));
this.stateMachine.states.set('patrol', new AIState('patrol', this));
this.stateMachine.states.set('chase', new AIState('chase', this));
this.stateMachine.states.set('attack', new AIState('attack', this));
this.stateMachine.states.set('flee', new AIState('flee', this));
// 设置状态转换
this.stateMachine.transitions.set('idle', [
{ targetState: 'patrol', condition: () => Math.random() > 0.8, priority: 1 },
{ targetState: 'chase', condition: () => this.perception.visibleEntities.length > 0, priority: 3 }
]);
this.stateMachine.transitions.set('patrol', [
{ targetState: 'idle', condition: () => Math.random() > 0.9, priority: 1 },
{ targetState: 'chase', condition: () => this.hasVisibleEnemies(), priority: 3 }
]);
}
/**
* 初始化行为树
*/
private initializeBehaviorTree(): void {
const root = this.config.behaviorTree.rootNode;
// 构建简单的行为树结构
const selectorNode = new BehaviorNode('selector');
const sequenceNode = new BehaviorNode('sequence');
const conditionNode = new BehaviorNode('condition');
const actionNode = new BehaviorNode('action');
root.addChild(selectorNode);
selectorNode.addChild(sequenceNode);
sequenceNode.addChild(conditionNode);
sequenceNode.addChild(actionNode);
// 设置黑板数据
this.config.behaviorTree.blackboard.set('lastPatrolPoint', new Vec3());
this.config.behaviorTree.blackboard.set('alertLevel', 0);
this.config.behaviorTree.blackboard.set('energy', 100);
}
/**
* 添加盟友(避免循环引用)
*/
public addAlly(allyEntityId: number): void {
if (!this.allyIds.includes(allyEntityId)) {
this.allyIds.push(allyEntityId);
// 更新关系记录
this.config.memory.relationships.set(allyEntityId, {
entityId: allyEntityId,
relationship: 'ally',
trustLevel: 0.8,
lastInteraction: Date.now(),
interactionHistory: []
});
}
}
/**
* 设置目标(避免循环引用)
*/
public setTarget(targetEntityId: number): void {
this.targetId = targetEntityId;
// 不再需要双向引用
}
/**
* 更新感知信息
*/
public updatePerception(deltaTime: number): void {
// 清理过期的感知信息
const currentTime = Date.now();
this.perception.visibleEntities = this.perception.visibleEntities.filter(
entity => currentTime - entity.lastSeen < 5000
);
this.perception.audibleSounds = this.perception.audibleSounds.filter(
sound => currentTime - sound.timestamp < 2000
);
// 更新记忆中的位置信息
this.config.memory.knownLocations.forEach(location => {
location.confidence *= 0.999; // 随时间衰减可信度
});
}
/**
* 检查是否有可见敌人
*/
private hasVisibleEnemies(): boolean {
return this.perception.visibleEntities.some(entity =>
this.config.memory.relationships.get(entity.entityId)?.relationship === 'enemy'
);
}
/**
* 重置组件
*/
public reset(): void {
// 清理ID数组不再需要处理循环引用
this.allyIds = [];
this.enemyIds = [];
this.targetId = null;
this.currentState = 'idle';
this.config.behaviorTree.blackboard.clear();
this.config.memory.relationships.clear();
this.perception.visibleEntities = [];
this.perception.audibleSounds = [];
this.perception.tacticleInfo = [];
}
}
/**
* 行为树节点
*/
class BehaviorNode {
public name: string;
public children: BehaviorNode[] = [];
public parent: BehaviorNode | null = null;
public data: Map<string, any> = new Map();
constructor(name: string) {
this.name = name;
}
public addChild(child: BehaviorNode): void {
this.children.push(child);
child.parent = this;
}
}
/**
* 行为执行记录
*/
interface BehaviorExecution {
nodeName: string;
startTime: number;
endTime: number;
result: 'success' | 'failure' | 'running';
data: any;
}
/**
* AI状态
*/
class AIState {
public name: string;
public owner: AIComponent;
public enterTime: number = 0;
public data: Map<string, any> = new Map();
constructor(name: string, owner: AIComponent) {
this.name = name;
this.owner = owner;
}
public enter(): void {
this.enterTime = Date.now();
}
public exit(): void {
// 记录状态历史
this.owner.stateMachine.stateHistory.push({
state: this.name,
enterTime: this.enterTime,
exitTime: Date.now(),
data: Object.fromEntries(this.data)
});
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "cc0d3d0d-0c12-4007-8568-11b2cafdfb8f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,66 +0,0 @@
import { Component } from '@esengine/ecs-framework';
/**
* 生命值组件
* 管理实体的生命值、最大生命值等
*/
export class Health extends Component {
/** 当前生命值 */
public currentHealth: number = 100;
/** 最大生命值 */
public maxHealth: number = 100;
/** 是否死亡 */
public isDead: boolean = false;
/** 生命值回复速度 (每秒) */
public regenRate: number = 0;
constructor(maxHealth: number = 100) {
super();
this.maxHealth = maxHealth;
this.currentHealth = maxHealth;
}
/**
* 受到伤害
*/
public takeDamage(damage: number): void {
this.currentHealth = Math.max(0, this.currentHealth - damage);
this.isDead = this.currentHealth <= 0;
}
/**
* 治疗
*/
public heal(amount: number): void {
if (!this.isDead) {
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
}
}
/**
* 复活
*/
public revive(healthPercent: number = 1.0): void {
this.isDead = false;
this.currentHealth = Math.floor(this.maxHealth * Math.max(0, Math.min(1, healthPercent)));
}
/**
* 获取生命值百分比
*/
public getHealthPercent(): number {
return this.maxHealth > 0 ? this.currentHealth / this.maxHealth : 0;
}
/**
* 重置组件
*/
public reset(): void {
this.currentHealth = this.maxHealth;
this.isDead = false;
this.regenRate = 0;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "90369635-a6cb-4313-adf1-64117b50f2bc",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,413 +0,0 @@
import { Component } from '@esengine/ecs-framework';
/**
* 网络组件 - 模拟复杂的网络连接和数据同步(已移除循环引用)
*/
export class NetworkComponent extends Component {
/** 网络ID */
public networkId: string = '';
/** 连接状态 */
public connectionState: 'disconnected' | 'connecting' | 'connected' | 'error' = 'disconnected';
/** 网络连接信息 */
public connection: {
sessionId: string;
serverId: string;
roomId: string;
playerId: string;
ping: number;
packetLoss: number;
bandwidth: number;
lastHeartbeat: number;
};
/** 同步数据 */
public syncData: {
dirtyFlags: Set<string>;
lastSyncTime: number;
syncHistory: Array<{
timestamp: number;
dataSize: number;
properties: string[];
success: boolean;
}>;
queuedUpdates: Array<{
property: string;
value: any;
timestamp: number;
priority: number;
}>;
};
/** 网络统计 */
public networkStats: {
totalBytesSent: number;
totalBytesReceived: number;
packetsPerSecond: number;
averageLatency: number;
latencyHistory: number[];
connectionQuality: 'excellent' | 'good' | 'fair' | 'poor';
errorCount: number;
reconnectCount: number;
lastErrorTime: number;
errorLog: Array<{
timestamp: number;
errorType: string;
message: string;
stack?: string;
}>;
};
/** 连接的玩家ID列表避免循环引用 */
public connectedPlayerIds: Set<string> = new Set();
/** 群组成员ID避免循环引用 */
public groupMemberIds: string[] = [];
/** 群组领导者ID避免循环引用 */
public groupLeaderId: string | null = null;
/** 复杂的网络配置 */
public config: {
autoReconnect: boolean;
maxReconnectAttempts: number;
heartbeatInterval: number;
syncFrequency: number;
compressionEnabled: boolean;
encryptionEnabled: boolean;
priorityLevels: Map<string, number>;
filters: Array<{
property: string;
condition: (value: any) => boolean;
action: 'allow' | 'deny' | 'transform';
transformer?: (value: any) => any;
}>;
bufferSettings: {
maxBufferSize: number;
flushInterval: number;
compressionThreshold: number;
};
};
/** 消息队列 */
public messageQueue: {
incoming: Array<{
senderId: string;
messageType: string;
data: any;
timestamp: number;
processed: boolean;
}>;
outgoing: Array<{
targetId: string;
messageType: string;
data: any;
priority: number;
attempts: number;
maxAttempts: number;
}>;
processing: Map<string, {
messageId: string;
startTime: number;
expectedDuration: number;
status: 'processing' | 'completed' | 'failed';
}>;
};
/** 复杂的网络缓存系统 */
public cacheSystem: {
playerCache: Map<string, {
playerId: string;
lastSeen: number;
cachedData: any;
cacheExpiry: number;
}>;
messageCache: Map<string, {
messageId: string;
content: any;
timestamp: number;
recipients: string[];
}>;
syncCache: Map<string, {
propertyPath: string;
value: any;
lastUpdated: number;
version: number;
}>;
};
constructor(networkId: string = '') {
super();
this.networkId = networkId || this.generateNetworkId();
this.connection = {
sessionId: '',
serverId: '',
roomId: '',
playerId: '',
ping: 0,
packetLoss: 0,
bandwidth: 0,
lastHeartbeat: 0
};
this.syncData = {
dirtyFlags: new Set(),
lastSyncTime: 0,
syncHistory: [],
queuedUpdates: []
};
this.networkStats = {
totalBytesSent: 0,
totalBytesReceived: 0,
packetsPerSecond: 0,
averageLatency: 0,
latencyHistory: [],
connectionQuality: 'excellent',
errorCount: 0,
reconnectCount: 0,
lastErrorTime: 0,
errorLog: []
};
this.config = {
autoReconnect: true,
maxReconnectAttempts: 5,
heartbeatInterval: 1000,
syncFrequency: 60,
compressionEnabled: true,
encryptionEnabled: false,
priorityLevels: new Map([
['critical', 10],
['high', 7],
['medium', 5],
['low', 2]
]),
filters: [],
bufferSettings: {
maxBufferSize: 1024 * 1024, // 1MB
flushInterval: 100,
compressionThreshold: 1024
}
};
this.messageQueue = {
incoming: [],
outgoing: [],
processing: new Map()
};
this.cacheSystem = {
playerCache: new Map(),
messageCache: new Map(),
syncCache: new Map()
};
}
/**
* 生成网络ID
*/
private generateNetworkId(): string {
return 'net_' + Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}
/**
* 连接到其他网络组件(避免循环引用)
*/
public connectToPlayer(playerNetworkId: string): void {
if (!this.connectedPlayerIds.has(playerNetworkId)) {
this.connectedPlayerIds.add(playerNetworkId);
// 记录连接事件
this.logNetworkEvent('player_connected', {
playerId: playerNetworkId,
timestamp: Date.now()
});
}
}
/**
* 加入群组(避免循环引用)
*/
public joinGroup(memberIds: string[], leaderId?: string): void {
this.groupMemberIds = [...memberIds];
this.groupLeaderId = leaderId || null;
// 更新缓存
memberIds.forEach(memberId => {
this.cacheSystem.playerCache.set(memberId, {
playerId: memberId,
lastSeen: Date.now(),
cachedData: {},
cacheExpiry: Date.now() + 300000 // 5分钟缓存
});
});
}
/**
* 发送消息
*/
public sendMessage(targetId: string, messageType: string, data: any, priority: number = 5): void {
const message = {
targetId,
messageType,
data: this.processOutgoingData(data),
priority,
attempts: 0,
maxAttempts: 3
};
this.messageQueue.outgoing.push(message);
this.messageQueue.outgoing.sort((a, b) => b.priority - a.priority);
// 更新统计
this.networkStats.totalBytesSent += JSON.stringify(data).length;
}
/**
* 处理传出数据
*/
private processOutgoingData(data: any): any {
let processedData = data;
// 应用过滤器
this.config.filters.forEach(filter => {
if (filter.condition(processedData)) {
if (filter.action === 'transform' && filter.transformer) {
processedData = filter.transformer(processedData);
}
}
});
// 压缩数据
if (this.config.compressionEnabled) {
processedData = this.compressData(processedData);
}
return processedData;
}
/**
* 压缩数据(模拟)
*/
private compressData(data: any): any {
// 模拟压缩算法
const serialized = JSON.stringify(data);
if (serialized.length > this.config.bufferSettings.compressionThreshold) {
// 模拟压缩
return {
compressed: true,
originalSize: serialized.length,
compressedSize: Math.floor(serialized.length * 0.6),
data: serialized.substring(0, Math.floor(serialized.length * 0.6))
};
}
return data;
}
/**
* 标记属性为脏
*/
public markDirty(property: string): void {
this.syncData.dirtyFlags.add(property);
}
/**
* 更新网络统计
*/
public updateNetworkStats(deltaTime: number): void {
// 更新延迟历史
if (this.networkStats.latencyHistory.length > 100) {
this.networkStats.latencyHistory.shift();
}
this.networkStats.latencyHistory.push(this.connection.ping);
// 计算平均延迟
this.networkStats.averageLatency = this.networkStats.latencyHistory.reduce((a, b) => a + b, 0) /
this.networkStats.latencyHistory.length;
// 更新连接质量
if (this.networkStats.averageLatency < 50) {
this.networkStats.connectionQuality = 'excellent';
} else if (this.networkStats.averageLatency < 100) {
this.networkStats.connectionQuality = 'good';
} else if (this.networkStats.averageLatency < 200) {
this.networkStats.connectionQuality = 'fair';
} else {
this.networkStats.connectionQuality = 'poor';
}
// 更新包率
this.networkStats.packetsPerSecond = this.messageQueue.outgoing.length / deltaTime;
// 清理过期缓存
this.cleanupExpiredCache();
}
/**
* 清理过期缓存
*/
private cleanupExpiredCache(): void {
const now = Date.now();
// 清理玩家缓存
for (const [key, value] of this.cacheSystem.playerCache) {
if (value.cacheExpiry < now) {
this.cacheSystem.playerCache.delete(key);
}
}
// 清理消息缓存
for (const [key, value] of this.cacheSystem.messageCache) {
if (value.timestamp < now - 600000) { // 10分钟过期
this.cacheSystem.messageCache.delete(key);
}
}
}
/**
* 记录网络事件
*/
private logNetworkEvent(eventType: string, data: any): void {
this.networkStats.errorLog.push({
timestamp: Date.now(),
errorType: eventType,
message: JSON.stringify(data)
});
// 限制日志大小
if (this.networkStats.errorLog.length > 1000) {
this.networkStats.errorLog = this.networkStats.errorLog.slice(-500);
}
}
/**
* 重置组件
*/
public reset(): void {
// 清理ID列表不再需要处理循环引用
this.connectedPlayerIds.clear();
this.groupMemberIds = [];
this.groupLeaderId = null;
this.connectionState = 'disconnected';
this.syncData.dirtyFlags.clear();
this.syncData.syncHistory = [];
this.syncData.queuedUpdates = [];
this.messageQueue.incoming = [];
this.messageQueue.outgoing = [];
this.messageQueue.processing.clear();
this.cacheSystem.playerCache.clear();
this.cacheSystem.messageCache.clear();
this.cacheSystem.syncCache.clear();
this.networkStats.errorLog = [];
this.networkStats.latencyHistory = [];
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "d9263549-7b26-4b4f-9a15-b82e7af5fbd5",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,346 +0,0 @@
import { Component } from '@esengine/ecs-framework';
import { Node, Vec3, Color, Sprite, Label } from 'cc';
/**
* Node组件 - 包含Cocos Creator节点引用已移除循环引用
*/
export class NodeComponent extends Component {
/** Cocos Creator节点引用 */
public node: Node | null = null;
/** 子节点列表 */
public children: Node[] = [];
/** 节点配置信息 */
public nodeConfig: {
name: string;
layer: number;
tag: string;
userData: Record<string, any>;
transformData: {
position: Vec3;
rotation: Vec3;
scale: Vec3;
};
renderData: {
color: Color;
opacity: number;
visible: boolean;
};
parentId: number | null; // 避免循环引用使用父节点实体ID
childIds: number[]; // 避免循环引用使用子节点实体ID列表
};
/** 渲染组件引用 */
public sprite: Sprite | null = null;
public label: Label | null = null;
/** 复杂嵌套对象 */
public complexData: {
statistics: {
frameCount: number;
lastUpdateTime: number;
performance: {
avgRenderTime: number;
maxRenderTime: number;
renderHistory: number[];
};
};
cache: {
textureCache: Map<string, any>;
materialCache: Map<string, any>;
shaderCache: Map<string, any>;
};
hierarchy: {
parentId: number | null; // 避免循环引用使用ID
rootId: number | null; // 避免循环引用使用ID
depth: number;
siblingIndex: number;
};
animation: {
isPlaying: boolean;
currentFrame: number;
totalFrames: number;
loopCount: number;
animationQueue: Array<{
name: string;
duration: number;
delay: number;
easing: string;
}>;
};
interaction: {
isInteractable: boolean;
touchEnabled: boolean;
hitTestResults: Array<{
position: Vec3;
timestamp: number;
result: boolean;
}>;
boundingBox: {
min: Vec3;
max: Vec3;
center: Vec3;
};
};
};
/** 复杂的渲染状态 */
public renderState: {
layerInfo: {
currentLayer: number;
layerStack: number[];
sortingOrder: number;
cullingMask: number;
};
materials: Array<{
materialId: string;
properties: Map<string, any>;
textures: Map<string, any>;
shaderParams: Record<string, any>;
}>;
lightingData: {
ambientColor: Color;
diffuseColor: Color;
specularColor: Color;
lightDirection: Vec3;
shadowData: {
castShadows: boolean;
receiveShadows: boolean;
shadowQuality: 'low' | 'medium' | 'high';
shadowDistance: number;
};
};
};
constructor(name: string = "DefaultNode") {
super();
this.nodeConfig = {
name: name,
layer: 0,
tag: "default",
userData: {},
transformData: {
position: new Vec3(),
rotation: new Vec3(),
scale: new Vec3(1, 1, 1)
},
renderData: {
color: new Color(255, 255, 255, 255),
opacity: 1.0,
visible: true
},
parentId: null,
childIds: []
};
this.complexData = {
statistics: {
frameCount: 0,
lastUpdateTime: 0,
performance: {
avgRenderTime: 0,
maxRenderTime: 0,
renderHistory: []
}
},
cache: {
textureCache: new Map(),
materialCache: new Map(),
shaderCache: new Map()
},
hierarchy: {
parentId: null,
rootId: null,
depth: 0,
siblingIndex: 0
},
animation: {
isPlaying: false,
currentFrame: 0,
totalFrames: 60,
loopCount: 0,
animationQueue: []
},
interaction: {
isInteractable: true,
touchEnabled: true,
hitTestResults: [],
boundingBox: {
min: new Vec3(-1, -1, -1),
max: new Vec3(1, 1, 1),
center: new Vec3()
}
}
};
this.renderState = {
layerInfo: {
currentLayer: 0,
layerStack: [0],
sortingOrder: 0,
cullingMask: 0xFFFFFFFF
},
materials: [],
lightingData: {
ambientColor: new Color(128, 128, 128, 255),
diffuseColor: new Color(255, 255, 255, 255),
specularColor: new Color(255, 255, 255, 255),
lightDirection: new Vec3(0, -1, 0),
shadowData: {
castShadows: true,
receiveShadows: true,
shadowQuality: 'medium',
shadowDistance: 100
}
}
};
}
/**
* 设置父节点组件(避免循环引用)
*/
public setParent(parentEntityId: number): void {
this.nodeConfig.parentId = parentEntityId;
this.complexData.hierarchy.parentId = parentEntityId;
// 深度需要通过其他方式计算,避免引用
}
/**
* 添加子节点
*/
public addChild(childEntityId: number): void {
if (!this.nodeConfig.childIds.includes(childEntityId)) {
this.nodeConfig.childIds.push(childEntityId);
}
}
/**
* 更新性能统计
*/
public updatePerformance(renderTime: number): void {
this.complexData.statistics.frameCount++;
this.complexData.statistics.lastUpdateTime = Date.now();
const perf = this.complexData.statistics.performance;
perf.renderHistory.push(renderTime);
// 保持历史记录在合理范围内
if (perf.renderHistory.length > 100) {
perf.renderHistory.shift();
}
// 计算平均值和最大值
perf.avgRenderTime = perf.renderHistory.reduce((a, b) => a + b, 0) / perf.renderHistory.length;
perf.maxRenderTime = Math.max(perf.maxRenderTime, renderTime);
}
/**
* 更新动画状态
*/
public updateAnimation(deltaTime: number): void {
if (this.complexData.animation.isPlaying) {
this.complexData.animation.currentFrame++;
if (this.complexData.animation.currentFrame >= this.complexData.animation.totalFrames) {
this.complexData.animation.currentFrame = 0;
this.complexData.animation.loopCount++;
// 处理动画队列
if (this.complexData.animation.animationQueue.length > 0) {
const nextAnim = this.complexData.animation.animationQueue.shift();
if (nextAnim) {
this.complexData.animation.totalFrames = Math.floor(nextAnim.duration * 60); // 假设60FPS
}
}
}
}
}
/**
* 添加材质
*/
public addMaterial(materialId: string, properties: Record<string, any>): void {
this.renderState.materials.push({
materialId,
properties: new Map(Object.entries(properties)),
textures: new Map(),
shaderParams: {}
});
}
/**
* 更新包围盒
*/
public updateBoundingBox(): void {
if (this.node) {
const worldPos = this.node.getWorldPosition();
const scale = this.node.getScale();
this.complexData.interaction.boundingBox.center = new Vec3(worldPos.x, worldPos.y, worldPos.z);
this.complexData.interaction.boundingBox.min = new Vec3(
worldPos.x - scale.x * 0.5,
worldPos.y - scale.y * 0.5,
worldPos.z - scale.z * 0.5
);
this.complexData.interaction.boundingBox.max = new Vec3(
worldPos.x + scale.x * 0.5,
worldPos.y + scale.y * 0.5,
worldPos.z + scale.z * 0.5
);
}
}
/**
* 执行点击测试
*/
public hitTest(point: Vec3): boolean {
const bbox = this.complexData.interaction.boundingBox;
const result = point.x >= bbox.min.x && point.x <= bbox.max.x &&
point.y >= bbox.min.y && point.y <= bbox.max.y &&
point.z >= bbox.min.z && point.z <= bbox.max.z;
// 记录测试结果
this.complexData.interaction.hitTestResults.push({
position: new Vec3(point.x, point.y, point.z),
timestamp: Date.now(),
result
});
// 限制历史记录大小
if (this.complexData.interaction.hitTestResults.length > 50) {
this.complexData.interaction.hitTestResults.shift();
}
return result;
}
/**
* 重置组件
*/
public reset(): void {
this.node = null;
this.children = [];
this.sprite = null;
this.label = null;
// 清理ID列表不再需要处理循环引用
this.nodeConfig.parentId = null;
this.nodeConfig.childIds = [];
this.complexData.hierarchy.parentId = null;
this.complexData.hierarchy.rootId = null;
this.complexData.hierarchy.depth = 0;
this.complexData.cache.textureCache.clear();
this.complexData.cache.materialCache.clear();
this.complexData.cache.shaderCache.clear();
this.complexData.animation.isPlaying = false;
this.complexData.animation.currentFrame = 0;
this.complexData.animation.animationQueue = [];
this.complexData.interaction.hitTestResults = [];
this.renderState.materials = [];
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "28e7e8cd-591e-4fde-bb14-d668724a6201",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,76 +0,0 @@
import { Component } from '@esengine/ecs-framework';
import { Color } from 'cc';
/**
* 渲染组件
* 存储实体的渲染相关信息
*/
export class Renderer extends Component {
/** 颜色 */
public color: Color = new Color(255, 255, 255, 255);
/** 是否可见 */
public visible: boolean = true;
/** 渲染层级 */
public layer: number = 0;
/** 精灵名称或纹理路径 */
public spriteName: string = '';
/** 大小 */
public size: { width: number, height: number } = { width: 32, height: 32 };
/** 透明度 (0-1) */
public alpha: number = 1.0;
constructor(spriteName: string = '', color?: Color) {
super();
this.spriteName = spriteName;
if (color) {
this.color = color;
}
}
/**
* 设置颜色
*/
public setColor(r: number, g: number, b: number, a: number = 255): void {
this.color.set(r, g, b, a);
}
/**
* 设置透明度
*/
public setAlpha(alpha: number): void {
this.alpha = Math.max(0, Math.min(1, alpha));
this.color.a = Math.floor(this.alpha * 255);
}
/**
* 设置大小
*/
public setSize(width: number, height: number): void {
this.size.width = width;
this.size.height = height;
}
/**
* 显示/隐藏
*/
public setVisible(visible: boolean): void {
this.visible = visible;
}
/**
* 重置组件
*/
public reset(): void {
this.color.set(255, 255, 255, 255);
this.visible = true;
this.layer = 0;
this.spriteName = '';
this.size = { width: 32, height: 32 };
this.alpha = 1.0;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "bf51f134-6ea5-4a26-8b15-0ed20fe1d605",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,50 +0,0 @@
import { Component } from '@esengine/ecs-framework';
import { Vec3 } from 'cc';
/**
* 变换组件
* 存储实体的位置、旋转和缩放信息
*/
export class Transform extends Component {
/** 位置 */
public position: Vec3 = new Vec3(0, 0, 0);
/** 旋转 (度数) */
public rotation: Vec3 = new Vec3(0, 0, 0);
/** 缩放 */
public scale: Vec3 = new Vec3(1, 1, 1);
/** 移动速度 */
public speed: number = 100;
constructor() {
super();
}
/**
* 设置位置
*/
public setPosition(x: number, y: number, z: number = 0): void {
this.position.set(x, y, z);
}
/**
* 移动
*/
public move(deltaX: number, deltaY: number, deltaZ: number = 0): void {
this.position.x += deltaX;
this.position.y += deltaY;
this.position.z += deltaZ;
}
/**
* 重置组件
*/
public reset(): void {
this.position.set(0, 0, 0);
this.rotation.set(0, 0, 0);
this.scale.set(1, 1, 1);
this.speed = 100;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "09de6e5b-7bb7-4de8-8038-67be5ae955bc",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,77 +0,0 @@
import { Component } from '@esengine/ecs-framework';
import { Vec3 } from 'cc';
/**
* 速度组件
* 存储实体的移动速度和方向
*/
export class Velocity extends Component {
/** 速度向量 */
public velocity: Vec3 = new Vec3(0, 0, 0);
/** 最大速度 */
public maxSpeed: number = 200;
/** 摩擦力 (0-1, 1表示无摩擦) */
public friction: number = 0.98;
constructor() {
super();
}
/**
* 设置速度
*/
public setVelocity(x: number, y: number, z: number = 0): void {
this.velocity.set(x, y, z);
this.clampToMaxSpeed();
}
/**
* 添加速度
*/
public addVelocity(x: number, y: number, z: number = 0): void {
this.velocity.x += x;
this.velocity.y += y;
this.velocity.z += z;
this.clampToMaxSpeed();
}
/**
* 应用摩擦力
*/
public applyFriction(): void {
this.velocity.multiplyScalar(this.friction);
// 当速度很小时直接设为0避免无限减小
if (this.velocity.length() < 0.1) {
this.velocity.set(0, 0, 0);
}
}
/**
* 限制到最大速度
*/
private clampToMaxSpeed(): void {
const currentSpeed = this.velocity.length();
if (currentSpeed > this.maxSpeed) {
this.velocity.normalize().multiplyScalar(this.maxSpeed);
}
}
/**
* 获取当前速度大小
*/
public getSpeed(): number {
return this.velocity.length();
}
/**
* 重置组件
*/
public reset(): void {
this.velocity.set(0, 0, 0);
this.maxSpeed = 200;
this.friction = 0.98;
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "10c40371-267a-4016-a8b5-9f803e68e72b",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,8 +0,0 @@
// 导出所有组件
export { Transform } from './Transform';
export { Health } from './Health';
export { Velocity } from './Velocity';
export { Renderer } from './Renderer';
export { NodeComponent } from './NodeComponent';
export { AIComponent } from './AIComponent';
export { NetworkComponent } from './NetworkComponent';

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "dfc46d23-7ad6-4a21-914c-35d948185f93",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "39da4804-e61e-440e-b73d-544963bd3901",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,321 +0,0 @@
import { Scene } from '@esengine/ecs-framework';
import { Color, Node } from 'cc';
import { MovementSystem, HealthSystem, RandomMovementSystem, AISystem, NetworkSystem, NodeRenderSystem } from '../systems';
import { Transform, Health, Velocity, Renderer, NodeComponent, AIComponent, NetworkComponent } from '../components';
/**
* 游戏场景
*
* 这是您的主游戏场景。在这里可以:
* - 添加游戏系统
* - 创建初始实体
* - 设置场景参数
*/
export class GameScene extends Scene {
/**
* 场景初始化
* 在场景创建时调用,用于设置基础配置
*/
public initialize(): void {
super.initialize();
// 设置场景名称
this.name = "MainGameScene";
console.log('🎯 游戏场景已创建');
// 添加游戏系统
this.addEntityProcessor(new MovementSystem());
this.addEntityProcessor(new HealthSystem());
this.addEntityProcessor(new RandomMovementSystem());
// this.addEntityProcessor(new AISystem());
// this.addEntityProcessor(new NetworkSystem());
// this.addEntityProcessor(new NodeRenderSystem());
// 创建大量复杂的测试实体
this.createComplexTestEntities();
}
/**
* 创建复杂的测试实体1000+个)
*/
private createComplexTestEntities(): void {
console.log('🚀 开始创建大量复杂测试实体...');
// 存储创建的AI和网络组件用于建立循环引用
const aiComponents: AIComponent[] = [];
const networkComponents: NetworkComponent[] = [];
const nodeComponents: NodeComponent[] = [];
// 1. 创建玩家实体(具有所有组件类型)
console.log('创建玩家实体...');
const player = this.createComplexEntity("Player", "player", new Color(0, 255, 0, 255), 0, 0, true, true, true);
if (player) {
const playerAI = player.getComponent(AIComponent);
const playerNetwork = player.getComponent(NetworkComponent);
const playerNode = player.getComponent(NodeComponent);
if (playerAI) aiComponents.push(playerAI);
if (playerNetwork) networkComponents.push(playerNetwork);
if (playerNode) nodeComponents.push(playerNode);
}
// 2. 创建AI智能体500个
console.log('创建AI智能体...');
for (let i = 0; i < 500; i++) {
const entityName = `AI_Agent_${i}`;
const x = (Math.random() - 0.5) * 2000;
const y = (Math.random() - 0.5) * 2000;
const color = new Color(
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
255
);
const entity = this.createComplexEntity(entityName, "ai_agent", color, x, y, true, true, Math.random() > 0.5);
if (entity) {
const ai = entity.getComponent(AIComponent);
const network = entity.getComponent(NetworkComponent);
const node = entity.getComponent(NodeComponent);
if (ai) {
aiComponents.push(ai);
// 设置随机AI个性
ai.config.personality.aggression = Math.random();
ai.config.personality.curiosity = Math.random();
ai.config.personality.loyalty = Math.random();
ai.config.personality.intelligence = Math.random();
}
if (network) {
networkComponents.push(network);
// 随机设置网络状态
if (Math.random() > 0.2) {
network.connectionState = 'connected';
}
}
if (node) {
nodeComponents.push(node);
// 设置随机节点属性
node.nodeConfig.layer = Math.floor(Math.random() * 10);
node.nodeConfig.tag = `layer_${node.nodeConfig.layer}`;
}
}
}
// 3. 创建网络节点300个
console.log('创建网络节点...');
for (let i = 0; i < 300; i++) {
const entityName = `Network_Node_${i}`;
const x = (Math.random() - 0.5) * 1500;
const y = (Math.random() - 0.5) * 1500;
const color = new Color(0, 150, 255, 200);
const entity = this.createComplexEntity(entityName, "network_node", color, x, y, false, true, true);
if (entity) {
const network = entity.getComponent(NetworkComponent);
const node = entity.getComponent(NodeComponent);
if (network) {
networkComponents.push(network);
network.connectionState = 'connected';
network.config.syncFrequency = 30 + Math.random() * 30; // 30-60Hz
}
if (node) {
nodeComponents.push(node);
// 创建复杂的层次结构
node.nodeConfig.layer = Math.floor(i / 10); // 每10个一层
}
}
}
// 4. 创建简单移动实体200个
console.log('创建简单移动实体...');
for (let i = 0; i < 200; i++) {
const entityName = `Simple_Mover_${i}`;
const x = (Math.random() - 0.5) * 1000;
const y = (Math.random() - 0.5) * 1000;
const color = new Color(255, 255, 255, 150);
this.createComplexEntity(entityName, "simple_mover", color, x, y, false, false, false);
}
// 5. 建立循环引用和复杂关系
console.log('建立实体间的复杂关系...');
this.establishComplexRelationships(aiComponents, networkComponents, nodeComponents);
const totalEntities = this.entities.count;
console.log(`✅ 创建完成!总共创建了 ${totalEntities} 个实体`);
console.log(` - AI组件: ${aiComponents.length}`);
console.log(` - 网络组件: ${networkComponents.length}`);
console.log(` - 节点组件: ${nodeComponents.length}`);
}
/**
* 创建复杂实体的辅助方法
*/
private createComplexEntity(
name: string,
type: string,
color: Color,
x: number,
y: number,
hasAI: boolean,
hasNetwork: boolean,
hasNode: boolean
): any {
const entity = this.createEntity(name);
// 基础组件
const transform = new Transform();
transform.setPosition(x, y);
transform.speed = 50 + Math.random() * 100;
entity.addComponent(transform);
const health = new Health(50 + Math.random() * 100);
health.regenRate = Math.random() * 10;
entity.addComponent(health);
const velocity = new Velocity();
velocity.maxSpeed = 80 + Math.random() * 120;
velocity.friction = 0.95 + Math.random() * 0.04;
entity.addComponent(velocity);
const renderer = new Renderer(type, color.clone());
renderer.alpha = 0.5 + Math.random() * 0.5;
renderer.layer = Math.floor(Math.random() * 5);
entity.addComponent(renderer);
// 复杂组件
if (hasAI) {
const ai = new AIComponent();
entity.addComponent(ai);
}
if (hasNetwork) {
const network = new NetworkComponent(`${type}_${name}`);
entity.addComponent(network);
}
if (hasNode) {
const node = new NodeComponent(name);
entity.addComponent(node);
}
return entity;
}
/**
* 建立实体间的复杂关系
*/
private establishComplexRelationships(
aiComponents: AIComponent[],
networkComponents: NetworkComponent[],
nodeComponents: NodeComponent[]
): void {
// 建立AI之间的盟友/敌人关系(避免循环引用)
for (let i = 0; i < Math.min(aiComponents.length, 100); i++) {
const ai = aiComponents[i];
// 随机添加盟友使用实体ID
const allyCount = Math.floor(Math.random() * 5);
for (let j = 0; j < allyCount; j++) {
const randomIndex = Math.floor(Math.random() * aiComponents.length);
const ally = aiComponents[randomIndex];
if (ally !== ai && !ai.allyIds.includes(ally.entity.id)) {
ai.addAlly(ally.entity.id);
}
}
// 随机设置目标使用实体ID
if (Math.random() > 0.7) {
const randomIndex = Math.floor(Math.random() * aiComponents.length);
const target = aiComponents[randomIndex];
if (target !== ai) {
ai.setTarget(target.entity.id);
}
}
}
// 建立网络连接(避免循环引用)
for (let i = 0; i < Math.min(networkComponents.length, 50); i++) {
const network = networkComponents[i];
// 连接到其他网络组件使用网络ID
const connectionCount = Math.floor(Math.random() * 8);
for (let j = 0; j < connectionCount; j++) {
const randomIndex = Math.floor(Math.random() * networkComponents.length);
const other = networkComponents[randomIndex];
if (other !== network) {
network.connectToPlayer(other.networkId);
}
}
// 创建群组使用网络ID
if (Math.random() > 0.8) {
const groupSize = Math.floor(Math.random() * 10) + 2;
const groupMemberIds: string[] = [network.networkId];
for (let k = 0; k < groupSize - 1; k++) {
const randomIndex = Math.floor(Math.random() * networkComponents.length);
const member = networkComponents[randomIndex];
if (!groupMemberIds.includes(member.networkId)) {
groupMemberIds.push(member.networkId);
}
}
network.joinGroup(groupMemberIds, network.networkId);
}
}
// 建立节点层次结构(避免循环引用)
for (let i = 0; i < Math.min(nodeComponents.length, 30); i++) {
const parent = nodeComponents[i];
// 添加一些子节点使用实体ID
const childCount = Math.floor(Math.random() * 5);
for (let j = 0; j < childCount; j++) {
const childIndex = Math.floor(Math.random() * nodeComponents.length);
const child = nodeComponents[childIndex];
if (child !== parent && !parent.nodeConfig.childIds.includes(child.entity.id)) {
parent.addChild(child.entity.id);
}
}
}
console.log('🔗 复杂关系建立完成!');
}
/**
* 场景开始运行
* 在场景开始时调用,用于执行启动逻辑
*/
public onStart(): void {
super.onStart();
console.log('🚀 游戏场景已启动');
// TODO: 在这里添加场景启动逻辑
// 例如创建UI、播放音乐、初始化游戏状态等
}
/**
* 场景卸载
* 在场景结束时调用,用于清理资源
*/
public unload(): void {
console.log('🛑 游戏场景已结束');
// TODO: 在这里添加清理逻辑
// 例如:清理缓存、释放资源等
super.unload();
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "dbf17c21-6f4a-4f87-8568-7ac5a2ec10cd",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "f8f0d97b-46f6-49ff-9fe5-b31eee989963",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,317 +0,0 @@
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { AIComponent, Transform, Health } from '../components';
/**
* AI系统 - 处理AI行为和状态机
*/
export class AISystem extends EntitySystem {
/** 系统处理的实体计数器 */
private processedEntityCount: number = 0;
/** 状态转换计数器 */
private stateTransitionCount: number = 0;
constructor() {
// 处理具有AI组件的实体
super(Matcher.empty().all(AIComponent));
}
/**
* 处理所有实体
*/
protected process(entities: Entity[]): void {
const deltaTime = Time.deltaTime;
const currentTime = Time.totalTime;
this.processedEntityCount = entities.length;
for (const entity of entities) {
this.processEntity(entity, deltaTime, currentTime);
}
// 批量处理AI间的交互
this.processAIInteractions(entities);
}
/**
* 处理单个实体
*/
private processEntity(entity: Entity, deltaTime: number, currentTime: number): void {
const ai = entity.getComponent(AIComponent);
const transform = entity.getComponent(Transform);
const health = entity.getComponent(Health);
if (!ai) return;
// 更新感知系统
ai.updatePerception(deltaTime);
// 处理状态机
this.updateStateMachine(ai, deltaTime);
// 更新行为树
this.updateBehaviorTree(ai, deltaTime);
// 处理AI能力如果有Transform和Health组件
if (transform && health) {
this.updateAICapabilities(ai, transform, health, deltaTime);
}
// 处理记忆衰减
this.updateMemory(ai, deltaTime);
}
/**
* 更新状态机
*/
private updateStateMachine(ai: AIComponent, deltaTime: number): void {
const currentStateName = ai.currentState;
const transitions = ai.stateMachine.transitions.get(currentStateName);
if (transitions) {
// 按优先级排序转换条件
const sortedTransitions = transitions.sort((a, b) => b.priority - a.priority);
for (const transition of sortedTransitions) {
if (transition.condition()) {
// 执行状态转换
const currentState = ai.stateMachine.states.get(currentStateName);
const newState = ai.stateMachine.states.get(transition.targetState);
if (currentState && newState) {
currentState.exit();
ai.currentState = transition.targetState as any;
newState.enter();
this.stateTransitionCount++;
break;
}
}
}
}
}
/**
* 更新行为树
*/
private updateBehaviorTree(ai: AIComponent, deltaTime: number): void {
const behaviorTree = ai.config.behaviorTree;
const blackboard = behaviorTree.blackboard;
// 更新黑板数据
blackboard.set('deltaTime', deltaTime);
blackboard.set('currentTime', Date.now());
// 模拟行为树执行
const executionResult = this.executeBehaviorNode(behaviorTree.rootNode, ai);
// 记录执行历史
behaviorTree.executionHistory.push({
nodeName: behaviorTree.rootNode.name,
startTime: Date.now(),
endTime: Date.now() + Math.random() * 10,
result: executionResult,
data: { deltaTime }
});
// 保持历史记录在合理范围内
if (behaviorTree.executionHistory.length > 50) {
behaviorTree.executionHistory.shift();
}
}
/**
* 执行行为树节点(模拟)
*/
private executeBehaviorNode(node: any, ai: AIComponent): 'success' | 'failure' | 'running' {
// 简单的行为树执行模拟
switch (node.name) {
case 'root':
case 'selector':
// 选择器节点:尝试执行子节点直到一个成功
for (const child of node.children) {
const result = this.executeBehaviorNode(child, ai);
if (result === 'success' || result === 'running') {
return result;
}
}
return 'failure';
case 'sequence':
// 序列节点:按顺序执行所有子节点
for (const child of node.children) {
const result = this.executeBehaviorNode(child, ai);
if (result === 'failure' || result === 'running') {
return result;
}
}
return 'success';
case 'condition':
// 条件节点检查AI状态
return ai.config.personality.intelligence > 0.5 ? 'success' : 'failure';
case 'action':
// 动作节点执行AI行为
return Math.random() > 0.3 ? 'success' : 'running';
default:
return 'failure';
}
}
/**
* 更新AI能力
*/
private updateAICapabilities(ai: AIComponent, transform: Transform, health: Health, deltaTime: number): void {
const capabilities = ai.config.capabilities;
// 根据健康状况调整能力
const healthRatio = health.currentHealth / health.maxHealth;
const effectiveSpeed = capabilities.movementSpeed * healthRatio;
// 更新移动速度
transform.speed = effectiveSpeed;
// 根据个性调整行为
if (ai.config.personality.aggression > 0.7 && healthRatio > 0.5) {
// 高攻击性且健康状况良好时更主动
ai.currentState = 'chase';
} else if (healthRatio < 0.3) {
// 生命值低时逃跑
ai.currentState = 'flee';
}
}
/**
* 更新记忆系统
*/
private updateMemory(ai: AIComponent, deltaTime: number): void {
const memory = ai.config.memory;
const currentTime = Date.now();
// 衰减已知位置的可信度
memory.knownLocations.forEach(location => {
const timeSinceVisit = currentTime - location.lastVisited;
const decayFactor = Math.exp(-timeSinceVisit / 30000); // 30秒衰减率
location.confidence *= decayFactor;
});
// 移除可信度过低的位置
memory.knownLocations = memory.knownLocations.filter(location => location.confidence > 0.1);
// 衰减关系信任度
memory.relationships.forEach(relation => {
const timeSinceInteraction = currentTime - relation.lastInteraction;
if (timeSinceInteraction > 60000) { // 60秒没有交互
relation.trustLevel *= 0.99; // 缓慢衰减
}
});
}
/**
* 处理AI间的交互
*/
private processAIInteractions(entities: Entity[]): void {
const aiEntities = entities.filter(e => e.getComponent(AIComponent));
for (let i = 0; i < aiEntities.length; i++) {
for (let j = i + 1; j < aiEntities.length; j++) {
this.processAIPair(aiEntities[i], aiEntities[j]);
}
}
}
/**
* 处理两个AI实体间的交互
*/
private processAIPair(entity1: Entity, entity2: Entity): void {
const ai1 = entity1.getComponent(AIComponent);
const ai2 = entity2.getComponent(AIComponent);
const transform1 = entity1.getComponent(Transform);
const transform2 = entity2.getComponent(Transform);
if (!ai1 || !ai2 || !transform1 || !transform2) return;
// 计算距离
const distance = Math.sqrt(
Math.pow(transform1.position.x - transform2.position.x, 2) +
Math.pow(transform1.position.y - transform2.position.y, 2)
);
// 视线范围内的交互
if (distance <= ai1.config.capabilities.sightRange) {
this.handleVisualContact(ai1, ai2, entity1, entity2, distance);
}
// 听力范围内的交互
if (distance <= ai1.config.capabilities.hearingRange) {
this.handleAudioContact(ai1, ai2, distance);
}
// 创建盟友关系(随机)
if (Math.random() < 0.001 && !ai1.allyIds.includes(entity2.id)) { // 0.1%概率每帧
ai1.addAlly(entity2.id);
}
}
/**
* 处理视觉接触
*/
private handleVisualContact(ai1: AIComponent, ai2: AIComponent, entity1: Entity, entity2: Entity, distance: number): void {
const currentTime = Date.now();
// 添加到可见实体列表
const existingEntry = ai1.perception.visibleEntities.find(e => e.entityId === entity2.id);
if (existingEntry) {
existingEntry.distance = distance;
existingEntry.lastSeen = currentTime;
existingEntry.componentId = ai2.id; // 使用组件ID避免循环引用
} else {
ai1.perception.visibleEntities.push({
entityId: entity2.id,
position: entity2.getComponent(Transform)!.position.clone(),
distance: distance,
angle: Math.atan2(
entity2.getComponent(Transform)!.position.y - entity1.getComponent(Transform)!.position.y,
entity2.getComponent(Transform)!.position.x - entity1.getComponent(Transform)!.position.x
),
lastSeen: currentTime,
componentId: ai2.id
});
}
}
/**
* 处理音频接触
*/
private handleAudioContact(ai1: AIComponent, ai2: AIComponent, distance: number): void {
const soundVolume = 1.0 - (distance / ai1.config.capabilities.hearingRange);
ai1.perception.audibleSounds.push({
source: ai2.entity.getComponent(Transform)!.position.clone(),
volume: soundVolume,
type: 'movement',
timestamp: Date.now()
});
}
/**
* 系统初始化时调用
*/
public initialize(): void {
super.initialize();
console.log('🤖 AI系统已启动');
}
/**
* 获取系统统计信息
*/
public getSystemStats(): any {
return {
processedEntities: this.processedEntityCount,
stateTransitions: this.stateTransitionCount,
systemName: 'AISystem'
};
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "449fa887-eece-424d-ae1d-7082454fac3f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,67 +0,0 @@
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { Health } from '../components';
/**
* 生命值系统
* 处理生命值回复、死亡检测等逻辑
*/
export class HealthSystem extends EntitySystem {
constructor() {
// 处理具有Health组件的实体
super(Matcher.empty().all(Health));
}
/**
* 处理所有实体
*/
protected process(entities: Entity[]): void {
const deltaTime = Time.deltaTime;
for (const entity of entities) {
this.processEntity(entity, deltaTime);
}
}
/**
* 处理单个实体
*/
private processEntity(entity: Entity, deltaTime: number): void {
const health = entity.getComponent(Health);
if (!health) return;
// 如果实体已死亡,跳过处理
if (health.isDead) return;
// 处理生命值回复
if (health.regenRate > 0) {
const regenAmount = health.regenRate * deltaTime;
health.heal(regenAmount);
}
// 检查是否需要标记为死亡
if (health.currentHealth <= 0 && !health.isDead) {
health.isDead = true;
this.onEntityDied(entity);
}
}
/**
* 当实体死亡时调用
*/
private onEntityDied(entity: Entity): void {
console.log(`💀 实体 ${entity.name} 已死亡`);
// 这里可以添加死亡相关的逻辑
// 比如播放死亡动画、掉落物品等
}
/**
* 系统初始化时调用
*/
public initialize(): void {
super.initialize();
console.log('❤️ 生命值系统已启动');
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "074b3e3a-351e-4d95-b502-5a7dab8efc8d",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,71 +0,0 @@
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { Transform, Velocity } from '../components';
/**
* 移动系统
* 处理具有Transform和Velocity组件的实体移动
*/
export class MovementSystem extends EntitySystem {
constructor() {
// 使用Matcher设置系统处理的组件类型
super(Matcher.empty().all(Transform, Velocity));
}
/**
* 处理所有实体
*/
protected process(entities: Entity[]): void {
const deltaTime = Time.deltaTime;
for (const entity of entities) {
this.processEntity(entity, deltaTime);
}
}
/**
* 处理单个实体
*/
private processEntity(entity: Entity, deltaTime: number): void {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (!transform || !velocity) return;
// 应用摩擦力
velocity.applyFriction();
// 根据速度更新位置
const deltaX = velocity.velocity.x * deltaTime;
const deltaY = velocity.velocity.y * deltaTime;
const deltaZ = velocity.velocity.z * deltaTime;
transform.move(deltaX, deltaY, deltaZ);
// 简单的边界检查 (假设游戏世界是 -500 到 500)
const bounds = 500;
if (transform.position.x > bounds) {
transform.position.x = bounds;
velocity.velocity.x = -Math.abs(velocity.velocity.x) * 0.5; // 反弹并减速
} else if (transform.position.x < -bounds) {
transform.position.x = -bounds;
velocity.velocity.x = Math.abs(velocity.velocity.x) * 0.5;
}
if (transform.position.y > bounds) {
transform.position.y = bounds;
velocity.velocity.y = -Math.abs(velocity.velocity.y) * 0.5;
} else if (transform.position.y < -bounds) {
transform.position.y = -bounds;
velocity.velocity.y = Math.abs(velocity.velocity.y) * 0.5;
}
}
/**
* 系统初始化时调用
*/
public initialize(): void {
super.initialize();
console.log('🏃 移动系统已启动');
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "5e556e6d-ddd5-415c-b074-3cbdb59ed503",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,448 +0,0 @@
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { NetworkComponent } from '../components';
/**
* 网络系统 - 处理网络同步和连接管理
*/
export class NetworkSystem extends EntitySystem {
/** 网络统计 */
private networkStats = {
totalEntities: 0,
connectedEntities: 0,
totalMessagesSent: 0,
totalMessagesReceived: 0,
averagePing: 0,
networkTraffic: 0
};
/** 消息处理队列 */
private globalMessageQueue: Array<{
from: string;
to: string;
messageType: string;
data: any;
timestamp: number;
priority: number;
}> = [];
constructor() {
// 处理具有网络组件的实体
super(Matcher.empty().all(NetworkComponent));
}
/**
* 处理所有实体
*/
protected process(entities: Entity[]): void {
const deltaTime = Time.deltaTime;
this.networkStats.totalEntities = entities.length;
this.networkStats.connectedEntities = entities.filter(e =>
e.getComponent(NetworkComponent)?.connectionState === 'connected'
).length;
for (const entity of entities) {
this.processEntity(entity, deltaTime);
}
// 处理全局消息队列
this.processGlobalMessages();
// 更新网络统计
this.updateGlobalNetworkStats(entities);
}
/**
* 处理单个实体
*/
private processEntity(entity: Entity, deltaTime: number): void {
const network = entity.getComponent(NetworkComponent);
if (!network) return;
// 更新网络统计
network.updateNetworkStats(deltaTime);
// 处理连接状态
this.updateConnectionState(network, deltaTime);
// 处理消息队列
this.processEntityMessages(network, entity);
// 处理数据同步
this.processSynchronization(network, deltaTime);
// 处理群组通信
this.processGroupCommunication(network);
}
/**
* 更新连接状态
*/
private updateConnectionState(network: NetworkComponent, deltaTime: number): void {
const currentTime = Date.now();
switch (network.connectionState) {
case 'disconnected':
// 尝试连接
if (network.config.autoReconnect &&
network.networkStats.reconnectCount < network.config.maxReconnectAttempts) {
network.connectionState = 'connecting';
network.connection.lastHeartbeat = currentTime;
}
break;
case 'connecting':
// 模拟连接过程
if (Math.random() > 0.1) { // 90% 成功率
network.connectionState = 'connected';
network.connection.sessionId = this.generateSessionId();
network.connection.serverId = 'server_001';
network.connection.lastHeartbeat = currentTime;
} else if (currentTime - network.connection.lastHeartbeat > 5000) {
// 连接超时
network.connectionState = 'error';
network.networkStats.errorCount++;
}
break;
case 'connected':
// 维持连接心跳
if (currentTime - network.connection.lastHeartbeat > network.config.heartbeatInterval) {
this.sendHeartbeat(network);
network.connection.lastHeartbeat = currentTime;
}
// 模拟网络质量变化
network.connection.ping = Math.random() * 100 + 20; // 20-120ms
network.connection.packetLoss = Math.random() * 0.05; // 0-5%
network.connection.bandwidth = 1000 + Math.random() * 500; // 1000-1500 Kbps
break;
case 'error':
// 错误状态,尝试重连
if (network.config.autoReconnect &&
network.networkStats.reconnectCount < network.config.maxReconnectAttempts) {
network.connectionState = 'disconnected';
network.networkStats.reconnectCount++;
}
break;
}
}
/**
* 处理实体消息
*/
private processEntityMessages(network: NetworkComponent, entity: Entity): void {
// 处理传出消息
const outgoingMessages = network.messageQueue.outgoing.slice();
network.messageQueue.outgoing = [];
for (const message of outgoingMessages) {
if (this.sendMessage(network, message)) {
this.networkStats.totalMessagesSent++;
network.networkStats.totalBytesSent += this.estimateMessageSize(message);
} else {
// 发送失败,重新加入队列
message.attempts++;
if (message.attempts < message.maxAttempts) {
network.messageQueue.outgoing.push(message);
}
}
}
// 处理传入消息
this.processIncomingMessages(network, entity);
}
/**
* 发送消息
*/
private sendMessage(network: NetworkComponent, message: any): boolean {
if (network.connectionState !== 'connected') {
return false;
}
// 模拟网络延迟和丢包
const shouldDelay = Math.random() < 0.3; // 30% 概率有延迟
const shouldDrop = Math.random() < network.connection.packetLoss;
if (shouldDrop) {
network.networkStats.errorCount++;
return false;
}
// 添加到全局消息队列
this.globalMessageQueue.push({
from: network.networkId,
to: message.targetId,
messageType: message.messageType,
data: message.data,
timestamp: Date.now() + (shouldDelay ? Math.random() * 200 : 0),
priority: message.priority
});
return true;
}
/**
* 处理传入消息
*/
private processIncomingMessages(network: NetworkComponent, entity: Entity): void {
// 从全局队列中获取发给此实体的消息
const incomingMessages = this.globalMessageQueue.filter(msg =>
msg.to === network.networkId && msg.timestamp <= Date.now()
);
// 从全局队列中移除这些消息
this.globalMessageQueue = this.globalMessageQueue.filter(msg =>
!(msg.to === network.networkId && msg.timestamp <= Date.now())
);
// 处理消息
for (const message of incomingMessages) {
network.messageQueue.incoming.push({
senderId: message.from,
messageType: message.messageType,
data: message.data,
timestamp: message.timestamp,
processed: false
});
this.networkStats.totalMessagesReceived++;
network.networkStats.totalBytesReceived += this.estimateMessageSize(message);
// 立即处理某些类型的消息
this.handleSpecialMessages(network, message);
}
}
/**
* 处理特殊消息类型
*/
private handleSpecialMessages(network: NetworkComponent, message: any): void {
switch (message.messageType) {
case 'player_join_group':
// 处理加入群组消息
const groupData = message.data;
if (groupData.members && Array.isArray(groupData.members)) {
// 查找对应的网络组件并建立连接
groupData.members.forEach((memberId: string) => {
// 直接使用成员ID建立连接
network.connectToPlayer(memberId);
});
}
break;
case 'heartbeat':
// 心跳响应
network.connection.ping = Date.now() - message.data.timestamp;
break;
case 'sync_request':
// 同步请求
this.handleSyncRequest(network, message);
break;
}
}
/**
* 处理数据同步
*/
private processSynchronization(network: NetworkComponent, deltaTime: number): void {
const currentTime = Date.now();
const syncInterval = 1000 / network.config.syncFrequency; // 转换为毫秒
if (currentTime - network.syncData.lastSyncTime >= syncInterval) {
this.performSynchronization(network);
network.syncData.lastSyncTime = currentTime;
}
// 处理排队的更新
this.processQueuedUpdates(network);
}
/**
* 执行同步
*/
private performSynchronization(network: NetworkComponent): void {
if (network.syncData.dirtyFlags.size === 0) {
return; // 没有需要同步的数据
}
const syncData = {
networkId: network.networkId,
timestamp: Date.now(),
properties: Array.from(network.syncData.dirtyFlags),
checksum: this.calculateChecksum(network)
};
// 发送同步数据给连接的玩家
network.connectedPlayerIds.forEach(playerId => {
network.sendMessage(playerId, 'sync_data', syncData, 7);
});
// 记录同步历史
network.syncData.syncHistory.push({
timestamp: syncData.timestamp,
dataSize: this.estimateMessageSize(syncData),
properties: syncData.properties,
success: true
});
// 清理脏标记
network.syncData.dirtyFlags.clear();
}
/**
* 处理排队的更新
*/
private processQueuedUpdates(network: NetworkComponent): void {
// 按优先级和时间戳排序
network.syncData.queuedUpdates.sort((a, b) => {
if (a.priority !== b.priority) {
return b.priority - a.priority; // 高优先级优先
}
return a.timestamp - b.timestamp; // 时间戳早的优先
});
// 处理前10个更新
const updatesToProcess = network.syncData.queuedUpdates.splice(0, 10);
for (const update of updatesToProcess) {
network.markDirty(update.property);
}
}
/**
* 处理群组通信
*/
private processGroupCommunication(network: NetworkComponent): void {
if (network.groupMemberIds.length === 0) {
return;
}
// 群组消息广播
if (Math.random() < 0.01) { // 1% 概率发送群组消息
const groupMessage = {
type: 'group_update',
data: {
sender: network.networkId,
timestamp: Date.now(),
groupSize: network.groupMemberIds.length,
status: network.connectionState
}
};
network.groupMemberIds.forEach(memberId => {
if (memberId !== network.networkId) {
network.sendMessage(memberId, 'group_message', groupMessage, 5);
}
});
}
}
/**
* 处理全局消息
*/
private processGlobalMessages(): void {
// 移除过期消息
const currentTime = Date.now();
this.globalMessageQueue = this.globalMessageQueue.filter(msg =>
currentTime - msg.timestamp < 30000 // 30秒过期
);
// 按优先级排序
this.globalMessageQueue.sort((a, b) => b.priority - a.priority);
}
/**
* 更新全局网络统计
*/
private updateGlobalNetworkStats(entities: Entity[]): void {
let totalPing = 0;
let connectedCount = 0;
let totalTraffic = 0;
for (const entity of entities) {
const network = entity.getComponent(NetworkComponent);
if (network && network.connectionState === 'connected') {
totalPing += network.connection.ping;
connectedCount++;
totalTraffic += network.networkStats.totalBytesSent + network.networkStats.totalBytesReceived;
}
}
this.networkStats.averagePing = connectedCount > 0 ? totalPing / connectedCount : 0;
this.networkStats.networkTraffic = totalTraffic;
}
/**
* 辅助方法
*/
private generateSessionId(): string {
return 'session_' + Math.random().toString(36).substring(2, 15);
}
private estimateMessageSize(message: any): number {
return JSON.stringify(message).length;
}
private calculateChecksum(network: NetworkComponent): string {
// 简单的校验和计算
const data = JSON.stringify({
networkId: network.networkId,
connectionState: network.connectionState
});
return btoa(data).substring(0, 8);
}
private sendHeartbeat(network: NetworkComponent): void {
network.sendMessage('server', 'heartbeat', { timestamp: Date.now() }, 10);
}
private findNetworkComponentById(networkId: string): NetworkComponent | null {
// 这里应该有一个全局的网络组件注册表
// 为了简化我们返回null
return null;
}
private handleSyncRequest(network: NetworkComponent, message: any): void {
// 处理同步请求
const response = {
requestId: message.data.requestId,
data: this.gatherSyncData(network),
timestamp: Date.now()
};
network.sendMessage(message.from, 'sync_response', response, 8);
}
private gatherSyncData(network: NetworkComponent): any {
return {
networkId: network.networkId,
connectionState: network.connectionState,
ping: network.connection.ping,
groupSize: network.groupMemberIds.length
};
}
/**
* 系统初始化时调用
*/
public initialize(): void {
super.initialize();
console.log('🌐 网络系统已启动');
}
/**
* 获取系统统计信息
*/
public getSystemStats(): any {
return {
...this.networkStats,
globalMessageQueueSize: this.globalMessageQueue.length,
systemName: 'NetworkSystem'
};
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "09cb67c9-12ef-48e0-949d-c8edf2c7ae22",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,475 +0,0 @@
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { NodeComponent, Transform, Renderer } from '../components';
import { Node, Vec3, Color } from 'cc';
/**
* 节点渲染系统 - 处理NodeComponent和Cocos Creator节点的同步
*/
export class NodeRenderSystem extends EntitySystem {
/** 渲染统计 */
private renderStats = {
totalNodes: 0,
visibleNodes: 0,
renderCalls: 0,
averageRenderTime: 0,
totalRenderTime: 0,
frameCount: 0
};
/** 节点池 */
private nodePool: Node[] = [];
/** 性能监控 */
private performanceMonitor = {
frameStartTime: 0,
renderTimeHistory: [] as number[],
cullCount: 0,
frustumCullCount: 0
};
constructor() {
// 处理具有NodeComponent的实体
super(Matcher.empty().all(NodeComponent));
}
/**
* 处理所有实体
*/
protected process(entities: Entity[]): void {
this.performanceMonitor.frameStartTime = performance.now();
this.renderStats.totalNodes = entities.length;
this.renderStats.visibleNodes = 0;
this.renderStats.renderCalls = 0;
for (const entity of entities) {
this.processEntity(entity);
}
// 处理节点层次结构
this.updateNodeHierarchy(entities);
// 更新性能统计
this.updatePerformanceStats();
// 清理过期的性能缓存
this.cleanupPerformanceCache(entities);
}
/**
* 处理单个实体
*/
private processEntity(entity: Entity): void {
const nodeComponent = entity.getComponent(NodeComponent);
const transform = entity.getComponent(Transform);
const renderer = entity.getComponent(Renderer);
if (!nodeComponent) return;
const renderStartTime = performance.now();
// 确保有对应的Cocos Creator节点
this.ensureNode(nodeComponent, entity);
// 同步Transform数据
if (transform && nodeComponent.node) {
this.syncTransform(nodeComponent, transform);
}
// 同步渲染数据
if (renderer && nodeComponent.node) {
this.syncRenderer(nodeComponent, renderer);
}
// 更新节点配置
this.updateNodeConfig(nodeComponent);
// 执行视锥体剔除
const isVisible = this.performCulling(nodeComponent);
if (isVisible) {
this.renderStats.visibleNodes++;
this.performRender(nodeComponent);
}
// 更新性能统计
const renderTime = performance.now() - renderStartTime;
nodeComponent.updatePerformance(renderTime);
this.renderStats.renderCalls++;
this.renderStats.totalRenderTime += renderTime;
}
/**
* 确保节点存在
*/
private ensureNode(nodeComponent: NodeComponent, entity: Entity): void {
if (!nodeComponent.node) {
// 从对象池中获取节点或创建新节点
nodeComponent.node = this.getNodeFromPool() || new Node(nodeComponent.nodeConfig.name);
// 初始化节点
this.initializeNode(nodeComponent.node, nodeComponent, entity);
}
}
/**
* 从对象池获取节点
*/
private getNodeFromPool(): Node | null {
return this.nodePool.pop() || null;
}
/**
* 初始化节点
*/
private initializeNode(node: Node, nodeComponent: NodeComponent, entity: Entity): void {
const config = nodeComponent.nodeConfig;
// 设置基本属性
node.name = config.name;
node.layer = config.layer;
node.active = config.renderData.visible;
// 设置变换
node.setPosition(config.transformData.position);
node.setRotationFromEuler(config.transformData.rotation);
node.setScale(config.transformData.scale);
// 设置渲染属性
const opacity = Math.floor(config.renderData.opacity * 255);
// 这里可以设置更多Cocos Creator特定的属性
// 添加用户数据
config.userData.entityId = entity.id;
config.userData.componentId = nodeComponent.id;
}
/**
* 同步Transform数据
*/
private syncTransform(nodeComponent: NodeComponent, transform: Transform): void {
const node = nodeComponent.node!;
const config = nodeComponent.nodeConfig;
// 更新配置中的变换数据
config.transformData.position.set(transform.position);
config.transformData.rotation.set(transform.rotation);
config.transformData.scale.set(transform.scale);
// 同步到Cocos Creator节点
node.setPosition(transform.position);
node.setRotationFromEuler(transform.rotation);
node.setScale(transform.scale);
// 更新缓存数据
nodeComponent.complexData.cache.textureCache.set('lastPosition', transform.position.clone());
}
/**
* 同步渲染数据
*/
private syncRenderer(nodeComponent: NodeComponent, renderer: Renderer): void {
const node = nodeComponent.node!;
const config = nodeComponent.nodeConfig;
// 更新配置中的渲染数据
config.renderData.color.set(renderer.color);
config.renderData.opacity = renderer.alpha;
config.renderData.visible = renderer.visible && renderer.alpha > 0;
// 同步到Cocos Creator节点
node.active = config.renderData.visible;
// 更新材质缓存
nodeComponent.complexData.cache.materialCache.set('currentColor', renderer.color.clone());
nodeComponent.complexData.cache.materialCache.set('alpha', renderer.alpha);
}
/**
* 更新节点配置
*/
private updateNodeConfig(nodeComponent: NodeComponent): void {
const config = nodeComponent.nodeConfig;
const currentTime = Date.now();
// 更新统计信息
nodeComponent.complexData.statistics.frameCount++;
nodeComponent.complexData.statistics.lastUpdateTime = currentTime;
// 更新用户数据
config.userData.lastFrameUpdate = currentTime;
config.userData.frameCount = nodeComponent.complexData.statistics.frameCount;
// 动态调整配置
if (Math.random() < 0.01) { // 1% 概率调整
config.renderData.opacity *= (0.95 + Math.random() * 0.1); // 轻微透明度变化
config.renderData.opacity = Math.max(0.1, Math.min(1.0, config.renderData.opacity));
}
}
/**
* 执行视锥体剔除
*/
private performCulling(nodeComponent: NodeComponent): boolean {
if (!nodeComponent.node) {
return false;
}
const config = nodeComponent.nodeConfig;
// 简单的可见性检查
if (!config.renderData.visible || config.renderData.opacity <= 0) {
this.performanceMonitor.cullCount++;
return false;
}
// 距离剔除
const position = config.transformData.position;
const distance = position.length();
if (distance > 1000) { // 超过1000单位距离的对象被剔除
this.performanceMonitor.frustumCullCount++;
return false;
}
// 层级剔除
if (config.layer < 0) {
this.performanceMonitor.cullCount++;
return false;
}
return true;
}
/**
* 执行渲染
*/
private performRender(nodeComponent: NodeComponent): void {
if (!nodeComponent.node) return;
const renderStartTime = performance.now();
// 模拟复杂的渲染过程
this.simulateRenderingWork(nodeComponent);
// 更新子节点
this.updateChildNodes(nodeComponent);
// 更新着色器缓存
this.updateShaderCache(nodeComponent);
const renderTime = performance.now() - renderStartTime;
// 更新性能统计
const perf = nodeComponent.complexData.statistics.performance;
perf.renderHistory.push(renderTime);
if (perf.renderHistory.length > 100) {
perf.renderHistory.shift();
}
perf.avgRenderTime = perf.renderHistory.reduce((a, b) => a + b, 0) / perf.renderHistory.length;
perf.maxRenderTime = Math.max(perf.maxRenderTime, renderTime);
}
/**
* 模拟渲染工作
*/
private simulateRenderingWork(nodeComponent: NodeComponent): void {
const complexity = nodeComponent.complexData.cache.materialCache.size +
nodeComponent.complexData.cache.textureCache.size;
// 模拟基于复杂度的计算工作
let iterations = Math.min(complexity * 10, 1000);
let result = 0;
for (let i = 0; i < iterations; i++) {
result += Math.sin(i * 0.001) * Math.cos(i * 0.002);
}
// 存储计算结果到缓存
nodeComponent.complexData.cache.shaderCache.set('computeResult', result);
}
/**
* 更新子节点
*/
private updateChildNodes(nodeComponent: NodeComponent): void {
if (nodeComponent.children.length === 0) return;
const parentNode = nodeComponent.node!;
// 同步子节点
for (let i = 0; i < nodeComponent.children.length; i++) {
const childNode = nodeComponent.children[i];
if (childNode && childNode.parent !== parentNode) {
parentNode.addChild(childNode);
}
}
// 更新层次结构数据
nodeComponent.complexData.hierarchy.siblingIndex = parentNode.getSiblingIndex();
// 更新子组件的层次深度(需要通过实体管理器查找)
// 这里省略了复杂的查找逻辑,避免循环引用
if (nodeComponent.nodeConfig.childIds.length > 0) {
// 实际项目中应该通过实体管理器查找子实体并更新深度
// 为了示例简化,我们只更新自己的深度
nodeComponent.complexData.hierarchy.depth = Math.max(0, nodeComponent.complexData.hierarchy.depth);
}
}
/**
* 更新着色器缓存
*/
private updateShaderCache(nodeComponent: NodeComponent): void {
const shaderCache = nodeComponent.complexData.cache.shaderCache;
// 模拟着色器参数更新
const currentTime = Date.now();
shaderCache.set('time', currentTime);
shaderCache.set('frameCount', nodeComponent.complexData.statistics.frameCount);
// 清理过期的着色器缓存
if (shaderCache.size > 50) {
const keys = Array.from(shaderCache.keys());
const oldestKey = keys[0];
shaderCache.delete(oldestKey);
}
}
/**
* 更新节点层次结构
*/
private updateNodeHierarchy(entities: Entity[]): void {
// 构建层次结构映射
const nodeMap = new Map<number, NodeComponent>();
entities.forEach(entity => {
const nodeComponent = entity.getComponent(NodeComponent);
if (nodeComponent) {
nodeMap.set(entity.id, nodeComponent);
}
});
// 更新层次关系使用ID避免循环引用
nodeMap.forEach((nodeComponent, entityId) => {
// 更新根节点ID
if (!nodeComponent.complexData.hierarchy.parentId) {
nodeComponent.complexData.hierarchy.rootId = entityId;
} else {
// 查找根节点ID简化版本避免深度遍历
let currentParentId = nodeComponent.complexData.hierarchy.parentId;
let depth = 0;
// 限制深度以避免无限循环
while (currentParentId && depth < 10) {
const parentNode = nodeMap.get(currentParentId);
if (parentNode && parentNode.complexData.hierarchy.parentId) {
currentParentId = parentNode.complexData.hierarchy.parentId;
depth++;
} else {
break;
}
}
nodeComponent.complexData.hierarchy.rootId = currentParentId || entityId;
}
});
}
/**
* 更新性能统计
*/
private updatePerformanceStats(): void {
const frameTime = performance.now() - this.performanceMonitor.frameStartTime;
this.performanceMonitor.renderTimeHistory.push(frameTime);
if (this.performanceMonitor.renderTimeHistory.length > 60) {
this.performanceMonitor.renderTimeHistory.shift();
}
this.renderStats.frameCount++;
if (this.renderStats.renderCalls > 0) {
this.renderStats.averageRenderTime = this.renderStats.totalRenderTime / this.renderStats.renderCalls;
}
}
/**
* 清理性能缓存
*/
private cleanupPerformanceCache(entities: Entity[]): void {
entities.forEach(entity => {
const nodeComponent = entity.getComponent(NodeComponent);
if (nodeComponent) {
const caches = nodeComponent.complexData.cache;
// 清理纹理缓存
if (caches.textureCache.size > 100) {
const keys = Array.from(caches.textureCache.keys());
const toDelete = keys.slice(0, 20); // 删除最旧的20个
toDelete.forEach(key => caches.textureCache.delete(key));
}
// 清理材质缓存
if (caches.materialCache.size > 50) {
const keys = Array.from(caches.materialCache.keys());
const toDelete = keys.slice(0, 10); // 删除最旧的10个
toDelete.forEach(key => caches.materialCache.delete(key));
}
}
});
}
/**
* 回收节点到对象池
*/
public recycleNode(node: Node): void {
if (this.nodePool.length < 100) { // 限制对象池大小
node.removeFromParent();
node.destroyAllChildren();
this.nodePool.push(node);
} else {
node.destroy();
}
}
/**
* 系统初始化时调用
*/
public initialize(): void {
super.initialize();
console.log('🎨 节点渲染系统已启动');
// 预热对象池
for (let i = 0; i < 10; i++) {
this.nodePool.push(new Node(`PooledNode_${i}`));
}
}
/**
* 当实体被移除时
*/
protected onRemoved(entity: Entity): void {
const nodeComponent = entity.getComponent(NodeComponent);
if (nodeComponent && nodeComponent.node) {
this.recycleNode(nodeComponent.node);
nodeComponent.node = null;
}
}
/**
* 获取系统统计信息
*/
public getSystemStats(): any {
return {
...this.renderStats,
cullCount: this.performanceMonitor.cullCount,
frustumCullCount: this.performanceMonitor.frustumCullCount,
nodePoolSize: this.nodePool.length,
averageFrameTime: this.performanceMonitor.renderTimeHistory.length > 0
? this.performanceMonitor.renderTimeHistory.reduce((a, b) => a + b, 0) / this.performanceMonitor.renderTimeHistory.length
: 0,
systemName: 'NodeRenderSystem'
};
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b4593488-685b-4e52-800b-b2a2990305d6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,80 +0,0 @@
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { Transform, Velocity } from '../components';
/**
* 随机移动系统
* 让实体随机改变移动方向
*/
export class RandomMovementSystem extends EntitySystem {
/** 每个实体的下次方向改变时间 */
private nextDirectionChangeTime: Map<number, number> = new Map();
constructor() {
// 处理具有Transform和Velocity组件的实体
super(Matcher.empty().all(Transform, Velocity));
}
/**
* 处理所有实体
*/
protected process(entities: Entity[]): void {
const currentTime = Time.totalTime;
for (const entity of entities) {
this.processEntity(entity, currentTime);
}
}
/**
* 处理单个实体
*/
private processEntity(entity: Entity, currentTime: number): void {
const velocity = entity.getComponent(Velocity);
if (!velocity) return;
// 检查是否需要改变方向
const nextChangeTime = this.nextDirectionChangeTime.get(entity.id) || 0;
if (currentTime >= nextChangeTime) {
// 随机生成新的移动方向
const angle = Math.random() * Math.PI * 2; // 0-360度
const speed = 50 + Math.random() * 100; // 50-150的随机速度
const newVelocityX = Math.cos(angle) * speed;
const newVelocityY = Math.sin(angle) * speed;
velocity.setVelocity(newVelocityX, newVelocityY);
// 设置下次改变方向的时间1-3秒后
const nextInterval = 1 + Math.random() * 2;
this.nextDirectionChangeTime.set(entity.id, currentTime + nextInterval);
}
}
/**
* 当实体被添加到系统时
*/
protected onAdded(entity: Entity): void {
// 为新实体设置初始方向改变时间
const initialDelay = Math.random() * 2; // 0-2秒的初始延迟
this.nextDirectionChangeTime.set(entity.id, Time.totalTime + initialDelay);
}
/**
* 当实体从系统中移除时
*/
protected onRemoved(entity: Entity): void {
// 清理实体的时间记录
this.nextDirectionChangeTime.delete(entity.id);
}
/**
* 系统初始化时调用
*/
public initialize(): void {
super.initialize();
console.log('🎲 随机移动系统已启动');
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ed1688a1-b44f-4588-ae6a-080a6af38a94",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,7 +0,0 @@
// 导出所有系统
export { MovementSystem } from './MovementSystem';
export { HealthSystem } from './HealthSystem';
export { RandomMovementSystem } from './RandomMovementSystem';
export { AISystem } from './AISystem';
export { NetworkSystem } from './NetworkSystem';
export { NodeRenderSystem } from './NodeRenderSystem';

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