采矿行为树示例

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

View File

@@ -1,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
}
}

View File

@@ -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"
}
]
}
}

View File

@@ -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": "移动属性"
}
]
}

View File

@@ -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"
}
}

View File

@@ -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"
], ],

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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": {

View File

@@ -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();
}
} }

View File

@@ -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;
} }
} }

View File

@@ -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);
} }
// 执行行为树 // 执行行为树

View File

@@ -0,0 +1,139 @@
import { Component, _decorator, Label, ProgressBar, Node, UITransform, Canvas, find, Camera, Vec3, director, Color, Layers, Graphics } from 'cc';
const { ccclass, property } = _decorator;
/**
* 矿工状态UI组件
*/
@ccclass('MinerStatusUI')
export class MinerStatusUI extends Component {
nameLabel: Label | null = null;
statusLabel: Label | null = null;
staminaBar: ProgressBar | null = null;
actionProgressBar: ProgressBar | null = null;
actionLabel: Label | null = null;
oreCountLabel: Label | null = null;
warehouseCountLabel: Label | null = null;
@property
followTarget: Node | null = null;
@property
yOffset: number = 100;
private camera: Camera | null = null;
private canvas: Canvas | null = null;
start() {
this.node.layer = Layers.Enum.UI_2D;
this.camera = find('Main Camera')?.getComponent(Camera) || director.getScene()?.getComponentInChildren(Camera);
this.canvas = find('Canvas')?.getComponent(Canvas) || director.getScene()?.getComponentInChildren(Canvas);
if (!this.camera) {
console.warn('[MinerStatusUI] 未找到主摄像机');
}
if (!this.canvas) {
console.warn('[MinerStatusUI] 未找到Canvas');
}
if (this.nameLabel && this.followTarget) {
this.nameLabel.string = this.followTarget.name;
}
this.updateStamina(100, 100);
this.updateStatus('待机中');
this.updateActionProgress('', 0);
}
update() {
if (this.followTarget && this.camera && this.canvas) {
this.updateUIPosition();
}
}
private updateUIPosition() {
if (!this.followTarget || !this.camera || !this.canvas) return;
const targetWorldPos = this.followTarget.worldPosition.clone();
targetWorldPos.y += this.yOffset / 100;
const screenPos = this.camera.worldToScreen(targetWorldPos);
const canvasTransform = this.canvas.node.getComponent(UITransform);
if (canvasTransform) {
const canvasPos = new Vec3(
screenPos.x - canvasTransform.width / 2,
screenPos.y - canvasTransform.height / 2,
0
);
this.node.setPosition(canvasPos);
}
}
setFollowTarget(target: Node) {
this.followTarget = target;
if (this.nameLabel) {
this.nameLabel.string = target.name;
}
}
updateStamina(current: number, max: number) {
if (this.staminaBar) {
this.staminaBar.progress = current / max;
}
if (this.staminaBar) {
const percentage = current / max;
const fillNode = this.staminaBar.node.getChildByName('Bar');
if (fillNode) {
const graphics = fillNode.getComponent(Graphics);
if (graphics) {
let color: Color;
if (percentage > 0.6) {
color = new Color(0, 255, 0, 255);
} else if (percentage > 0.3) {
color = new Color(255, 255, 0, 255);
} else {
color = new Color(255, 0, 0, 255);
}
graphics.clear();
graphics.fillColor = color;
graphics.rect(-75, -4, 150 * percentage, 8);
graphics.fill();
}
}
}
}
updateStatus(status: string) {
if (this.statusLabel) {
this.statusLabel.string = status;
}
}
updateActionProgress(actionName: string, progress: number) {
if (this.actionLabel) {
this.actionLabel.string = actionName;
}
if (this.actionProgressBar) {
this.actionProgressBar.progress = Math.max(0, Math.min(1, progress));
this.actionProgressBar.node.active = actionName !== '' && progress > 0;
}
}
setVisible(visible: boolean) {
this.node.active = visible;
}
updateOreCount(hasOre: boolean, warehouseTotal: number) {
if (this.oreCountLabel) {
this.oreCountLabel.string = hasOre ? '💎1' : '💎0';
}
}
}

View File

@@ -2,7 +2,7 @@
"ver": "4.0.24", "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": {}

View File

@@ -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()
};
}
} }

View File

@@ -1,10 +1,9 @@
import { _decorator, Component, Node, Vec3, MeshRenderer, BoxCollider, RigidBody, Mesh, Material, Color, primitives, utils } from 'cc'; import { _decorator, Component, Node, Vec3, MeshRenderer, BoxCollider, RigidBody, Material, Color, primitives, utils } from 'cc';
const { ccclass, property } = _decorator; 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;
} }
} }

View File

