# 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 { BehaviorTreeAssetSerializer, BehaviorTreeAssetLoader, BehaviorTreeStarter, BlackboardComponent } from '@esengine/behavior-tree'; import { resources } from 'cc'; const { ccclass, property } = _decorator; @ccclass('EnemyAIComponent') export class EnemyAIComponent extends Component { @property behaviorTreeAsset: string = 'behaviors/enemy-ai.btree'; private aiEntity: Entity | null = null; async start() { // 加载行为树资产 await this.loadBehaviorTree(); } private async loadBehaviorTree() { try { // 获取Core管理的场景 const scene = Core.scene; if (!scene) { console.error('场景未初始化'); return; } // 从 resources 加载JSON资产 resources.load(this.behaviorTreeAsset, (err, jsonAsset: any) => { if (err) { console.error('加载行为树失败:', err); return; } // 获取JSON字符串 const jsonString = jsonAsset.json ? JSON.stringify(jsonAsset.json) : jsonAsset.text; // 反序列化 const btAsset = BehaviorTreeAssetSerializer.deserialize(jsonString); // 实例化 this.aiEntity = BehaviorTreeAssetLoader.instantiate( btAsset, scene, { namePrefix: this.node.name } ); // 设置黑板初始值 const blackboard = this.aiEntity.getComponent(BlackboardComponent); if (blackboard) { // 可以在这里设置引用到 Cocos 节点 blackboard.setValue('cocosNode', this.node); blackboard.setValue('position', this.node.position.clone()); } // 启动 AI BehaviorTreeStarter.start(this.aiEntity); console.log('敌人 AI 已启动'); }); } catch (error) { console.error('初始化行为树失败:', error); } } onDestroy() { // 停止 AI if (this.aiEntity) { BehaviorTreeStarter.stop(this.aiEntity); } } } ``` ## 与 Cocos 节点交互 ### 在编辑器ExecuteAction节点中编写代码 在行为树编辑器中,可以使用 `Execute Action` 节点,并编写代码: ```javascript // 获取 Cocos 节点 const cocosNode = blackboard.getValue('cocosNode'); // 播放攻击动画 const animation = cocosNode.getComponent('Animation'); animation.play('attack'); return TaskStatus.Success; ``` ## 完整示例:敌人 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 { BlackboardComponent } 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 blackboard: BlackboardComponent | null = null; start() { // 假设AI组件在同一节点上 const aiComponent = this.node.getComponent('EnemyAIComponent') as any; if (aiComponent && aiComponent.aiEntity) { this.blackboard = aiComponent.aiEntity.getComponent(BlackboardComponent); } } update(deltaTime: number) { if (!this.blackboard || !this.player) { return; } // 计算距离 const distance = Vec3.distance(this.node.position, this.player.position); // 更新黑板 this.blackboard.setValue('playerNode', this.player); this.blackboard.setValue('playerInRange', distance <= this.detectionRange); this.blackboard.setValue('distanceToPlayer', distance); } } ``` ## 资源管理 ### 预加载行为树资产 在游戏启动时预加载所有行为树资产: ```typescript import { resources } from 'cc'; async function preloadBehaviorTrees() { const assets = [ 'behaviors/enemy-ai', 'behaviors/boss-ai', 'behaviors/patrol' ]; for (const path of assets) { await new Promise((resolve, reject) => { resources.preload(path, (err) => { if (err) reject(err); else resolve(null); }); }); } console.log('行为树资产预加载完成'); } ``` ### 使用 AssetManager 对于动态加载,可以使用 Cocos 的 AssetManager: ```typescript import { assetManager } from 'cc'; assetManager.loadBundle('behaviors', (err, bundle) => { if (err) { console.error('加载 bundle 失败:', err); return; } bundle.load('enemy-ai', (err, asset) => { if (!err) { // 使用资产 } }); }); ``` ## 调试 ### 可视化调试信息 创建调试组件显示 AI 状态: ```typescript import { _decorator, Component, Label } from 'cc'; import { BlackboardComponent } from '@esengine/behavior-tree'; const { ccclass, property } = _decorator; @ccclass('AIDebugger') export class AIDebugger extends Component { @property(Label) debugLabel: Label = null; private blackboard: BlackboardComponent | null = null; start() { const aiComponent = this.node.getComponent('EnemyAIComponent') as any; if (aiComponent && aiComponent.aiEntity) { this.blackboard = aiComponent.aiEntity.getComponent(BlackboardComponent); } } update() { if (!this.blackboard || !this.debugLabel) { return; } const health = this.blackboard.getValue('health'); const state = this.blackboard.getValue('currentState'); this.debugLabel.string = `Health: ${health}\nState: ${state}`; } } ``` ## 性能优化 ### 1. 使用对象池 为 AI 实体使用对象池: ```typescript class AIEntityPool { private pool: Entity[] = []; private scene: Scene; constructor(scene: Scene) { this.scene = scene; } acquire(behaviorTreeAsset: any): Entity { if (this.pool.length > 0) { const entity = this.pool.pop()!; BehaviorTreeStarter.restart(entity); return entity; } return BehaviorTreeAssetLoader.instantiate(behaviorTreeAsset, this.scene); } release(entity: Entity) { BehaviorTreeStarter.stop(entity); this.pool.push(entity); } } ``` ### 2. 限制更新频率 对于远离相机的敌人,可以在行为树内部使用节流机制: ```typescript // 在行为树的Action节点中实现节流 function throttledAction(entity, blackboard, deltaTime) { let lastUpdate = blackboard?.getValue('lastUpdateTime') || 0; const currentTime = Date.now(); // 根据距离决定更新间隔 const distance = getDistanceToCamera(); const updateInterval = distance < 10 ? 0 : 200; // 远处敌人200ms更新一次 if (currentTime - lastUpdate < updateInterval) { return TaskStatus.Running; } blackboard?.setValue('lastUpdateTime', currentTime); // 执行实际逻辑 performAILogic(); return TaskStatus.Success; } ``` ### 3. 使用二进制格式 在构建时将 JSON 转换为二进制格式以减小包体: ```bash # 在构建脚本中 node scripts/convert-bt-to-binary.js ``` ## 多平台发布 ### Web 平台 在 Web 平台,确保资源路径正确: ```typescript // 使用相对路径 const assetPath = 'behaviors/enemy-ai'; ``` ### 原生平台 原生平台可以使用二进制格式以获得更好的性能: ```typescript // 检测平台 if (sys.isNative) { // 加载二进制格式 assetPath = 'behaviors/enemy-ai.btree.bin'; } else { // 加载 JSON 格式 assetPath = 'behaviors/enemy-ai.btree.json'; } ``` ## 常见问题 ### 行为树无法加载? 检查: 1. 资源路径是否正确(相对于 `resources` 目录) 2. 文件是否已添加到项目中 3. 检查控制台错误信息 ### AI 不执行? 确保: 1. `Main` 组件的 `update` 方法被调用 2. `Scene.update()` 在每帧被调用 3. 行为树已通过 `BehaviorTreeStarter.start()` 启动 ### 黑板变量不更新? 检查: 1. 变量名拼写是否正确 2. 是否在正确的时机更新变量 3. 使用 `BlackboardComponent.getValue()` 和 `setValue()` 方法 ## 下一步 - 查看[高级用法](./advanced-usage.md)了解子树和异步加载 - 学习[最佳实践](./best-practices.md)优化你的 AI