采矿行为树示例
This commit is contained in:
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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": "移动属性"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "2.0.1",
|
"ver": "2.0.1",
|
||||||
"importer": "json",
|
"importer": "json",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "7ecbbfe3-a63f-4558-89e2-85e3f084ee02",
|
"uuid": "b328a163-97a8-4435-b87c-9369dd76862d",
|
||||||
"files": [
|
"files": [
|
||||||
".json"
|
".json"
|
||||||
],
|
],
|
||||||
1395
extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree
Normal file
1395
extensions/cocos/cocos-ecs/assets/resources/miner-stamina-ai.btree
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
"ver": "1.0.0",
|
"ver": "1.0.0",
|
||||||
"importer": "*",
|
"importer": "*",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "2dded9a7-03cd-465f-8419-7199e562c403",
|
"uuid": "24c6e7e6-4ff0-4e7b-b470-9468bfa66b5d",
|
||||||
"files": [
|
"files": [
|
||||||
".btree",
|
".btree",
|
||||||
".json"
|
".json"
|
||||||
@@ -26,10 +26,10 @@
|
|||||||
"__id__": 7
|
"__id__": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id__": 9
|
"__id__": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id__": 10
|
"__id__": 14
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": true,
|
||||||
@@ -255,72 +255,6 @@
|
|||||||
"_trackingType": 0,
|
"_trackingType": 0,
|
||||||
"_id": "7dWQTpwS5LrIHnc1zAPUtf"
|
"_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",
|
"__type__": "cc.Node",
|
||||||
"_name": "GameWorld",
|
"_name": "GameWorld",
|
||||||
@@ -372,19 +306,19 @@
|
|||||||
},
|
},
|
||||||
"_children": [
|
"_children": [
|
||||||
{
|
{
|
||||||
"__id__": 11
|
"__id__": 9
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": true,
|
||||||
"_components": [
|
"_components": [
|
||||||
|
{
|
||||||
|
"__id__": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__id__": 12
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"__id__": 13
|
"__id__": 13
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 15
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_prefab": null,
|
"_prefab": null,
|
||||||
@@ -423,13 +357,13 @@
|
|||||||
"_objFlags": 0,
|
"_objFlags": 0,
|
||||||
"__editorExtras__": {},
|
"__editorExtras__": {},
|
||||||
"_parent": {
|
"_parent": {
|
||||||
"__id__": 10
|
"__id__": 8
|
||||||
},
|
},
|
||||||
"_children": [],
|
"_children": [],
|
||||||
"_active": true,
|
"_active": true,
|
||||||
"_components": [
|
"_components": [
|
||||||
{
|
{
|
||||||
"__id__": 12
|
"__id__": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_prefab": null,
|
"_prefab": null,
|
||||||
@@ -468,7 +402,7 @@
|
|||||||
"_objFlags": 0,
|
"_objFlags": 0,
|
||||||
"__editorExtras__": {},
|
"__editorExtras__": {},
|
||||||
"node": {
|
"node": {
|
||||||
"__id__": 11
|
"__id__": 9
|
||||||
},
|
},
|
||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
@@ -476,7 +410,7 @@
|
|||||||
"_priority": 1073741824,
|
"_priority": 1073741824,
|
||||||
"_fov": 45,
|
"_fov": 45,
|
||||||
"_fovAxis": 0,
|
"_fovAxis": 0,
|
||||||
"_orthoHeight": 499.8270893371758,
|
"_orthoHeight": 360,
|
||||||
"_near": 1,
|
"_near": 1,
|
||||||
"_far": 2000,
|
"_far": 2000,
|
||||||
"_color": {
|
"_color": {
|
||||||
@@ -514,7 +448,7 @@
|
|||||||
"_objFlags": 0,
|
"_objFlags": 0,
|
||||||
"__editorExtras__": {},
|
"__editorExtras__": {},
|
||||||
"node": {
|
"node": {
|
||||||
"__id__": 10
|
"__id__": 8
|
||||||
},
|
},
|
||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
@@ -536,12 +470,12 @@
|
|||||||
"_objFlags": 0,
|
"_objFlags": 0,
|
||||||
"__editorExtras__": {},
|
"__editorExtras__": {},
|
||||||
"node": {
|
"node": {
|
||||||
"__id__": 10
|
"__id__": 8
|
||||||
},
|
},
|
||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"_cameraComponent": {
|
"_cameraComponent": {
|
||||||
"__id__": 12
|
"__id__": 10
|
||||||
},
|
},
|
||||||
"_alignCanvasWithScreen": true,
|
"_alignCanvasWithScreen": true,
|
||||||
"_id": "9d3SdE3ORAOZ6AG/imW6NO"
|
"_id": "9d3SdE3ORAOZ6AG/imW6NO"
|
||||||
@@ -552,7 +486,7 @@
|
|||||||
"_objFlags": 0,
|
"_objFlags": 0,
|
||||||
"__editorExtras__": {},
|
"__editorExtras__": {},
|
||||||
"node": {
|
"node": {
|
||||||
"__id__": 10
|
"__id__": 8
|
||||||
},
|
},
|
||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
@@ -576,6 +510,65 @@
|
|||||||
"_lockFlags": 0,
|
"_lockFlags": 0,
|
||||||
"_id": "4a8iJypC1J8pMml467hQ6c"
|
"_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",
|
"__type__": "cc.SceneGlobals",
|
||||||
"ambient": {
|
"ambient": {
|
||||||
|
|||||||
@@ -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 { SimplePrefabFactory } from './components/SimplePrefabFactory';
|
||||||
import { UnitController } from './components/UnitController';
|
import { BehaviorTreeComponent } from './components/BehaviorTreeComponent';
|
||||||
import { BehaviorTreeManager } from './components/BehaviorTreeManager';
|
import { StatusUIManager } from './components/StatusUIManager';
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简化版矿工挖矿演示
|
* 矿工AI演示场景
|
||||||
* 核心逻辑:矿工挖矿 → 运输 → 存储 → 重复
|
|
||||||
*/
|
*/
|
||||||
@ccclass('MinerDemo')
|
@ccclass('SimpleMinerDemo')
|
||||||
export class MinerDemo extends Component {
|
export class SimpleMinerDemo extends Component {
|
||||||
|
|
||||||
@property
|
@property
|
||||||
minerCount: number = 3; // 矿工数量
|
minerCount: number = 2;
|
||||||
|
|
||||||
@property
|
@property
|
||||||
oreCount: number = 8; // 矿石数量
|
goldMineCount: number = 3;
|
||||||
|
|
||||||
private factory: SimplePrefabFactory = new SimplePrefabFactory();
|
|
||||||
private miners: Node[] = [];
|
private miners: Node[] = [];
|
||||||
private ores: Node[] = [];
|
private goldMines: Node[] = [];
|
||||||
private warehouse: Node | null = null;
|
private warehouse: Node | null = null;
|
||||||
private ground: Node | null = null;
|
private ground: Node | null = null;
|
||||||
|
private totalOresCollected: number = 0;
|
||||||
|
private warehouseUI: any = null;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
console.log('🎮 启动矿工挖矿演示');
|
|
||||||
this.createWorld();
|
this.createWorld();
|
||||||
this.createWarehouse();
|
this.createWarehouse();
|
||||||
this.createOres();
|
this.createGoldMines();
|
||||||
this.createMiners();
|
this.createMiners();
|
||||||
this.logGameStatus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建游戏世界
|
|
||||||
*/
|
|
||||||
private createWorld() {
|
private createWorld() {
|
||||||
// 创建地面
|
this.ground = SimplePrefabFactory.createGround(new Vec3(20, 0.2, 20));
|
||||||
this.ground = this.factory.createGround(this.node, new Vec3(0, 0, 0), new Vec3(20, 0.2, 20));
|
this.node.addChild(this.ground);
|
||||||
console.log('🌍 创建游戏世界:20x20地面');
|
this.ground.setWorldPosition(new Vec3(0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建仓库
|
|
||||||
*/
|
|
||||||
private createWarehouse() {
|
private createWarehouse() {
|
||||||
// 在地图中心创建仓库
|
this.warehouse = SimplePrefabFactory.createBuilding('Warehouse', new Vec3(2, 2, 2), Color.GRAY);
|
||||||
this.warehouse = this.factory.createBuilding(
|
this.node.addChild(this.warehouse);
|
||||||
this.node,
|
this.warehouse.setWorldPosition(new Vec3(0, 1, 0));
|
||||||
new Vec3(0, 1, 0),
|
this.createWarehouseUI();
|
||||||
new Vec3(2, 2, 2),
|
|
||||||
Color.GRAY,
|
|
||||||
'warehouse'
|
|
||||||
);
|
|
||||||
console.log('🏭 创建仓库:位置(0,1,0)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private createGoldMines() {
|
||||||
* 创建矿石
|
for (let i = 0; i < this.goldMineCount; i++) {
|
||||||
*/
|
const angle = (i / this.goldMineCount) * Math.PI * 2;
|
||||||
private createOres() {
|
const radius = 6 + Math.random() * 2;
|
||||||
console.log(`⛏️ 创建${this.oreCount}个矿石`);
|
const position = new Vec3(
|
||||||
|
Math.cos(angle) * radius,
|
||||||
for (let i = 0; i < this.oreCount; i++) {
|
0.8,
|
||||||
// 随机分布矿石,避开仓库区域
|
Math.sin(angle) * radius
|
||||||
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'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.ores.push(ore);
|
const goldMine = SimplePrefabFactory.createResource(`GoldMine_${i + 1}`, Color.YELLOW);
|
||||||
console.log(` 💎 矿石${i+1}:位置(${position.x.toFixed(1)}, ${position.y.toFixed(1)}, ${position.z.toFixed(1)})`);
|
this.node.addChild(goldMine);
|
||||||
|
goldMine.setWorldPosition(position);
|
||||||
|
goldMine.setScale(new Vec3(1.2, 1.2, 1.2));
|
||||||
|
this.goldMines.push(goldMine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建矿工
|
|
||||||
*/
|
|
||||||
private createMiners() {
|
private createMiners() {
|
||||||
console.log(`👷 创建${this.minerCount}个矿工`);
|
|
||||||
|
|
||||||
for (let i = 0; i < this.minerCount; i++) {
|
for (let i = 0; i < this.minerCount; i++) {
|
||||||
// 矿工围绕仓库分布
|
|
||||||
const angle = (i / this.minerCount) * Math.PI * 2;
|
const angle = (i / this.minerCount) * Math.PI * 2;
|
||||||
const radius = 3;
|
const radius = 3;
|
||||||
const position = new Vec3(
|
const position = new Vec3(
|
||||||
@@ -103,72 +72,59 @@ export class MinerDemo extends Component {
|
|||||||
Math.sin(angle) * radius
|
Math.sin(angle) * radius
|
||||||
);
|
);
|
||||||
|
|
||||||
const miner = this.factory.createUnit(
|
const miner = SimplePrefabFactory.createUnit(`Miner_${i + 1}`, Color.BLUE);
|
||||||
this.node,
|
this.node.addChild(miner);
|
||||||
position,
|
miner.setWorldPosition(position);
|
||||||
new Vec3(0.8, 0.8, 0.8),
|
|
||||||
Color.BLUE,
|
|
||||||
'miner'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 添加矿工控制器
|
const behaviorTree = miner.addComponent(BehaviorTreeComponent);
|
||||||
const unitController = miner.addComponent(UnitController);
|
behaviorTree.behaviorTreeFile = 'miner-stamina-ai.bt';
|
||||||
unitController.unitType = 'miner';
|
behaviorTree.debugMode = true;
|
||||||
unitController.maxHealth = 100;
|
|
||||||
unitController.currentHealth = 100;
|
|
||||||
unitController.moveSpeed = 2.0;
|
|
||||||
unitController.currentCommand = 'mine'; // 默认挖矿命令
|
|
||||||
|
|
||||||
// 添加行为树管理器
|
this.scheduleOnce(() => {
|
||||||
const behaviorManager = miner.addComponent(BehaviorTreeManager);
|
const blackboard = behaviorTree.getBlackboard();
|
||||||
|
if (blackboard) {
|
||||||
// 初始化行为树
|
blackboard.setValue('homePosition', position.clone());
|
||||||
behaviorManager.initializeBehaviorTree('miner-ai', unitController);
|
}
|
||||||
|
}, 0.5);
|
||||||
|
|
||||||
this.miners.push(miner);
|
this.miners.push(miner);
|
||||||
console.log(` 👷 矿工${i+1}:位置(${position.x.toFixed(1)}, ${position.y.toFixed(1)}, ${position.z.toFixed(1)})`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public getAllGoldMines(): Node[] {
|
||||||
* 记录游戏状态
|
return this.goldMines.filter(mine => mine && mine.isValid);
|
||||||
*/
|
|
||||||
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. 存储矿石并重复循环');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有矿石位置(供AI使用)
|
|
||||||
*/
|
|
||||||
public getAllOres(): Node[] {
|
|
||||||
return this.ores.filter(ore => ore && ore.isValid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取仓库位置(供AI使用)
|
|
||||||
*/
|
|
||||||
public getWarehouse(): Node | null {
|
public getWarehouse(): Node | null {
|
||||||
return this.warehouse;
|
return this.warehouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public mineGoldOre(miner: Node): boolean {
|
||||||
* 移除已开采的矿石
|
this.totalOresCollected++;
|
||||||
*/
|
this.updateWarehouseUI();
|
||||||
public removeOre(ore: Node) {
|
return true;
|
||||||
const index = this.ores.indexOf(ore);
|
}
|
||||||
if (index > -1) {
|
|
||||||
this.ores.splice(index, 1);
|
public getTotalOresCollected(): number {
|
||||||
ore.destroy();
|
return this.totalOresCollected;
|
||||||
console.log(`💎 矿石已开采,剩余${this.ores.length}个矿石`);
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,72 @@
|
|||||||
import { Node, resources, JsonAsset } from 'cc';
|
import { Node, resources, JsonAsset, Component, _decorator, Vec3, tween, instantiate, Prefab } from 'cc';
|
||||||
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, TaskStatus, BehaviorTreeJSONConfig } from '@esengine/ai';
|
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, TaskStatus, BehaviorTreeJSONConfig, EventRegistry, IBehaviorTreeContext, ActionResult } from '@esengine/ai';
|
||||||
import { ECSComponent } from './UnitComponent';
|
import { MinerStatusUI } from './MinerStatusUI';
|
||||||
|
import { StatusUIManager } from './StatusUIManager';
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行为树组件 - ECS组件,管理单个实体的行为树
|
* 行为树组件 - 纯Cocos Creator组件,管理单个节点的行为树
|
||||||
*/
|
*/
|
||||||
export class BehaviorTreeComponent extends ECSComponent {
|
@ccclass('BehaviorTreeComponent')
|
||||||
public behaviorTreeFile: string = '';
|
export class BehaviorTreeComponent extends Component {
|
||||||
public cocosNode: Node | null = null;
|
|
||||||
|
@property
|
||||||
|
behaviorTreeFile: string = '';
|
||||||
|
|
||||||
|
@property
|
||||||
|
autoStart: boolean = true;
|
||||||
|
|
||||||
|
@property
|
||||||
|
debugMode: boolean = false;
|
||||||
|
|
||||||
|
@property
|
||||||
|
showStatusUI: boolean = true;
|
||||||
|
|
||||||
|
@property(Prefab)
|
||||||
|
statusUIPrefab: Prefab | null = null;
|
||||||
|
|
||||||
private behaviorTree: BehaviorTree<any> | null = null;
|
private behaviorTree: BehaviorTree<any> | null = null;
|
||||||
|
private statusUI: MinerStatusUI | null = null;
|
||||||
private blackboard: Blackboard | null = null;
|
private blackboard: Blackboard | null = null;
|
||||||
private context: any = null;
|
private context: any = null;
|
||||||
|
private eventRegistry: EventRegistry | null = null;
|
||||||
private isLoaded: boolean = false;
|
private isLoaded: boolean = false;
|
||||||
private isRunning: boolean = false;
|
private isRunning: boolean = false;
|
||||||
private lastTickTime: number = 0;
|
|
||||||
private tickInterval: number = 0.1; // 行为树更新间隔(秒)
|
private actionStates: Map<string, {
|
||||||
|
isExecuting: boolean;
|
||||||
|
startTime: number;
|
||||||
|
duration: number;
|
||||||
|
}> = new Map();
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this.autoStart && this.behaviorTreeFile) {
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.showStatusUI) {
|
||||||
|
this.createStatusUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化行为树
|
* 初始化行为树
|
||||||
*/
|
*/
|
||||||
async initialize() {
|
async initialize() {
|
||||||
if (!this.behaviorTreeFile) {
|
if (!this.behaviorTreeFile) {
|
||||||
console.error('行为树文件路径未设置');
|
console.error(`[${this.node.name}] 行为树文件路径未设置`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.loadBehaviorTree();
|
await this.loadBehaviorTree();
|
||||||
this.setupBlackboard();
|
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
console.log(`行为树组件初始化成功: ${this.behaviorTreeFile}`);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`行为树组件初始化失败: ${this.behaviorTreeFile}`, error);
|
console.error(`[${this.node.name}] 行为树组件初始化失败: ${this.behaviorTreeFile}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +75,10 @@ export class BehaviorTreeComponent extends ECSComponent {
|
|||||||
*/
|
*/
|
||||||
private async loadBehaviorTree(): Promise<void> {
|
private async loadBehaviorTree(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 移除.btree扩展名,使用.bt.json
|
let jsonPath = this.behaviorTreeFile;
|
||||||
const jsonPath = this.behaviorTreeFile.replace('.btree', '.bt.json');
|
|
||||||
|
|
||||||
resources.load(jsonPath, JsonAsset, (err, asset) => {
|
resources.load(jsonPath, JsonAsset, (err, asset) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(`加载行为树文件失败: ${jsonPath}`, err);
|
console.error(`[${this.node.name}] 加载行为树文件失败: ${jsonPath}`, err);
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -57,7 +88,7 @@ export class BehaviorTreeComponent extends ECSComponent {
|
|||||||
this.buildBehaviorTree(treeData);
|
this.buildBehaviorTree(treeData);
|
||||||
resolve();
|
resolve();
|
||||||
} catch (buildError) {
|
} catch (buildError) {
|
||||||
console.error(`构建行为树失败: ${jsonPath}`, buildError);
|
console.error(`[${this.node.name}] 构建行为树失败: ${jsonPath}`, buildError);
|
||||||
reject(buildError);
|
reject(buildError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -68,68 +99,431 @@ export class BehaviorTreeComponent extends ECSComponent {
|
|||||||
* 构建行为树
|
* 构建行为树
|
||||||
*/
|
*/
|
||||||
private buildBehaviorTree(treeData: BehaviorTreeJSONConfig) {
|
private buildBehaviorTree(treeData: BehaviorTreeJSONConfig) {
|
||||||
|
// 创建事件注册表并注册基础动作
|
||||||
|
this.eventRegistry = new EventRegistry();
|
||||||
|
this.setupEventHandlers();
|
||||||
|
|
||||||
// 创建基础执行上下文
|
// 创建基础执行上下文
|
||||||
const baseContext = {
|
const baseContext = {
|
||||||
cocosNode: this.cocosNode,
|
node: this.node,
|
||||||
unitComponent: this
|
component: this,
|
||||||
|
eventRegistry: this.eventRegistry
|
||||||
};
|
};
|
||||||
|
|
||||||
// 使用@esengine/ai的BehaviorTreeBuilder构建行为树
|
// 使用@esengine/ai的BehaviorTreeBuilder构建行为树
|
||||||
// 这会自动创建黑板并设置所有配置
|
|
||||||
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(treeData, baseContext);
|
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(treeData, baseContext);
|
||||||
this.behaviorTree = result.tree;
|
this.behaviorTree = result.tree;
|
||||||
this.blackboard = result.blackboard;
|
this.blackboard = result.blackboard;
|
||||||
this.context = result.context;
|
this.context = result.context;
|
||||||
|
|
||||||
|
// 初始化黑板变量
|
||||||
|
this.initializeBlackboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置黑板
|
* 设置事件处理器 - 根据行为树文件中实际使用的事件名称注册
|
||||||
*/
|
*/
|
||||||
private setupBlackboard() {
|
private setupEventHandlers() {
|
||||||
if (!this.blackboard || !this.cocosNode) return;
|
if (!this.eventRegistry) return;
|
||||||
|
|
||||||
// 注意:只设置行为树中实际定义的变量
|
// 根据miner-stamina-ai.bt.json中的实际事件名称注册处理器
|
||||||
// 这些变量需要在对应的.btree文件的blackboard数组中预先定义
|
this.eventRegistry.registerAction('go-home-rest', (context, params) => {
|
||||||
|
return this.handleGoHomeRest(context, params);
|
||||||
|
});
|
||||||
|
|
||||||
// 设置基础信息 - 注释掉未在行为树中定义的变量
|
this.eventRegistry.registerAction('recover-stamina', (context, params) => {
|
||||||
// this.blackboard.setValue('entityName', this.cocosNode.name);
|
return this.handleRecoverStamina(context, params);
|
||||||
// this.blackboard.setValue('currentTime', Date.now() / 1000);
|
});
|
||||||
// this.blackboard.setValue('deltaTime', 0.016);
|
|
||||||
// this.blackboard.setValue('worldPosition', this.cocosNode.worldPosition);
|
|
||||||
|
|
||||||
console.log('BehaviorTreeComponent黑板设置完成,未设置任何变量以避免警告');
|
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;
|
||||||
|
|
||||||
|
// 简单初始化矿工状态
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
|
||||||
|
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) {
|
update(deltaTime: number) {
|
||||||
if (!this.isLoaded || !this.isRunning || !this.behaviorTree || !this.context) return;
|
// 简单执行行为树
|
||||||
|
if (this.behaviorTree && this.isRunning) {
|
||||||
// 控制更新频率
|
this.behaviorTree.tick(deltaTime);
|
||||||
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);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行行为树
|
// 更新UI显示
|
||||||
try {
|
if (this.showStatusUI) {
|
||||||
this.behaviorTree.tick();
|
this.updateStatusUI();
|
||||||
} catch (error) {
|
|
||||||
console.error(`行为树执行错误:`, error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置更新频率 - 已废弃,现在每帧执行
|
||||||
|
*/
|
||||||
|
setTickInterval(interval: number) {
|
||||||
|
// 方法保留以保持兼容性,但不再有实际作用
|
||||||
|
console.warn(`[${this.node.name}] setTickInterval已废弃,行为树现在每帧执行`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取黑板
|
* 获取黑板
|
||||||
*/
|
*/
|
||||||
@@ -137,11 +531,21 @@ export class BehaviorTreeComponent extends ECSComponent {
|
|||||||
return this.blackboard;
|
return this.blackboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取行为树
|
||||||
|
*/
|
||||||
|
getBehaviorTree(): BehaviorTree<any> | null {
|
||||||
|
return this.behaviorTree;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停行为树
|
* 暂停行为树
|
||||||
*/
|
*/
|
||||||
pause() {
|
pause() {
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
|
if (this.debugMode) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,6 +554,9 @@ export class BehaviorTreeComponent extends ECSComponent {
|
|||||||
resume() {
|
resume() {
|
||||||
if (this.isLoaded) {
|
if (this.isLoaded) {
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
|
if (this.debugMode) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,5 +568,46 @@ export class BehaviorTreeComponent extends ECSComponent {
|
|||||||
if (this.behaviorTree) {
|
if (this.behaviorTree) {
|
||||||
this.behaviorTree.reset();
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { _decorator, Component, resources, JsonAsset, Vec3 } from 'cc';
|
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 { UnitController } from './UnitController';
|
||||||
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
|
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
|
||||||
|
|
||||||
@@ -59,10 +59,7 @@ export class BehaviorTreeManager extends Component {
|
|||||||
this.setupBlackboard();
|
this.setupBlackboard();
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
this.isRunning = 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) {
|
} catch (error) {
|
||||||
console.error(`行为树初始化失败: ${behaviorTreeName}`, error);
|
console.error(`行为树初始化失败: ${behaviorTreeName}`, error);
|
||||||
}
|
}
|
||||||
@@ -74,7 +71,7 @@ export class BehaviorTreeManager extends Component {
|
|||||||
private async loadBehaviorTree(behaviorTreeName: string): Promise<void> {
|
private async loadBehaviorTree(behaviorTreeName: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const jsonPath = `${behaviorTreeName}.bt`;
|
const jsonPath = `${behaviorTreeName}.bt`;
|
||||||
console.log(`🔍 尝试加载行为树文件: ${jsonPath}`);
|
|
||||||
|
|
||||||
resources.load(jsonPath, JsonAsset, (err, asset) => {
|
resources.load(jsonPath, JsonAsset, (err, asset) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -116,11 +113,13 @@ export class BehaviorTreeManager extends Component {
|
|||||||
private createEventRegistry(): EventRegistry {
|
private createEventRegistry(): EventRegistry {
|
||||||
const registry = new EventRegistry();
|
const registry = new EventRegistry();
|
||||||
|
|
||||||
// 注册简化的矿工行为事件处理器
|
// 注册体力系统矿工行为事件处理器
|
||||||
const eventHandlers = {
|
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),
|
'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)
|
'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) {
|
if (!this.behaviorHandler) {
|
||||||
console.error(`BehaviorTreeManager: RTSBehaviorHandler未初始化 - ${this.node.name}`);
|
console.error(`BehaviorTreeManager: RTSBehaviorHandler未初始化 - ${this.node.name}`);
|
||||||
return 'failure';
|
return 'failure';
|
||||||
@@ -145,9 +144,7 @@ export class BehaviorTreeManager extends Component {
|
|||||||
// 直接调用RTSBehaviorHandler的方法
|
// 直接调用RTSBehaviorHandler的方法
|
||||||
const method = (this.behaviorHandler as any)[methodName];
|
const method = (this.behaviorHandler as any)[methodName];
|
||||||
if (typeof method === 'function') {
|
if (typeof method === 'function') {
|
||||||
console.log(`🎯 调用行为处理器: ${methodName} (${this.node.name})`);
|
|
||||||
const result = method.call(this.behaviorHandler, params);
|
const result = method.call(this.behaviorHandler, params);
|
||||||
console.log(`📤 行为处理器返回: ${methodName} -> "${result}" (${this.node.name})`);
|
|
||||||
return result || 'success'; // 确保有返回值
|
return result || 'success'; // 确保有返回值
|
||||||
} else {
|
} else {
|
||||||
console.error(`BehaviorTreeManager: 方法不存在: ${methodName}`);
|
console.error(`BehaviorTreeManager: 方法不存在: ${methodName}`);
|
||||||
@@ -174,6 +171,14 @@ export class BehaviorTreeManager extends Component {
|
|||||||
this.blackboard.setValue('hasTarget', false);
|
this.blackboard.setValue('hasTarget', false);
|
||||||
this.blackboard.setValue('targetPosition', null);
|
this.blackboard.setValue('targetPosition', null);
|
||||||
this.blackboard.setValue('isMoving', false);
|
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) {
|
if (this.unitController) {
|
||||||
|
// 基础属性
|
||||||
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
|
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
|
||||||
this.blackboard.setValue('currentCommand', this.unitController.currentCommand);
|
this.blackboard.setValue('currentCommand', this.unitController.currentCommand);
|
||||||
this.blackboard.setValue('hasTarget', this.unitController.targetPosition && !this.unitController.targetPosition.equals(Vec3.ZERO));
|
this.blackboard.setValue('hasTarget', this.unitController.targetPosition && !this.unitController.targetPosition.equals(Vec3.ZERO));
|
||||||
this.blackboard.setValue('targetPosition', this.unitController.targetPosition);
|
this.blackboard.setValue('targetPosition', this.unitController.targetPosition);
|
||||||
this.blackboard.setValue('isMoving', this.unitController.targetPosition && !this.unitController.targetPosition.equals(Vec3.ZERO));
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行行为树
|
// 执行行为树
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "4.0.24",
|
"ver": "4.0.24",
|
||||||
"importer": "typescript",
|
"importer": "typescript",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "dabc6540-6e9f-450a-9d22-b9af94c20d6d",
|
"uuid": "5f877c25-5c26-49c6-bbb5-7ff36323e0a1",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -4,14 +4,17 @@ import { UnitController } from './UnitController';
|
|||||||
const { ccclass } = _decorator;
|
const { ccclass } = _decorator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 矿工行为处理器 - 专门处理矿工的三个核心行为
|
* 矿工体力系统行为处理器 - 处理挖矿、休息、存储的完整循环
|
||||||
* 展示如何使用黑板变量参数和事件系统
|
* 展示体力驱动的工作-休息循环系统
|
||||||
*/
|
*/
|
||||||
@ccclass('RTSBehaviorHandler')
|
@ccclass('RTSBehaviorHandler')
|
||||||
export class RTSBehaviorHandler extends Component {
|
export class RTSBehaviorHandler extends Component {
|
||||||
|
|
||||||
private unitController: UnitController | null = null;
|
private unitController: UnitController | null = null;
|
||||||
private minerDemo: any = null; // MinerDemo组件引用
|
private minerDemo: any = null; // MinerDemo组件引用
|
||||||
|
private lastActionTime: number = 0;
|
||||||
|
private actionCooldown: number = 0.5; // 动作冷却时间,避免频繁切换
|
||||||
|
private minerIndex: number = -1; // 矿工索引,用于找到对应的家
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.unitController = this.getComponent(UnitController);
|
this.unitController = this.getComponent(UnitController);
|
||||||
@@ -24,62 +27,106 @@ export class RTSBehaviorHandler extends Component {
|
|||||||
if (!this.minerDemo) {
|
if (!this.minerDemo) {
|
||||||
console.error('RTSBehaviorHandler: 未找到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 事件参数,包含黑板变量值
|
* @param params 事件参数,包含黑板变量值
|
||||||
*/
|
*/
|
||||||
onFindAndMineOre(params: any = {}): string {
|
onMineGoldOre(params: any = {}): string {
|
||||||
if (!this.unitController || !this.minerDemo) return 'failure';
|
if (!this.unitController || !this.minerDemo) {
|
||||||
|
|
||||||
// 从参数中获取黑板变量值
|
|
||||||
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}: 没有可挖掘的矿石了`);
|
|
||||||
return 'failure';
|
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;
|
const currentPos = this.node.worldPosition;
|
||||||
let nearestOre: Node | null = null;
|
let nearestMine: Node | null = null;
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
|
|
||||||
for (const ore of ores) {
|
for (const mine of goldMines) {
|
||||||
const distance = Vec3.distance(currentPos, ore.worldPosition);
|
if (!mine || !mine.isValid) continue;
|
||||||
|
|
||||||
|
const distance = Vec3.distance(currentPos, mine.worldPosition);
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
nearestOre = ore;
|
nearestMine = mine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nearestOre) return 'failure';
|
if (!nearestMine) {
|
||||||
|
return 'failure';
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否已经到达矿石位置
|
// 检查是否已经到达金矿位置
|
||||||
if (minDistance < 1.5) {
|
if (minDistance < 2.0) {
|
||||||
// 开始挖掘
|
// 检查是否正在移动
|
||||||
console.log(`⛏️ ${this.node.name}: 开始挖掘矿石`);
|
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.unitController.setBlackboardValue('hasOre', true);
|
||||||
|
|
||||||
// 移除矿石
|
// 通知演示管理器
|
||||||
this.minerDemo.removeOre(nearestOre);
|
this.minerDemo.mineGoldOre(this.node);
|
||||||
|
|
||||||
// 清除移动目标
|
// 清除移动目标
|
||||||
this.unitController.clearTarget();
|
this.unitController.clearTarget();
|
||||||
|
this.unitController.setBlackboardValue('isMoving', false);
|
||||||
|
|
||||||
|
this.updateActionTime();
|
||||||
return 'success';
|
return 'success';
|
||||||
} else {
|
} else {
|
||||||
// 移动到矿石位置
|
// 设置移动目标
|
||||||
this.unitController.setTarget(nearestOre.worldPosition);
|
this.unitController.setTarget(nearestMine.worldPosition);
|
||||||
console.log(`🚶 ${this.node.name}: 前往矿石位置 距离${minDistance.toFixed(1)}米`);
|
|
||||||
return 'running';
|
return 'running';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,17 +136,23 @@ export class RTSBehaviorHandler extends Component {
|
|||||||
* @param params 事件参数,包含黑板变量值
|
* @param params 事件参数,包含黑板变量值
|
||||||
*/
|
*/
|
||||||
onStoreOre(params: any = {}): string {
|
onStoreOre(params: any = {}): string {
|
||||||
if (!this.unitController || !this.minerDemo) return 'failure';
|
if (!this.unitController || !this.minerDemo) {
|
||||||
|
return 'failure';
|
||||||
|
}
|
||||||
|
|
||||||
// 从参数中获取黑板变量值
|
// 检查是否携带矿石
|
||||||
const unitType = params.unitType || 'unknown';
|
const hasOre = this.unitController.getBlackboardValue('hasOre');
|
||||||
const targetPosition = params.targetPosition || null;
|
if (!hasOre) {
|
||||||
|
return 'failure';
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`🏭 ${unitType}矿工前往仓库存储 (目标位置: ${JSON.stringify(targetPosition)})`);
|
// 动作冷却检查
|
||||||
|
if (this.isActionOnCooldown()) {
|
||||||
|
return 'running';
|
||||||
|
}
|
||||||
|
|
||||||
const warehouse = this.minerDemo.getWarehouse();
|
const warehouse = this.minerDemo.getWarehouse();
|
||||||
if (!warehouse) {
|
if (!warehouse || !warehouse.isValid) {
|
||||||
console.log(`👷 ${this.node.name}: 找不到仓库`);
|
|
||||||
return 'failure';
|
return 'failure';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,20 +163,110 @@ export class RTSBehaviorHandler extends Component {
|
|||||||
|
|
||||||
// 检查是否已经到达仓库
|
// 检查是否已经到达仓库
|
||||||
if (distance < 2.5) {
|
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.setBlackboardValue('hasOre', false);
|
||||||
|
|
||||||
// 清除移动目标
|
// 清除移动目标
|
||||||
this.unitController.clearTarget();
|
this.unitController.clearTarget();
|
||||||
|
this.unitController.setBlackboardValue('isMoving', false);
|
||||||
|
|
||||||
|
this.updateActionTime();
|
||||||
return 'success';
|
return 'success';
|
||||||
} else {
|
} else {
|
||||||
// 移动到仓库
|
// 设置移动目标
|
||||||
this.unitController.setTarget(warehousePos);
|
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';
|
return 'running';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,10 +276,59 @@ export class RTSBehaviorHandler extends Component {
|
|||||||
* @param params 事件参数,包含黑板变量值
|
* @param params 事件参数,包含黑板变量值
|
||||||
*/
|
*/
|
||||||
onIdleBehavior(params: any = {}): string {
|
onIdleBehavior(params: any = {}): string {
|
||||||
// 从参数中获取黑板变量值
|
if (!this.unitController) {
|
||||||
const unitType = params.unitType || 'unknown';
|
return 'failure';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除移动目标,确保停止移动
|
||||||
|
this.unitController.clearTarget();
|
||||||
|
this.unitController.setBlackboardValue('isMoving', false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log(`😴 ${unitType}矿工待机中`);
|
|
||||||
return 'success';
|
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()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简单预制体工厂 - 创建基本的游戏对象
|
* 简单预制体工厂
|
||||||
* 用于在没有预制体资源时创建基本的单位、建筑和资源
|
|
||||||
*/
|
*/
|
||||||
@ccclass('SimplePrefabFactory')
|
@ccclass('SimplePrefabFactory')
|
||||||
export class SimplePrefabFactory extends Component {
|
export class SimplePrefabFactory extends Component {
|
||||||
@@ -36,7 +35,6 @@ export class SimplePrefabFactory extends Component {
|
|||||||
const rigidBody = unit.addComponent(RigidBody);
|
const rigidBody = unit.addComponent(RigidBody);
|
||||||
rigidBody.type = RigidBody.Type.KINEMATIC;
|
rigidBody.type = RigidBody.Type.KINEMATIC;
|
||||||
|
|
||||||
console.log(`创建单位: ${name}`);
|
|
||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +65,6 @@ export class SimplePrefabFactory extends Component {
|
|||||||
const collider = building.addComponent(BoxCollider);
|
const collider = building.addComponent(BoxCollider);
|
||||||
collider.size = size;
|
collider.size = size;
|
||||||
|
|
||||||
console.log(`创建建筑: ${name}`);
|
|
||||||
return building;
|
return building;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +91,6 @@ export class SimplePrefabFactory extends Component {
|
|||||||
const collider = resource.addComponent(BoxCollider);
|
const collider = resource.addComponent(BoxCollider);
|
||||||
collider.size = new Vec3(1, 1, 1);
|
collider.size = new Vec3(1, 1, 1);
|
||||||
|
|
||||||
console.log(`创建资源: ${name}`);
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +121,6 @@ export class SimplePrefabFactory extends Component {
|
|||||||
const collider = ground.addComponent(BoxCollider);
|
const collider = ground.addComponent(BoxCollider);
|
||||||
collider.size = size;
|
collider.size = size;
|
||||||
|
|
||||||
console.log(`创建地面: ${size}`);
|
|
||||||
return ground;
|
return ground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "4.0.24",
|
"ver": "4.0.24",
|
||||||
"importer": "typescript",
|
"importer": "typescript",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "61885e67-b7a2-4e51-857e-ccbea4fdea03",
|
"uuid": "7478e794-dd80-4661-9421-8e147d33c51e",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -1,345 +0,0 @@
|
|||||||
import { _decorator, Component, Node, Vec3, Material, MeshRenderer, Color, tween } from 'cc';
|
|
||||||
import { BehaviorTreeComponent } from './BehaviorTreeComponent';
|
|
||||||
|
|
||||||
// 简化的ECS组件基类
|
|
||||||
export class ECSComponent {
|
|
||||||
public entity: any = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简化的Entity类
|
|
||||||
export class Entity {
|
|
||||||
public name: string = '';
|
|
||||||
private components: Map<any, any> = new Map();
|
|
||||||
|
|
||||||
constructor(name: string) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
addComponent(component: any) {
|
|
||||||
this.components.set(component.constructor, component);
|
|
||||||
component.entity = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getComponent<T>(componentClass: any): T | null {
|
|
||||||
return this.components.get(componentClass) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasComponent(componentClass: any): boolean {
|
|
||||||
return this.components.has(componentClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简化的Core类
|
|
||||||
export class Core {
|
|
||||||
static entityManager = {
|
|
||||||
entities: [] as Entity[],
|
|
||||||
createEntity: (name: string) => {
|
|
||||||
const entity = new Entity(name);
|
|
||||||
Core.entityManager.entities.push(entity);
|
|
||||||
return entity;
|
|
||||||
},
|
|
||||||
destroyEntity: (entity: Entity) => {
|
|
||||||
const index = Core.entityManager.entities.indexOf(entity);
|
|
||||||
if (index !== -1) {
|
|
||||||
Core.entityManager.entities.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static create(config?: any) {
|
|
||||||
console.log('ECS Core initialized with config:', config);
|
|
||||||
}
|
|
||||||
|
|
||||||
static update(deltaTime: number) {
|
|
||||||
// 简化的更新逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单位配置接口
|
|
||||||
*/
|
|
||||||
export interface UnitConfig {
|
|
||||||
unitType: string;
|
|
||||||
behaviorTreeFile: string;
|
|
||||||
maxHealth: number;
|
|
||||||
moveSpeed: number;
|
|
||||||
attackRange: number;
|
|
||||||
attackDamage: number;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单位状态组件
|
|
||||||
*/
|
|
||||||
export class UnitStateComponent extends ECSComponent {
|
|
||||||
public unitType: string = '';
|
|
||||||
public maxHealth: number = 100;
|
|
||||||
public currentHealth: number = 100;
|
|
||||||
public moveSpeed: number = 3;
|
|
||||||
public attackRange: number = 2;
|
|
||||||
public attackDamage: number = 25;
|
|
||||||
public isSelected: boolean = false;
|
|
||||||
public currentCommand: string = 'idle';
|
|
||||||
public targetPosition: Vec3 = Vec3.ZERO.clone();
|
|
||||||
public targetNode: Node | null = null;
|
|
||||||
public lastAttackTime: number = 0;
|
|
||||||
public attackCooldown: number = 1.5;
|
|
||||||
public color: string = 'white';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单位组件 - Cocos Creator组件,管理单位的可视化和ECS实体
|
|
||||||
*/
|
|
||||||
@ccclass('UnitComponent')
|
|
||||||
export class UnitComponent extends Component {
|
|
||||||
|
|
||||||
@property
|
|
||||||
showDebugInfo: boolean = true;
|
|
||||||
|
|
||||||
private entity: Entity | null = null;
|
|
||||||
private unitState: UnitStateComponent | null = null;
|
|
||||||
private behaviorTreeComponent: BehaviorTreeComponent | null = null;
|
|
||||||
private meshRenderer: MeshRenderer | null = null;
|
|
||||||
private originalMaterial: Material | null = null;
|
|
||||||
private selectionMaterial: Material | null = null;
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
this.meshRenderer = this.getComponent(MeshRenderer);
|
|
||||||
if (this.meshRenderer && this.meshRenderer.material) {
|
|
||||||
this.originalMaterial = this.meshRenderer.material;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置单位配置
|
|
||||||
*/
|
|
||||||
setup(config: UnitConfig) {
|
|
||||||
// 创建ECS实体
|
|
||||||
this.entity = Core.entityManager.createEntity(`Unit_${this.node.name}`);
|
|
||||||
|
|
||||||
// 添加单位状态组件
|
|
||||||
this.unitState = new UnitStateComponent();
|
|
||||||
this.unitState.unitType = config.unitType;
|
|
||||||
this.unitState.maxHealth = config.maxHealth;
|
|
||||||
this.unitState.currentHealth = config.maxHealth;
|
|
||||||
this.unitState.moveSpeed = config.moveSpeed;
|
|
||||||
this.unitState.attackRange = config.attackRange;
|
|
||||||
this.unitState.attackDamage = config.attackDamage;
|
|
||||||
this.unitState.color = config.color;
|
|
||||||
this.entity.addComponent(this.unitState);
|
|
||||||
|
|
||||||
// 添加行为树组件
|
|
||||||
this.behaviorTreeComponent = new BehaviorTreeComponent();
|
|
||||||
this.behaviorTreeComponent.behaviorTreeFile = config.behaviorTreeFile;
|
|
||||||
this.behaviorTreeComponent.cocosNode = this.node;
|
|
||||||
this.entity.addComponent(this.behaviorTreeComponent);
|
|
||||||
|
|
||||||
// 设置材质颜色
|
|
||||||
this.setUnitColor(config.color);
|
|
||||||
|
|
||||||
console.log(`单位 ${this.node.name} 设置完成 - 类型: ${config.unitType}, 行为树: ${config.behaviorTreeFile}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置单位颜色
|
|
||||||
*/
|
|
||||||
private setUnitColor(colorName: string) {
|
|
||||||
if (!this.meshRenderer || !this.meshRenderer.material) return;
|
|
||||||
|
|
||||||
const colorMap: { [key: string]: Color } = {
|
|
||||||
'red': Color.RED,
|
|
||||||
'green': Color.GREEN,
|
|
||||||
'blue': Color.BLUE,
|
|
||||||
'yellow': Color.YELLOW,
|
|
||||||
'white': Color.WHITE,
|
|
||||||
'cyan': Color.CYAN,
|
|
||||||
'magenta': Color.MAGENTA
|
|
||||||
};
|
|
||||||
|
|
||||||
const color = colorMap[colorName] || Color.WHITE;
|
|
||||||
this.meshRenderer.material.setProperty('mainColor', color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置选择状态
|
|
||||||
*/
|
|
||||||
setSelected(selected: boolean) {
|
|
||||||
if (!this.unitState) return;
|
|
||||||
|
|
||||||
this.unitState.isSelected = selected;
|
|
||||||
|
|
||||||
// 视觉效果
|
|
||||||
if (selected) {
|
|
||||||
this.showSelectionEffect();
|
|
||||||
} else {
|
|
||||||
this.hideSelectionEffect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示选择效果
|
|
||||||
*/
|
|
||||||
private showSelectionEffect() {
|
|
||||||
// 添加选择圈效果
|
|
||||||
tween(this.node)
|
|
||||||
.to(0.3, { scale: new Vec3(1.1, 1.1, 1.1) })
|
|
||||||
.to(0.3, { scale: Vec3.ONE })
|
|
||||||
.union()
|
|
||||||
.repeatForever()
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 隐藏选择效果
|
|
||||||
*/
|
|
||||||
private hideSelectionEffect() {
|
|
||||||
// 停止所有缩放动画
|
|
||||||
tween(this.node).stop();
|
|
||||||
this.node.setScale(Vec3.ONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发布命令
|
|
||||||
*/
|
|
||||||
issueCommand(command: string, target?: Vec3 | Node) {
|
|
||||||
if (!this.unitState || !this.behaviorTreeComponent) return;
|
|
||||||
|
|
||||||
this.unitState.currentCommand = command;
|
|
||||||
|
|
||||||
// 设置目标
|
|
||||||
if (target instanceof Vec3) {
|
|
||||||
this.unitState.targetPosition = target.clone();
|
|
||||||
this.unitState.targetNode = null;
|
|
||||||
} else if (target instanceof Node) {
|
|
||||||
this.unitState.targetPosition = target.worldPosition.clone();
|
|
||||||
this.unitState.targetNode = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过黑板更新行为树状态
|
|
||||||
const blackboard = this.behaviorTreeComponent.getBlackboard();
|
|
||||||
if (blackboard) {
|
|
||||||
blackboard.setValue('currentCommand', command);
|
|
||||||
blackboard.setValue('hasTarget', target !== undefined);
|
|
||||||
blackboard.setValue('targetPosition', this.unitState.targetPosition);
|
|
||||||
|
|
||||||
if (target instanceof Node) {
|
|
||||||
blackboard.setValue('targetType', target.name.includes('Resource') ? 'resource' :
|
|
||||||
target.name.includes('Building') ? 'building' : 'unit');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`单位 ${this.node.name} 接收命令: ${command}`, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单位状态
|
|
||||||
*/
|
|
||||||
getUnitState(): UnitStateComponent | null {
|
|
||||||
return this.unitState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取行为树组件
|
|
||||||
*/
|
|
||||||
getBehaviorTreeComponent(): BehaviorTreeComponent | null {
|
|
||||||
return this.behaviorTreeComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 受到伤害
|
|
||||||
*/
|
|
||||||
takeDamage(damage: number) {
|
|
||||||
if (!this.unitState) return;
|
|
||||||
|
|
||||||
this.unitState.currentHealth = Math.max(0, this.unitState.currentHealth - damage);
|
|
||||||
|
|
||||||
// 更新黑板
|
|
||||||
const blackboard = this.behaviorTreeComponent?.getBlackboard();
|
|
||||||
if (blackboard) {
|
|
||||||
blackboard.setValue('currentHealth', this.unitState.currentHealth);
|
|
||||||
blackboard.setValue('healthPercentage', this.unitState.currentHealth / this.unitState.maxHealth);
|
|
||||||
blackboard.setValue('isLowHealth', this.unitState.currentHealth < this.unitState.maxHealth * 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 视觉效果
|
|
||||||
this.showDamageEffect();
|
|
||||||
|
|
||||||
if (this.unitState.currentHealth <= 0) {
|
|
||||||
this.die();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示受伤效果
|
|
||||||
*/
|
|
||||||
private showDamageEffect() {
|
|
||||||
if (!this.meshRenderer || !this.meshRenderer.material) return;
|
|
||||||
|
|
||||||
// 闪红效果
|
|
||||||
const originalColor = this.meshRenderer.material.getProperty('mainColor') as Color;
|
|
||||||
this.meshRenderer.material.setProperty('mainColor', Color.RED);
|
|
||||||
|
|
||||||
this.scheduleOnce(() => {
|
|
||||||
if (this.meshRenderer && this.meshRenderer.material) {
|
|
||||||
this.meshRenderer.material.setProperty('mainColor', originalColor);
|
|
||||||
}
|
|
||||||
}, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单位死亡
|
|
||||||
*/
|
|
||||||
private die() {
|
|
||||||
console.log(`单位 ${this.node.name} 死亡`);
|
|
||||||
|
|
||||||
// 从ECS系统中移除实体
|
|
||||||
if (this.entity) {
|
|
||||||
Core.entityManager.destroyEntity(this.entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 播放死亡动画后销毁节点
|
|
||||||
tween(this.node)
|
|
||||||
.to(0.5, { scale: Vec3.ZERO })
|
|
||||||
.call(() => {
|
|
||||||
this.node.destroy();
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaTime: number) {
|
|
||||||
if (!this.unitState || !this.behaviorTreeComponent) return;
|
|
||||||
|
|
||||||
// 更新黑板中的时间相关变量
|
|
||||||
const blackboard = this.behaviorTreeComponent.getBlackboard();
|
|
||||||
if (blackboard) {
|
|
||||||
blackboard.setValue('deltaTime', deltaTime);
|
|
||||||
blackboard.setValue('currentTime', Date.now() / 1000);
|
|
||||||
blackboard.setValue('worldPosition', this.node.worldPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调试信息显示
|
|
||||||
if (this.showDebugInfo) {
|
|
||||||
this.updateDebugInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新调试信息
|
|
||||||
*/
|
|
||||||
private updateDebugInfo() {
|
|
||||||
// 可以在这里添加调试信息的显示逻辑
|
|
||||||
// 比如在单位上方显示状态文本等
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
// 清理ECS实体
|
|
||||||
if (this.entity) {
|
|
||||||
Core.entityManager.destroyEntity(this.entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止所有动画
|
|
||||||
tween(this.node).stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 { BehaviorTreeManager } from './BehaviorTreeManager';
|
||||||
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
|
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export interface UnitConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单位控制器 - 纯Cocos Creator组件,管理单位的行为和状态
|
* 单位控制器
|
||||||
*/
|
*/
|
||||||
@ccclass('UnitController')
|
@ccclass('UnitController')
|
||||||
export class UnitController extends Component {
|
export class UnitController extends Component {
|
||||||
@@ -41,6 +41,13 @@ export class UnitController extends Component {
|
|||||||
public attackCooldown: number = 1.5;
|
public attackCooldown: number = 1.5;
|
||||||
public color: string = 'white';
|
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 isMoving: boolean = false;
|
||||||
private moveStartTime: number = 0;
|
private moveStartTime: number = 0;
|
||||||
@@ -61,7 +68,7 @@ export class UnitController extends Component {
|
|||||||
// 添加RTSBehaviorHandler组件
|
// 添加RTSBehaviorHandler组件
|
||||||
this.behaviorHandler = this.addComponent(RTSBehaviorHandler);
|
this.behaviorHandler = this.addComponent(RTSBehaviorHandler);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('RTSBehaviorHandler组件添加失败,将使用默认行为处理', error);
|
console.warn('RTSBehaviorHandler组件添加失败', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +94,6 @@ export class UnitController extends Component {
|
|||||||
if (this.behaviorTreeManager) {
|
if (this.behaviorTreeManager) {
|
||||||
this.behaviorTreeManager.initializeBehaviorTree(config.behaviorTreeName, this);
|
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);
|
this.meshRenderer.material.setProperty('mainColor', color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置选择状态
|
* 设置选择状态
|
||||||
*/
|
*/
|
||||||
@@ -181,8 +184,6 @@ export class UnitController extends Component {
|
|||||||
target.name.includes('Building') ? 'building' : 'unit');
|
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) {
|
setTarget(position: Vec3) {
|
||||||
this.targetPosition = position.clone();
|
this.targetPosition = position.clone();
|
||||||
|
this.isMoving = true;
|
||||||
|
this.moveStartTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,6 +216,7 @@ export class UnitController extends Component {
|
|||||||
*/
|
*/
|
||||||
clearTarget() {
|
clearTarget() {
|
||||||
this.targetPosition = Vec3.ZERO.clone();
|
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 {
|
moveToTarget(targetPos: Vec3, speed?: number, deltaTime?: number): boolean {
|
||||||
const currentPos = this.node.worldPosition;
|
const currentPos = this.node.worldPosition;
|
||||||
|
const distance = Vec3.distance(currentPos, targetPos);
|
||||||
|
|
||||||
// 只计算水平面距离(忽略Y轴)
|
if (distance < 0.5) {
|
||||||
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) { // 增加到达阈值,减少抖动
|
|
||||||
this.isMoving = false;
|
this.isMoving = false;
|
||||||
return true; // 已到达目标
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 平滑移动逻辑(只在水平面)
|
const actualSpeed = speed || this.moveSpeed;
|
||||||
const direction2D = targetPos2D.subtract(currentPos2D).normalize();
|
const actualDeltaTime = deltaTime || 0.016;
|
||||||
const moveSpeed = speed || this.moveSpeed;
|
const direction = new Vec3();
|
||||||
const dt = deltaTime || 0.016; // 使用传入的deltaTime或默认值
|
Vec3.subtract(direction, targetPos, currentPos);
|
||||||
|
direction.normalize();
|
||||||
|
|
||||||
// 计算移动距离,确保不会超过目标位置
|
const moveDistance = actualSpeed * actualDeltaTime;
|
||||||
const moveDistance = Math.min(moveSpeed * dt, distance);
|
const newPosition = new Vec3();
|
||||||
const movement2D = direction2D.multiplyScalar(moveDistance);
|
Vec3.scaleAndAdd(newPosition, currentPos, direction, moveDistance);
|
||||||
|
|
||||||
// 新位置保持原有的Y轴位置
|
|
||||||
const newPosition = new Vec3(
|
|
||||||
currentPos.x + movement2D.x,
|
|
||||||
currentPos.y, // 保持Y轴不变
|
|
||||||
currentPos.z + movement2D.z
|
|
||||||
);
|
|
||||||
|
|
||||||
this.node.setWorldPosition(newPosition);
|
this.node.setWorldPosition(newPosition);
|
||||||
this.isMoving = true;
|
this.isMoving = true;
|
||||||
|
|
||||||
// 减少日志输出频率
|
return false;
|
||||||
if (Date.now() - this.moveStartTime > 1000) { // 每秒输出一次
|
|
||||||
console.log(`${this.node.name}: 移动中 距离目标${distance.toFixed(2)}米`);
|
|
||||||
this.moveStartTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // 还在移动中
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 攻击目标
|
* 攻击目标
|
||||||
*/
|
*/
|
||||||
attackTarget(): boolean {
|
attackTarget(): boolean {
|
||||||
const currentTime = Date.now() / 1000;
|
const currentTime = Date.now();
|
||||||
|
if (currentTime - this.lastAttackTime < this.attackCooldown * 1000) {
|
||||||
if (currentTime - this.lastAttackTime < this.attackCooldown) {
|
return false;
|
||||||
return false; // 冷却中
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行攻击
|
if (this.targetNode && this.targetNode.isValid) {
|
||||||
console.log(`${this.node.name} 执行攻击`);
|
const distance = Vec3.distance(this.node.worldPosition, this.targetNode.worldPosition);
|
||||||
this.lastAttackTime = currentTime;
|
if (distance <= this.attackRange) {
|
||||||
|
this.lastAttackTime = currentTime;
|
||||||
// 更新行为树黑板
|
return true;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新行为树黑板中的核心变量
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime: number) {
|
||||||
if (this.behaviorTreeManager) {
|
if (this.behaviorTreeManager) {
|
||||||
// 基础属性更新
|
this.behaviorTreeManager.update(deltaTime);
|
||||||
this.behaviorTreeManager.updateBlackboardValue('currentHealth', this.currentHealth);
|
}
|
||||||
this.behaviorTreeManager.updateBlackboardValue('healthPercentage', this.currentHealth / this.maxHealth);
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('isLowHealth', this.currentHealth < this.maxHealth * 0.3);
|
|
||||||
|
|
||||||
// 命令状态更新
|
if (this.isMoving && !this.targetPosition.equals(Vec3.ZERO)) {
|
||||||
this.behaviorTreeManager.updateBlackboardValue('currentCommand', this.currentCommand);
|
const reached = this.moveToTarget(this.targetPosition, this.moveSpeed, deltaTime);
|
||||||
this.behaviorTreeManager.updateBlackboardValue('hasTarget', this.targetPosition && !this.targetPosition.equals(Vec3.ZERO));
|
if (reached) {
|
||||||
this.behaviorTreeManager.updateBlackboardValue('targetPosition', this.targetPosition);
|
this.clearTarget();
|
||||||
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') {
|
|
||||||
// 侦察兵特有的变量
|
|
||||||
// 这里可以添加侦察兵特有的状态更新
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "84a4754f-3fad-4031-8aeb-e699584cfb92",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "00e982f3-dcf3-44b0-9b63-5e2877c1971e",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Submodule extensions/cocos/cocos-ecs/extensions/behaviour-tree updated: 2915663478...07270b77ff
8
extensions/cocos/cocos-ecs/package-lock.json
generated
8
extensions/cocos/cocos-ecs/package-lock.json
generated
@@ -6,14 +6,14 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "cocos-ecs",
|
"name": "cocos-ecs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esengine/ai": "^2.0.10",
|
"@esengine/ai": "^2.0.13",
|
||||||
"@esengine/ecs-framework": "^2.1.22"
|
"@esengine/ecs-framework": "^2.1.22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esengine/ai": {
|
"node_modules/@esengine/ai": {
|
||||||
"version": "2.0.10",
|
"version": "2.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.13.tgz",
|
||||||
"integrity": "sha512-L0jWaYHDe8fw/XMpVniFqMIFDVzP9wib+HZKKq+NOXV3zWj+F3XpOgNrb7+4ZN5yR8rK9wjEh+PNkVdTkLOl8g==",
|
"integrity": "sha512-Iwbdw7UPVEARph/zvq6XL5XIkku3TmdFk/XHxT5aU2Y3RvPNnpd4OwDaranIpjuBL4WkquZx4C/Mw7jh4FNwFg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esengine/ecs-framework": "^2.1.20"
|
"@esengine/ecs-framework": "^2.1.20"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esengine/ecs-framework": "^2.1.22",
|
"@esengine/ecs-framework": "^2.1.22",
|
||||||
"@esengine/ai": "^2.0.10"
|
"@esengine/ai": "^2.0.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2
thirdparty/BehaviourTree-ai
vendored
2
thirdparty/BehaviourTree-ai
vendored
Submodule thirdparty/BehaviourTree-ai updated: a2107c1132...f3e91b9f34
Reference in New Issue
Block a user