@@ -0,0 +1,289 @@
import { Component, _decorator, Node, Label, ProgressBar, UITransform, Widget, Canvas, find, director, Color, Sprite, Layers, Graphics } from 'cc';
import { MinerStatusUI } from './MinerStatusUI';
const { ccclass, property } = _decorator;
/**
* 状态UI管理器
* 负责创建和管理游戏对象的状态显示界面
*/
@ccclass('StatusUIManager')
export class StatusUIManager extends Component {
/**
* 为矿工创建状态显示UI
*/
static createStatusUIForMiner(miner: Node): MinerStatusUI | null {
const canvas = find('Canvas') || director.getScene()?.getChildByName('Canvas');
if (!canvas) {
console.error('[StatusUIManager] 未找到Canvas');
return null;
}
const minerIndex = this.extractMinerIndex(miner.name);
const yOffset = minerIndex * 20;
const uiRoot = new Node(`${miner.name}_StatusUI`);
canvas.addChild(uiRoot);
const uiTransform = uiRoot.addComponent(UITransform);
uiTransform.setContentSize(200, 100);
const borderNode = new Node('Border');
uiRoot.addChild(borderNode);
const borderTransform = borderNode.addComponent(UITransform);
borderTransform.setContentSize(202, 102);
const borderGraphics = borderNode.addComponent(Graphics);
borderGraphics.fillColor = new Color(100, 100, 100, 120);
borderGraphics.rect(-101, -51, 202, 102);
borderGraphics.fill();
const borderWidget = borderNode.addComponent(Widget);
borderWidget.isAlignTop = true;
borderWidget.isAlignBottom = true;
borderWidget.isAlignLeft = true;
borderWidget.isAlignRight = true;
borderWidget.top = -1;
borderWidget.bottom = -1;
borderWidget.left = -1;
borderWidget.right = -1;
borderWidget.updateAlignment();
const backgroundNode = new Node('Background');
uiRoot.addChild(backgroundNode);
const backgroundTransform = backgroundNode.addComponent(UITransform);
backgroundTransform.setContentSize(200, 100);
const backgroundGraphics = backgroundNode.addComponent(Graphics);
backgroundGraphics.fillColor = new Color(0, 0, 0, 100);
backgroundGraphics.rect(-100, -50, 200, 100);
backgroundGraphics.fill();
const statusUI = uiRoot.addComponent(MinerStatusUI);
statusUI.setFollowTarget(miner);
statusUI.yOffset = 100 + yOffset;
const nameNode = new Node('NameLabel');
uiRoot.addChild(nameNode);
const nameTransform = nameNode.addComponent(UITransform);
nameTransform.setContentSize(200, 25);
const nameLabel = nameNode.addComponent(Label);
nameLabel.string = miner.name;
nameLabel.fontSize = 16;
nameLabel.color = new Color(255, 255, 255, 255);
const nameWidget = nameNode.addComponent(Widget);
nameWidget.isAlignTop = true;
nameWidget.top = 0;
nameWidget.isAlignHorizontalCenter = true;
nameWidget.updateAlignment();
// 创建状态标签
const statusNode = new Node('StatusLabel');
uiRoot.addChild(statusNode);
const statusTransform = statusNode.addComponent(UITransform);
statusTransform.setContentSize(200, 20);
const statusLabel = statusNode.addComponent(Label);
statusLabel.string = '待机中';
statusLabel.fontSize = 14;
statusLabel.color = new Color(200, 200, 200, 255);
// 设置状态标签位置
const statusWidget = statusNode.addComponent(Widget);
statusWidget.isAlignTop = true;
statusWidget.top = 25;
statusWidget.isAlignHorizontalCenter = true;
statusWidget.updateAlignment();
// 创建体力进度条
const staminaBarNode = new Node('StaminaBar');
uiRoot.addChild(staminaBarNode);
const staminaBarTransform = staminaBarNode.addComponent(UITransform);
staminaBarTransform.setContentSize(150, 8);
const staminaBar = staminaBarNode.addComponent(ProgressBar);
staminaBar.progress = 1.0;
// 创建体力进度条背景
const staminaBgNode = new Node('Background');
staminaBarNode.addChild(staminaBgNode);
const staminaBgTransform = staminaBgNode.addComponent(UITransform);
staminaBgTransform.setContentSize(150, 8);
const staminaBgGraphics = staminaBgNode.addComponent(Graphics);
staminaBgGraphics.fillColor = new Color(50, 50, 50, 255);
staminaBgGraphics.rect(-75, -4, 150, 8);
staminaBgGraphics.fill();
// 创建体力进度条填充
const staminaFillNode = new Node('Bar');
staminaBarNode.addChild(staminaFillNode);
const staminaFillTransform = staminaFillNode.addComponent(UITransform);
staminaFillTransform.setContentSize(150, 8);
const staminaFillGraphics = staminaFillNode.addComponent(Graphics);
staminaFillGraphics.fillColor = new Color(0, 255, 0, 255);
staminaFillGraphics.rect(-75, -4, 150, 8);
staminaFillGraphics.fill();
// 设置体力进度条位置
const staminaWidget = staminaBarNode.addComponent(Widget);
staminaWidget.isAlignTop = true;
staminaWidget.top = 45;
staminaWidget.isAlignHorizontalCenter = true;
staminaWidget.updateAlignment();
// 创建动作进度条
const actionBarNode = new Node('ActionProgressBar');
uiRoot.addChild(actionBarNode);
const actionBarTransform = actionBarNode.addComponent(UITransform);
actionBarTransform.setContentSize(150, 6);
const actionBar = actionBarNode.addComponent(ProgressBar);
actionBar.progress = 0;
actionBarNode.active = false; // 初始隐藏
// 创建动作进度条背景
const actionBgNode = new Node('Background');
actionBarNode.addChild(actionBgNode);
const actionBgTransform = actionBgNode.addComponent(UITransform);
actionBgTransform.setContentSize(150, 6);
const actionBgGraphics = actionBgNode.addComponent(Graphics);
actionBgGraphics.fillColor = new Color(50, 50, 50, 255);
actionBgGraphics.rect(-75, -3, 150, 6);
actionBgGraphics.fill();
// 创建动作进度条填充
const actionFillNode = new Node('Bar');
actionBarNode.addChild(actionFillNode);
const actionFillTransform = actionFillNode.addComponent(UITransform);
actionFillTransform.setContentSize(150, 6);
const actionFillGraphics = actionFillNode.addComponent(Graphics);
actionFillGraphics.fillColor = new Color(255, 255, 0, 255);
actionFillGraphics.rect(-75, -3, 150, 6);
actionFillGraphics.fill();
// 设置动作进度条位置
const actionWidget = actionBarNode.addComponent(Widget);
actionWidget.isAlignTop = true;
actionWidget.top = 55;
actionWidget.isAlignHorizontalCenter = true;
actionWidget.updateAlignment();
// 创建动作标签
const actionLabelNode = new Node('ActionLabel');
uiRoot.addChild(actionLabelNode);
const actionLabelTransform = actionLabelNode.addComponent(UITransform);
actionLabelTransform.setContentSize(200, 15);
const actionLabel = actionLabelNode.addComponent(Label);
actionLabel.string = '';
actionLabel.fontSize = 12;
actionLabel.color = new Color(255, 255, 0, 255);
// 设置动作标签位置
const actionLabelWidget = actionLabelNode.addComponent(Widget);
actionLabelWidget.isAlignTop = true;
actionLabelWidget.top = 65;
actionLabelWidget.isAlignHorizontalCenter = true;
actionLabelWidget.updateAlignment();
// 创建矿石数量标签
const oreCountNode = new Node('OreCountLabel');
uiRoot.addChild(oreCountNode);
const oreCountTransform = oreCountNode.addComponent(UITransform);
oreCountTransform.setContentSize(100, 15);
const oreCountLabel = oreCountNode.addComponent(Label);
oreCountLabel.string = '💎0';
oreCountLabel.fontSize = 12;
oreCountLabel.color = new Color(255, 215, 0, 255); // 金色
// 设置矿石数量标签位置(居中显示)
const oreCountWidget = oreCountNode.addComponent(Widget);
oreCountWidget.isAlignTop = true;
oreCountWidget.top = 80;
oreCountWidget.isAlignHorizontalCenter = true;
oreCountWidget.updateAlignment();
statusUI.nameLabel = nameLabel;
statusUI.statusLabel = statusLabel;
statusUI.staminaBar = staminaBar;
statusUI.actionProgressBar = actionBar;
statusUI.actionLabel = actionLabel;
statusUI.oreCountLabel = oreCountLabel;
statusUI.warehouseCountLabel = null;
StatusUIManager.setNodeLayerRecursively(uiRoot, Layers.Enum.UI_2D);
return statusUI;
}
/**
* 递归设置节点及其子节点的层级
*/
private static setNodeLayerRecursively(node: Node, layer: number) {
node.layer = layer;
for (const child of node.children) {
StatusUIManager.setNodeLayerRecursively(child, layer);
}
}
/**
* 从矿工名字中提取索引号
*/
private static extractMinerIndex(minerName: string): number {
const match = minerName.match(/Miner_(\d+)/);
if (match) {
return parseInt(match[1]) - 1;
}
return 0;
}
/**
* 为仓库创建存储量显示UI
*/
static createWarehouseUI(warehouse: Node): MinerStatusUI | null {
const canvas = find('Canvas') || director.getScene()?.getChildByName('Canvas');
if (!canvas) {
console.error('[StatusUIManager] 未找到Canvas');
return null;
}
const uiRoot = new Node(`${warehouse.name}_StorageUI`);
canvas.addChild(uiRoot);
const uiTransform = uiRoot.addComponent(UITransform);
uiTransform.setContentSize(120, 40);
const backgroundNode = new Node('Background');
uiRoot.addChild(backgroundNode);
const backgroundTransform = backgroundNode.addComponent(UITransform);
backgroundTransform.setContentSize(120, 40);
const backgroundGraphics = backgroundNode.addComponent(Graphics);
backgroundGraphics.fillColor = new Color(0, 0, 0, 120);
backgroundGraphics.rect(-60, -20, 120, 40);
backgroundGraphics.fill();
const storageNode = new Node('StorageLabel');
uiRoot.addChild(storageNode);
const storageTransform = storageNode.addComponent(UITransform);
storageTransform.setContentSize(120, 30);
const storageLabel = storageNode.addComponent(Label);
storageLabel.string = '🏭 总存储: 0';
storageLabel.fontSize = 14;
storageLabel.color = new Color(255, 255, 255, 255);
const storageWidget = storageNode.addComponent(Widget);
storageWidget.isAlignHorizontalCenter = true;
storageWidget.isAlignVerticalCenter = true;
storageWidget.updateAlignment();
const statusUI = uiRoot.addComponent(MinerStatusUI);
statusUI.setFollowTarget(warehouse);
statusUI.yOffset = 150;
statusUI.nameLabel = null;
statusUI.statusLabel = null;
statusUI.staminaBar = null;
statusUI.actionProgressBar = null;
statusUI.actionLabel = null;
statusUI.oreCountLabel = null;
statusUI.warehouseCountLabel = storageLabel;
StatusUIManager.setNodeLayerRecursively(uiRoot, Layers.Enum.UI_2D);
return statusUI;
}
}

