Files
esengine/docs/guide/behavior-tree/asset-management.md
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

507 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 资产管理
本文介绍如何加载、管理和复用行为树资产。
## 为什么需要资产管理?
在实际游戏开发中,你可能会遇到以下场景:
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)优化你的行为树设计