Cocos Creator 集成
本教程将引导你在 Cocos Creator 项目中集成和使用行为树系统。
- Cocos Creator 3.x 或更高版本
- 基本的 TypeScript 知识
- 已完成快速开始教程
步骤1:安装依赖
Section titled “步骤1:安装依赖”在你的 Cocos Creator 项目根目录下:
npm install @esengine/ecs-framework @esengine/behavior-tree步骤2:配置 tsconfig.json
Section titled “步骤2:配置 tsconfig.json”确保 tsconfig.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 和行为树
Section titled “初始化 ECS 和行为树”创建主入口组件
Section titled “创建主入口组件”创建 assets/scripts/Main.ts:
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(); }}添加组件到场景
Section titled “添加组件到场景”- 在场景中创建一个空节点(命名为
GameManager) - 添加
Main组件到该节点
创建 AI 组件
Section titled “创建 AI 组件”创建 assets/scripts/ai/EnemyAIComponent.ts:
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 节点交互
Section titled “与 Cocos 节点交互”创建自定义执行器
Section titled “创建自定义执行器”要实现与Cocos节点的交互,需要创建自定义执行器:
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
Section titled “完整示例:敌人 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 - 当前路点索引
实现检测系统
Section titled “实现检测系统”创建 assets/scripts/ai/PlayerDetector.ts:
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
Section titled “使用 BehaviorTreeAssetManager”框架提供了 BehaviorTreeAssetManager 来统一管理行为树资产,避免重复创建:
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 资源加载
Section titled “从 Cocos Creator 资源加载”1. 将行为树 JSON 放入 resources 目录
Section titled “1. 将行为树 JSON 放入 resources 目录”assets/└── resources/ └── behaviors/ ├── enemy-ai.btree.json └── boss-ai.btree.json2. 创建资源加载器
Section titled “2. 创建资源加载器”创建 assets/scripts/BehaviorTreeLoader.ts:
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. 在游戏启动时预加载
Section titled “3. 在游戏启动时预加载”修改 Main.ts:
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. 在敌人组件中使用
Section titled “4. 在敌人组件中使用”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); } }}可视化调试信息
Section titled “可视化调试信息”创建调试组件显示 AI 状态:
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. 限制行为树数量
Section titled “1. 限制行为树数量”合理控制同时运行的行为树数量:
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. 使用冷却装饰器
Section titled “2. 使用冷却装饰器”对于不需要每帧更新的AI,使用冷却装饰器:
const tree = BehaviorTreeBuilder.create('ThrottledAI') .cooldown(0.2, 'ThrottleRoot') // 每0.2秒执行一次 .selector('MainBehavior') // AI逻辑... .end() .end() .build();3. 缓存计算结果
Section titled “3. 缓存计算结果”在自定义执行器中缓存昂贵的计算:
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; }}多平台注意事项
Section titled “多平台注意事项”不同平台的性能差异:
- Web平台: 受浏览器性能限制,建议减少同时运行的AI数量
- 原生平台: 性能较好,可以运行更多AI
- 小游戏平台: 内存受限,注意控制行为树数量和复杂度
import { sys } from 'cc';
// 根据平台调整AI数量const maxAIs = sys.isNative ? 50 : (sys.isBrowser ? 20 : 30);
// 根据平台调整更新频率const updateInterval = sys.isNative ? 0.016 : 0.05;行为树无法加载?
Section titled “行为树无法加载?”检查:
- 资源路径是否正确(相对于
resources目录) - 文件是否已添加到项目中
- 检查控制台错误信息
AI 不执行?
Section titled “AI 不执行?”确保:
Main组件的update方法被调用Scene.update()在每帧被调用- 行为树已通过
BehaviorTreeStarter.start()启动
黑板变量不更新?
Section titled “黑板变量不更新?”检查:
- 变量名拼写是否正确
- 是否在正确的时机更新变量
- 使用
BehaviorTreeRuntimeComponent.getBlackboardValue()和setBlackboardValue()方法