View File

@@ -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": {}

View File

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

View File

@@ -1,4 +1,4 @@
import { _decorator, Component, Node, Vec3, Material, MeshRenderer, Color, tween } from 'cc'; import { _decorator, Component, Node, Vec3, MeshRenderer, Color, tween } from 'cc';
import { BehaviorTreeManager } from './BehaviorTreeManager'; import { 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);
if (distance <= this.attackRange) {
this.lastAttackTime = currentTime; this.lastAttackTime = currentTime;
return true;
// 更新行为树黑板 }
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('lastAttackTime', currentTime);
} }
return true; // 攻击成功 return false;
} }
update(deltaTime: number) { 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) { if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('isMoving', false); this.behaviorTreeManager.update(deltaTime);
// 不要设置hasTarget为false让行为树自己管理
}
} else {
this.isMoving = true;
// 更新移动状态到黑板
if (this.behaviorTreeManager) {
this.behaviorTreeManager.updateBlackboardValue('isMoving', true);
}
}
} }
// 更新行为树黑板中的核心变量 if (this.isMoving && !this.targetPosition.equals(Vec3.ZERO)) {
if (this.behaviorTreeManager) { const reached = this.moveToTarget(this.targetPosition, this.moveSpeed, deltaTime);
// 基础属性更新 if (reached) {
this.behaviorTreeManager.updateBlackboardValue('currentHealth', this.currentHealth); this.clearTarget();
this.behaviorTreeManager.updateBlackboardValue('healthPercentage', this.currentHealth / this.maxHealth);
this.behaviorTreeManager.updateBlackboardValue('isLowHealth', this.currentHealth < this.maxHealth * 0.3);
// 命令状态更新
this.behaviorTreeManager.updateBlackboardValue('currentCommand', this.currentCommand);
this.behaviorTreeManager.updateBlackboardValue('hasTarget', this.targetPosition && !this.targetPosition.equals(Vec3.ZERO));
this.behaviorTreeManager.updateBlackboardValue('targetPosition', this.targetPosition);
this.behaviorTreeManager.updateBlackboardValue('isSelected', this.isSelected);
this.behaviorTreeManager.updateBlackboardValue('isMoving', this.isMoving);
// 位置信息更新
this.behaviorTreeManager.updateBlackboardValue('worldPosition', this.node.worldPosition);
// 根据单位类型设置特定的黑板变量
if (this.unitType === 'worker') {
// 工人特有的变量
// 这里可以添加工人特有的状态更新
} else if (this.unitType === 'soldier') {
// 士兵特有的变量
this.behaviorTreeManager.updateBlackboardValue('lastAttackTime', this.lastAttackTime);
} else if (this.unitType === 'scout') {
// 侦察兵特有的变量
// 这里可以添加侦察兵特有的状态更新
} }
} }

View File

@@ -1,9 +0,0 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "84a4754f-3fad-4031-8aeb-e699584cfb92",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -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}`);
}
}

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "00e982f3-dcf3-44b0-9b63-5e2877c1971e",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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"
}
}

View File

@@ -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"
}, },

View File

@@ -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"
} }
} }