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