From 0b4a6b77e2c9d69a95c1530e5e455508e057000d Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Wed, 25 Jun 2025 17:50:40 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=87=E7=9F=BF=E8=A1=8C=E4=B8=BA=E6=A0=91?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cocos-ecs/.ecs-framework-settings.json | 54 - .../assets/resources/miner-ai.bt.json | 111 -- .../cocos-ecs/assets/resources/miner-ai.btree | 454 ------ .../assets/resources/miner-stamina-ai.bt.json | 317 ++++ ...son.meta => miner-stamina-ai.bt.json.meta} | 2 +- .../assets/resources/miner-stamina-ai.btree | 1395 +++++++++++++++++ ...btree.meta => miner-stamina-ai.btree.meta} | 2 +- .../cocos/cocos-ecs/assets/scenes/scene.scene | 159 +- .../cocos/cocos-ecs/assets/scripts/RTSDemo.ts | 188 +-- .../components/BehaviorTreeComponent.ts | 558 ++++++- .../scripts/components/BehaviorTreeManager.ts | 37 +- .../scripts/components/MinerStatusUI.ts | 139 ++ .../MinerStatusUI.ts.meta} | 2 +- .../scripts/components/RTSBehaviorHandler.ts | 286 +++- .../scripts/components/SimplePrefabFactory.ts | 9 +- .../scripts/components/StatusUIManager.ts | 289 ++++ ...ponent.ts.meta => StatusUIManager.ts.meta} | 2 +- .../scripts/components/UnitComponent.ts | 345 ---- .../scripts/components/UnitController.ts | 150 +- .../cocos-ecs/assets/scripts/controllers.meta | 9 - .../controllers/RTSCameraController.ts | 203 --- .../scripts/controllers/UIController.ts | 225 --- .../scripts/controllers/UIController.ts.meta | 9 - extensions/cocos/cocos-ecs/enemy-ai.bt.json | 214 --- .../cocos/cocos-ecs/extensions/behaviour-tree | 2 +- extensions/cocos/cocos-ecs/package-lock.json | 8 +- extensions/cocos/cocos-ecs/package.json | 2 +- thirdparty/BehaviourTree-ai | 2 +- 28 files changed, 3119 insertions(+), 2054 deletions(-) delete mode 100644 extensions/cocos/cocos-ecs/.ecs-framework-settings.json delete mode 100644 extensions/cocos/cocos-ecs/assets/resources/miner-ai.bt.json delete mode 100644 extensions/cocos/cocos-ecs/assets/resources/miner-ai.btree create mode 100644 extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.bt.json rename extensions/cocos/cocos-ecs/assets/resources/{miner-ai.bt.json.meta => miner-stamina-ai.bt.json.meta} (72%) create mode 100644 extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree rename extensions/cocos/cocos-ecs/assets/resources/{miner-ai.btree.meta => miner-stamina-ai.btree.meta} (73%) create mode 100644 extensions/cocos/cocos-ecs/assets/scripts/components/MinerStatusUI.ts rename extensions/cocos/cocos-ecs/assets/scripts/{controllers/RTSCameraController.ts.meta => components/MinerStatusUI.ts.meta} (70%) create mode 100644 extensions/cocos/cocos-ecs/assets/scripts/components/StatusUIManager.ts rename extensions/cocos/cocos-ecs/assets/scripts/components/{UnitComponent.ts.meta => StatusUIManager.ts.meta} (70%) delete mode 100644 extensions/cocos/cocos-ecs/assets/scripts/components/UnitComponent.ts delete mode 100644 extensions/cocos/cocos-ecs/assets/scripts/controllers.meta delete mode 100644 extensions/cocos/cocos-ecs/assets/scripts/controllers/RTSCameraController.ts delete mode 100644 extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts delete mode 100644 extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts.meta delete mode 100644 extensions/cocos/cocos-ecs/enemy-ai.bt.json diff --git a/extensions/cocos/cocos-ecs/.ecs-framework-settings.json b/extensions/cocos/cocos-ecs/.ecs-framework-settings.json deleted file mode 100644 index 62dce418..00000000 --- a/extensions/cocos/cocos-ecs/.ecs-framework-settings.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "codeGeneration": { - "template": "typescript", - "useStrictMode": true, - "generateComments": true, - "generateImports": true, - "componentSuffix": "Component", - "systemSuffix": "System", - "indentStyle": "spaces", - "indentSize": 4 - }, - "performance": { - "enableMonitoring": true, - "warningThreshold": 16.67, - "criticalThreshold": 33.33, - "memoryWarningMB": 100, - "memoryCriticalMB": 200, - "maxRecentSamples": 60, - "enableFpsMonitoring": true, - "targetFps": 60 - }, - "debugging": { - "enableDebugMode": true, - "showEntityCount": true, - "showSystemExecutionTime": true, - "enablePerformanceWarnings": true, - "logLevel": "info", - "enableDetailedLogs": false - }, - "editor": { - "autoRefreshAssets": true, - "showWelcomePanelOnStartup": true, - "enableAutoUpdates": false, - "updateChannel": "stable", - "enableNotifications": true - }, - "template": { - "defaultEntityName": "GameEntity", - "defaultComponentName": "CustomComponent", - "defaultSystemName": "CustomSystem", - "createExampleFiles": true, - "includeDocumentation": true, - "useFactoryPattern": true - }, - "events": { - "enableEventSystem": true, - "defaultEventPriority": 0, - "enableAsyncEvents": true, - "enableEventBatching": false, - "batchSize": 10, - "batchDelay": 16, - "maxEventListeners": 100 - } -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.bt.json b/extensions/cocos/cocos-ecs/assets/resources/miner-ai.bt.json deleted file mode 100644 index f1cb9b73..00000000 --- a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.bt.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "name": "miner-ai", - "description": "简化版矿工AI - 挖矿存储循环", - "blackboard": { - "variables": [ - { - "name": "unitType", - "type": "string", - "value": "miner", - "group": "基础属性", - "description": "单位类型" - }, - { - "name": "currentCommand", - "type": "string", - "value": "mine", - "group": "命令状态", - "description": "当前命令" - }, - { - "name": "hasOre", - "type": "boolean", - "value": false, - "group": "工作状态", - "description": "是否携带矿石" - }, - { - "name": "targetPosition", - "type": "vector3", - "value": null, - "group": "移动属性", - "description": "目标位置" - }, - { - "name": "hasTarget", - "type": "boolean", - "value": false, - "group": "移动属性", - "description": "是否有目标" - }, - { - "name": "isMoving", - "type": "boolean", - "value": false, - "group": "移动属性", - "description": "是否正在移动" - }, - { - "name": "currentHealth", - "type": "number", - "value": 100, - "group": "基础属性", - "description": "当前生命值" - }, - { - "name": "maxHealth", - "type": "number", - "value": 100, - "group": "基础属性", - "description": "最大生命值" - } - ] - }, - "root": { - "type": "selector", - "name": "矿工主选择器", - "children": [ - { - "type": "sequence", - "name": "存储矿石序列", - "children": [ - { - "type": "blackboard-value-comparison", - "name": "检查是否携带矿石", - "variable": "hasOre", - "operator": "==", - "value": true - }, - { - "type": "execute-action", - "name": "前往仓库存储", - "action": "store-ore" - } - ] - }, - { - "type": "sequence", - "name": "挖矿序列", - "children": [ - { - "type": "blackboard-value-comparison", - "name": "检查是否没有矿石", - "variable": "hasOre", - "operator": "==", - "value": false - }, - { - "type": "execute-action", - "name": "寻找并挖掘矿石", - "action": "find-and-mine-ore" - } - ] - }, - { - "type": "execute-action", - "name": "默认待机", - "action": "idle-behavior" - } - ] - } -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.btree b/extensions/cocos/cocos-ecs/assets/resources/miner-ai.btree deleted file mode 100644 index 2c21b4b0..00000000 --- a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.btree +++ /dev/null @@ -1,454 +0,0 @@ -{ - "nodes": [ - { - "id": "root", - "type": "root", - "name": "矿工AI根节点", - "position": { - "x": 400, - "y": 100 - }, - "properties": {}, - "children": [ - "main-selector" - ], - "canHaveChildren": true, - "canHaveParent": false, - "hasError": false - }, - { - "id": "main-selector", - "type": "selector", - "name": "矿工主选择器", - "position": { - "x": 400, - "y": 200 - }, - "properties": { - "description": { - "name": "描述", - "type": "string", - "value": "矿工主要行为选择:有矿石就存储,没矿石就挖矿,否则待机", - "required": false - } - }, - "children": [ - "store-conditional", - "mine-conditional", - "idle-action" - ], - "canHaveChildren": true, - "canHaveParent": true, - "hasError": false - }, - { - "id": "store-conditional", - "type": "conditional-decorator", - "name": "检查是否携带矿石", - "position": { - "x": 200, - "y": 250 - }, - "properties": { - "conditionType": { - "name": "条件类型", - "type": "string", - "value": "blackboardCompare", - "description": "条件判断类型", - "required": false - }, - "executeWhenTrue": { - "name": "条件为真时执行", - "type": "boolean", - "value": true, - "description": "条件为真时是否执行子节点", - "required": false - }, - "executeWhenFalse": { - "name": "条件为假时执行", - "type": "boolean", - "value": false, - "description": "条件为假时是否执行子节点", - "required": false - }, - "checkInterval": { - "name": "检查间隔", - "type": "number", - "value": 0, - "description": "条件检查间隔时间(秒),0表示每帧检查", - "required": false - }, - "abortType": { - "name": "中止类型", - "type": "select", - "value": "None", - "description": "决定节点在何种情况下会被中止", - "options": ["None", "LowerPriority", "Self", "Both"], - "required": false - }, - "shouldReevaluate": true, - "variableName": "hasOre", - "operator": "equal", - "compareValue": "true" - }, - "children": [ - "store-sequence" - ], - "attachedCondition": { - "type": "blackboard-value-comparison", - "name": "黑板值比较", - "icon": "⚖️" - }, - "conditionExpanded": false, - "canHaveChildren": true, - "canHaveParent": true, - "hasError": false - }, - { - "id": "mine-conditional", - "type": "conditional-decorator", - "name": "检查是否没有矿石", - "position": { - "x": 400, - "y": 250 - }, - "properties": { - "conditionType": { - "name": "条件类型", - "type": "string", - "value": "blackboardCompare", - "description": "条件判断类型", - "required": false - }, - "executeWhenTrue": { - "name": "条件为真时执行", - "type": "boolean", - "value": true, - "description": "条件为真时是否执行子节点", - "required": false - }, - "executeWhenFalse": { - "name": "条件为假时执行", - "type": "boolean", - "value": false, - "description": "条件为假时是否执行子节点", - "required": false - }, - "checkInterval": { - "name": "检查间隔", - "type": "number", - "value": 0, - "description": "条件检查间隔时间(秒),0表示每帧检查", - "required": false - }, - "abortType": { - "name": "中止类型", - "type": "select", - "value": "None", - "description": "决定节点在何种情况下会被中止", - "options": ["None", "LowerPriority", "Self", "Both"], - "required": false - }, - "shouldReevaluate": true, - "variableName": "hasOre", - "operator": "equal", - "compareValue": "false" - }, - "children": [ - "mine-sequence" - ], - "attachedCondition": { - "type": "blackboard-value-comparison", - "name": "黑板值比较", - "icon": "⚖️" - }, - "conditionExpanded": false, - "canHaveChildren": true, - "canHaveParent": true, - "hasError": false - }, - { - "id": "store-sequence", - "type": "sequence", - "name": "存储矿石序列", - "position": { - "x": 200, - "y": 350 - }, - "properties": { - "description": { - "name": "description", - "type": "string", - "value": "去仓库存储矿石,然后设置状态为无矿石", - "required": false - } - }, - "children": [ - "store-ore-action", - "set-no-ore" - ], - "canHaveChildren": true, - "canHaveParent": true, - "hasError": false - }, - { - "id": "mine-sequence", - "type": "sequence", - "name": "挖矿序列", - "position": { - "x": 400, - "y": 350 - }, - "properties": { - "description": { - "name": "description", - "type": "string", - "value": "寻找并挖掘矿石,然后设置状态为有矿石", - "required": false - } - }, - "children": [ - "mine-ore-action", - "set-has-ore" - ], - "canHaveChildren": true, - "canHaveParent": true, - "hasError": false - }, - { - "id": "store-ore-action", - "type": "event-action", - "name": "前往仓库存储", - "position": { - "x": 150, - "y": 450 - }, - "properties": { - "eventName": { - "name": "事件名称", - "type": "string", - "value": "store-ore", - "description": "要执行的事件名称", - "required": true - }, - "parameters": { - "name": "事件参数", - "type": "string", - "value": "{}", - "description": "传递给事件处理函数的参数(JSON格式)", - "required": false - }, - "timeout": { - "name": "超时时间", - "type": "number", - "value": 0, - "description": "事件执行超时时间(秒),0表示无限制", - "required": false - } - }, - "children": [], - "canHaveChildren": false, - "canHaveParent": true, - "hasError": false - }, - { - "id": "set-no-ore", - "type": "set-blackboard-value", - "name": "设置无矿石状态", - "position": { - "x": 250, - "y": 450 - }, - "properties": { - "variableName": { - "name": "变量名", - "type": "string", - "value": "hasOre", - "description": "黑板变量名", - "required": true - }, - "value": { - "name": "设置值", - "type": "string", - "value": "false", - "description": "要设置的值(留空则使用源变量)", - "required": false - }, - "force": { - "name": "强制设置", - "type": "boolean", - "value": false, - "description": "是否忽略只读限制", - "required": false - } - }, - "children": [], - "canHaveChildren": false, - "canHaveParent": true, - "hasError": false - }, - { - "id": "mine-ore-action", - "type": "event-action", - "name": "寻找并挖掘矿石", - "position": { - "x": 350, - "y": 450 - }, - "properties": { - "eventName": { - "name": "事件名称", - "type": "string", - "value": "find-and-mine-ore", - "description": "要执行的事件名称", - "required": true - }, - "parameters": { - "name": "事件参数", - "type": "string", - "value": "{}", - "description": "传递给事件处理函数的参数(JSON格式)", - "required": false - }, - "timeout": { - "name": "超时时间", - "type": "number", - "value": 0, - "description": "事件执行超时时间(秒),0表示无限制", - "required": false - } - }, - "children": [], - "canHaveChildren": false, - "canHaveParent": true, - "hasError": false - }, - { - "id": "set-has-ore", - "type": "set-blackboard-value", - "name": "设置有矿石状态", - "position": { - "x": 450, - "y": 450 - }, - "properties": { - "variableName": { - "name": "变量名", - "type": "string", - "value": "hasOre", - "description": "黑板变量名", - "required": true - }, - "value": { - "name": "设置值", - "type": "string", - "value": "true", - "description": "要设置的值(留空则使用源变量)", - "required": false - }, - "force": { - "name": "强制设置", - "type": "boolean", - "value": false, - "description": "是否忽略只读限制", - "required": false - } - }, - "children": [], - "canHaveChildren": false, - "canHaveParent": true, - "hasError": false - }, - { - "id": "idle-action", - "type": "event-action", - "name": "默认待机", - "position": { - "x": 600, - "y": 250 - }, - "properties": { - "eventName": { - "name": "事件名称", - "type": "string", - "value": "idle-behavior", - "description": "要执行的事件名称", - "required": true - }, - "parameters": { - "name": "事件参数", - "type": "string", - "value": "{}", - "description": "传递给事件处理函数的参数(JSON格式)", - "required": false - }, - "timeout": { - "name": "超时时间", - "type": "number", - "value": 0, - "description": "事件执行超时时间(秒),0表示无限制", - "required": false - } - }, - "children": [], - "canHaveChildren": false, - "canHaveParent": true, - "hasError": false - } - ], - "connections": [], - "metadata": { - "name": "miner-ai", - "description": "矿工AI示例 - 展示条件装饰器的正确使用方式", - "version": "1.0", - "created": "2025-06-24T15:00:13.826Z" - }, - "blackboard": [ - { - "name": "unitType", - "type": "string", - "value": "miner", - "description": "单位类型 - 可拖拽到事件参数中", - "group": "基础属性" - }, - { - "name": "hasOre", - "type": "boolean", - "value": false, - "description": "是否携带矿石 - 核心状态变量,被条件节点和设置节点使用", - "group": "工作状态" - }, - { - "name": "currentHealth", - "type": "number", - "value": 100, - "description": "当前生命值 - 可拖拽用于健康检查条件", - "group": "基础属性" - }, - { - "name": "maxHealth", - "type": "number", - "value": 100, - "description": "最大生命值", - "group": "基础属性" - }, - { - "name": "targetPosition", - "type": "object", - "value": { - "x": 0, - "y": 0, - "z": 0 - }, - "description": "目标位置 - 移动系统使用", - "group": "移动属性" - }, - { - "name": "isMoving", - "type": "boolean", - "value": false, - "description": "是否正在移动", - "group": "移动属性" - } - ] -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.bt.json b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.bt.json new file mode 100644 index 00000000..12cc75ca --- /dev/null +++ b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.bt.json @@ -0,0 +1,317 @@ +{ + "nodes": [ + { + "id": "node_15iffhg4p", + "type": "root", + "name": "根节点", + "description": "行为树的根节点,每棵树只能有一个根节点", + "children": [ + "node_o6tsnrxyg" + ] + }, + { + "id": "node_o6tsnrxyg", + "type": "selector", + "name": "选择器", + "description": "按顺序执行子节点,任一成功则整体成功", + "properties": { + "abortType": "LowerPriority" + }, + "children": [ + "node_tljchzbno", + "node_txhx0hau5", + "node_r9kvcwv8u", + "node_520hedw22" + ] + }, + { + "id": "node_tljchzbno", + "type": "conditional-decorator", + "name": "休息条件装饰器", + "description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)", + "properties": { + "conditionType": "blackboardCompare", + "executeWhenTrue": true, + "abortType": "LowerPriority", + "shouldReevaluate": true, + "variableName": "{{isLowStamina}}", + "operator": "equal", + "compareValue": "true" + }, + "children": [ + "node_ulp8qx68h" + ], + "condition": { + "type": "blackboard-value-comparison", + "properties": {} + } + }, + { + "id": "node_txhx0hau5", + "type": "conditional-decorator", + "name": "存储条件装饰器", + "description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)", + "properties": { + "conditionType": "blackboardCompare", + "executeWhenTrue": true, + "abortType": "LowerPriority", + "shouldReevaluate": true, + "variableName": "{{hasOre}}", + "operator": "equal", + "compareValue": "true" + }, + "children": [ + "node_dhsz8rgl1" + ], + "condition": { + "type": "blackboard-value-comparison", + "properties": {} + } + }, + { + "id": "node_r9kvcwv8u", + "type": "conditional-decorator", + "name": "挖矿条件装饰器", + "description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)", + "properties": { + "conditionType": "blackboardCompare", + "executeWhenTrue": true, + "abortType": "LowerPriority", + "shouldReevaluate": true, + "variableName": "{{isLowStamina}}", + "operator": "equal", + "compareValue": "false" + }, + "children": [ + "node_zguxml6u7" + ], + "condition": { + "type": "blackboard-value-comparison", + "properties": {} + } + }, + { + "id": "node_ulp8qx68h", + "type": "sequence", + "name": "序列器", + "description": "按顺序执行子节点,任一失败则整体失败", + "properties": { + "abortType": "None" + }, + "children": [ + "node_0fgq85ovw", + "node_9v13vpqyr" + ] + }, + { + "id": "node_0fgq85ovw", + "type": "event-action", + "name": "回家休息", + "description": "执行已注册的事件处理函数(推荐)", + "properties": { + "eventName": "go-home-rest", + "parameters": "{}" + } + }, + { + "id": "node_9v13vpqyr", + "type": "event-action", + "name": "恢复体力", + "description": "执行已注册的事件处理函数(推荐)", + "properties": { + "eventName": "recover-stamina", + "parameters": "{}" + } + }, + { + "id": "node_ui4ja9mlj", + "type": "event-action", + "name": "前往仓库存储", + "description": "执行已注册的事件处理函数(推荐)", + "properties": { + "eventName": "store-ore", + "parameters": "{}" + } + }, + { + "id": "node_969njccy2", + "type": "event-action", + "name": "挖掘金矿", + "description": "执行已注册的事件处理函数(推荐)", + "properties": { + "eventName": "mine-gold-ore", + "parameters": "{}" + } + }, + { + "id": "node_520hedw22", + "type": "event-action", + "name": "默认待机", + "description": "执行已注册的事件处理函数(推荐)", + "properties": { + "eventName": "idle-behavior", + "parameters": "{}" + } + }, + { + "id": "node_o5c7hv5wx", + "type": "set-blackboard-value", + "name": "设置黑板变量", + "description": "设置黑板变量的值", + "properties": { + "variableName": "{{hasOre}}", + "value": "false" + } + }, + { + "id": "node_zf0sgkqev", + "type": "set-blackboard-value", + "name": "设置黑板变量", + "description": "设置黑板变量的值", + "properties": { + "variableName": "{{hasOre}}", + "value": "true" + } + }, + { + "id": "node_dhsz8rgl1", + "type": "sequence", + "name": "序列器", + "description": "按顺序执行子节点,任一失败则整体失败", + "properties": { + "abortType": "None" + }, + "children": [ + "node_ui4ja9mlj", + "node_o5c7hv5wx" + ] + }, + { + "id": "node_zguxml6u7", + "type": "sequence", + "name": "序列器", + "description": "按顺序执行子节点,任一失败则整体失败", + "properties": { + "abortType": "None" + }, + "children": [ + "node_969njccy2", + "node_zf0sgkqev" + ] + } + ], + "blackboard": [ + { + "name": "unitType", + "type": "string", + "value": "miner", + "description": "单位类型", + "group": "基础属性" + }, + { + "name": "currentHealth", + "type": "number", + "value": 100, + "description": "当前生命值", + "group": "基础属性" + }, + { + "name": "maxHealth", + "type": "number", + "value": 100, + "description": "最大生命值", + "group": "基础属性" + }, + { + "name": "stamina", + "type": "number", + "value": 100, + "description": "当前体力值 - 挖矿会消耗体力", + "group": "体力系统" + }, + { + "name": "maxStamina", + "type": "number", + "value": 100, + "description": "最大体力值", + "group": "体力系统" + }, + { + "name": "staminaPercentage", + "type": "number", + "value": 1, + "description": "体力百分比", + "group": "体力系统" + }, + { + "name": "isLowStamina", + "type": "boolean", + "value": false, + "description": "是否低体力 - 体力低于20%时为true", + "group": "体力系统" + }, + { + "name": "isResting", + "type": "boolean", + "value": false, + "description": "是否正在休息", + "group": "体力系统" + }, + { + "name": "homePosition", + "type": "vector3", + "value": { + "x": 0, + "y": 0, + "z": 0 + }, + "description": "家的位置 - 矿工休息的地方", + "group": "体力系统" + }, + { + "name": "hasOre", + "type": "boolean", + "value": false, + "description": "是否携带矿石", + "group": "工作状态" + }, + { + "name": "currentCommand", + "type": "string", + "value": "mine", + "description": "当前命令", + "group": "工作状态" + }, + { + "name": "hasTarget", + "type": "boolean", + "value": false, + "description": "是否有目标", + "group": "工作状态" + }, + { + "name": "targetPosition", + "type": "vector3", + "value": { + "x": 0, + "y": 0, + "z": 0 + }, + "description": "目标位置", + "group": "移动属性" + }, + { + "name": "isMoving", + "type": "boolean", + "value": false, + "description": "是否正在移动", + "group": "移动属性" + } + ], + "metadata": { + "name": "behavior-tree", + "created": "2025-06-25T08:41:25.401Z", + "version": "1.0", + "exportType": "clean" + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.bt.json.meta b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.bt.json.meta similarity index 72% rename from extensions/cocos/cocos-ecs/assets/resources/miner-ai.bt.json.meta rename to extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.bt.json.meta index b2c22a48..b6cb3901 100644 --- a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.bt.json.meta +++ b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.bt.json.meta @@ -2,7 +2,7 @@ "ver": "2.0.1", "importer": "json", "imported": true, - "uuid": "7ecbbfe3-a63f-4558-89e2-85e3f084ee02", + "uuid": "b328a163-97a8-4435-b87c-9369dd76862d", "files": [ ".json" ], diff --git a/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree new file mode 100644 index 00000000..62dc4f05 --- /dev/null +++ b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree @@ -0,0 +1,1395 @@ +{ + "version": "1.0", + "type": "behavior-tree-editor", + "metadata": { + "name": "assets/resources/miner-stamina-ai.btree", + "created": "2025-06-25T08:41:23.672Z", + "modified": "2025-06-25T08:41:23.672Z", + "version": "1.0", + "editorVersion": "1.0.0" + }, + "nodes": [ + { + "id": "node_15iffhg4p", + "type": "root", + "name": "根节点", + "icon": "🌳", + "description": "行为树的根节点,每棵树只能有一个根节点", + "x": 1080, + "y": 50, + "children": [ + "node_o6tsnrxyg" + ], + "properties": {}, + "canHaveChildren": true, + "canHaveParent": false, + "maxChildren": 1, + "minChildren": 1, + "hasError": false + }, + { + "id": "node_o6tsnrxyg", + "type": "selector", + "name": "选择器", + "icon": "?", + "description": "按顺序执行子节点,任一成功则整体成功", + "x": 1090, + "y": 208, + "children": [ + "node_tljchzbno", + "node_txhx0hau5", + "node_r9kvcwv8u", + "node_520hedw22" + ], + "properties": { + "abortType": { + "name": "中止类型", + "type": "select", + "value": "LowerPriority", + "description": "决定节点在何种情况下会被中止", + "options": [ + "None", + "LowerPriority", + "Self", + "Both" + ], + "required": false + } + }, + "canHaveChildren": true, + "canHaveParent": true, + "minChildren": 1, + "hasError": false, + "parent": "node_15iffhg4p" + }, + { + "id": "node_tljchzbno", + "type": "conditional-decorator", + "name": "休息条件装饰器", + "icon": "🔀", + "description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)", + "x": 515, + "y": 414, + "children": [ + "node_ulp8qx68h" + ], + "properties": { + "conditionType": { + "name": "条件类型", + "type": "select", + "value": "blackboardCompare", + "description": "装饰器使用的条件类型", + "options": [ + "custom", + "random", + "hasComponent", + "hasTag", + "isActive", + "numericCompare", + "propertyExists" + ], + "required": false + }, + "executeWhenTrue": { + "name": "条件为真时执行", + "type": "boolean", + "value": true, + "description": "条件为真时是否执行子节点", + "required": false + }, + "executeWhenFalse": { + "name": "条件为假时执行", + "type": "boolean", + "value": false, + "description": "条件为假时是否执行子节点", + "required": false + }, + "checkInterval": { + "name": "检查间隔", + "type": "number", + "value": 0, + "description": "条件检查间隔时间(秒),0表示每帧检查", + "required": false + }, + "abortType": { + "name": "中止类型", + "type": "select", + "value": "LowerPriority", + "description": "决定节点在何种情况下会被中止", + "options": [ + "None", + "LowerPriority", + "Self", + "Both" + ], + "required": false + }, + "shouldReevaluate": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": true, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "variableName": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": "{{isLowStamina}}", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "operator": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": "equal", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "compareValue": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": "true", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "compareVariable": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": "", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + } + }, + "canHaveChildren": true, + "canHaveParent": true, + "maxChildren": 1, + "minChildren": 1, + "hasError": false, + "parent": "node_o6tsnrxyg", + "attachedCondition": { + "type": "blackboard-value-comparison", + "name": "黑板值比较", + "icon": "⚖️" + }, + "conditionExpanded": false + }, + { + "id": "node_txhx0hau5", + "type": "conditional-decorator", + "name": "存储条件装饰器", + "icon": "🔀", + "description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)", + "x": 975, + "y": 414, + "children": [ + "node_dhsz8rgl1" + ], + "properties": { + "conditionType": { + "name": "条件类型", + "type": "select", + "value": "blackboardCompare", + "description": "装饰器使用的条件类型", + "options": [ + "custom", + "random", + "hasComponent", + "hasTag", + "isActive", + "numericCompare", + "propertyExists" + ], + "required": false + }, + "executeWhenTrue": { + "name": "条件为真时执行", + "type": "boolean", + "value": true, + "description": "条件为真时是否执行子节点", + "required": false + }, + "executeWhenFalse": { + "name": "条件为假时执行", + "type": "boolean", + "value": false, + "description": "条件为假时是否执行子节点", + "required": false + }, + "checkInterval": { + "name": "检查间隔", + "type": "number", + "value": 0, + "description": "条件检查间隔时间(秒),0表示每帧检查", + "required": false + }, + "abortType": { + "name": "中止类型", + "type": "select", + "value": "LowerPriority", + "description": "决定节点在何种情况下会被中止", + "options": [ + "None", + "LowerPriority", + "Self", + "Both" + ], + "required": false + }, + "shouldReevaluate": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": true, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "variableName": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": "{{hasOre}}", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "operator": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": "equal", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "compareValue": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": "true", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "compareVariable": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": "", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + } + }, + "canHaveChildren": true, + "canHaveParent": true, + "maxChildren": 1, + "minChildren": 1, + "hasError": false, + "parent": "node_o6tsnrxyg", + "attachedCondition": { + "type": "blackboard-value-comparison", + "name": "黑板值比较", + "icon": "⚖️" + }, + "conditionExpanded": false + }, + { + "id": "node_r9kvcwv8u", + "type": "conditional-decorator", + "name": "挖矿条件装饰器", + "icon": "🔀", + "description": "基于条件执行子节点(拖拽条件节点到此装饰器来配置条件)", + "x": 1435, + "y": 414, + "children": [ + "node_zguxml6u7" + ], + "properties": { + "conditionType": { + "name": "条件类型", + "type": "select", + "value": "blackboardCompare", + "description": "装饰器使用的条件类型", + "options": [ + "custom", + "random", + "hasComponent", + "hasTag", + "isActive", + "numericCompare", + "propertyExists" + ], + "required": false + }, + "executeWhenTrue": { + "name": "条件为真时执行", + "type": "boolean", + "value": true, + "description": "条件为真时是否执行子节点", + "required": false + }, + "executeWhenFalse": { + "name": "条件为假时执行", + "type": "boolean", + "value": false, + "description": "条件为假时是否执行子节点", + "required": false + }, + "checkInterval": { + "name": "检查间隔", + "type": "number", + "value": 0, + "description": "条件检查间隔时间(秒),0表示每帧检查", + "required": false + }, + "abortType": { + "name": "中止类型", + "type": "select", + "value": "LowerPriority", + "description": "决定节点在何种情况下会被中止", + "options": [ + "None", + "LowerPriority", + "Self", + "Both" + ], + "required": false + }, + "shouldReevaluate": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": { + "name": "shouldReevaluate", + "type": "string", + "value": true, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "variableName": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": { + "name": "variableName", + "type": "string", + "value": "{{isLowStamina}}", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "operator": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": { + "name": "operator", + "type": "string", + "value": "equal", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "compareValue": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": { + "name": "compareValue", + "type": "string", + "value": "false", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "compareVariable": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": { + "name": "compareVariable", + "type": "string", + "value": "", + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + }, + "description": "", + "required": false + } + }, + "canHaveChildren": true, + "canHaveParent": true, + "maxChildren": 1, + "minChildren": 1, + "hasError": false, + "parent": "node_o6tsnrxyg", + "attachedCondition": { + "type": "blackboard-value-comparison", + "name": "黑板值比较", + "icon": "⚖️" + }, + "conditionExpanded": false + }, + { + "id": "node_ulp8qx68h", + "type": "sequence", + "name": "序列器", + "icon": "→", + "description": "按顺序执行子节点,任一失败则整体失败", + "x": 515, + "y": 804, + "children": [ + "node_0fgq85ovw", + "node_9v13vpqyr" + ], + "properties": { + "abortType": { + "name": "中止类型", + "type": "select", + "value": "None", + "description": "决定节点在何种情况下会被中止", + "options": [ + "None", + "LowerPriority", + "Self", + "Both" + ], + "required": false + } + }, + "canHaveChildren": true, + "canHaveParent": true, + "minChildren": 1, + "hasError": false, + "parent": "node_tljchzbno" + }, + { + "id": "node_0fgq85ovw", + "type": "event-action", + "name": "回家休息", + "icon": "📢", + "description": "执行已注册的事件处理函数(推荐)", + "x": 400, + "y": 1010, + "children": [], + "properties": { + "eventName": { + "name": "事件名称", + "type": "string", + "value": "go-home-rest", + "description": "要执行的事件名称(如:enemy.attack, player.move)", + "required": true + }, + "parameters": { + "name": "事件参数", + "type": "string", + "value": "{}", + "description": "传递给事件处理函数的参数(JSON格式)", + "required": false + }, + "timeout": { + "name": "超时时间", + "type": "number", + "value": 0, + "description": "事件执行超时时间(秒),0表示无限制", + "required": false + } + }, + "canHaveChildren": false, + "canHaveParent": true, + "maxChildren": 0, + "hasError": false, + "parent": "node_ulp8qx68h" + }, + { + "id": "node_9v13vpqyr", + "type": "event-action", + "name": "恢复体力", + "icon": "📢", + "description": "执行已注册的事件处理函数(推荐)", + "x": 630, + "y": 1010, + "children": [], + "properties": { + "eventName": { + "name": "事件名称", + "type": "string", + "value": "recover-stamina", + "description": "要执行的事件名称(如:enemy.attack, player.move)", + "required": true + }, + "parameters": { + "name": "事件参数", + "type": "string", + "value": "{}", + "description": "传递给事件处理函数的参数(JSON格式)", + "required": false + }, + "timeout": { + "name": "超时时间", + "type": "number", + "value": 0, + "description": "事件执行超时时间(秒),0表示无限制", + "required": false + } + }, + "canHaveChildren": false, + "canHaveParent": true, + "maxChildren": 0, + "hasError": false, + "parent": "node_ulp8qx68h" + }, + { + "id": "node_ui4ja9mlj", + "type": "event-action", + "name": "前往仓库存储", + "icon": "📢", + "description": "执行已注册的事件处理函数(推荐)", + "x": 860, + "y": 1010, + "children": [], + "properties": { + "eventName": { + "name": "事件名称", + "type": "string", + "value": "store-ore", + "description": "要执行的事件名称(如:enemy.attack, player.move)", + "required": true + }, + "parameters": { + "name": "事件参数", + "type": "string", + "value": "{}", + "description": "传递给事件处理函数的参数(JSON格式)", + "required": false + }, + "timeout": { + "name": "超时时间", + "type": "number", + "value": 0, + "description": "事件执行超时时间(秒),0表示无限制", + "required": false + } + }, + "canHaveChildren": false, + "canHaveParent": true, + "maxChildren": 0, + "hasError": false, + "parent": "node_dhsz8rgl1" + }, + { + "id": "node_969njccy2", + "type": "event-action", + "name": "挖掘金矿", + "icon": "📢", + "description": "执行已注册的事件处理函数(推荐)", + "x": 1320, + "y": 1010, + "children": [], + "properties": { + "eventName": { + "name": "事件名称", + "type": "string", + "value": "mine-gold-ore", + "description": "要执行的事件名称(如:enemy.attack, player.move)", + "required": true + }, + "parameters": { + "name": "事件参数", + "type": "string", + "value": "{}", + "description": "传递给事件处理函数的参数(JSON格式)", + "required": false + }, + "timeout": { + "name": "超时时间", + "type": "number", + "value": 0, + "description": "事件执行超时时间(秒),0表示无限制", + "required": false + } + }, + "canHaveChildren": false, + "canHaveParent": true, + "maxChildren": 0, + "hasError": false, + "parent": "node_zguxml6u7" + }, + { + "id": "node_520hedw22", + "type": "event-action", + "name": "默认待机", + "icon": "📢", + "description": "执行已注册的事件处理函数(推荐)", + "x": 1780, + "y": 414, + "children": [], + "properties": { + "eventName": { + "name": "事件名称", + "type": "string", + "value": "idle-behavior", + "description": "要执行的事件名称(如:enemy.attack, player.move)", + "required": true + }, + "parameters": { + "name": "事件参数", + "type": "string", + "value": "{}", + "description": "传递给事件处理函数的参数(JSON格式)", + "required": false + }, + "timeout": { + "name": "超时时间", + "type": "number", + "value": 0, + "description": "事件执行超时时间(秒),0表示无限制", + "required": false + } + }, + "canHaveChildren": false, + "canHaveParent": true, + "maxChildren": 0, + "hasError": false, + "parent": "node_o6tsnrxyg" + }, + { + "id": "node_o5c7hv5wx", + "type": "set-blackboard-value", + "name": "设置黑板变量", + "icon": "📝", + "description": "设置黑板变量的值", + "x": 1090, + "y": 1010, + "children": [], + "properties": { + "variableName": { + "name": "变量名", + "type": "string", + "value": "{{hasOre}}", + "description": "黑板变量名", + "required": true + }, + "value": { + "name": "设置值", + "type": "string", + "value": "false", + "description": "要设置的值(留空则使用源变量)", + "required": false + }, + "sourceVariable": { + "name": "源变量名", + "type": "string", + "value": "", + "description": "从另一个黑板变量复制值", + "required": false + }, + "force": { + "name": "强制设置", + "type": "boolean", + "value": false, + "description": "是否忽略只读限制", + "required": false + } + }, + "canHaveChildren": false, + "canHaveParent": true, + "maxChildren": 0, + "hasError": false, + "parent": "node_dhsz8rgl1" + }, + { + "id": "node_zf0sgkqev", + "type": "set-blackboard-value", + "name": "设置黑板变量", + "icon": "📝", + "description": "设置黑板变量的值", + "x": 1550, + "y": 1010, + "children": [], + "properties": { + "variableName": { + "name": "变量名", + "type": "string", + "value": "{{hasOre}}", + "description": "黑板变量名", + "required": true + }, + "value": { + "name": "设置值", + "type": "string", + "value": "true", + "description": "要设置的值(留空则使用源变量)", + "required": false + }, + "sourceVariable": { + "name": "源变量名", + "type": "string", + "value": "", + "description": "从另一个黑板变量复制值", + "required": false + }, + "force": { + "name": "强制设置", + "type": "boolean", + "value": false, + "description": "是否忽略只读限制", + "required": false + } + }, + "canHaveChildren": false, + "canHaveParent": true, + "maxChildren": 0, + "hasError": false, + "parent": "node_zguxml6u7" + }, + { + "id": "node_dhsz8rgl1", + "type": "sequence", + "name": "序列器", + "icon": "→", + "description": "按顺序执行子节点,任一失败则整体失败", + "x": 975, + "y": 804, + "children": [ + "node_ui4ja9mlj", + "node_o5c7hv5wx" + ], + "properties": { + "abortType": { + "name": "中止类型", + "type": "select", + "value": "None", + "description": "决定节点在何种情况下会被中止", + "options": [ + "None", + "LowerPriority", + "Self", + "Both" + ], + "required": false + } + }, + "canHaveChildren": true, + "canHaveParent": true, + "minChildren": 1, + "hasError": false, + "parent": "node_txhx0hau5" + }, + { + "id": "node_zguxml6u7", + "type": "sequence", + "name": "序列器", + "icon": "→", + "description": "按顺序执行子节点,任一失败则整体失败", + "x": 1435, + "y": 804, + "children": [ + "node_969njccy2", + "node_zf0sgkqev" + ], + "properties": { + "abortType": { + "name": "中止类型", + "type": "select", + "value": "None", + "description": "决定节点在何种情况下会被中止", + "options": [ + "None", + "LowerPriority", + "Self", + "Both" + ], + "required": false + } + }, + "canHaveChildren": true, + "canHaveParent": true, + "minChildren": 1, + "hasError": false, + "parent": "node_r9kvcwv8u" + } + ], + "connections": [ + { + "id": "node_15iffhg4p-node_o6tsnrxyg", + "sourceId": "node_15iffhg4p", + "targetId": "node_o6tsnrxyg", + "path": "M 1159.999999999999 144.9999999999999 C 1159.999999999999 176.69639587402332 1170.5931091308585 176.69639587402332 1170.5931091308585 208.39279174804673", + "active": false + }, + { + "id": "node_o6tsnrxyg-node_tljchzbno", + "sourceId": "node_o6tsnrxyg", + "targetId": "node_tljchzbno", + "path": "M 1169.4068908691397 336.32621765136696 C 1169.4068908691397 376.16310119628884 625.0000381469722 376.16310119628884 625.0000381469722 415.99998474121065", + "active": false + }, + { + "id": "node_o6tsnrxyg-node_txhx0hau5", + "sourceId": "node_o6tsnrxyg", + "targetId": "node_txhx0hau5", + "path": "M 1169.4068908691397 336.32621765136696 C 1169.4068908691397 376.16310119628884 1084.999999999999 376.16310119628884 1084.999999999999 415.99998474121065", + "active": false + }, + { + "id": "node_o6tsnrxyg-node_r9kvcwv8u", + "sourceId": "node_o6tsnrxyg", + "targetId": "node_r9kvcwv8u", + "path": "M 1169.4068908691397 336.32621765136696 C 1169.4068908691397 376.16310119628884 1545.0000762939442 376.16310119628884 1545.0000762939442 415.99998474121065", + "active": false + }, + { + "id": "node_o6tsnrxyg-node_520hedw22", + "sourceId": "node_o6tsnrxyg", + "targetId": "node_520hedw22", + "path": "M 1169.4068908691397 336.32621765136696 C 1169.4068908691397 376.16310119628884 1860.000076293944 376.16310119628884 1860.000076293944 415.99998474121065", + "active": false + }, + { + "id": "node_tljchzbno-node_ulp8qx68h", + "sourceId": "node_tljchzbno", + "targetId": "node_ulp8qx68h", + "path": "M 625.0000381469722 642.0781707763667 C 625.0000381469722 722.0781707763667 595.0000381469722 726.0000610351557 595.0000381469722 806.0000610351557", + "active": false + }, + { + "id": "node_txhx0hau5-node_dhsz8rgl1", + "sourceId": "node_txhx0hau5", + "targetId": "node_dhsz8rgl1", + "path": "M 1084.999999999999 642.0781707763667 C 1084.999999999999 722.0781707763667 1054.9999999999993 726.0000610351557 1054.9999999999993 806.0000610351557", + "active": false + }, + { + "id": "node_r9kvcwv8u-node_zguxml6u7", + "sourceId": "node_r9kvcwv8u", + "targetId": "node_zguxml6u7", + "path": "M 1545.0000762939442 642.0781707763667 C 1545.0000762939442 722.0781707763667 1515.0000762939442 726.0000610351557 1515.0000762939442 806.0000610351557", + "active": false + }, + { + "id": "node_ulp8qx68h-node_0fgq85ovw", + "sourceId": "node_ulp8qx68h", + "targetId": "node_0fgq85ovw", + "path": "M 595.0000381469722 932.5937652587884 C 595.0000381469722 972.2969055175774 480.0000381469723 972.2969055175774 480.0000381469723 1012.0000457763664", + "active": false + }, + { + "id": "node_ulp8qx68h-node_9v13vpqyr", + "sourceId": "node_ulp8qx68h", + "targetId": "node_9v13vpqyr", + "path": "M 595.0000381469722 932.5937652587884 C 595.0000381469722 972.2969055175774 709.9999999999994 972.2969055175774 709.9999999999994 1012.0000457763664", + "active": false + }, + { + "id": "node_dhsz8rgl1-node_ui4ja9mlj", + "sourceId": "node_dhsz8rgl1", + "targetId": "node_ui4ja9mlj", + "path": "M 1054.9999999999993 932.5937652587884 C 1054.9999999999993 972.2969055175774 939.9999999999993 972.2969055175774 939.9999999999993 1012.0000457763664", + "active": false + }, + { + "id": "node_dhsz8rgl1-node_o5c7hv5wx", + "sourceId": "node_dhsz8rgl1", + "targetId": "node_o5c7hv5wx", + "path": "M 1054.9999999999993 932.5937652587884 C 1054.9999999999993 972.2969055175774 1169.999999999999 972.2969055175774 1169.999999999999 1012.0000457763664", + "active": false + }, + { + "id": "node_zguxml6u7-node_969njccy2", + "sourceId": "node_zguxml6u7", + "targetId": "node_969njccy2", + "path": "M 1515.0000762939442 932.5937652587884 C 1515.0000762939442 972.2969055175774 1400.0000762939442 972.2969055175774 1400.0000762939442 1012.0000457763664", + "active": false + }, + { + "id": "node_zguxml6u7-node_zf0sgkqev", + "sourceId": "node_zguxml6u7", + "targetId": "node_zf0sgkqev", + "path": "M 1515.0000762939442 932.5937652587884 C 1515.0000762939442 972.2969055175774 1630.0000762939442 972.2969055175774 1630.0000762939442 1012.0000457763664", + "active": false + } + ], + "blackboard": [ + { + "name": "unitType", + "type": "string", + "value": "miner", + "defaultValue": "miner", + "description": "单位类型", + "group": "基础属性", + "readOnly": false + }, + { + "name": "currentHealth", + "type": "number", + "value": 100, + "defaultValue": 100, + "description": "当前生命值", + "group": "基础属性", + "readOnly": false + }, + { + "name": "maxHealth", + "type": "number", + "value": 100, + "defaultValue": 100, + "description": "最大生命值", + "group": "基础属性", + "readOnly": false + }, + { + "name": "stamina", + "type": "number", + "value": 100, + "defaultValue": 100, + "description": "当前体力值 - 挖矿会消耗体力", + "group": "体力系统", + "readOnly": false + }, + { + "name": "maxStamina", + "type": "number", + "value": 100, + "defaultValue": 100, + "description": "最大体力值", + "group": "体力系统", + "readOnly": false + }, + { + "name": "staminaPercentage", + "type": "number", + "value": 1, + "defaultValue": 1, + "description": "体力百分比", + "group": "体力系统", + "readOnly": false + }, + { + "name": "isLowStamina", + "type": "boolean", + "value": false, + "defaultValue": false, + "description": "是否低体力 - 体力低于20%时为true", + "group": "体力系统", + "readOnly": false + }, + { + "name": "isResting", + "type": "boolean", + "value": false, + "defaultValue": false, + "description": "是否正在休息", + "group": "体力系统", + "readOnly": false + }, + { + "name": "homePosition", + "type": "vector3", + "value": { + "x": 0, + "y": 0, + "z": 0 + }, + "defaultValue": { + "x": 0, + "y": 0, + "z": 0 + }, + "description": "家的位置 - 矿工休息的地方", + "group": "体力系统", + "readOnly": false + }, + { + "name": "hasOre", + "type": "boolean", + "value": false, + "defaultValue": false, + "description": "是否携带矿石", + "group": "工作状态", + "readOnly": false + }, + { + "name": "currentCommand", + "type": "string", + "value": "mine", + "defaultValue": "mine", + "description": "当前命令", + "group": "工作状态", + "readOnly": false + }, + { + "name": "hasTarget", + "type": "boolean", + "value": false, + "defaultValue": false, + "description": "是否有目标", + "group": "工作状态", + "readOnly": false + }, + { + "name": "targetPosition", + "type": "vector3", + "value": { + "x": 0, + "y": 0, + "z": 0 + }, + "defaultValue": { + "x": 0, + "y": 0, + "z": 0 + }, + "description": "目标位置", + "group": "移动属性", + "readOnly": false + }, + { + "name": "isMoving", + "type": "boolean", + "value": false, + "defaultValue": false, + "description": "是否正在移动", + "group": "移动属性", + "readOnly": false + } + ], + "editorState": { + "canvasView": { + "panX": 0, + "panY": 0, + "zoomLevel": 1 + }, + "selectedNodeId": "node_o6tsnrxyg", + "uiSettings": { + "showDescriptions": true, + "showNodeProperties": true, + "layoutAlgorithm": "compact" + } + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.btree.meta b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree.meta similarity index 73% rename from extensions/cocos/cocos-ecs/assets/resources/miner-ai.btree.meta rename to extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree.meta index b22c01c8..4a8ea423 100644 --- a/extensions/cocos/cocos-ecs/assets/resources/miner-ai.btree.meta +++ b/extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree.meta @@ -2,7 +2,7 @@ "ver": "1.0.0", "importer": "*", "imported": true, - "uuid": "2dded9a7-03cd-465f-8419-7199e562c403", + "uuid": "24c6e7e6-4ff0-4e7b-b470-9468bfa66b5d", "files": [ ".btree", ".json" diff --git a/extensions/cocos/cocos-ecs/assets/scenes/scene.scene b/extensions/cocos/cocos-ecs/assets/scenes/scene.scene index c8f49023..5c5262a5 100644 --- a/extensions/cocos/cocos-ecs/assets/scenes/scene.scene +++ b/extensions/cocos/cocos-ecs/assets/scenes/scene.scene @@ -26,10 +26,10 @@ "__id__": 7 }, { - "__id__": 9 + "__id__": 8 }, { - "__id__": 10 + "__id__": 14 } ], "_active": true, @@ -255,72 +255,6 @@ "_trackingType": 0, "_id": "7dWQTpwS5LrIHnc1zAPUtf" }, - { - "__type__": "cc.Node", - "_name": "RTSDemo", - "_objFlags": 0, - "__editorExtras__": {}, - "_parent": { - "__id__": 1 - }, - "_children": [], - "_active": true, - "_components": [ - { - "__id__": 8 - } - ], - "_prefab": null, - "_lpos": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_lrot": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_lscale": { - "__type__": "cc.Vec3", - "x": 1, - "y": 1, - "z": 1 - }, - "_mobility": 0, - "_layer": 1073741824, - "_euler": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_id": "aaq4MG2HtHboRayLQRrQKj" - }, - { - "__type__": "c33869Km+9Bb7dw/OyRztvE", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 7 - }, - "_enabled": true, - "__prefab": null, - "gameWorld": { - "__id__": 9 - }, - "mainCamera": { - "__id__": 6 - }, - "uiRoot": { - "__id__": 10 - }, - "_id": "950MqG3LtHsLiq9V5owd43" - }, { "__type__": "cc.Node", "_name": "GameWorld", @@ -372,19 +306,19 @@ }, "_children": [ { - "__id__": 11 + "__id__": 9 } ], "_active": true, "_components": [ + { + "__id__": 11 + }, + { + "__id__": 12 + }, { "__id__": 13 - }, - { - "__id__": 14 - }, - { - "__id__": 15 } ], "_prefab": null, @@ -423,13 +357,13 @@ "_objFlags": 0, "__editorExtras__": {}, "_parent": { - "__id__": 10 + "__id__": 8 }, "_children": [], "_active": true, "_components": [ { - "__id__": 12 + "__id__": 10 } ], "_prefab": null, @@ -468,7 +402,7 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 11 + "__id__": 9 }, "_enabled": true, "__prefab": null, @@ -476,7 +410,7 @@ "_priority": 1073741824, "_fov": 45, "_fovAxis": 0, - "_orthoHeight": 499.8270893371758, + "_orthoHeight": 360, "_near": 1, "_far": 2000, "_color": { @@ -514,7 +448,7 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 10 + "__id__": 8 }, "_enabled": true, "__prefab": null, @@ -536,12 +470,12 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 10 + "__id__": 8 }, "_enabled": true, "__prefab": null, "_cameraComponent": { - "__id__": 12 + "__id__": 10 }, "_alignCanvasWithScreen": true, "_id": "9d3SdE3ORAOZ6AG/imW6NO" @@ -552,7 +486,7 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 10 + "__id__": 8 }, "_enabled": true, "__prefab": null, @@ -576,6 +510,65 @@ "_lockFlags": 0, "_id": "4a8iJypC1J8pMml467hQ6c" }, + { + "__type__": "cc.Node", + "_name": "RTSDemo", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 15 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "89cmsd2gNNsq155xC7mob8" + }, + { + "__type__": "c33869Km+9Bb7dw/OyRztvE", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 14 + }, + "_enabled": true, + "__prefab": null, + "minerCount": 2, + "goldMineCount": 3, + "_id": "86AIY7iYlMNqJsDC/+LIMU" + }, { "__type__": "cc.SceneGlobals", "ambient": { diff --git a/extensions/cocos/cocos-ecs/assets/scripts/RTSDemo.ts b/extensions/cocos/cocos-ecs/assets/scripts/RTSDemo.ts index 7ca9c139..8b867d4c 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/RTSDemo.ts +++ b/extensions/cocos/cocos-ecs/assets/scripts/RTSDemo.ts @@ -1,100 +1,69 @@ -import { _decorator, Component, Node, Vec3, Color, MeshRenderer, Material, BoxCollider, geometry, PhysicsSystem, director } from 'cc'; +import { _decorator, Component, Node, Vec3, Color } from 'cc'; import { SimplePrefabFactory } from './components/SimplePrefabFactory'; -import { UnitController } from './components/UnitController'; -import { BehaviorTreeManager } from './components/BehaviorTreeManager'; +import { BehaviorTreeComponent } from './components/BehaviorTreeComponent'; +import { StatusUIManager } from './components/StatusUIManager'; const { ccclass, property } = _decorator; /** - * 简化版矿工挖矿演示 - * 核心逻辑:矿工挖矿 → 运输 → 存储 → 重复 + * 矿工AI演示场景 */ -@ccclass('MinerDemo') -export class MinerDemo extends Component { +@ccclass('SimpleMinerDemo') +export class SimpleMinerDemo extends Component { @property - minerCount: number = 3; // 矿工数量 + minerCount: number = 2; @property - oreCount: number = 8; // 矿石数量 + goldMineCount: number = 3; - private factory: SimplePrefabFactory = new SimplePrefabFactory(); private miners: Node[] = []; - private ores: Node[] = []; + private goldMines: Node[] = []; private warehouse: Node | null = null; private ground: Node | null = null; + private totalOresCollected: number = 0; + private warehouseUI: any = null; start() { - console.log('🎮 启动矿工挖矿演示'); this.createWorld(); this.createWarehouse(); - this.createOres(); + this.createGoldMines(); this.createMiners(); - this.logGameStatus(); } - /** - * 创建游戏世界 - */ private createWorld() { - // 创建地面 - this.ground = this.factory.createGround(this.node, new Vec3(0, 0, 0), new Vec3(20, 0.2, 20)); - console.log('🌍 创建游戏世界:20x20地面'); + this.ground = SimplePrefabFactory.createGround(new Vec3(20, 0.2, 20)); + this.node.addChild(this.ground); + this.ground.setWorldPosition(new Vec3(0, 0, 0)); } - /** - * 创建仓库 - */ private createWarehouse() { - // 在地图中心创建仓库 - this.warehouse = this.factory.createBuilding( - this.node, - new Vec3(0, 1, 0), - new Vec3(2, 2, 2), - Color.GRAY, - 'warehouse' - ); - console.log('🏭 创建仓库:位置(0,1,0)'); + this.warehouse = SimplePrefabFactory.createBuilding('Warehouse', new Vec3(2, 2, 2), Color.GRAY); + this.node.addChild(this.warehouse); + this.warehouse.setWorldPosition(new Vec3(0, 1, 0)); + this.createWarehouseUI(); } - /** - * 创建矿石 - */ - private createOres() { - console.log(`⛏️ 创建${this.oreCount}个矿石`); - - for (let i = 0; i < this.oreCount; i++) { - // 随机分布矿石,避开仓库区域 - let position: Vec3; - do { - position = new Vec3( - (Math.random() - 0.5) * 16, // -8到8 - 0.5, - (Math.random() - 0.5) * 16 // -8到8 - ); - } while (Vec3.distance(position, new Vec3(0, 0.5, 0)) < 4); // 距离仓库至少4米 - - const ore = this.factory.createResource( - this.node, - position, - new Vec3(0.8, 0.8, 0.8), - Color.YELLOW, - 'ore' + private createGoldMines() { + for (let i = 0; i < this.goldMineCount; i++) { + const angle = (i / this.goldMineCount) * Math.PI * 2; + const radius = 6 + Math.random() * 2; + const position = new Vec3( + Math.cos(angle) * radius, + 0.8, + Math.sin(angle) * radius ); - this.ores.push(ore); - console.log(` 💎 矿石${i+1}:位置(${position.x.toFixed(1)}, ${position.y.toFixed(1)}, ${position.z.toFixed(1)})`); + const goldMine = SimplePrefabFactory.createResource(`GoldMine_${i + 1}`, Color.YELLOW); + this.node.addChild(goldMine); + goldMine.setWorldPosition(position); + goldMine.setScale(new Vec3(1.2, 1.2, 1.2)); + this.goldMines.push(goldMine); } } - /** - * 创建矿工 - */ private createMiners() { - console.log(`👷 创建${this.minerCount}个矿工`); - for (let i = 0; i < this.minerCount; i++) { - // 矿工围绕仓库分布 const angle = (i / this.minerCount) * Math.PI * 2; const radius = 3; const position = new Vec3( @@ -103,72 +72,59 @@ export class MinerDemo extends Component { Math.sin(angle) * radius ); - const miner = this.factory.createUnit( - this.node, - position, - new Vec3(0.8, 0.8, 0.8), - Color.BLUE, - 'miner' - ); + const miner = SimplePrefabFactory.createUnit(`Miner_${i + 1}`, Color.BLUE); + this.node.addChild(miner); + miner.setWorldPosition(position); - // 添加矿工控制器 - const unitController = miner.addComponent(UnitController); - unitController.unitType = 'miner'; - unitController.maxHealth = 100; - unitController.currentHealth = 100; - unitController.moveSpeed = 2.0; - unitController.currentCommand = 'mine'; // 默认挖矿命令 + const behaviorTree = miner.addComponent(BehaviorTreeComponent); + behaviorTree.behaviorTreeFile = 'miner-stamina-ai.bt'; + behaviorTree.debugMode = true; - // 添加行为树管理器 - const behaviorManager = miner.addComponent(BehaviorTreeManager); - - // 初始化行为树 - behaviorManager.initializeBehaviorTree('miner-ai', unitController); + this.scheduleOnce(() => { + const blackboard = behaviorTree.getBlackboard(); + if (blackboard) { + blackboard.setValue('homePosition', position.clone()); + } + }, 0.5); this.miners.push(miner); - console.log(` 👷 矿工${i+1}:位置(${position.x.toFixed(1)}, ${position.y.toFixed(1)}, ${position.z.toFixed(1)})`); } } - /** - * 记录游戏状态 - */ - private logGameStatus() { - console.log('\n📊 游戏状态总览:'); - console.log(` 🏭 仓库:1个`); - console.log(` 💎 矿石:${this.ores.length}个`); - console.log(` 👷 矿工:${this.miners.length}个`); - console.log(` 🎯 游戏目标:矿工自动挖矿并运输到仓库`); - console.log('\n🎮 游戏逻辑:'); - console.log(' 1. 矿工寻找最近的矿石'); - console.log(' 2. 移动到矿石位置并挖掘'); - console.log(' 3. 携带矿石返回仓库'); - console.log(' 4. 存储矿石并重复循环'); + public getAllGoldMines(): Node[] { + return this.goldMines.filter(mine => mine && mine.isValid); } - /** - * 获取所有矿石位置(供AI使用) - */ - public getAllOres(): Node[] { - return this.ores.filter(ore => ore && ore.isValid); - } - - /** - * 获取仓库位置(供AI使用) - */ public getWarehouse(): Node | null { return this.warehouse; } - /** - * 移除已开采的矿石 - */ - public removeOre(ore: Node) { - const index = this.ores.indexOf(ore); - if (index > -1) { - this.ores.splice(index, 1); - ore.destroy(); - console.log(`💎 矿石已开采,剩余${this.ores.length}个矿石`); + public mineGoldOre(miner: Node): boolean { + this.totalOresCollected++; + this.updateWarehouseUI(); + return true; + } + + public getTotalOresCollected(): number { + return this.totalOresCollected; + } + + private createWarehouseUI() { + if (!this.warehouse) return; + + this.warehouseUI = StatusUIManager.createWarehouseUI(this.warehouse); + if (this.warehouseUI) { + this.updateWarehouseUI(); } } + + private updateWarehouseUI() { + if (this.warehouseUI && this.warehouseUI.warehouseCountLabel) { + this.warehouseUI.warehouseCountLabel.string = `🏭 总存储: ${this.totalOresCollected}`; + } + } + + onDestroy() { + this.unscheduleAllCallbacks(); + } } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeComponent.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeComponent.ts index 53b8514b..8b875fe0 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeComponent.ts +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeComponent.ts @@ -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 | 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 = 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 { 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 | 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; } } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeManager.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeManager.ts index 810b3850..cd99a001 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeManager.ts +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/BehaviorTreeManager.ts @@ -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 { 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); } // 执行行为树 diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/MinerStatusUI.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/MinerStatusUI.ts new file mode 100644 index 00000000..91db877a --- /dev/null +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/MinerStatusUI.ts @@ -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'; + } + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/controllers/RTSCameraController.ts.meta b/extensions/cocos/cocos-ecs/assets/scripts/components/MinerStatusUI.ts.meta similarity index 70% rename from extensions/cocos/cocos-ecs/assets/scripts/controllers/RTSCameraController.ts.meta rename to extensions/cocos/cocos-ecs/assets/scripts/components/MinerStatusUI.ts.meta index 38a55d66..2e217bf8 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/controllers/RTSCameraController.ts.meta +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/MinerStatusUI.ts.meta @@ -2,7 +2,7 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "dabc6540-6e9f-450a-9d22-b9af94c20d6d", + "uuid": "5f877c25-5c26-49c6-bbb5-7ff36323e0a1", "files": [], "subMetas": {}, "userData": {} diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/RTSBehaviorHandler.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/RTSBehaviorHandler.ts index 21a8481f..28555859 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/components/RTSBehaviorHandler.ts +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/RTSBehaviorHandler.ts @@ -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() + }; + } } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/SimplePrefabFactory.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/SimplePrefabFactory.ts index 166d880d..a2173cea 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/components/SimplePrefabFactory.ts +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/SimplePrefabFactory.ts @@ -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; } } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/StatusUIManager.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/StatusUIManager.ts new file mode 100644 index 00000000..e9d69b81 --- /dev/null +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/StatusUIManager.ts @@ -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; + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/UnitComponent.ts.meta b/extensions/cocos/cocos-ecs/assets/scripts/components/StatusUIManager.ts.meta similarity index 70% rename from extensions/cocos/cocos-ecs/assets/scripts/components/UnitComponent.ts.meta rename to extensions/cocos/cocos-ecs/assets/scripts/components/StatusUIManager.ts.meta index 7fd57854..805a46bd 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/components/UnitComponent.ts.meta +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/StatusUIManager.ts.meta @@ -2,7 +2,7 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "61885e67-b7a2-4e51-857e-ccbea4fdea03", + "uuid": "7478e794-dd80-4661-9421-8e147d33c51e", "files": [], "subMetas": {}, "userData": {} diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/UnitComponent.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/UnitComponent.ts deleted file mode 100644 index 74006e36..00000000 --- a/extensions/cocos/cocos-ecs/assets/scripts/components/UnitComponent.ts +++ /dev/null @@ -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 = new Map(); - - constructor(name: string) { - this.name = name; - } - - addComponent(component: any) { - this.components.set(component.constructor, component); - component.entity = this; - } - - getComponent(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(); - } -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/components/UnitController.ts b/extensions/cocos/cocos-ecs/assets/scripts/components/UnitController.ts index 60aa691f..032233e1 100644 --- a/extensions/cocos/cocos-ecs/assets/scripts/components/UnitController.ts +++ b/extensions/cocos/cocos-ecs/assets/scripts/components/UnitController.ts @@ -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(); } } diff --git a/extensions/cocos/cocos-ecs/assets/scripts/controllers.meta b/extensions/cocos/cocos-ecs/assets/scripts/controllers.meta deleted file mode 100644 index 34700c8d..00000000 --- a/extensions/cocos/cocos-ecs/assets/scripts/controllers.meta +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ver": "1.2.0", - "importer": "directory", - "imported": true, - "uuid": "84a4754f-3fad-4031-8aeb-e699584cfb92", - "files": [], - "subMetas": {}, - "userData": {} -} diff --git a/extensions/cocos/cocos-ecs/assets/scripts/controllers/RTSCameraController.ts b/extensions/cocos/cocos-ecs/assets/scripts/controllers/RTSCameraController.ts deleted file mode 100644 index ec647b61..00000000 --- a/extensions/cocos/cocos-ecs/assets/scripts/controllers/RTSCameraController.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { _decorator, Component, Node, Camera, Vec3, input, Input, EventMouse, EventTouch, KeyCode, Quat } from 'cc'; - -const { ccclass, property } = _decorator; - -/** - * RTS相机控制器 - * 提供RTS游戏常用的相机控制功能 - */ -@ccclass('RTSCameraController') -export class RTSCameraController extends Component { - - @property - moveSpeed: number = 10; - - @property - rotateSpeed: number = 60; - - @property - zoomSpeed: number = 5; - - @property - minZoom: number = 5; - - @property - maxZoom: number = 30; - - @property - boundarySize: number = 50; - - private camera: Camera = null!; - private isMouseDown: boolean = false; - private lastMousePosition: Vec3 = Vec3.ZERO.clone(); - private currentZoom: number = 15; - - // 键盘输入状态 - private keyStates: { [key: string]: boolean } = {}; - - onLoad() { - this.camera = this.getComponent(Camera)!; - this.currentZoom = this.node.position.y; - - // 注册输入事件 - input.on(Input.EventType.MOUSE_DOWN, this.onMouseDown, this); - input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this); - input.on(Input.EventType.MOUSE_MOVE, this.onMouseMove, this); - input.on(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this); - input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this); - input.on(Input.EventType.KEY_UP, this.onKeyUp, this); - } - - onDestroy() { - // 取消注册输入事件 - input.off(Input.EventType.MOUSE_DOWN, this.onMouseDown, this); - input.off(Input.EventType.MOUSE_UP, this.onMouseUp, this); - input.off(Input.EventType.MOUSE_MOVE, this.onMouseMove, this); - input.off(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this); - input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this); - input.off(Input.EventType.KEY_UP, this.onKeyUp, this); - } - - /** - * 鼠标按下事件 - */ - private onMouseDown(event: EventMouse) { - if (event.getButton() === EventMouse.BUTTON_MIDDLE) { - this.isMouseDown = true; - this.lastMousePosition.set(event.getLocationX(), event.getLocationY(), 0); - } - } - - /** - * 鼠标抬起事件 - */ - private onMouseUp(event: EventMouse) { - if (event.getButton() === EventMouse.BUTTON_MIDDLE) { - this.isMouseDown = false; - } - } - - /** - * 鼠标移动事件 - */ - private onMouseMove(event: EventMouse) { - if (this.isMouseDown) { - const deltaX = event.getLocationX() - this.lastMousePosition.x; - const deltaY = event.getLocationY() - this.lastMousePosition.y; - - // 相机平移 - const moveVector = new Vec3(-deltaX * 0.01, 0, deltaY * 0.01); - this.node.translate(moveVector); - - this.lastMousePosition.set(event.getLocationX(), event.getLocationY(), 0); - - // 限制相机边界 - this.clampCameraBounds(); - } - } - - /** - * 鼠标滚轮事件 - */ - private onMouseWheel(event: EventMouse) { - const scrollY = event.getScrollY(); - this.currentZoom -= scrollY * this.zoomSpeed * 0.1; - this.currentZoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.currentZoom)); - - const pos = this.node.position; - this.node.setPosition(pos.x, this.currentZoom, pos.z); - } - - /** - * 键盘按下事件 - */ - private onKeyDown(event: any) { - this.keyStates[event.keyCode] = true; - } - - /** - * 键盘抬起事件 - */ - private onKeyUp(event: any) { - this.keyStates[event.keyCode] = false; - } - - /** - * 更新相机移动 - */ - update(deltaTime: number) { - this.handleKeyboardMovement(deltaTime); - } - - /** - * 处理键盘移动 - */ - private handleKeyboardMovement(deltaTime: number) { - const moveDistance = this.moveSpeed * deltaTime; - let moveVector = Vec3.ZERO.clone(); - - // WASD 移动 - if (this.keyStates[KeyCode.KEY_W] || this.keyStates[KeyCode.ARROW_UP]) { - moveVector.z -= moveDistance; - } - if (this.keyStates[KeyCode.KEY_S] || this.keyStates[KeyCode.ARROW_DOWN]) { - moveVector.z += moveDistance; - } - if (this.keyStates[KeyCode.KEY_A] || this.keyStates[KeyCode.ARROW_LEFT]) { - moveVector.x -= moveDistance; - } - if (this.keyStates[KeyCode.KEY_D] || this.keyStates[KeyCode.ARROW_RIGHT]) { - moveVector.x += moveDistance; - } - - // 应用移动 - if (!moveVector.equals(Vec3.ZERO)) { - this.node.translate(moveVector); - this.clampCameraBounds(); - } - - // QE 旋转 - if (this.keyStates[KeyCode.KEY_Q]) { - const rotateAngle = this.rotateSpeed * deltaTime * Math.PI / 180; - const currentRotation = this.node.rotation.clone(); - const yRotation = Quat.fromAxisAngle(new Quat(), Vec3.UP, rotateAngle); - Quat.multiply(currentRotation, currentRotation, yRotation); - this.node.rotation = currentRotation; - } - if (this.keyStates[KeyCode.KEY_E]) { - const rotateAngle = -this.rotateSpeed * deltaTime * Math.PI / 180; - const currentRotation = this.node.rotation.clone(); - const yRotation = Quat.fromAxisAngle(new Quat(), Vec3.UP, rotateAngle); - Quat.multiply(currentRotation, currentRotation, yRotation); - this.node.rotation = currentRotation; - } - } - - /** - * 限制相机边界 - */ - private clampCameraBounds() { - const pos = this.node.position; - const clampedX = Math.max(-this.boundarySize, Math.min(this.boundarySize, pos.x)); - const clampedZ = Math.max(-this.boundarySize, Math.min(this.boundarySize, pos.z)); - - this.node.setPosition(clampedX, pos.y, clampedZ); - } - - /** - * 设置相机位置 - */ - setCameraPosition(position: Vec3) { - this.node.setPosition(position); - this.clampCameraBounds(); - } - - /** - * 聚焦到目标位置 - */ - focusOnTarget(target: Vec3, duration: number = 1.0) { - const targetPos = new Vec3(target.x, this.currentZoom, target.z); - // 这里可以添加缓动动画 - this.setCameraPosition(targetPos); - } -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts b/extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts deleted file mode 100644 index 4136aece..00000000 --- a/extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { _decorator, Component, Node, Vec3, Label, Button, EventHandler } from 'cc'; - -const { ccclass, property } = _decorator; - -/** - * UI控制器 - 管理RTS演示的用户界面 - */ -@ccclass('UIController') -export class UIController extends Component { - - @property(Label) - selectedUnitsLabel: Label = null!; - - @property(Button) - moveButton: Button = null!; - - @property(Button) - attackButton: Button = null!; - - @property(Button) - gatherButton: Button = null!; - - @property(Button) - patrolButton: Button = null!; - - @property(Label) - infoLabel: Label = null!; - - // 回调函数 - public onUnitSelected: ((units: Node[]) => void) | null = null; - public onCommandIssued: ((command: string, target?: Vec3 | Node) => void) | null = null; - - private selectedUnitsCount: number = 0; - - onLoad() { - this.setupUI(); - this.updateSelectedUnitsDisplay(); - this.updateInfoDisplay('RTS 行为树演示 - 点击单位选择,然后发布命令'); - } - - /** - * 设置UI事件 - */ - private setupUI() { - // 移动命令按钮 - if (this.moveButton) { - const moveHandler = new EventHandler(); - moveHandler.target = this.node; - moveHandler.component = 'UIController'; - moveHandler.handler = 'onMoveCommand'; - this.moveButton.clickEvents.push(moveHandler); - } - - // 攻击命令按钮 - if (this.attackButton) { - const attackHandler = new EventHandler(); - attackHandler.target = this.node; - attackHandler.component = 'UIController'; - attackHandler.handler = 'onAttackCommand'; - this.attackButton.clickEvents.push(attackHandler); - } - - // 收集命令按钮 - if (this.gatherButton) { - const gatherHandler = new EventHandler(); - gatherHandler.target = this.node; - gatherHandler.component = 'UIController'; - gatherHandler.handler = 'onGatherCommand'; - this.gatherButton.clickEvents.push(gatherHandler); - } - - // 巡逻命令按钮 - if (this.patrolButton) { - const patrolHandler = new EventHandler(); - patrolHandler.target = this.node; - patrolHandler.component = 'UIController'; - patrolHandler.handler = 'onPatrolCommand'; - this.patrolButton.clickEvents.push(patrolHandler); - } - } - - /** - * 设置选中单位数量 - */ - setSelectedUnitsCount(count: number) { - this.selectedUnitsCount = count; - this.updateSelectedUnitsDisplay(); - this.updateButtonStates(); - } - - /** - * 更新选中单位显示 - */ - private updateSelectedUnitsDisplay() { - if (this.selectedUnitsLabel) { - this.selectedUnitsLabel.string = `选中单位: ${this.selectedUnitsCount}`; - } - } - - /** - * 更新按钮状态 - */ - private updateButtonStates() { - const hasSelection = this.selectedUnitsCount > 0; - - if (this.moveButton) { - this.moveButton.interactable = hasSelection; - } - if (this.attackButton) { - this.attackButton.interactable = hasSelection; - } - if (this.gatherButton) { - this.gatherButton.interactable = hasSelection; - } - if (this.patrolButton) { - this.patrolButton.interactable = hasSelection; - } - } - - /** - * 更新信息显示 - */ - updateInfoDisplay(message: string) { - if (this.infoLabel) { - this.infoLabel.string = message; - } - console.log(`[UI] ${message}`); - } - - /** - * 移动命令 - */ - onMoveCommand() { - if (this.selectedUnitsCount === 0) { - this.updateInfoDisplay('请先选择单位'); - return; - } - - // 生成随机目标位置 - const targetPos = new Vec3( - (Math.random() - 0.5) * 20, - 0, - (Math.random() - 0.5) * 20 - ); - - this.onCommandIssued?.('move', targetPos); - this.updateInfoDisplay(`发布移动命令到位置 (${targetPos.x.toFixed(1)}, ${targetPos.z.toFixed(1)})`); - } - - /** - * 攻击命令 - */ - onAttackCommand() { - if (this.selectedUnitsCount === 0) { - this.updateInfoDisplay('请先选择单位'); - return; - } - - // 生成随机攻击位置 - const targetPos = new Vec3( - (Math.random() - 0.5) * 15, - 0, - (Math.random() - 0.5) * 15 - ); - - this.onCommandIssued?.('attack', targetPos); - this.updateInfoDisplay(`发布攻击命令到位置 (${targetPos.x.toFixed(1)}, ${targetPos.z.toFixed(1)})`); - } - - /** - * 收集命令 - */ - onGatherCommand() { - if (this.selectedUnitsCount === 0) { - this.updateInfoDisplay('请先选择单位'); - return; - } - - this.onCommandIssued?.('gather'); - this.updateInfoDisplay('发布收集资源命令'); - } - - /** - * 巡逻命令 - */ - onPatrolCommand() { - if (this.selectedUnitsCount === 0) { - this.updateInfoDisplay('请先选择单位'); - return; - } - - this.onCommandIssued?.('patrol'); - this.updateInfoDisplay('发布巡逻命令'); - } - - /** - * 显示单位信息 - */ - showUnitInfo(unitName: string, unitType: string, health: number, maxHealth: number) { - const healthPercent = Math.round((health / maxHealth) * 100); - this.updateInfoDisplay(`${unitName} (${unitType}) - 生命值: ${health}/${maxHealth} (${healthPercent}%)`); - } - - /** - * 显示行为树状态 - */ - showBehaviorTreeStatus(unitName: string, currentBehavior: string) { - this.updateInfoDisplay(`${unitName} 当前行为: ${currentBehavior}`); - } - - /** - * 显示错误信息 - */ - showError(message: string) { - this.updateInfoDisplay(`错误: ${message}`); - console.error(`[UI Error] ${message}`); - } - - /** - * 显示成功信息 - */ - showSuccess(message: string) { - this.updateInfoDisplay(`成功: ${message}`); - } -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts.meta b/extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts.meta deleted file mode 100644 index 5ccc4c29..00000000 --- a/extensions/cocos/cocos-ecs/assets/scripts/controllers/UIController.ts.meta +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ver": "4.0.24", - "importer": "typescript", - "imported": true, - "uuid": "00e982f3-dcf3-44b0-9b63-5e2877c1971e", - "files": [], - "subMetas": {}, - "userData": {} -} diff --git a/extensions/cocos/cocos-ecs/enemy-ai.bt.json b/extensions/cocos/cocos-ecs/enemy-ai.bt.json deleted file mode 100644 index c4fd48d0..00000000 --- a/extensions/cocos/cocos-ecs/enemy-ai.bt.json +++ /dev/null @@ -1,214 +0,0 @@ -{ - "nodes": [ - { - "id": "root", - "type": "root", - "name": "敌人AI根节点", - "description": "敌人AI行为树的根节点,控制敌人的所有行为逻辑。包含攻击、追击和巡逻三种主要行为模式。", - "children": [ - "main-selector" - ] - }, - { - "id": "main-selector", - "type": "selector", - "name": "主要行为选择器", - "description": "选择器节点:按优先级执行子节点(攻击>追击>巡逻)。这确保敌人在有目标时优先战斗,没有目标时进行巡逻。", - "children": [ - "attack-behavior", - "chase-behavior", - "patrol-behavior" - ] - }, - { - "id": "attack-behavior", - "type": "conditional-decorator", - "name": "攻击行为", - "description": "条件装饰器:只有当发现目标且距离足够近时才进行攻击。这确保敌人只在有效攻击范围内战斗。", - "children": [ - "attack-sequence" - ], - "condition": { - "type": "blackboard-value-comparison", - "properties": { - "variableName": "hasTarget", - "compareOperator": "equals", - "compareValue": true - } - } - }, - { - "id": "attack-sequence", - "type": "sequence", - "name": "攻击序列", - "description": "序列节点:按顺序执行攻击相关的所有步骤,确保攻击逻辑的完整性。", - "children": [ - "set-attack-state", - "attack-cooldown-check", - "perform-attack", - "reset-attack-cooldown" - ] - }, - { - "id": "set-attack-state", - "type": "set-blackboard-value", - "name": "设置攻击状态", - "description": "黑板赋值节点:将敌人状态设置为'attacking',便于其他系统了解敌人当前行为。" - }, - { - "id": "attack-cooldown-check", - "type": "conditional-decorator", - "name": "攻击冷却检查", - "description": "条件装饰器:检查攻击冷却是否结束。只有冷却时间为0时才能执行攻击,防止过于频繁的攻击。", - "children": [ - "attack-action" - ], - "condition": { - "type": "blackboard-value-comparison", - "properties": { - "variableName": "attackCooldown", - "compareOperator": "lessEqual", - "compareValue": 0 - } - } - }, - { - "id": "attack-action", - "type": "event-action", - "name": "执行攻击", - "description": "事件动作节点:触发实际的攻击事件,造成伤害并播放攻击动画和音效。" - }, - { - "id": "perform-attack", - "type": "log-action", - "name": "攻击日志", - "description": "日志动作节点:记录攻击行为,用于调试和监控敌人AI的行为表现。" - }, - { - "id": "reset-attack-cooldown", - "type": "set-blackboard-value", - "name": "重置攻击冷却", - "description": "黑板赋值节点:设置攻击冷却时间为1.5秒,控制攻击频率,避免过于频繁的攻击。" - }, - { - "id": "chase-behavior", - "type": "conditional-decorator", - "name": "追击行为", - "description": "条件装饰器:当发现目标但距离较远时进行追击。这是攻击和巡逻之间的中间状态。", - "children": [ - "chase-sequence" - ], - "condition": { - "type": "blackboard-value-comparison", - "properties": { - "variableName": "hasTarget", - "compareOperator": "equals", - "compareValue": true - } - } - }, - { - "id": "chase-sequence", - "type": "sequence", - "name": "追击序列", - "description": "序列节点:执行追击相关的行为,包括状态设置、移动和冷却更新。", - "children": [ - "set-chase-state", - "move-to-target", - "update-attack-cooldown" - ] - }, - { - "id": "set-chase-state", - "type": "set-blackboard-value", - "name": "设置追击状态", - "description": "黑板赋值节点:将敌人状态设置为'chasing',表示正在追击目标。" - }, - { - "id": "move-to-target", - "type": "event-action", - "name": "向目标移动", - "description": "事件动作节点:触发移动事件,让敌人朝目标方向移动。移动速度比巡逻时更快。" - }, - { - "id": "update-attack-cooldown", - "type": "math-blackboard-operation", - "name": "更新攻击冷却", - "description": "数学运算节点:减少攻击冷却时间,让敌人在追击过程中为下次攻击做准备。" - }, - { - "id": "patrol-behavior", - "type": "sequence", - "name": "巡逻行为", - "description": "序列节点:当没有目标时执行巡逻行为。这是敌人的默认行为,确保敌人始终在活动。", - "children": [ - "patrol-sequence" - ] - }, - { - "id": "patrol-sequence", - "type": "sequence", - "name": "巡逻序列", - "description": "序列节点:执行完整的巡逻循环,包括状态设置、移动和等待。", - "children": [ - "set-patrol-state", - "patrol-move", - "patrol-wait" - ] - }, - { - "id": "set-patrol-state", - "type": "set-blackboard-value", - "name": "设置巡逻状态", - "description": "黑板赋值节点:将敌人状态设置为'patrolling',表示正在巡逻。" - }, - { - "id": "patrol-move", - "type": "event-action", - "name": "巡逻移动", - "description": "事件动作节点:触发巡逻移动事件,让敌人在预设的巡逻路径上移动。" - }, - { - "id": "patrol-wait", - "type": "wait-action", - "name": "巡逻等待", - "description": "等待动作节点:在巡逻点停留2秒,模拟敌人观察周围环境。这让巡逻看起来更自然。" - } - ], - "blackboard": [ - { - "name": "currentState", - "type": "string", - "value": "idle", - "description": "敌人的当前状态(idle、attacking、chasing、patrolling),用于状态跟踪和AI调试", - "group": "状态" - }, - { - "name": "hasTarget", - "type": "boolean", - "value": "false", - "description": "是否发现了有效目标,决定敌人是否应该从巡逻切换到追击模式", - "group": "目标" - }, - { - "name": "attackCooldown", - "type": "number", - "value": "0", - "description": "攻击冷却倒计时(秒),控制攻击频率,0表示可以攻击,大于0表示正在冷却", - "group": "战斗" - }, - { - "name": "lastKnownTargetPosition", - "type": "string", - "value": "0,0", - "description": "最后已知的目标位置,用于敌人在失去目标后继续搜索一段时间", - "group": "目标" - } - ], - "metadata": { - "name": "behavior-tree", - "created": "2025-06-24T03:38:29.653Z", - "version": "1.0", - "exportType": "clean" - } -} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/behaviour-tree b/extensions/cocos/cocos-ecs/extensions/behaviour-tree index 29156634..07270b77 160000 --- a/extensions/cocos/cocos-ecs/extensions/behaviour-tree +++ b/extensions/cocos/cocos-ecs/extensions/behaviour-tree @@ -1 +1 @@ -Subproject commit 29156634783c1774fa981c63b0522104cfeb0e34 +Subproject commit 07270b77ffc6c4dc44ce78b7a05cabf26bad702d diff --git a/extensions/cocos/cocos-ecs/package-lock.json b/extensions/cocos/cocos-ecs/package-lock.json index e775be23..68b5d762 100644 --- a/extensions/cocos/cocos-ecs/package-lock.json +++ b/extensions/cocos/cocos-ecs/package-lock.json @@ -6,14 +6,14 @@ "": { "name": "cocos-ecs", "dependencies": { - "@esengine/ai": "^2.0.10", + "@esengine/ai": "^2.0.13", "@esengine/ecs-framework": "^2.1.22" } }, "node_modules/@esengine/ai": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.10.tgz", - "integrity": "sha512-L0jWaYHDe8fw/XMpVniFqMIFDVzP9wib+HZKKq+NOXV3zWj+F3XpOgNrb7+4ZN5yR8rK9wjEh+PNkVdTkLOl8g==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.13.tgz", + "integrity": "sha512-Iwbdw7UPVEARph/zvq6XL5XIkku3TmdFk/XHxT5aU2Y3RvPNnpd4OwDaranIpjuBL4WkquZx4C/Mw7jh4FNwFg==", "dependencies": { "@esengine/ecs-framework": "^2.1.20" }, diff --git a/extensions/cocos/cocos-ecs/package.json b/extensions/cocos/cocos-ecs/package.json index f1df228a..0dd710c1 100644 --- a/extensions/cocos/cocos-ecs/package.json +++ b/extensions/cocos/cocos-ecs/package.json @@ -6,6 +6,6 @@ }, "dependencies": { "@esengine/ecs-framework": "^2.1.22", - "@esengine/ai": "^2.0.10" + "@esengine/ai": "^2.0.13" } } \ No newline at end of file diff --git a/thirdparty/BehaviourTree-ai b/thirdparty/BehaviourTree-ai index a2107c11..f3e91b9f 160000 --- a/thirdparty/BehaviourTree-ai +++ b/thirdparty/BehaviourTree-ai @@ -1 +1 @@ -Subproject commit a2107c11326dc7e636673939239877057a363bc0 +Subproject commit f3e91b9f34eaa41d38a5a284be01597e0376c3b3