采矿行为树示例

This commit is contained in:
YHH
2025-06-25 17:50:40 +08:00
parent 01084a8897
commit 0b4a6b77e2
28 changed files with 3119 additions and 2054 deletions

View File

@@ -1,39 +1,72 @@
import { Node, resources, JsonAsset } from 'cc';
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, TaskStatus, BehaviorTreeJSONConfig } from '@esengine/ai';
import { ECSComponent } from './UnitComponent';
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;
/**
* 行为树组件 - ECS组件,管理单个实体的行为树
* 行为树组件 - 纯Cocos Creator组件,管理单个节点的行为树
*/
export class BehaviorTreeComponent extends ECSComponent {
public behaviorTreeFile: string = '';
public cocosNode: Node | null = null;
@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 lastTickTime: number = 0;
private tickInterval: number = 0.1; // 行为树更新间隔(秒)
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) {
console.error('行为树文件路径未设置');
console.error(`[${this.node.name}] 行为树文件路径未设置`);
return;
}
try {
await this.loadBehaviorTree();
this.setupBlackboard();
this.isLoaded = true;
this.isRunning = true;
console.log(`行为树组件初始化成功: ${this.behaviorTreeFile}`);
} catch (error) {
console.error(`行为树组件初始化失败: ${this.behaviorTreeFile}`, error);
console.error(`[${this.node.name}] 行为树组件初始化失败: ${this.behaviorTreeFile}`, error);
}
}
@@ -42,12 +75,10 @@ export class BehaviorTreeComponent extends ECSComponent {
*/
private async loadBehaviorTree(): Promise<void> {
return new Promise((resolve, reject) => {
// 移除.btree扩展名使用.bt.json
const jsonPath = this.behaviorTreeFile.replace('.btree', '.bt.json');
let jsonPath = this.behaviorTreeFile;
resources.load(jsonPath, JsonAsset, (err, asset) => {
if (err) {
console.error(`加载行为树文件失败: ${jsonPath}`, err);
console.error(`[${this.node.name}] 加载行为树文件失败: ${jsonPath}`, err);
reject(err);
return;
}
@@ -57,7 +88,7 @@ export class BehaviorTreeComponent extends ECSComponent {
this.buildBehaviorTree(treeData);
resolve();
} catch (buildError) {
console.error(`构建行为树失败: ${jsonPath}`, buildError);
console.error(`[${this.node.name}] 构建行为树失败: ${jsonPath}`, buildError);
reject(buildError);
}
});
@@ -68,68 +99,431 @@ export class BehaviorTreeComponent extends ECSComponent {
* 构建行为树
*/
private buildBehaviorTree(treeData: BehaviorTreeJSONConfig) {
// 创建事件注册表并注册基础动作
this.eventRegistry = new EventRegistry();
this.setupEventHandlers();
// 创建基础执行上下文
const baseContext = {
cocosNode: this.cocosNode,
unitComponent: this
node: this.node,
component: this,
eventRegistry: this.eventRegistry
};
// 使用@esengine/ai的BehaviorTreeBuilder构建行为树
// 这会自动创建黑板并设置所有配置
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(treeData, baseContext);
this.behaviorTree = result.tree;
this.blackboard = result.blackboard;
this.context = result.context;
// 初始化黑板变量
this.initializeBlackboard();
}
/**
* 设置黑板
* 设置事件处理器 - 根据行为树文件中实际使用的事件名称注册
*/
private setupBlackboard() {
if (!this.blackboard || !this.cocosNode) return;
private setupEventHandlers() {
if (!this.eventRegistry) return;
// 根据miner-stamina-ai.bt.json中的实际事件名称注册处理器
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;
// 注意:只设置行为树中实际定义的变量
// 这些变量需要在对应的.btree文件的blackboard数组中预先定义
// 简单初始化矿工状态
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);
}
/**
* 创建状态UI
*/
private createStatusUI() {
if (!this.statusUIPrefab) {
this.createSimpleStatusUI();
return;
}
// 设置基础信息 - 注释掉未在行为树中定义的变量
// this.blackboard.setValue('entityName', this.cocosNode.name);
// this.blackboard.setValue('currentTime', Date.now() / 1000);
// this.blackboard.setValue('deltaTime', 0.016);
// this.blackboard.setValue('worldPosition', this.cocosNode.worldPosition);
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() {
console.log('BehaviorTreeComponent黑板设置完成未设置任何变量以避免警告');
this.statusUI = StatusUIManager.createStatusUIForMiner(this.node);
if (!this.statusUI) {
console.warn(`[${this.node.name}] 状态UI创建失败`);
}
}
/**
* 更新行为树
* 更新状态UI显示
*/
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();
}
/**
* 更新动作进度UI
*/
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';
// 清理其他动作状态
this.clearActionState('mine-gold-ore');
this.clearActionState('store-ore');
// 回到出生点休息
const homePos = blackboard.getValue('homePosition') || this.node.worldPosition;
this.moveToPosition(homePos, 2.0);
blackboard.setValue('isResting', true);
return 'success';
}
/**
* 恢复体力 - 优化版本,缓慢恢复
*/
private handleRecoverStamina(context: any, params: any): ActionResult {
const blackboard = this.blackboard;
if (!blackboard) return 'failure';
const actionKey = 'recover-stamina';
const currentTime = Date.now();
// 初始化动作状态
if (!this.actionStates.has(actionKey)) {
this.actionStates.set(actionKey, {
isExecuting: true,
startTime: currentTime,
duration: 2000 // 2秒恢复一次
});
// 设置休息状态,确保不会被其他任务中断
blackboard.setValue('isResting', true);
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); // 每次恢复10点
blackboard.setValue('stamina', newStamina);
blackboard.setValue('staminaPercentage', newStamina / 100);
blackboard.setValue('isLowStamina', newStamina < 20);
// 体力满了就完成休息
if (newStamina >= 100) {
blackboard.setValue('isResting', false); // 只有完全恢复后才结束休息状态
this.actionStates.delete(actionKey);
return 'success';
}
// 重置计时器继续恢复,保持休息状态
actionState.startTime = currentTime;
}
return 'running';
}
/**
* 挖掘金矿 - 优化版本,需要时间挖掘
*/
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');
if (hasOre || isLowStamina) {
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 // 3秒挖掘时间
});
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';
}
// 清理其他动作状态
this.clearActionState('mine-gold-ore');
this.clearActionState('recover-stamina');
// 找到仓库并移动过去
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 // 1.5秒存储时间
});
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.isLoaded || !this.isRunning || !this.behaviorTree || !this.context) return;
// 控制更新频率
this.lastTickTime += deltaTime;
if (this.lastTickTime < this.tickInterval) return;
this.lastTickTime = 0;
// 更新黑板中的时间信息 - 注释掉未在行为树中定义的变量
if (this.blackboard) {
// 只更新行为树中实际定义的变量
// this.blackboard.setValue('deltaTime', deltaTime);
// this.blackboard.setValue('currentTime', Date.now() / 1000);
// if (this.cocosNode) {
// this.blackboard.setValue('worldPosition', this.cocosNode.worldPosition);
// }
// 简单执行行为树
if (this.behaviorTree && this.isRunning) {
this.behaviorTree.tick(deltaTime);
}
// 执行行为树
try {
this.behaviorTree.tick();
} catch (error) {
console.error(`行为树执行错误:`, error);
// 更新UI显示
if (this.showStatusUI) {
this.updateStatusUI();
}
}
/**
* 设置更新频率 - 已废弃,现在每帧执行
*/
setTickInterval(interval: number) {
// 方法保留以保持兼容性,但不再有实际作用
console.warn(`[${this.node.name}] setTickInterval已废弃行为树现在每帧执行`);
}
/**
* 获取黑板
*/
@@ -137,11 +531,21 @@ export class BehaviorTreeComponent extends ECSComponent {
return this.blackboard;
}
/**
* 获取行为树
*/
getBehaviorTree(): BehaviorTree<any> | null {
return this.behaviorTree;
}
/**
* 暂停行为树
*/
pause() {
this.isRunning = false;
if (this.debugMode) {
}
}
/**
@@ -150,6 +554,9 @@ export class BehaviorTreeComponent extends ECSComponent {
resume() {
if (this.isLoaded) {
this.isRunning = true;
if (this.debugMode) {
}
}
}
@@ -161,5 +568,46 @@ export class BehaviorTreeComponent extends ECSComponent {
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,5 +1,5 @@
import { _decorator, Component, resources, JsonAsset, Vec3 } from 'cc';
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, BehaviorTreeJSONConfig, ExecutionContext, EventRegistry } from '@esengine/ai';
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, BehaviorTreeJSONConfig, ExecutionContext, EventRegistry, ActionResult } from '@esengine/ai';
import { UnitController } from './UnitController';
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
@@ -59,10 +59,7 @@ export class BehaviorTreeManager extends Component {
this.setupBlackboard();
this.isLoaded = true;
this.isRunning = true;
console.log(`✅ 行为树初始化成功: ${behaviorTreeName} for ${this.unitController.node.name}`);
console.log(` - 行为树: ${this.behaviorTree ? '已创建' : '未创建'}`);
console.log(` - 黑板变量: ${this.blackboard ? '已创建' : '未创建'}`);
console.log(` - 运行状态: ${this.isRunning ? '运行中' : '已停止'}`);
} catch (error) {
console.error(`行为树初始化失败: ${behaviorTreeName}`, error);
}
@@ -74,7 +71,7 @@ export class BehaviorTreeManager extends Component {
private async loadBehaviorTree(behaviorTreeName: string): Promise<void> {
return new Promise((resolve, reject) => {
const jsonPath = `${behaviorTreeName}.bt`;
console.log(`🔍 尝试加载行为树文件: ${jsonPath}`);
resources.load(jsonPath, JsonAsset, (err, asset) => {
if (err) {
@@ -116,11 +113,13 @@ export class BehaviorTreeManager extends Component {
private createEventRegistry(): EventRegistry {
const registry = new EventRegistry();
// 注册简化的矿工行为事件处理器
// 注册体力系统矿工行为事件处理器
const eventHandlers = {
// 矿工核心行为
'find-and-mine-ore': (context: any, params?: any) => this.callBehaviorHandler('onFindAndMineOre', params),
// 矿工体力系统核心行为
'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)
};
@@ -135,7 +134,7 @@ export class BehaviorTreeManager extends Component {
/**
* 调用行为处理器的方法
*/
private callBehaviorHandler(methodName: string, params: any = {}): string {
private callBehaviorHandler(methodName: string, params: any = {}): ActionResult {
if (!this.behaviorHandler) {
console.error(`BehaviorTreeManager: RTSBehaviorHandler未初始化 - ${this.node.name}`);
return 'failure';
@@ -145,9 +144,7 @@ export class BehaviorTreeManager extends Component {
// 直接调用RTSBehaviorHandler的方法
const method = (this.behaviorHandler as any)[methodName];
if (typeof method === 'function') {
console.log(`🎯 调用行为处理器: ${methodName} (${this.node.name})`);
const result = method.call(this.behaviorHandler, params);
console.log(`📤 行为处理器返回: ${methodName} -> "${result}" (${this.node.name})`);
return result || 'success'; // 确保有返回值
} else {
console.error(`BehaviorTreeManager: 方法不存在: ${methodName}`);
@@ -174,6 +171,14 @@ export class BehaviorTreeManager extends Component {
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);
}
/**
@@ -220,11 +225,19 @@ export class BehaviorTreeManager extends Component {
// 更新矿工状态信息
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);
}
// 执行行为树

View File

@@ -0,0 +1,139 @@
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.camera) {
console.warn('[MinerStatusUI] 未找到主摄像机');
}
if (!this.canvas) {
console.warn('[MinerStatusUI] 未找到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();
targetWorldPos.y += this.yOffset / 100;
const screenPos = this.camera.worldToScreen(targetWorldPos);
const canvasTransform = this.canvas.node.getComponent(UITransform);
if (canvasTransform) {
const canvasPos = new Vec3(
screenPos.x - canvasTransform.width / 2,
screenPos.y - canvasTransform.height / 2,
0
);
this.node.setPosition(canvasPos);
}
}
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

@@ -2,7 +2,7 @@
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "61885e67-b7a2-4e51-857e-ccbea4fdea03",
"uuid": "5f877c25-5c26-49c6-bbb5-7ff36323e0a1",
"files": [],
"subMetas": {},
"userData": {}

View File

@@ -4,14 +4,17 @@ 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);
@@ -24,62 +27,106 @@ export class RTSBehaviorHandler extends Component {
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 事件参数,包含黑板变量值
*/
onFindAndMineOre(params: any = {}): string {
if (!this.unitController || !this.minerDemo) return 'failure';
// 从参数中获取黑板变量值
const unitType = params.unitType || 'unknown';
const currentHealth = params.currentHealth || 100;
console.log(`⛏️ ${unitType}矿工开始寻找矿石 (生命值: ${currentHealth})`);
// 获取所有可用矿石
const ores = this.minerDemo.getAllOres();
if (ores.length === 0) {
console.log(`👷 ${this.node.name}: 没有可挖掘的矿石了`);
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 nearestOre: Node | null = null;
let nearestMine: Node | null = null;
let minDistance = Infinity;
for (const ore of ores) {
const distance = Vec3.distance(currentPos, ore.worldPosition);
for (const mine of goldMines) {
if (!mine || !mine.isValid) continue;
const distance = Vec3.distance(currentPos, mine.worldPosition);
if (distance < minDistance) {
minDistance = distance;
nearestOre = ore;
nearestMine = mine;
}
}
if (!nearestOre) return 'failure';
if (!nearestMine) {
return 'failure';
}
// 检查是否已经到达矿位置
if (minDistance < 1.5) {
// 开始挖掘
console.log(`⛏️ ${this.node.name}: 开始挖掘矿石`);
// 检查是否已经到达矿位置
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.removeOre(nearestOre);
// 通知演示管理器
this.minerDemo.mineGoldOre(this.node);
// 清除移动目标
this.unitController.clearTarget();
this.unitController.setBlackboardValue('isMoving', false);
this.updateActionTime();
return 'success';
} else {
// 移动到矿石位置
this.unitController.setTarget(nearestOre.worldPosition);
console.log(`🚶 ${this.node.name}: 前往矿石位置 距离${minDistance.toFixed(1)}`);
// 设置移动目标
this.unitController.setTarget(nearestMine.worldPosition);
return 'running';
}
}
@@ -89,17 +136,23 @@ export class RTSBehaviorHandler extends Component {
* @param params 事件参数,包含黑板变量值
*/
onStoreOre(params: any = {}): string {
if (!this.unitController || !this.minerDemo) return 'failure';
if (!this.unitController || !this.minerDemo) {
return 'failure';
}
// 从参数中获取黑板变量值
const unitType = params.unitType || 'unknown';
const targetPosition = params.targetPosition || null;
// 检查是否携带矿石
const hasOre = this.unitController.getBlackboardValue('hasOre');
if (!hasOre) {
return 'failure';
}
console.log(`🏭 ${unitType}矿工前往仓库存储 (目标位置: ${JSON.stringify(targetPosition)})`);
// 动作冷却检查
if (this.isActionOnCooldown()) {
return 'running';
}
const warehouse = this.minerDemo.getWarehouse();
if (!warehouse) {
console.log(`👷 ${this.node.name}: 找不到仓库`);
if (!warehouse || !warehouse.isValid) {
return 'failure';
}
@@ -110,20 +163,110 @@ export class RTSBehaviorHandler extends Component {
// 检查是否已经到达仓库
if (distance < 2.5) {
// 存储矿石
console.log(`🏭 ${this.node.name}: 在仓库存储矿石`);
// 检查是否正在移动
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);
console.log(`🚚 ${this.node.name}: 运输矿石到仓库 距离${distance.toFixed(1)}`);
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';
}
}
@@ -133,10 +276,59 @@ export class RTSBehaviorHandler extends Component {
* @param params 事件参数,包含黑板变量值
*/
onIdleBehavior(params: any = {}): string {
// 从参数中获取黑板变量值
const unitType = params.unitType || 'unknown';
if (!this.unitController) {
return 'failure';
}
// 清除移动目标,确保停止移动
this.unitController.clearTarget();
this.unitController.setBlackboardValue('isMoving', false);
console.log(`😴 ${unitType}矿工待机中`);
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,10 +1,9 @@
import { _decorator, Component, Node, Vec3, MeshRenderer, BoxCollider, RigidBody, Mesh, Material, Color, primitives, utils } from 'cc';
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 {
@@ -36,7 +35,6 @@ export class SimplePrefabFactory extends Component {
const rigidBody = unit.addComponent(RigidBody);
rigidBody.type = RigidBody.Type.KINEMATIC;
console.log(`创建单位: ${name}`);
return unit;
}
@@ -67,7 +65,6 @@ export class SimplePrefabFactory extends Component {
const collider = building.addComponent(BoxCollider);
collider.size = size;
console.log(`创建建筑: ${name}`);
return building;
}
@@ -94,7 +91,6 @@ export class SimplePrefabFactory extends Component {
const collider = resource.addComponent(BoxCollider);
collider.size = new Vec3(1, 1, 1);
console.log(`创建资源: ${name}`);
return resource;
}
@@ -125,7 +121,6 @@ export class SimplePrefabFactory extends Component {
const collider = ground.addComponent(BoxCollider);
collider.size = size;
console.log(`创建地面: ${size}`);
return ground;
}
}

View File

@@ -0,0 +1,289 @@
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) {
console.error('[StatusUIManager] 未找到Canvas');
return null;
}
const minerIndex = this.extractMinerIndex(miner.name);
const yOffset = minerIndex * 20;
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);
statusUI.yOffset = 100 + yOffset;
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) {
console.error('[StatusUIManager] 未找到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.yOffset = 150;
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

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

View File

@@ -1,345 +0,0 @@
import { _decorator, Component, Node, Vec3, Material, MeshRenderer, Color, tween } from 'cc';
import { BehaviorTreeComponent } from './BehaviorTreeComponent';
// 简化的ECS组件基类
export class ECSComponent {
public entity: any = null;
}
// 简化的Entity类
export class Entity {
public name: string = '';
private components: Map<any, any> = new Map();
constructor(name: string) {
this.name = name;
}
addComponent(component: any) {
this.components.set(component.constructor, component);
component.entity = this;
}
getComponent<T>(componentClass: any): T | null {
return this.components.get(componentClass) || null;
}
hasComponent(componentClass: any): boolean {
return this.components.has(componentClass);
}
}
// 简化的Core类
export class Core {
static entityManager = {
entities: [] as Entity[],
createEntity: (name: string) => {
const entity = new Entity(name);
Core.entityManager.entities.push(entity);
return entity;
},
destroyEntity: (entity: Entity) => {
const index = Core.entityManager.entities.indexOf(entity);
if (index !== -1) {
Core.entityManager.entities.splice(index, 1);
}
}
};
static create(config?: any) {
console.log('ECS Core initialized with config:', config);
}
static update(deltaTime: number) {
// 简化的更新逻辑
}
}
const { ccclass, property } = _decorator;
/**
* 单位配置接口
*/
export interface UnitConfig {
unitType: string;
behaviorTreeFile: string;
maxHealth: number;
moveSpeed: number;
attackRange: number;
attackDamage: number;
color: string;
}
/**
* 单位状态组件
*/
export class UnitStateComponent extends ECSComponent {
public unitType: string = '';
public maxHealth: number = 100;
public currentHealth: number = 100;
public moveSpeed: number = 3;
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';
}
/**
* 单位组件 - Cocos Creator组件管理单位的可视化和ECS实体
*/
@ccclass('UnitComponent')
export class UnitComponent extends Component {
@property
showDebugInfo: boolean = true;
private entity: Entity | null = null;
private unitState: UnitStateComponent | null = null;
private behaviorTreeComponent: BehaviorTreeComponent | null = null;
private meshRenderer: MeshRenderer | null = null;
private originalMaterial: Material | null = null;
private selectionMaterial: Material | null = null;
onLoad() {
this.meshRenderer = this.getComponent(MeshRenderer);
if (this.meshRenderer && this.meshRenderer.material) {
this.originalMaterial = this.meshRenderer.material;
}
}
/**
* 设置单位配置
*/
setup(config: UnitConfig) {
// 创建ECS实体
this.entity = Core.entityManager.createEntity(`Unit_${this.node.name}`);
// 添加单位状态组件
this.unitState = new UnitStateComponent();
this.unitState.unitType = config.unitType;
this.unitState.maxHealth = config.maxHealth;
this.unitState.currentHealth = config.maxHealth;
this.unitState.moveSpeed = config.moveSpeed;
this.unitState.attackRange = config.attackRange;
this.unitState.attackDamage = config.attackDamage;
this.unitState.color = config.color;
this.entity.addComponent(this.unitState);
// 添加行为树组件
this.behaviorTreeComponent = new BehaviorTreeComponent();
this.behaviorTreeComponent.behaviorTreeFile = config.behaviorTreeFile;
this.behaviorTreeComponent.cocosNode = this.node;
this.entity.addComponent(this.behaviorTreeComponent);
// 设置材质颜色
this.setUnitColor(config.color);
console.log(`单位 ${this.node.name} 设置完成 - 类型: ${config.unitType}, 行为树: ${config.behaviorTreeFile}`);
}
/**
* 设置单位颜色
*/
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) {
if (!this.unitState) return;
this.unitState.isSelected = selected;
// 视觉效果
if (selected) {
this.showSelectionEffect();
} else {
this.hideSelectionEffect();
}
}
/**
* 显示选择效果
*/
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) {
if (!this.unitState || !this.behaviorTreeComponent) return;
this.unitState.currentCommand = command;
// 设置目标
if (target instanceof Vec3) {
this.unitState.targetPosition = target.clone();
this.unitState.targetNode = null;
} else if (target instanceof Node) {
this.unitState.targetPosition = target.worldPosition.clone();
this.unitState.targetNode = target;
}
// 通过黑板更新行为树状态
const blackboard = this.behaviorTreeComponent.getBlackboard();
if (blackboard) {
blackboard.setValue('currentCommand', command);
blackboard.setValue('hasTarget', target !== undefined);
blackboard.setValue('targetPosition', this.unitState.targetPosition);
if (target instanceof Node) {
blackboard.setValue('targetType', target.name.includes('Resource') ? 'resource' :
target.name.includes('Building') ? 'building' : 'unit');
}
}
console.log(`单位 ${this.node.name} 接收命令: ${command}`, target);
}
/**
* 获取单位状态
*/
getUnitState(): UnitStateComponent | null {
return this.unitState;
}
/**
* 获取行为树组件
*/
getBehaviorTreeComponent(): BehaviorTreeComponent | null {
return this.behaviorTreeComponent;
}
/**
* 受到伤害
*/
takeDamage(damage: number) {
if (!this.unitState) return;
this.unitState.currentHealth = Math.max(0, this.unitState.currentHealth - damage);
// 更新黑板
const blackboard = this.behaviorTreeComponent?.getBlackboard();
if (blackboard) {
blackboard.setValue('currentHealth', this.unitState.currentHealth);
blackboard.setValue('healthPercentage', this.unitState.currentHealth / this.unitState.maxHealth);
blackboard.setValue('isLowHealth', this.unitState.currentHealth < this.unitState.maxHealth * 0.3);
}
// 视觉效果
this.showDamageEffect();
if (this.unitState.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} 死亡`);
// 从ECS系统中移除实体
if (this.entity) {
Core.entityManager.destroyEntity(this.entity);
}
// 播放死亡动画后销毁节点
tween(this.node)
.to(0.5, { scale: Vec3.ZERO })
.call(() => {
this.node.destroy();
})
.start();
}
update(deltaTime: number) {
if (!this.unitState || !this.behaviorTreeComponent) return;
// 更新黑板中的时间相关变量
const blackboard = this.behaviorTreeComponent.getBlackboard();
if (blackboard) {
blackboard.setValue('deltaTime', deltaTime);
blackboard.setValue('currentTime', Date.now() / 1000);
blackboard.setValue('worldPosition', this.node.worldPosition);
}
// 调试信息显示
if (this.showDebugInfo) {
this.updateDebugInfo();
}
}
/**
* 更新调试信息
*/
private updateDebugInfo() {
// 可以在这里添加调试信息的显示逻辑
// 比如在单位上方显示状态文本等
}
onDestroy() {
// 清理ECS实体
if (this.entity) {
Core.entityManager.destroyEntity(this.entity);
}
// 停止所有动画
tween(this.node).stop();
}
}

View File

@@ -1,4 +1,4 @@
import { _decorator, Component, Node, Vec3, Material, MeshRenderer, Color, tween } from 'cc';
import { _decorator, Component, Node, Vec3, MeshRenderer, Color, tween } from 'cc';
import { BehaviorTreeManager } from './BehaviorTreeManager';
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
@@ -18,7 +18,7 @@ export interface UnitConfig {
}
/**
* 单位控制器 - 纯Cocos Creator组件管理单位的行为和状态
* 单位控制器
*/
@ccclass('UnitController')
export class UnitController extends Component {
@@ -41,6 +41,13 @@ export class UnitController extends Component {
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;
@@ -61,7 +68,7 @@ export class UnitController extends Component {
// 添加RTSBehaviorHandler组件
this.behaviorHandler = this.addComponent(RTSBehaviorHandler);
} catch (error) {
console.warn('RTSBehaviorHandler组件添加失败,将使用默认行为处理', error);
console.warn('RTSBehaviorHandler组件添加失败', error);
}
}
@@ -87,8 +94,6 @@ export class UnitController extends Component {
if (this.behaviorTreeManager) {
this.behaviorTreeManager.initializeBehaviorTree(config.behaviorTreeName, this);
}
console.log(`🎮 单位设置完成: ${this.node.name} | 类型: ${config.unitType.toUpperCase()} | 行为树: ${config.behaviorTreeName}`);
}
/**
@@ -111,8 +116,6 @@ export class UnitController extends Component {
this.meshRenderer.material.setProperty('mainColor', color);
}
/**
* 设置选择状态
*/
@@ -181,8 +184,6 @@ export class UnitController extends Component {
target.name.includes('Building') ? 'building' : 'unit');
}
}
console.log(`单位 ${this.node.name} 接收命令: ${command}`, target);
}
/**
@@ -194,11 +195,20 @@ export class UnitController extends Component {
}
}
/**
* 获取黑板变量值
*/
getBlackboardValue(key: string): any {
return this.behaviorTreeManager?.getBlackboardValue(key);
}
/**
* 设置移动目标
*/
setTarget(position: Vec3) {
this.targetPosition = position.clone();
this.isMoving = true;
this.moveStartTime = Date.now();
}
/**
@@ -206,6 +216,7 @@ export class UnitController extends Component {
*/
clearTarget() {
this.targetPosition = Vec3.ZERO.clone();
this.isMoving = false;
}
/**
@@ -266,117 +277,58 @@ export class UnitController extends Component {
*/
moveToTarget(targetPos: Vec3, speed?: number, deltaTime?: number): boolean {
const currentPos = this.node.worldPosition;
const distance = Vec3.distance(currentPos, targetPos);
// 只计算水平面距离忽略Y轴
const currentPos2D = new Vec3(currentPos.x, 0, currentPos.z);
const targetPos2D = new Vec3(targetPos.x, 0, targetPos.z);
const distance = currentPos2D.subtract(targetPos2D).length();
if (distance < 0.8) { // 增加到达阈值,减少抖动
if (distance < 0.5) {
this.isMoving = false;
return true; // 已到达目标
return true;
}
// 平滑移动逻辑(只在水平面)
const direction2D = targetPos2D.subtract(currentPos2D).normalize();
const moveSpeed = speed || this.moveSpeed;
const dt = deltaTime || 0.016; // 使用传入的deltaTime或默认值
const actualSpeed = speed || this.moveSpeed;
const actualDeltaTime = deltaTime || 0.016;
const direction = new Vec3();
Vec3.subtract(direction, targetPos, currentPos);
direction.normalize();
// 计算移动距离,确保不会超过目标位置
const moveDistance = Math.min(moveSpeed * dt, distance);
const movement2D = direction2D.multiplyScalar(moveDistance);
// 新位置保持原有的Y轴位置
const newPosition = new Vec3(
currentPos.x + movement2D.x,
currentPos.y, // 保持Y轴不变
currentPos.z + movement2D.z
);
const moveDistance = actualSpeed * actualDeltaTime;
const newPosition = new Vec3();
Vec3.scaleAndAdd(newPosition, currentPos, direction, moveDistance);
this.node.setWorldPosition(newPosition);
this.isMoving = true;
// 减少日志输出频率
if (Date.now() - this.moveStartTime > 1000) { // 每秒输出一次
console.log(`${this.node.name}: 移动中 距离目标${distance.toFixed(2)}`);
this.moveStartTime = Date.now();
}
return false; // 还在移动中
return false;
}
/**
* 攻击目标
*/
attackTarget(): boolean {
const currentTime = Date.now() / 1000;
if (currentTime - this.lastAttackTime < this.attackCooldown) {
return false; // 冷却中
const currentTime = Date.now();
if (currentTime - this.lastAttackTime < this.attackCooldown * 1000) {
return false;
}
// 执行攻击
console.log(`${this.node.name} 执行攻击`);
this.lastAttackTime = currentTime;
// 更新行为树黑板
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('lastAttackTime', currentTime);
}
return true; // 攻击成功
}
update(deltaTime: number) {
// 自动移动逻辑 - 如果有目标位置就自动移动
if (this.targetPosition && !this.targetPosition.equals(Vec3.ZERO)) {
const arrived = this.moveToTarget(this.targetPosition, undefined, deltaTime);
if (arrived) {
// 不要清除目标位置,让行为树决定下一步动作
this.isMoving = false;
// 更新黑板状态
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('isMoving', false);
// 不要设置hasTarget为false让行为树自己管理
}
} else {
this.isMoving = true;
// 更新移动状态到黑板
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('isMoving', true);
}
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.updateBlackboardValue('currentHealth', this.currentHealth);
this.behaviorTreeManager.updateBlackboardValue('healthPercentage', this.currentHealth / this.maxHealth);
this.behaviorTreeManager.updateBlackboardValue('isLowHealth', this.currentHealth < this.maxHealth * 0.3);
// 命令状态更新
this.behaviorTreeManager.updateBlackboardValue('currentCommand', this.currentCommand);
this.behaviorTreeManager.updateBlackboardValue('hasTarget', this.targetPosition && !this.targetPosition.equals(Vec3.ZERO));
this.behaviorTreeManager.updateBlackboardValue('targetPosition', this.targetPosition);
this.behaviorTreeManager.updateBlackboardValue('isSelected', this.isSelected);
this.behaviorTreeManager.updateBlackboardValue('isMoving', this.isMoving);
// 位置信息更新
this.behaviorTreeManager.updateBlackboardValue('worldPosition', this.node.worldPosition);
// 根据单位类型设置特定的黑板变量
if (this.unitType === 'worker') {
// 工人特有的变量
// 这里可以添加工人特有的状态更新
} else if (this.unitType === 'soldier') {
// 士兵特有的变量
this.behaviorTreeManager.updateBlackboardValue('lastAttackTime', this.lastAttackTime);
} else if (this.unitType === 'scout') {
// 侦察兵特有的变量
// 这里可以添加侦察兵特有的状态更新
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();
}
}