Feature/ecs behavior tree (#188)
* feat(behavior-tree): 完全 ECS 化的行为树系统 * feat(editor-app): 添加行为树可视化编辑器 * chore: 移除 Cocos Creator 扩展目录 * feat(editor-app): 行为树编辑器功能增强 * fix(editor-app): 修复 TypeScript 类型错误 * feat(editor-app): 使用 FlexLayout 重构面板系统并优化资产浏览器 * feat(editor-app): 改进编辑器UI样式并修复行为树执行顺序 * feat(behavior-tree,editor-app): 添加装饰器系统并优化编辑器性能 * feat(behavior-tree,editor-app): 添加属性绑定系统 * feat(editor-app,behavior-tree): 优化编辑器UI并改进行为树功能 * feat(editor-app,behavior-tree): 添加全局黑板系统并增强资产浏览器功能 * feat(behavior-tree,editor-app): 添加运行时资产导出系统 * feat(behavior-tree,editor-app): 添加SubTree系统和资产选择器 * feat(behavior-tree,editor-app): 优化系统架构并改进编辑器文件管理 * fix(behavior-tree,editor-app): 修复SubTree节点错误显示空节点警告 * fix(editor-app): 修复局部黑板类型定义文件扩展名错误
This commit is contained in:
15
.gitmodules
vendored
15
.gitmodules
vendored
@@ -4,27 +4,12 @@
|
|||||||
[submodule "thirdparty/admin-backend"]
|
[submodule "thirdparty/admin-backend"]
|
||||||
path = thirdparty/admin-backend
|
path = thirdparty/admin-backend
|
||||||
url = https://github.com/esengine/admin-backend.git
|
url = https://github.com/esengine/admin-backend.git
|
||||||
[submodule "extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension"]
|
|
||||||
path = extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension
|
|
||||||
url = https://github.com/esengine/cocos-ecs-extension.git
|
|
||||||
[submodule "extensions/cocos/cocos-ecs/extensions/behaviour-tree"]
|
|
||||||
path = extensions/cocos/cocos-ecs/extensions/behaviour-tree
|
|
||||||
url = https://github.com/esengine/behaviour-tree.git
|
|
||||||
[submodule "extensions/cocos/cocos-ecs/extensions/cocos-terrain-gen"]
|
|
||||||
path = extensions/cocos/cocos-ecs/extensions/cocos-terrain-gen
|
|
||||||
url = https://github.com/esengine/cocos-terrain-gen.git
|
|
||||||
[submodule "extensions/cocos/cocos-ecs/extensions/mvvm-designer"]
|
|
||||||
path = extensions/cocos/cocos-ecs/extensions/mvvm-designer
|
|
||||||
url = https://github.com/esengine/mvvm-designer.git
|
|
||||||
[submodule "thirdparty/mvvm-ui-framework"]
|
[submodule "thirdparty/mvvm-ui-framework"]
|
||||||
path = thirdparty/mvvm-ui-framework
|
path = thirdparty/mvvm-ui-framework
|
||||||
url = https://github.com/esengine/mvvm-ui-framework.git
|
url = https://github.com/esengine/mvvm-ui-framework.git
|
||||||
[submodule "thirdparty/cocos-nexus"]
|
[submodule "thirdparty/cocos-nexus"]
|
||||||
path = thirdparty/cocos-nexus
|
path = thirdparty/cocos-nexus
|
||||||
url = https://github.com/esengine/cocos-nexus.git
|
url = https://github.com/esengine/cocos-nexus.git
|
||||||
[submodule "extensions/cocos/cocos-ecs/extensions/utilityai_designer"]
|
|
||||||
path = extensions/cocos/cocos-ecs/extensions/utilityai_designer
|
|
||||||
url = https://github.com/esengine/utilityai_designer.git
|
|
||||||
[submodule "thirdparty/ecs-astar"]
|
[submodule "thirdparty/ecs-astar"]
|
||||||
path = thirdparty/ecs-astar
|
path = thirdparty/ecs-astar
|
||||||
url = https://github.com/esengine/ecs-astar.git
|
url = https://github.com/esengine/ecs-astar.git
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
[InternetShortcut]
|
|
||||||
URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template
|
|
||||||
24
extensions/cocos/cocos-ecs/.gitignore
vendored
24
extensions/cocos/cocos-ecs/.gitignore
vendored
@@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
#///////////////////////////
|
|
||||||
# Cocos Creator 3D Project
|
|
||||||
#///////////////////////////
|
|
||||||
library/
|
|
||||||
temp/
|
|
||||||
local/
|
|
||||||
build/
|
|
||||||
profiles/
|
|
||||||
native
|
|
||||||
#//////////////////////////
|
|
||||||
# NPM
|
|
||||||
#//////////////////////////
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
#//////////////////////////
|
|
||||||
# VSCode
|
|
||||||
#//////////////////////////
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
#//////////////////////////
|
|
||||||
# WebStorm
|
|
||||||
#//////////////////////////
|
|
||||||
.idea/
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "2a691dda-d56d-4a72-9fef-111a999415db",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {
|
|
||||||
"isBundle": true,
|
|
||||||
"bundleConfigID": "default",
|
|
||||||
"bundleName": "resources",
|
|
||||||
"priority": 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "8c25761f-50d6-498b-a95f-d863bf1fbff1",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "3a66cbbc-6612-4408-838b-875d0bb2e9a3",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,317 +0,0 @@
|
|||||||
{
|
|
||||||
"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-25T14:06:55.596Z",
|
|
||||||
"version": "1.0",
|
|
||||||
"exportType": "clean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "2.0.1",
|
|
||||||
"importer": "json",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "598e1450-8c7a-46c7-9540-398f9809d627",
|
|
||||||
"files": [
|
|
||||||
".json"
|
|
||||||
],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.0.0",
|
|
||||||
"importer": "*",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "24c6e7e6-4ff0-4e7b-b470-9468bfa66b5d",
|
|
||||||
"files": [
|
|
||||||
".btree",
|
|
||||||
".json"
|
|
||||||
],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "2bf3ded8-4054-4d8f-a367-c76b21eaf538",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.1.50",
|
|
||||||
"importer": "prefab",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "51a6e245-2983-4258-be9f-9e21378f7f9f",
|
|
||||||
"files": [
|
|
||||||
".json"
|
|
||||||
],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {
|
|
||||||
"syncNodeName": "Panel_Node"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "4fd895f7-6b0f-4357-aa3a-7c2e88ffac9a",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "829183be-61a1-4494-bf64-3df359c0e8e7",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "240e4a78-e55f-47a8-84de-39220bba1321",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,757 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"__type__": "cc.SceneAsset",
|
|
||||||
"_name": "behaviour-example-scene",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_native": "",
|
|
||||||
"scene": {
|
|
||||||
"__id__": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Scene",
|
|
||||||
"_name": "behaviour-example-scene",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_parent": null,
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"__id__": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 14
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_active": true,
|
|
||||||
"_components": [],
|
|
||||||
"_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
|
|
||||||
},
|
|
||||||
"autoReleaseAssets": false,
|
|
||||||
"_globals": {
|
|
||||||
"__id__": 16
|
|
||||||
},
|
|
||||||
"_id": "ff354f0b-c2f5-4dea-8ffb-0152d175d11c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Node",
|
|
||||||
"_name": "Main Light",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_parent": {
|
|
||||||
"__id__": 1
|
|
||||||
},
|
|
||||||
"_children": [],
|
|
||||||
"_active": true,
|
|
||||||
"_components": [
|
|
||||||
{
|
|
||||||
"__id__": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_prefab": null,
|
|
||||||
"_lpos": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"_lrot": {
|
|
||||||
"__type__": "cc.Quat",
|
|
||||||
"x": -0.06397656665577071,
|
|
||||||
"y": -0.44608233363525845,
|
|
||||||
"z": -0.8239028751062036,
|
|
||||||
"w": -0.3436591377065261
|
|
||||||
},
|
|
||||||
"_lscale": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1
|
|
||||||
},
|
|
||||||
"_mobility": 0,
|
|
||||||
"_layer": 1073741824,
|
|
||||||
"_euler": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": -117.894,
|
|
||||||
"y": -194.909,
|
|
||||||
"z": 38.562
|
|
||||||
},
|
|
||||||
"_id": "c0y6F5f+pAvI805TdmxIjx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.DirectionalLight",
|
|
||||||
"_name": "",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"node": {
|
|
||||||
"__id__": 2
|
|
||||||
},
|
|
||||||
"_enabled": true,
|
|
||||||
"__prefab": null,
|
|
||||||
"_color": {
|
|
||||||
"__type__": "cc.Color",
|
|
||||||
"r": 255,
|
|
||||||
"g": 250,
|
|
||||||
"b": 240,
|
|
||||||
"a": 255
|
|
||||||
},
|
|
||||||
"_useColorTemperature": false,
|
|
||||||
"_colorTemperature": 6550,
|
|
||||||
"_staticSettings": {
|
|
||||||
"__id__": 4
|
|
||||||
},
|
|
||||||
"_visibility": -325058561,
|
|
||||||
"_illuminanceHDR": 65000,
|
|
||||||
"_illuminance": 65000,
|
|
||||||
"_illuminanceLDR": 1.6927083333333335,
|
|
||||||
"_shadowEnabled": false,
|
|
||||||
"_shadowPcf": 0,
|
|
||||||
"_shadowBias": 0.00001,
|
|
||||||
"_shadowNormalBias": 0,
|
|
||||||
"_shadowSaturation": 1,
|
|
||||||
"_shadowDistance": 50,
|
|
||||||
"_shadowInvisibleOcclusionRange": 200,
|
|
||||||
"_csmLevel": 4,
|
|
||||||
"_csmLayerLambda": 0.75,
|
|
||||||
"_csmOptimizationMode": 2,
|
|
||||||
"_csmAdvancedOptions": false,
|
|
||||||
"_csmLayersTransition": false,
|
|
||||||
"_csmTransitionRange": 0.05,
|
|
||||||
"_shadowFixedArea": false,
|
|
||||||
"_shadowNear": 0.1,
|
|
||||||
"_shadowFar": 10,
|
|
||||||
"_shadowOrthoSize": 5,
|
|
||||||
"_id": "597uMYCbhEtJQc0ffJlcgA"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.StaticLightSettings",
|
|
||||||
"_baked": false,
|
|
||||||
"_editorOnly": false,
|
|
||||||
"_castShadow": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Node",
|
|
||||||
"_name": "Main Camera",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_parent": {
|
|
||||||
"__id__": 1
|
|
||||||
},
|
|
||||||
"_children": [],
|
|
||||||
"_active": true,
|
|
||||||
"_components": [
|
|
||||||
{
|
|
||||||
"__id__": 6
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_prefab": null,
|
|
||||||
"_lpos": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": -10,
|
|
||||||
"y": 10,
|
|
||||||
"z": 10
|
|
||||||
},
|
|
||||||
"_lrot": {
|
|
||||||
"__type__": "cc.Quat",
|
|
||||||
"x": -0.27781593346944056,
|
|
||||||
"y": -0.36497167621709875,
|
|
||||||
"z": -0.11507512748638377,
|
|
||||||
"w": 0.8811195706053617
|
|
||||||
},
|
|
||||||
"_lscale": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1
|
|
||||||
},
|
|
||||||
"_mobility": 0,
|
|
||||||
"_layer": 1073741824,
|
|
||||||
"_euler": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": -35,
|
|
||||||
"y": -45,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"_id": "c9DMICJLFO5IeO07EPon7U"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Camera",
|
|
||||||
"_name": "",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"node": {
|
|
||||||
"__id__": 5
|
|
||||||
},
|
|
||||||
"_enabled": true,
|
|
||||||
"__prefab": null,
|
|
||||||
"_projection": 1,
|
|
||||||
"_priority": 0,
|
|
||||||
"_fov": 45,
|
|
||||||
"_fovAxis": 0,
|
|
||||||
"_orthoHeight": 10,
|
|
||||||
"_near": 1,
|
|
||||||
"_far": 1000,
|
|
||||||
"_color": {
|
|
||||||
"__type__": "cc.Color",
|
|
||||||
"r": 51,
|
|
||||||
"g": 51,
|
|
||||||
"b": 51,
|
|
||||||
"a": 255
|
|
||||||
},
|
|
||||||
"_depth": 1,
|
|
||||||
"_stencil": 0,
|
|
||||||
"_clearFlags": 14,
|
|
||||||
"_rect": {
|
|
||||||
"__type__": "cc.Rect",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
},
|
|
||||||
"_aperture": 19,
|
|
||||||
"_shutter": 7,
|
|
||||||
"_iso": 0,
|
|
||||||
"_screenScale": 1,
|
|
||||||
"_visibility": 1822425087,
|
|
||||||
"_targetTexture": null,
|
|
||||||
"_postProcess": null,
|
|
||||||
"_usePostProcess": false,
|
|
||||||
"_cameraType": -1,
|
|
||||||
"_trackingType": 0,
|
|
||||||
"_id": "7dWQTpwS5LrIHnc1zAPUtf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Node",
|
|
||||||
"_name": "GameWorld",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_parent": {
|
|
||||||
"__id__": 1
|
|
||||||
},
|
|
||||||
"_children": [],
|
|
||||||
"_active": true,
|
|
||||||
"_components": [],
|
|
||||||
"_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": "8b9QorrGZIl64tVv0Z0vRQ"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Node",
|
|
||||||
"_name": "Canvas",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_parent": {
|
|
||||||
"__id__": 1
|
|
||||||
},
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"__id__": 9
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_active": true,
|
|
||||||
"_components": [
|
|
||||||
{
|
|
||||||
"__id__": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 13
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_prefab": null,
|
|
||||||
"_lpos": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 640,
|
|
||||||
"y": 360,
|
|
||||||
"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": "4edRVPFLtIz5pR5edsryvx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Node",
|
|
||||||
"_name": "Camera",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_parent": {
|
|
||||||
"__id__": 8
|
|
||||||
},
|
|
||||||
"_children": [],
|
|
||||||
"_active": true,
|
|
||||||
"_components": [
|
|
||||||
{
|
|
||||||
"__id__": 10
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_prefab": null,
|
|
||||||
"_lpos": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 1000
|
|
||||||
},
|
|
||||||
"_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": "dfyZdh0bxJop4PyQrmHEP6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Camera",
|
|
||||||
"_name": "",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"node": {
|
|
||||||
"__id__": 9
|
|
||||||
},
|
|
||||||
"_enabled": true,
|
|
||||||
"__prefab": null,
|
|
||||||
"_projection": 0,
|
|
||||||
"_priority": 1073741824,
|
|
||||||
"_fov": 45,
|
|
||||||
"_fovAxis": 0,
|
|
||||||
"_orthoHeight": 360,
|
|
||||||
"_near": 1,
|
|
||||||
"_far": 2000,
|
|
||||||
"_color": {
|
|
||||||
"__type__": "cc.Color",
|
|
||||||
"r": 0,
|
|
||||||
"g": 0,
|
|
||||||
"b": 0,
|
|
||||||
"a": 255
|
|
||||||
},
|
|
||||||
"_depth": 1,
|
|
||||||
"_stencil": 0,
|
|
||||||
"_clearFlags": 6,
|
|
||||||
"_rect": {
|
|
||||||
"__type__": "cc.Rect",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
},
|
|
||||||
"_aperture": 19,
|
|
||||||
"_shutter": 7,
|
|
||||||
"_iso": 0,
|
|
||||||
"_screenScale": 1,
|
|
||||||
"_visibility": 41943040,
|
|
||||||
"_targetTexture": null,
|
|
||||||
"_postProcess": null,
|
|
||||||
"_usePostProcess": false,
|
|
||||||
"_cameraType": -1,
|
|
||||||
"_trackingType": 0,
|
|
||||||
"_id": "48lLOhLY5Onqokj70aNP+E"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.UITransform",
|
|
||||||
"_name": "",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"node": {
|
|
||||||
"__id__": 8
|
|
||||||
},
|
|
||||||
"_enabled": true,
|
|
||||||
"__prefab": null,
|
|
||||||
"_contentSize": {
|
|
||||||
"__type__": "cc.Size",
|
|
||||||
"width": 1280,
|
|
||||||
"height": 720
|
|
||||||
},
|
|
||||||
"_anchorPoint": {
|
|
||||||
"__type__": "cc.Vec2",
|
|
||||||
"x": 0.5,
|
|
||||||
"y": 0.5
|
|
||||||
},
|
|
||||||
"_id": "c3qBrLTLNImoltQDlZ6coz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Canvas",
|
|
||||||
"_name": "",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"node": {
|
|
||||||
"__id__": 8
|
|
||||||
},
|
|
||||||
"_enabled": true,
|
|
||||||
"__prefab": null,
|
|
||||||
"_cameraComponent": {
|
|
||||||
"__id__": 10
|
|
||||||
},
|
|
||||||
"_alignCanvasWithScreen": true,
|
|
||||||
"_id": "9d3SdE3ORAOZ6AG/imW6NO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Widget",
|
|
||||||
"_name": "",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"node": {
|
|
||||||
"__id__": 8
|
|
||||||
},
|
|
||||||
"_enabled": true,
|
|
||||||
"__prefab": null,
|
|
||||||
"_alignFlags": 45,
|
|
||||||
"_target": null,
|
|
||||||
"_left": 0,
|
|
||||||
"_right": 0,
|
|
||||||
"_top": 0,
|
|
||||||
"_bottom": 0,
|
|
||||||
"_horizontalCenter": 0,
|
|
||||||
"_verticalCenter": 0,
|
|
||||||
"_isAbsLeft": true,
|
|
||||||
"_isAbsRight": true,
|
|
||||||
"_isAbsTop": true,
|
|
||||||
"_isAbsBottom": true,
|
|
||||||
"_isAbsHorizontalCenter": true,
|
|
||||||
"_isAbsVerticalCenter": true,
|
|
||||||
"_originalWidth": 0,
|
|
||||||
"_originalHeight": 0,
|
|
||||||
"_alignMode": 2,
|
|
||||||
"_lockFlags": 0,
|
|
||||||
"_id": "4a8iJypC1J8pMml467hQ6c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.Node",
|
|
||||||
"_name": "RTSDemo",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"_parent": {
|
|
||||||
"__id__": 1
|
|
||||||
},
|
|
||||||
"_children": [],
|
|
||||||
"_active": true,
|
|
||||||
"_components": [
|
|
||||||
{
|
|
||||||
"__id__": 15
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_prefab": null,
|
|
||||||
"_lpos": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"_lrot": {
|
|
||||||
"__type__": "cc.Quat",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0,
|
|
||||||
"w": 1
|
|
||||||
},
|
|
||||||
"_lscale": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1
|
|
||||||
},
|
|
||||||
"_mobility": 0,
|
|
||||||
"_layer": 1073741824,
|
|
||||||
"_euler": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"_id": "89cmsd2gNNsq155xC7mob8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "c33869Km+9Bb7dw/OyRztvE",
|
|
||||||
"_name": "",
|
|
||||||
"_objFlags": 0,
|
|
||||||
"__editorExtras__": {},
|
|
||||||
"node": {
|
|
||||||
"__id__": 14
|
|
||||||
},
|
|
||||||
"_enabled": true,
|
|
||||||
"__prefab": null,
|
|
||||||
"minerCount": 1,
|
|
||||||
"goldMineCount": 3,
|
|
||||||
"_id": "86AIY7iYlMNqJsDC/+LIMU"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.SceneGlobals",
|
|
||||||
"ambient": {
|
|
||||||
"__id__": 17
|
|
||||||
},
|
|
||||||
"shadows": {
|
|
||||||
"__id__": 18
|
|
||||||
},
|
|
||||||
"_skybox": {
|
|
||||||
"__id__": 19
|
|
||||||
},
|
|
||||||
"fog": {
|
|
||||||
"__id__": 20
|
|
||||||
},
|
|
||||||
"octree": {
|
|
||||||
"__id__": 21
|
|
||||||
},
|
|
||||||
"skin": {
|
|
||||||
"__id__": 22
|
|
||||||
},
|
|
||||||
"lightProbeInfo": {
|
|
||||||
"__id__": 23
|
|
||||||
},
|
|
||||||
"postSettings": {
|
|
||||||
"__id__": 24
|
|
||||||
},
|
|
||||||
"bakedWithStationaryMainLight": false,
|
|
||||||
"bakedWithHighpLightmap": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.AmbientInfo",
|
|
||||||
"_skyColorHDR": {
|
|
||||||
"__type__": "cc.Vec4",
|
|
||||||
"x": 0.2,
|
|
||||||
"y": 0.5,
|
|
||||||
"z": 0.8,
|
|
||||||
"w": 0.520833125
|
|
||||||
},
|
|
||||||
"_skyColor": {
|
|
||||||
"__type__": "cc.Vec4",
|
|
||||||
"x": 0.2,
|
|
||||||
"y": 0.5,
|
|
||||||
"z": 0.8,
|
|
||||||
"w": 0.520833125
|
|
||||||
},
|
|
||||||
"_skyIllumHDR": 20000,
|
|
||||||
"_skyIllum": 20000,
|
|
||||||
"_groundAlbedoHDR": {
|
|
||||||
"__type__": "cc.Vec4",
|
|
||||||
"x": 0.2,
|
|
||||||
"y": 0.2,
|
|
||||||
"z": 0.2,
|
|
||||||
"w": 1
|
|
||||||
},
|
|
||||||
"_groundAlbedo": {
|
|
||||||
"__type__": "cc.Vec4",
|
|
||||||
"x": 0.2,
|
|
||||||
"y": 0.2,
|
|
||||||
"z": 0.2,
|
|
||||||
"w": 1
|
|
||||||
},
|
|
||||||
"_skyColorLDR": {
|
|
||||||
"__type__": "cc.Vec4",
|
|
||||||
"x": 0.452588,
|
|
||||||
"y": 0.607642,
|
|
||||||
"z": 0.755699,
|
|
||||||
"w": 0
|
|
||||||
},
|
|
||||||
"_skyIllumLDR": 0.8,
|
|
||||||
"_groundAlbedoLDR": {
|
|
||||||
"__type__": "cc.Vec4",
|
|
||||||
"x": 0.618555,
|
|
||||||
"y": 0.577848,
|
|
||||||
"z": 0.544564,
|
|
||||||
"w": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.ShadowsInfo",
|
|
||||||
"_enabled": false,
|
|
||||||
"_type": 0,
|
|
||||||
"_normal": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 0,
|
|
||||||
"y": 1,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"_distance": 0,
|
|
||||||
"_planeBias": 1,
|
|
||||||
"_shadowColor": {
|
|
||||||
"__type__": "cc.Color",
|
|
||||||
"r": 76,
|
|
||||||
"g": 76,
|
|
||||||
"b": 76,
|
|
||||||
"a": 255
|
|
||||||
},
|
|
||||||
"_maxReceived": 4,
|
|
||||||
"_size": {
|
|
||||||
"__type__": "cc.Vec2",
|
|
||||||
"x": 1024,
|
|
||||||
"y": 1024
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.SkyboxInfo",
|
|
||||||
"_envLightingType": 0,
|
|
||||||
"_envmapHDR": {
|
|
||||||
"__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0",
|
|
||||||
"__expectedType__": "cc.TextureCube"
|
|
||||||
},
|
|
||||||
"_envmap": {
|
|
||||||
"__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0",
|
|
||||||
"__expectedType__": "cc.TextureCube"
|
|
||||||
},
|
|
||||||
"_envmapLDR": {
|
|
||||||
"__uuid__": "6f01cf7f-81bf-4a7e-bd5d-0afc19696480@b47c0",
|
|
||||||
"__expectedType__": "cc.TextureCube"
|
|
||||||
},
|
|
||||||
"_diffuseMapHDR": null,
|
|
||||||
"_diffuseMapLDR": null,
|
|
||||||
"_enabled": true,
|
|
||||||
"_useHDR": true,
|
|
||||||
"_editableMaterial": null,
|
|
||||||
"_reflectionHDR": null,
|
|
||||||
"_reflectionLDR": null,
|
|
||||||
"_rotationAngle": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.FogInfo",
|
|
||||||
"_type": 0,
|
|
||||||
"_fogColor": {
|
|
||||||
"__type__": "cc.Color",
|
|
||||||
"r": 200,
|
|
||||||
"g": 200,
|
|
||||||
"b": 200,
|
|
||||||
"a": 255
|
|
||||||
},
|
|
||||||
"_enabled": false,
|
|
||||||
"_fogDensity": 0.3,
|
|
||||||
"_fogStart": 0.5,
|
|
||||||
"_fogEnd": 300,
|
|
||||||
"_fogAtten": 5,
|
|
||||||
"_fogTop": 1.5,
|
|
||||||
"_fogRange": 1.2,
|
|
||||||
"_accurate": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.OctreeInfo",
|
|
||||||
"_enabled": false,
|
|
||||||
"_minPos": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": -1024,
|
|
||||||
"y": -1024,
|
|
||||||
"z": -1024
|
|
||||||
},
|
|
||||||
"_maxPos": {
|
|
||||||
"__type__": "cc.Vec3",
|
|
||||||
"x": 1024,
|
|
||||||
"y": 1024,
|
|
||||||
"z": 1024
|
|
||||||
},
|
|
||||||
"_depth": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.SkinInfo",
|
|
||||||
"_enabled": true,
|
|
||||||
"_blurRadius": 0.01,
|
|
||||||
"_sssIntensity": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.LightProbeInfo",
|
|
||||||
"_giScale": 1,
|
|
||||||
"_giSamples": 1024,
|
|
||||||
"_bounces": 2,
|
|
||||||
"_reduceRinging": 0,
|
|
||||||
"_showProbe": true,
|
|
||||||
"_showWireframe": true,
|
|
||||||
"_showConvex": false,
|
|
||||||
"_data": null,
|
|
||||||
"_lightProbeSphereVolume": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__type__": "cc.PostSettingsInfo",
|
|
||||||
"_toneMappingType": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.1.50",
|
|
||||||
"importer": "scene",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "ff354f0b-c2f5-4dea-8ffb-0152d175d11c",
|
|
||||||
"files": [
|
|
||||||
".json"
|
|
||||||
],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.1.50",
|
|
||||||
"importer": "scene",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "fcbf2917-6d43-4528-8829-7ee089594879",
|
|
||||||
"files": [
|
|
||||||
".json"
|
|
||||||
],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "1556cd72-9618-4f9f-b9e7-28152a33bde9",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
import { _decorator, Component, Node, Vec3, Color } from 'cc';
|
|
||||||
import { SimplePrefabFactory } from './components/SimplePrefabFactory';
|
|
||||||
import { BehaviorTreeComponent } from './components/BehaviorTreeComponent';
|
|
||||||
import { StatusUIManager } from './components/StatusUIManager';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 矿工AI演示场景
|
|
||||||
*/
|
|
||||||
@ccclass('SimpleMinerDemo')
|
|
||||||
export class SimpleMinerDemo extends Component {
|
|
||||||
|
|
||||||
@property
|
|
||||||
minerCount: number = 1;
|
|
||||||
|
|
||||||
@property
|
|
||||||
goldMineCount: number = 3;
|
|
||||||
|
|
||||||
private miners: Node[] = [];
|
|
||||||
private goldMines: Node[] = [];
|
|
||||||
private warehouse: Node | null = null;
|
|
||||||
private ground: Node | null = null;
|
|
||||||
private totalOresCollected: number = 0;
|
|
||||||
private warehouseUI: any = null;
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.createWorld();
|
|
||||||
this.createWarehouse();
|
|
||||||
this.createGoldMines();
|
|
||||||
this.createMiners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private createWorld() {
|
|
||||||
this.ground = SimplePrefabFactory.createGround(new Vec3(20, 0.2, 20));
|
|
||||||
this.node.addChild(this.ground);
|
|
||||||
this.ground.setWorldPosition(new Vec3(0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private createWarehouse() {
|
|
||||||
this.warehouse = SimplePrefabFactory.createBuilding('Warehouse', new Vec3(2, 2, 2), Color.GRAY);
|
|
||||||
this.node.addChild(this.warehouse);
|
|
||||||
this.warehouse.setWorldPosition(new Vec3(0, 1, 0));
|
|
||||||
this.createWarehouseUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private createGoldMines() {
|
|
||||||
for (let i = 0; i < this.goldMineCount; i++) {
|
|
||||||
const angle = (i / this.goldMineCount) * Math.PI * 2;
|
|
||||||
const radius = 6 + Math.random() * 2;
|
|
||||||
const position = new Vec3(
|
|
||||||
Math.cos(angle) * radius,
|
|
||||||
0.8,
|
|
||||||
Math.sin(angle) * radius
|
|
||||||
);
|
|
||||||
|
|
||||||
const goldMine = SimplePrefabFactory.createResource(`GoldMine_${i + 1}`, Color.YELLOW);
|
|
||||||
this.node.addChild(goldMine);
|
|
||||||
goldMine.setWorldPosition(position);
|
|
||||||
goldMine.setScale(new Vec3(1.2, 1.2, 1.2));
|
|
||||||
this.goldMines.push(goldMine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createMiners() {
|
|
||||||
for (let i = 0; i < this.minerCount; i++) {
|
|
||||||
const angle = (i / this.minerCount) * Math.PI * 2;
|
|
||||||
const radius = 3;
|
|
||||||
const position = new Vec3(
|
|
||||||
Math.cos(angle) * radius,
|
|
||||||
1,
|
|
||||||
Math.sin(angle) * radius
|
|
||||||
);
|
|
||||||
|
|
||||||
const miner = SimplePrefabFactory.createUnit(`Miner_${i + 1}`, Color.BLUE);
|
|
||||||
this.node.addChild(miner);
|
|
||||||
miner.setWorldPosition(position);
|
|
||||||
|
|
||||||
const behaviorTree = miner.addComponent(BehaviorTreeComponent);
|
|
||||||
behaviorTree.behaviorTreeFile = 'miner-stamina-ai.bt';
|
|
||||||
behaviorTree.debugMode = true;
|
|
||||||
|
|
||||||
this.scheduleOnce(() => {
|
|
||||||
const blackboard = behaviorTree.getBlackboard();
|
|
||||||
if (blackboard) {
|
|
||||||
blackboard.setValue('homePosition', position.clone());
|
|
||||||
}
|
|
||||||
}, 0.5);
|
|
||||||
|
|
||||||
this.miners.push(miner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAllGoldMines(): Node[] {
|
|
||||||
return this.goldMines.filter(mine => mine && mine.isValid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWarehouse(): Node | null {
|
|
||||||
return this.warehouse;
|
|
||||||
}
|
|
||||||
|
|
||||||
public mineGoldOre(miner: Node): boolean {
|
|
||||||
this.totalOresCollected++;
|
|
||||||
this.updateWarehouseUI();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalOresCollected(): number {
|
|
||||||
return this.totalOresCollected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createWarehouseUI() {
|
|
||||||
if (!this.warehouse) return;
|
|
||||||
|
|
||||||
this.warehouseUI = StatusUIManager.createWarehouseUI(this.warehouse);
|
|
||||||
if (this.warehouseUI) {
|
|
||||||
this.updateWarehouseUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateWarehouseUI() {
|
|
||||||
if (this.warehouseUI && this.warehouseUI.warehouseCountLabel) {
|
|
||||||
this.warehouseUI.warehouseCountLabel.string = `🏭 总存储: ${this.totalOresCollected}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
this.unscheduleAllCallbacks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "c3386f4a-9bef-416f-b770-fcec91cedbc4",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "d07d95ad-f180-4b6e-9d0a-7248e75ec795",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,518 +0,0 @@
|
|||||||
import { Node, resources, JsonAsset, Component, _decorator, Vec3, tween, instantiate, Prefab } from 'cc';
|
|
||||||
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, TaskStatus, BehaviorTreeJSONConfig, EventRegistry, IBehaviorTreeContext, ActionResult } from '@esengine/ai';
|
|
||||||
import { MinerStatusUI } from './MinerStatusUI';
|
|
||||||
import { StatusUIManager } from './StatusUIManager';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
@ccclass('BehaviorTreeComponent')
|
|
||||||
export class BehaviorTreeComponent extends Component {
|
|
||||||
|
|
||||||
@property
|
|
||||||
behaviorTreeFile: string = '';
|
|
||||||
|
|
||||||
@property
|
|
||||||
autoStart: boolean = true;
|
|
||||||
|
|
||||||
@property
|
|
||||||
debugMode: boolean = false;
|
|
||||||
|
|
||||||
@property
|
|
||||||
showStatusUI: boolean = true;
|
|
||||||
|
|
||||||
@property(Prefab)
|
|
||||||
statusUIPrefab: Prefab | null = null;
|
|
||||||
|
|
||||||
private behaviorTree: BehaviorTree<any> | null = null;
|
|
||||||
private statusUI: MinerStatusUI | null = null;
|
|
||||||
private blackboard: Blackboard | null = null;
|
|
||||||
private context: any = null;
|
|
||||||
private eventRegistry: EventRegistry | null = null;
|
|
||||||
private isLoaded: boolean = false;
|
|
||||||
private isRunning: boolean = false;
|
|
||||||
|
|
||||||
private actionStates: Map<string, {
|
|
||||||
isExecuting: boolean;
|
|
||||||
startTime: number;
|
|
||||||
duration: number;
|
|
||||||
}> = new Map();
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if (this.autoStart && this.behaviorTreeFile) {
|
|
||||||
this.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.showStatusUI) {
|
|
||||||
this.createStatusUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
if (!this.behaviorTreeFile) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.loadBehaviorTree();
|
|
||||||
this.isLoaded = true;
|
|
||||||
this.isRunning = true;
|
|
||||||
} catch (error) {
|
|
||||||
// 静默处理
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadBehaviorTree(): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let jsonPath = this.behaviorTreeFile;
|
|
||||||
resources.load(jsonPath, JsonAsset, (err, asset) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const treeData = asset.json as BehaviorTreeJSONConfig;
|
|
||||||
this.buildBehaviorTree(treeData);
|
|
||||||
resolve();
|
|
||||||
} catch (buildError) {
|
|
||||||
reject(buildError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildBehaviorTree(treeData: BehaviorTreeJSONConfig) {
|
|
||||||
this.eventRegistry = new EventRegistry();
|
|
||||||
this.setupEventHandlers();
|
|
||||||
|
|
||||||
const baseContext = {
|
|
||||||
node: this.node,
|
|
||||||
component: this,
|
|
||||||
eventRegistry: this.eventRegistry
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(treeData, baseContext);
|
|
||||||
this.behaviorTree = result.tree;
|
|
||||||
this.blackboard = result.blackboard;
|
|
||||||
this.context = result.context;
|
|
||||||
|
|
||||||
this.initializeBlackboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupEventHandlers() {
|
|
||||||
if (!this.eventRegistry) return;
|
|
||||||
|
|
||||||
this.eventRegistry.registerAction('go-home-rest', (context, params) => {
|
|
||||||
return this.handleGoHomeRest(context, params);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventRegistry.registerAction('recover-stamina', (context, params) => {
|
|
||||||
return this.handleRecoverStamina(context, params);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventRegistry.registerAction('store-ore', (context, params) => {
|
|
||||||
return this.handleStoreOre(context, params);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventRegistry.registerAction('mine-gold-ore', (context, params) => {
|
|
||||||
return this.handleMineGoldOre(context, params);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventRegistry.registerAction('idle-behavior', (context, params) => {
|
|
||||||
return this.handleIdleBehavior(context, params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeBlackboard() {
|
|
||||||
if (!this.blackboard) return;
|
|
||||||
|
|
||||||
this.blackboard.setValue('stamina', 100);
|
|
||||||
this.blackboard.setValue('staminaPercentage', 1.0);
|
|
||||||
this.blackboard.setValue('isLowStamina', false);
|
|
||||||
this.blackboard.setValue('hasOre', false);
|
|
||||||
this.blackboard.setValue('isResting', false);
|
|
||||||
this.blackboard.setValue('homePosition', this.node.worldPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private createStatusUI() {
|
|
||||||
if (!this.statusUIPrefab) {
|
|
||||||
this.createSimpleStatusUI();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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';
|
|
||||||
|
|
||||||
// 检查是否已经在家了
|
|
||||||
const homePos = blackboard.getValue('homePosition') || this.node.worldPosition;
|
|
||||||
const distance = Vec3.distance(this.node.worldPosition, homePos);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (distance > 1.0) {
|
|
||||||
// 还没到家,继续移动
|
|
||||||
this.moveToPosition(homePos, 2.0);
|
|
||||||
return 'running';
|
|
||||||
} else {
|
|
||||||
this.clearActionState('mine-gold-ore');
|
|
||||||
this.clearActionState('store-ore');
|
|
||||||
blackboard.setValue('isResting', true);
|
|
||||||
const actionKey = 'go-home-rest';
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
// 初始化休息状态
|
|
||||||
if (!this.actionStates.has(actionKey)) {
|
|
||||||
this.actionStates.set(actionKey, {
|
|
||||||
isExecuting: true,
|
|
||||||
startTime: currentTime,
|
|
||||||
duration: 2000 // 2秒恢复一次
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
|
|
||||||
blackboard.setValue('stamina', newStamina);
|
|
||||||
blackboard.setValue('staminaPercentage', newStamina / 100);
|
|
||||||
|
|
||||||
if (newStamina >= 80) {
|
|
||||||
blackboard.setValue('isResting', false);
|
|
||||||
blackboard.setValue('isLowStamina', false);
|
|
||||||
this.actionStates.delete(actionKey);
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
|
|
||||||
actionState.startTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRecoverStamina(context: any, params: any): ActionResult {
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
const isResting = blackboard.getValue('isResting');
|
|
||||||
|
|
||||||
if (hasOre || isLowStamina || isResting) {
|
|
||||||
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
|
|
||||||
});
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLowStamina = blackboard.getValue('isLowStamina');
|
|
||||||
if (isLowStamina) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.clearActionState('mine-gold-ore');
|
|
||||||
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
|
|
||||||
});
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionState = this.actionStates.get(actionKey)!;
|
|
||||||
const elapsed = currentTime - actionState.startTime;
|
|
||||||
|
|
||||||
if (elapsed >= actionState.duration) {
|
|
||||||
blackboard.setValue('hasOre', false);
|
|
||||||
(gameManager as any).mineGoldOre(this.node);
|
|
||||||
this.actionStates.delete(actionKey);
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleIdleBehavior(context: any, params: any): ActionResult {
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
|
|
||||||
private moveToPosition(targetPos: Vec3, duration: number) {
|
|
||||||
tween(this.node).stop();
|
|
||||||
tween(this.node).to(duration, { worldPosition: targetPos }).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaTime: number) {
|
|
||||||
if (this.behaviorTree && this.isRunning) {
|
|
||||||
this.behaviorTree.tick(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.showStatusUI) {
|
|
||||||
this.updateStatusUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取黑板
|
|
||||||
*/
|
|
||||||
getBlackboard(): Blackboard | null {
|
|
||||||
return this.blackboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取行为树
|
|
||||||
*/
|
|
||||||
getBehaviorTree(): BehaviorTree<any> | null {
|
|
||||||
return this.behaviorTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 暂停行为树
|
|
||||||
*/
|
|
||||||
pause() {
|
|
||||||
this.isRunning = false;
|
|
||||||
if (this.debugMode) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复行为树
|
|
||||||
*/
|
|
||||||
resume() {
|
|
||||||
if (this.isLoaded) {
|
|
||||||
this.isRunning = true;
|
|
||||||
if (this.debugMode) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止行为树
|
|
||||||
*/
|
|
||||||
stop() {
|
|
||||||
this.isRunning = false;
|
|
||||||
if (this.behaviorTree) {
|
|
||||||
this.behaviorTree.reset();
|
|
||||||
}
|
|
||||||
if (this.debugMode) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新加载行为树
|
|
||||||
*/
|
|
||||||
async reload() {
|
|
||||||
this.stop();
|
|
||||||
await this.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置行为树状态
|
|
||||||
*/
|
|
||||||
reset() {
|
|
||||||
if (this.behaviorTree) {
|
|
||||||
this.behaviorTree.reset();
|
|
||||||
}
|
|
||||||
if (this.debugMode) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
this.stop();
|
|
||||||
if (this.eventRegistry) {
|
|
||||||
this.eventRegistry.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理UI
|
|
||||||
if (this.statusUI) {
|
|
||||||
this.statusUI.node.destroy();
|
|
||||||
this.statusUI = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.behaviorTree = null;
|
|
||||||
this.blackboard = null;
|
|
||||||
this.context = null;
|
|
||||||
this.eventRegistry = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "8efd182b-9891-4903-bef2-eb07b5184263",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
import { _decorator, Component, resources, JsonAsset, Vec3 } from 'cc';
|
|
||||||
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, BehaviorTreeJSONConfig, ExecutionContext, EventRegistry, ActionResult } from '@esengine/ai';
|
|
||||||
import { UnitController } from './UnitController';
|
|
||||||
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 游戏执行上下文接口
|
|
||||||
* 继承框架的ExecutionContext,添加游戏特定的属性
|
|
||||||
*/
|
|
||||||
interface GameExecutionContext extends ExecutionContext {
|
|
||||||
unitController: UnitController;
|
|
||||||
gameObject: any;
|
|
||||||
eventRegistry?: EventRegistry;
|
|
||||||
// 确保继承索引签名
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行为树管理器 - 使用@esengine/ai包管理行为树
|
|
||||||
*/
|
|
||||||
@ccclass('BehaviorTreeManager')
|
|
||||||
export class BehaviorTreeManager extends Component {
|
|
||||||
|
|
||||||
@property
|
|
||||||
debugMode: boolean = true;
|
|
||||||
|
|
||||||
@property
|
|
||||||
tickInterval: number = 0.1; // 行为树更新间隔(秒)- 10fps更新频率,平衡性能和响应性
|
|
||||||
|
|
||||||
private behaviorTree: BehaviorTree<GameExecutionContext> | null = null;
|
|
||||||
private blackboard: Blackboard | null = null;
|
|
||||||
private context: GameExecutionContext | null = null;
|
|
||||||
private eventRegistry: EventRegistry | null = null;
|
|
||||||
private isLoaded: boolean = false;
|
|
||||||
private isRunning: boolean = false;
|
|
||||||
private lastTickTime: number = 0;
|
|
||||||
private unitController: UnitController | null = null;
|
|
||||||
private currentBehaviorTreeName: string = '';
|
|
||||||
private behaviorHandler: RTSBehaviorHandler | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化行为树
|
|
||||||
*/
|
|
||||||
async initializeBehaviorTree(behaviorTreeName: string, unitController: UnitController) {
|
|
||||||
this.currentBehaviorTreeName = behaviorTreeName;
|
|
||||||
this.unitController = unitController;
|
|
||||||
|
|
||||||
// 获取RTSBehaviorHandler组件
|
|
||||||
this.behaviorHandler = this.getComponent(RTSBehaviorHandler);
|
|
||||||
if (!this.behaviorHandler) {
|
|
||||||
console.error(`BehaviorTreeManager: 未找到RTSBehaviorHandler组件 - ${this.node.name}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.loadBehaviorTree(behaviorTreeName);
|
|
||||||
this.setupBlackboard();
|
|
||||||
this.isLoaded = true;
|
|
||||||
this.isRunning = true;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`行为树初始化失败: ${behaviorTreeName}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载行为树文件
|
|
||||||
*/
|
|
||||||
private async loadBehaviorTree(behaviorTreeName: string): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const jsonPath = `${behaviorTreeName}.bt`;
|
|
||||||
|
|
||||||
|
|
||||||
resources.load(jsonPath, JsonAsset, (err, asset) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(`加载行为树文件失败: ${jsonPath}`, err);
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const behaviorTreeData = asset.json as BehaviorTreeJSONConfig;
|
|
||||||
|
|
||||||
// 创建执行上下文
|
|
||||||
this.blackboard = new Blackboard();
|
|
||||||
this.eventRegistry = this.createEventRegistry();
|
|
||||||
this.context = {
|
|
||||||
blackboard: this.blackboard,
|
|
||||||
unitController: this.unitController!,
|
|
||||||
gameObject: this.node,
|
|
||||||
eventRegistry: this.eventRegistry
|
|
||||||
} as GameExecutionContext;
|
|
||||||
|
|
||||||
// 从JSON数据创建行为树
|
|
||||||
const buildResult = BehaviorTreeBuilder.fromBehaviorTreeConfig<GameExecutionContext>(behaviorTreeData, this.context);
|
|
||||||
this.behaviorTree = buildResult.tree;
|
|
||||||
this.blackboard = buildResult.blackboard;
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error(`创建行为树失败: ${jsonPath}`, parseError);
|
|
||||||
reject(parseError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建事件注册表
|
|
||||||
*/
|
|
||||||
private createEventRegistry(): EventRegistry {
|
|
||||||
const registry = new EventRegistry();
|
|
||||||
|
|
||||||
// 注册体力系统矿工行为事件处理器
|
|
||||||
const eventHandlers = {
|
|
||||||
// 矿工体力系统核心行为
|
|
||||||
'mine-gold-ore': (context: any, params?: any) => this.callBehaviorHandler('onMineGoldOre', params),
|
|
||||||
'store-ore': (context: any, params?: any) => this.callBehaviorHandler('onStoreOre', params),
|
|
||||||
'go-home-rest': (context: any, params?: any) => this.callBehaviorHandler('onGoHomeRest', params),
|
|
||||||
'recover-stamina': (context: any, params?: any) => this.callBehaviorHandler('onRecoverStamina', params),
|
|
||||||
'idle-behavior': (context: any, params?: any) => this.callBehaviorHandler('onIdleBehavior', params)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 将事件处理器注册到EventRegistry
|
|
||||||
Object.entries(eventHandlers).forEach(([eventName, handler]) => {
|
|
||||||
registry.registerAction(eventName, handler);
|
|
||||||
});
|
|
||||||
|
|
||||||
return registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调用行为处理器的方法
|
|
||||||
*/
|
|
||||||
private callBehaviorHandler(methodName: string, params: any = {}): ActionResult {
|
|
||||||
if (!this.behaviorHandler) {
|
|
||||||
console.error(`BehaviorTreeManager: RTSBehaviorHandler未初始化 - ${this.node.name}`);
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 直接调用RTSBehaviorHandler的方法
|
|
||||||
const method = (this.behaviorHandler as any)[methodName];
|
|
||||||
if (typeof method === 'function') {
|
|
||||||
const result = method.call(this.behaviorHandler, params);
|
|
||||||
return result || 'success'; // 确保有返回值
|
|
||||||
} else {
|
|
||||||
console.error(`BehaviorTreeManager: 方法不存在: ${methodName}`);
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`BehaviorTreeManager: 调用方法失败: ${methodName}`, error);
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置黑板基础信息
|
|
||||||
*/
|
|
||||||
private setupBlackboard() {
|
|
||||||
if (!this.unitController || !this.blackboard) return;
|
|
||||||
|
|
||||||
// 设置矿工基础信息
|
|
||||||
this.blackboard.setValue('unitType', this.unitController.unitType);
|
|
||||||
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
|
|
||||||
this.blackboard.setValue('maxHealth', this.unitController.maxHealth);
|
|
||||||
this.blackboard.setValue('currentCommand', 'mine');
|
|
||||||
this.blackboard.setValue('hasOre', false);
|
|
||||||
this.blackboard.setValue('hasTarget', false);
|
|
||||||
this.blackboard.setValue('targetPosition', null);
|
|
||||||
this.blackboard.setValue('isMoving', false);
|
|
||||||
|
|
||||||
// 设置体力系统信息
|
|
||||||
this.blackboard.setValue('stamina', this.unitController.currentStamina);
|
|
||||||
this.blackboard.setValue('maxStamina', this.unitController.maxStamina);
|
|
||||||
this.blackboard.setValue('staminaPercentage', this.unitController.currentStamina / this.unitController.maxStamina);
|
|
||||||
this.blackboard.setValue('isLowStamina', this.unitController.currentStamina < this.unitController.maxStamina * 0.2);
|
|
||||||
this.blackboard.setValue('isResting', false);
|
|
||||||
this.blackboard.setValue('homePosition', this.unitController.homePosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新黑板值
|
|
||||||
*/
|
|
||||||
updateBlackboardValue(key: string, value: any) {
|
|
||||||
if (this.blackboard) {
|
|
||||||
this.blackboard.setValue(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取黑板值
|
|
||||||
*/
|
|
||||||
getBlackboardValue(key: string): any {
|
|
||||||
return this.blackboard?.getValue(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取黑板
|
|
||||||
*/
|
|
||||||
getBlackboard(): Blackboard | null {
|
|
||||||
return this.blackboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取行为树
|
|
||||||
*/
|
|
||||||
getBehaviorTree(): BehaviorTree<GameExecutionContext> | null {
|
|
||||||
return this.behaviorTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新行为树
|
|
||||||
*/
|
|
||||||
update(deltaTime: number) {
|
|
||||||
if (!this.isLoaded || !this.isRunning || !this.behaviorTree || !this.blackboard) return;
|
|
||||||
|
|
||||||
// 控制更新频率
|
|
||||||
this.lastTickTime += deltaTime;
|
|
||||||
if (this.lastTickTime < this.tickInterval) return;
|
|
||||||
|
|
||||||
this.lastTickTime = 0;
|
|
||||||
|
|
||||||
// 更新矿工状态信息
|
|
||||||
if (this.unitController) {
|
|
||||||
// 基础属性
|
|
||||||
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
|
|
||||||
this.blackboard.setValue('currentCommand', this.unitController.currentCommand);
|
|
||||||
this.blackboard.setValue('hasTarget', this.unitController.targetPosition && !this.unitController.targetPosition.equals(Vec3.ZERO));
|
|
||||||
this.blackboard.setValue('targetPosition', this.unitController.targetPosition);
|
|
||||||
this.blackboard.setValue('isMoving', this.unitController.targetPosition && !this.unitController.targetPosition.equals(Vec3.ZERO));
|
|
||||||
|
|
||||||
// 体力系统状态
|
|
||||||
this.blackboard.setValue('stamina', this.unitController.currentStamina);
|
|
||||||
this.blackboard.setValue('maxStamina', this.unitController.maxStamina);
|
|
||||||
this.blackboard.setValue('staminaPercentage', this.unitController.currentStamina / this.unitController.maxStamina);
|
|
||||||
this.blackboard.setValue('isLowStamina', this.unitController.currentStamina < this.unitController.maxStamina * 0.2);
|
|
||||||
this.blackboard.setValue('homePosition', this.unitController.homePosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行行为树
|
|
||||||
try {
|
|
||||||
this.behaviorTree.tick(deltaTime);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`行为树执行错误: ${this.node.name}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 暂停行为树
|
|
||||||
*/
|
|
||||||
pause() {
|
|
||||||
this.isRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复行为树
|
|
||||||
*/
|
|
||||||
resume() {
|
|
||||||
if (this.isLoaded) {
|
|
||||||
this.isRunning = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止行为树
|
|
||||||
*/
|
|
||||||
stop() {
|
|
||||||
this.isRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新加载行为树
|
|
||||||
*/
|
|
||||||
async reloadBehaviorTree() {
|
|
||||||
if (this.currentBehaviorTreeName && this.unitController) {
|
|
||||||
this.stop();
|
|
||||||
await this.initializeBehaviorTree(this.currentBehaviorTreeName, this.unitController);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置行为树
|
|
||||||
*/
|
|
||||||
reset() {
|
|
||||||
if (this.behaviorTree) {
|
|
||||||
this.behaviorTree.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
this.stop();
|
|
||||||
this.behaviorTree = null;
|
|
||||||
this.blackboard = null;
|
|
||||||
this.context = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "891c88fc-282d-4791-a961-8d85244bfee7",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
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.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();
|
|
||||||
// 根据目标类型设置不同的Y偏移
|
|
||||||
if (this.followTarget.name.includes('Warehouse')) {
|
|
||||||
targetWorldPos.y += 3.0; // 仓库偏移更高
|
|
||||||
} else {
|
|
||||||
targetWorldPos.y += 2.0; // 矿工偏移
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将世界坐标直接转换为UI坐标
|
|
||||||
const uiPos = new Vec3();
|
|
||||||
this.camera.convertToUINode(targetWorldPos, this.canvas.node, uiPos);
|
|
||||||
this.node.setPosition(uiPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "5f877c25-5c26-49c6-bbb5-7ff36323e0a1",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
import { _decorator, Component, Vec3, Node } from 'cc';
|
|
||||||
import { UnitController } from './UnitController';
|
|
||||||
|
|
||||||
const { ccclass } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 矿工体力系统行为处理器 - 处理挖矿、休息、存储的完整循环
|
|
||||||
* 展示体力驱动的工作-休息循环系统
|
|
||||||
*/
|
|
||||||
@ccclass('RTSBehaviorHandler')
|
|
||||||
export class RTSBehaviorHandler extends Component {
|
|
||||||
|
|
||||||
private unitController: UnitController | null = null;
|
|
||||||
private minerDemo: any = null; // MinerDemo组件引用
|
|
||||||
private lastActionTime: number = 0;
|
|
||||||
private actionCooldown: number = 0.5; // 动作冷却时间,避免频繁切换
|
|
||||||
private minerIndex: number = -1; // 矿工索引,用于找到对应的家
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.unitController = this.getComponent(UnitController);
|
|
||||||
// 获取场景中的MinerDemo组件
|
|
||||||
this.minerDemo = this.node.parent?.getComponent('MinerDemo');
|
|
||||||
|
|
||||||
if (!this.unitController) {
|
|
||||||
console.error('RTSBehaviorHandler: 未找到UnitController组件');
|
|
||||||
}
|
|
||||||
if (!this.minerDemo) {
|
|
||||||
console.error('RTSBehaviorHandler: 未找到MinerDemo组件');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从节点名称中提取矿工索引
|
|
||||||
const match = this.node.name.match(/Miner_(\d+)/);
|
|
||||||
if (match) {
|
|
||||||
this.minerIndex = parseInt(match[1]) - 1; // 转换为0基索引
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastActionTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查动作冷却
|
|
||||||
*/
|
|
||||||
private isActionOnCooldown(): boolean {
|
|
||||||
return (Date.now() - this.lastActionTime) < (this.actionCooldown * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新动作时间
|
|
||||||
*/
|
|
||||||
private updateActionTime() {
|
|
||||||
this.lastActionTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 挖掘金矿(永不枯竭)
|
|
||||||
* @param params 事件参数,包含黑板变量值
|
|
||||||
*/
|
|
||||||
onMineGoldOre(params: any = {}): string {
|
|
||||||
if (!this.unitController || !this.minerDemo) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查体力是否充足
|
|
||||||
if (this.unitController.currentStamina < this.unitController.staminaCostPerMining) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已经携带矿石
|
|
||||||
const hasOre = this.unitController.getBlackboardValue('hasOre');
|
|
||||||
if (hasOre) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动作冷却检查
|
|
||||||
if (this.isActionOnCooldown()) {
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有金矿
|
|
||||||
const goldMines = this.minerDemo.getAllGoldMines();
|
|
||||||
if (goldMines.length === 0) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 寻找最近的金矿
|
|
||||||
const currentPos = this.node.worldPosition;
|
|
||||||
let nearestMine: Node | null = null;
|
|
||||||
let minDistance = Infinity;
|
|
||||||
|
|
||||||
for (const mine of goldMines) {
|
|
||||||
if (!mine || !mine.isValid) continue;
|
|
||||||
|
|
||||||
const distance = Vec3.distance(currentPos, mine.worldPosition);
|
|
||||||
if (distance < minDistance) {
|
|
||||||
minDistance = distance;
|
|
||||||
nearestMine = mine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nearestMine) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已经到达金矿位置
|
|
||||||
if (minDistance < 2.0) {
|
|
||||||
// 检查是否正在移动
|
|
||||||
const isMoving = this.unitController.getBlackboardValue('isMoving');
|
|
||||||
if (isMoving) {
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消耗体力
|
|
||||||
this.unitController.currentStamina = Math.max(0, this.unitController.currentStamina - this.unitController.staminaCostPerMining);
|
|
||||||
|
|
||||||
// 设置携带矿石状态
|
|
||||||
this.unitController.setBlackboardValue('hasOre', true);
|
|
||||||
|
|
||||||
// 通知演示管理器
|
|
||||||
this.minerDemo.mineGoldOre(this.node);
|
|
||||||
|
|
||||||
// 清除移动目标
|
|
||||||
this.unitController.clearTarget();
|
|
||||||
this.unitController.setBlackboardValue('isMoving', false);
|
|
||||||
|
|
||||||
this.updateActionTime();
|
|
||||||
return 'success';
|
|
||||||
} else {
|
|
||||||
// 设置移动目标
|
|
||||||
this.unitController.setTarget(nearestMine.worldPosition);
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 前往仓库存储矿石
|
|
||||||
* @param params 事件参数,包含黑板变量值
|
|
||||||
*/
|
|
||||||
onStoreOre(params: any = {}): string {
|
|
||||||
if (!this.unitController || !this.minerDemo) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否携带矿石
|
|
||||||
const hasOre = this.unitController.getBlackboardValue('hasOre');
|
|
||||||
if (!hasOre) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动作冷却检查
|
|
||||||
if (this.isActionOnCooldown()) {
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
|
|
||||||
const warehouse = this.minerDemo.getWarehouse();
|
|
||||||
if (!warehouse || !warehouse.isValid) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算到仓库的距离
|
|
||||||
const currentPos = this.node.worldPosition;
|
|
||||||
const warehousePos = warehouse.worldPosition;
|
|
||||||
const distance = Vec3.distance(currentPos, warehousePos);
|
|
||||||
|
|
||||||
// 检查是否已经到达仓库
|
|
||||||
if (distance < 2.5) {
|
|
||||||
// 检查是否正在移动
|
|
||||||
const isMoving = this.unitController.getBlackboardValue('isMoving');
|
|
||||||
if (isMoving) {
|
|
||||||
return 'running';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除携带矿石状态
|
|
||||||
this.unitController.setBlackboardValue('hasOre', false);
|
|
||||||
|
|
||||||
// 清除移动目标
|
|
||||||
this.unitController.clearTarget();
|
|
||||||
this.unitController.setBlackboardValue('isMoving', false);
|
|
||||||
|
|
||||||
this.updateActionTime();
|
|
||||||
return 'success';
|
|
||||||
} else {
|
|
||||||
// 设置移动目标
|
|
||||||
this.unitController.setTarget(warehousePos);
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 待机行为
|
|
||||||
* @param params 事件参数,包含黑板变量值
|
|
||||||
*/
|
|
||||||
onIdleBehavior(params: any = {}): string {
|
|
||||||
if (!this.unitController) {
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除移动目标,确保停止移动
|
|
||||||
this.unitController.clearTarget();
|
|
||||||
this.unitController.setBlackboardValue('isMoving', false);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取矿工状态摘要
|
|
||||||
*/
|
|
||||||
getMinerStatus(): string {
|
|
||||||
if (!this.unitController) return 'Unknown';
|
|
||||||
|
|
||||||
const hasOre = this.unitController.getBlackboardValue('hasOre');
|
|
||||||
const isMoving = this.unitController.getBlackboardValue('isMoving');
|
|
||||||
const isResting = this.unitController.getBlackboardValue('isResting');
|
|
||||||
const stamina = this.unitController.currentStamina;
|
|
||||||
const maxStamina = this.unitController.maxStamina;
|
|
||||||
|
|
||||||
let status = '';
|
|
||||||
if (isResting) {
|
|
||||||
status = '😴休息中';
|
|
||||||
} else if (hasOre) {
|
|
||||||
status = isMoving ? '🚚运输中' : '📦携带矿石';
|
|
||||||
} else {
|
|
||||||
status = isMoving ? '🚶移动中' : '⛏️挖矿';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${status} (体力:${stamina.toFixed(0)}/${maxStamina})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试信息
|
|
||||||
*/
|
|
||||||
getDebugInfo(): any {
|
|
||||||
if (!this.unitController) return {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: this.node.name,
|
|
||||||
hasOre: this.unitController.getBlackboardValue('hasOre'),
|
|
||||||
isMoving: this.unitController.getBlackboardValue('isMoving'),
|
|
||||||
isResting: this.unitController.getBlackboardValue('isResting'),
|
|
||||||
stamina: this.unitController.currentStamina,
|
|
||||||
maxStamina: this.unitController.maxStamina,
|
|
||||||
staminaPercentage: this.unitController.currentStamina / this.unitController.maxStamina,
|
|
||||||
isLowStamina: this.unitController.currentStamina < this.unitController.maxStamina * 0.2,
|
|
||||||
status: this.getMinerStatus()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "739ff9ee-42d5-4542-bb5b-3e7611c729e2",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { _decorator, Component, Node, Vec3, MeshRenderer, BoxCollider, RigidBody, Material, Color, primitives, utils } from 'cc';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 简单预制体工厂
|
|
||||||
*/
|
|
||||||
@ccclass('SimplePrefabFactory')
|
|
||||||
export class SimplePrefabFactory extends Component {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建单位节点
|
|
||||||
*/
|
|
||||||
static createUnit(name: string, color: Color = Color.WHITE): Node {
|
|
||||||
const unit = new Node(name);
|
|
||||||
|
|
||||||
// 添加网格渲染器
|
|
||||||
const meshRenderer = unit.addComponent(MeshRenderer);
|
|
||||||
|
|
||||||
// 创建立方体网格
|
|
||||||
const mesh = utils.createMesh(primitives.box({ width: 1, height: 1, length: 1 }));
|
|
||||||
meshRenderer.mesh = mesh;
|
|
||||||
|
|
||||||
// 创建材质
|
|
||||||
const material = new Material();
|
|
||||||
material.initialize({ effectName: 'builtin-unlit' });
|
|
||||||
material.setProperty('mainColor', color);
|
|
||||||
meshRenderer.material = material;
|
|
||||||
|
|
||||||
// 添加碰撞器
|
|
||||||
const collider = unit.addComponent(BoxCollider);
|
|
||||||
collider.size = new Vec3(1, 1, 1);
|
|
||||||
|
|
||||||
// 添加刚体
|
|
||||||
const rigidBody = unit.addComponent(RigidBody);
|
|
||||||
rigidBody.type = RigidBody.Type.KINEMATIC;
|
|
||||||
|
|
||||||
return unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建建筑节点
|
|
||||||
*/
|
|
||||||
static createBuilding(name: string, size: Vec3 = new Vec3(2, 2, 2), color: Color = Color.GRAY): Node {
|
|
||||||
const building = new Node(name);
|
|
||||||
|
|
||||||
// 添加网格渲染器
|
|
||||||
const meshRenderer = building.addComponent(MeshRenderer);
|
|
||||||
|
|
||||||
// 创建立方体网格
|
|
||||||
const mesh = utils.createMesh(primitives.box({
|
|
||||||
width: size.x,
|
|
||||||
height: size.y,
|
|
||||||
length: size.z
|
|
||||||
}));
|
|
||||||
meshRenderer.mesh = mesh;
|
|
||||||
|
|
||||||
// 创建材质
|
|
||||||
const material = new Material();
|
|
||||||
material.initialize({ effectName: 'builtin-unlit' });
|
|
||||||
material.setProperty('mainColor', color);
|
|
||||||
meshRenderer.material = material;
|
|
||||||
|
|
||||||
// 添加碰撞器
|
|
||||||
const collider = building.addComponent(BoxCollider);
|
|
||||||
collider.size = size;
|
|
||||||
|
|
||||||
return building;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建资源节点
|
|
||||||
*/
|
|
||||||
static createResource(name: string, color: Color = Color.YELLOW): Node {
|
|
||||||
const resource = new Node(name);
|
|
||||||
|
|
||||||
// 添加网格渲染器
|
|
||||||
const meshRenderer = resource.addComponent(MeshRenderer);
|
|
||||||
|
|
||||||
// 创建球体网格
|
|
||||||
const mesh = utils.createMesh(primitives.sphere(0.5));
|
|
||||||
meshRenderer.mesh = mesh;
|
|
||||||
|
|
||||||
// 创建材质
|
|
||||||
const material = new Material();
|
|
||||||
material.initialize({ effectName: 'builtin-unlit' });
|
|
||||||
material.setProperty('mainColor', color);
|
|
||||||
meshRenderer.material = material;
|
|
||||||
|
|
||||||
// 添加碰撞器
|
|
||||||
const collider = resource.addComponent(BoxCollider);
|
|
||||||
collider.size = new Vec3(1, 1, 1);
|
|
||||||
|
|
||||||
return resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建地面节点
|
|
||||||
*/
|
|
||||||
static createGround(size: Vec3 = new Vec3(50, 0.1, 50), color: Color = new Color(100, 150, 100, 255)): Node {
|
|
||||||
const ground = new Node('Ground');
|
|
||||||
|
|
||||||
// 添加网格渲染器
|
|
||||||
const meshRenderer = ground.addComponent(MeshRenderer);
|
|
||||||
|
|
||||||
// 创建平面网格
|
|
||||||
const mesh = utils.createMesh(primitives.box({
|
|
||||||
width: size.x,
|
|
||||||
height: size.y,
|
|
||||||
length: size.z
|
|
||||||
}));
|
|
||||||
meshRenderer.mesh = mesh;
|
|
||||||
|
|
||||||
// 创建材质
|
|
||||||
const material = new Material();
|
|
||||||
material.initialize({ effectName: 'builtin-unlit' });
|
|
||||||
material.setProperty('mainColor', color);
|
|
||||||
meshRenderer.material = material;
|
|
||||||
|
|
||||||
// 添加碰撞器
|
|
||||||
const collider = ground.addComponent(BoxCollider);
|
|
||||||
collider.size = size;
|
|
||||||
|
|
||||||
return ground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "ac45cfc7-cf47-4315-bdf0-ba002b45b4b6",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
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) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "7478e794-dd80-4661-9421-8e147d33c51e",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
import { _decorator, Component, Node, Vec3, MeshRenderer, Color, tween } from 'cc';
|
|
||||||
import { BehaviorTreeManager } from './BehaviorTreeManager';
|
|
||||||
import { RTSBehaviorHandler } from './RTSBehaviorHandler';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单位配置接口
|
|
||||||
*/
|
|
||||||
export interface UnitConfig {
|
|
||||||
unitType: string;
|
|
||||||
behaviorTreeName: string;
|
|
||||||
maxHealth: number;
|
|
||||||
moveSpeed: number;
|
|
||||||
attackRange: number;
|
|
||||||
attackDamage: number;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单位控制器
|
|
||||||
*/
|
|
||||||
@ccclass('UnitController')
|
|
||||||
export class UnitController extends Component {
|
|
||||||
|
|
||||||
@property
|
|
||||||
showDebugInfo: boolean = true;
|
|
||||||
|
|
||||||
// 单位属性
|
|
||||||
public unitType: string = '';
|
|
||||||
public maxHealth: number = 100;
|
|
||||||
public currentHealth: number = 100;
|
|
||||||
public moveSpeed: number = 1.5;
|
|
||||||
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';
|
|
||||||
|
|
||||||
// 体力系统属性
|
|
||||||
public maxStamina: number = 100;
|
|
||||||
public currentStamina: number = 100;
|
|
||||||
public homePosition: Vec3 = Vec3.ZERO.clone();
|
|
||||||
public staminaRecoveryRate: number = 20; // 每秒恢复的体力
|
|
||||||
public staminaCostPerMining: number = 15; // 每次挖矿消耗的体力
|
|
||||||
|
|
||||||
// 移动状态管理
|
|
||||||
private isMoving: boolean = false;
|
|
||||||
private moveStartTime: number = 0;
|
|
||||||
private lastTargetUpdateTime: number = 0;
|
|
||||||
|
|
||||||
private behaviorTreeManager: BehaviorTreeManager | null = null;
|
|
||||||
private behaviorHandler: Component | null = null;
|
|
||||||
private meshRenderer: MeshRenderer | null = null;
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
this.meshRenderer = this.getComponent(MeshRenderer);
|
|
||||||
|
|
||||||
// 创建行为树管理器
|
|
||||||
this.behaviorTreeManager = this.addComponent(BehaviorTreeManager);
|
|
||||||
|
|
||||||
// 添加RTS行为处理器
|
|
||||||
try {
|
|
||||||
// 添加RTSBehaviorHandler组件
|
|
||||||
this.behaviorHandler = this.addComponent(RTSBehaviorHandler);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('RTSBehaviorHandler组件添加失败', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置单位配置
|
|
||||||
*/
|
|
||||||
setup(config: UnitConfig) {
|
|
||||||
this.unitType = config.unitType;
|
|
||||||
this.maxHealth = config.maxHealth;
|
|
||||||
this.currentHealth = config.maxHealth;
|
|
||||||
this.moveSpeed = config.moveSpeed;
|
|
||||||
this.attackRange = config.attackRange;
|
|
||||||
this.attackDamage = config.attackDamage;
|
|
||||||
this.color = config.color;
|
|
||||||
|
|
||||||
// 设置材质颜色
|
|
||||||
this.setUnitColor(config.color);
|
|
||||||
|
|
||||||
// 设置节点名称显示单位类型
|
|
||||||
this.node.name = `${config.unitType.toUpperCase()}_${this.node.name}`;
|
|
||||||
|
|
||||||
// 初始化行为树
|
|
||||||
if (this.behaviorTreeManager) {
|
|
||||||
this.behaviorTreeManager.initializeBehaviorTree(config.behaviorTreeName, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置单位颜色
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
this.isSelected = selected;
|
|
||||||
|
|
||||||
// 视觉效果
|
|
||||||
if (selected) {
|
|
||||||
this.showSelectionEffect();
|
|
||||||
} else {
|
|
||||||
this.hideSelectionEffect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新行为树黑板
|
|
||||||
if (this.behaviorTreeManager) {
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('isSelected', selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示选择效果
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
this.currentCommand = command;
|
|
||||||
|
|
||||||
// 设置目标
|
|
||||||
if (target instanceof Vec3) {
|
|
||||||
this.targetPosition = target.clone();
|
|
||||||
this.targetNode = null;
|
|
||||||
} else if (target instanceof Node) {
|
|
||||||
this.targetPosition = target.worldPosition.clone();
|
|
||||||
this.targetNode = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新行为树黑板
|
|
||||||
if (this.behaviorTreeManager) {
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('currentCommand', command);
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('hasTarget', target !== undefined);
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('targetPosition', this.targetPosition);
|
|
||||||
|
|
||||||
if (target instanceof Node) {
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('targetType',
|
|
||||||
target.name.includes('Resource') ? 'resource' :
|
|
||||||
target.name.includes('Building') ? 'building' : 'unit');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置黑板变量值
|
|
||||||
*/
|
|
||||||
setBlackboardValue(key: string, value: any) {
|
|
||||||
if (this.behaviorTreeManager) {
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取黑板变量值
|
|
||||||
*/
|
|
||||||
getBlackboardValue(key: string): any {
|
|
||||||
return this.behaviorTreeManager?.getBlackboardValue(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置移动目标
|
|
||||||
*/
|
|
||||||
setTarget(position: Vec3) {
|
|
||||||
this.targetPosition = position.clone();
|
|
||||||
this.isMoving = true;
|
|
||||||
this.moveStartTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除移动目标
|
|
||||||
*/
|
|
||||||
clearTarget() {
|
|
||||||
this.targetPosition = Vec3.ZERO.clone();
|
|
||||||
this.isMoving = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 受到伤害
|
|
||||||
*/
|
|
||||||
takeDamage(damage: number) {
|
|
||||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
|
||||||
|
|
||||||
// 更新行为树黑板
|
|
||||||
if (this.behaviorTreeManager) {
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('currentHealth', this.currentHealth);
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('healthPercentage', this.currentHealth / this.maxHealth);
|
|
||||||
this.behaviorTreeManager.updateBlackboardValue('isLowHealth', this.currentHealth < this.maxHealth * 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 视觉效果
|
|
||||||
this.showDamageEffect();
|
|
||||||
|
|
||||||
if (this.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} 死亡`);
|
|
||||||
|
|
||||||
// 播放死亡动画后销毁节点
|
|
||||||
tween(this.node)
|
|
||||||
.to(0.5, { scale: Vec3.ZERO })
|
|
||||||
.call(() => {
|
|
||||||
this.node.destroy();
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动到目标位置(只在水平面移动,不改变Y轴)
|
|
||||||
*/
|
|
||||||
moveToTarget(targetPos: Vec3, speed?: number, deltaTime?: number): boolean {
|
|
||||||
const currentPos = this.node.worldPosition;
|
|
||||||
const distance = Vec3.distance(currentPos, targetPos);
|
|
||||||
|
|
||||||
if (distance < 0.5) {
|
|
||||||
this.isMoving = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actualSpeed = speed || this.moveSpeed;
|
|
||||||
const actualDeltaTime = deltaTime || 0.016;
|
|
||||||
const direction = new Vec3();
|
|
||||||
Vec3.subtract(direction, targetPos, currentPos);
|
|
||||||
direction.normalize();
|
|
||||||
|
|
||||||
const moveDistance = actualSpeed * actualDeltaTime;
|
|
||||||
const newPosition = new Vec3();
|
|
||||||
Vec3.scaleAndAdd(newPosition, currentPos, direction, moveDistance);
|
|
||||||
|
|
||||||
this.node.setWorldPosition(newPosition);
|
|
||||||
this.isMoving = true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 攻击目标
|
|
||||||
*/
|
|
||||||
attackTarget(): boolean {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
if (currentTime - this.lastAttackTime < this.attackCooldown * 1000) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.targetNode && this.targetNode.isValid) {
|
|
||||||
const distance = Vec3.distance(this.node.worldPosition, this.targetNode.worldPosition);
|
|
||||||
if (distance <= this.attackRange) {
|
|
||||||
this.lastAttackTime = currentTime;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaTime: number) {
|
|
||||||
if (this.behaviorTreeManager) {
|
|
||||||
this.behaviorTreeManager.update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isMoving && !this.targetPosition.equals(Vec3.ZERO)) {
|
|
||||||
const reached = this.moveToTarget(this.targetPosition, this.moveSpeed, deltaTime);
|
|
||||||
if (reached) {
|
|
||||||
this.clearTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调试信息显示
|
|
||||||
if (this.showDebugInfo) {
|
|
||||||
this.updateDebugInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新调试信息
|
|
||||||
*/
|
|
||||||
private updateDebugInfo() {
|
|
||||||
// 可以在这里添加调试信息的显示逻辑
|
|
||||||
// 比如在单位上方显示状态文本等
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
// 停止所有动画
|
|
||||||
tween(this.node).stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "4ac64480-2d09-4de6-a22c-add022790676",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "a1f43720-46e1-4d07-b56a-c9307e45726c",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import { Core } from '@esengine/ecs-framework';
|
|
||||||
import { Component, _decorator } from 'cc';
|
|
||||||
import { GameScene } from './scenes/GameScene';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ECS管理器 - Cocos Creator组件
|
|
||||||
* 将此组件添加到场景中的任意节点上即可启动ECS框架
|
|
||||||
*
|
|
||||||
* 使用说明:
|
|
||||||
* 1. 在Cocos Creator场景中创建一个空节点
|
|
||||||
* 2. 将此ECSManager组件添加到该节点
|
|
||||||
* 3. 运行场景即可自动启动ECS框架
|
|
||||||
*/
|
|
||||||
@ccclass('ECSManager')
|
|
||||||
export class ECSManager extends Component {
|
|
||||||
|
|
||||||
@property({
|
|
||||||
tooltip: '是否启用调试模式(建议开发阶段开启)'
|
|
||||||
})
|
|
||||||
public debugMode: boolean = true;
|
|
||||||
|
|
||||||
private isInitialized: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件启动时初始化ECS
|
|
||||||
*/
|
|
||||||
start() {
|
|
||||||
this.initializeECS();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化ECS框架
|
|
||||||
*/
|
|
||||||
private initializeECS(): void {
|
|
||||||
if (this.isInitialized) return;
|
|
||||||
|
|
||||||
// ECS框架初始化开始
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 创建Core实例,启用调试功能
|
|
||||||
if (this.debugMode) {
|
|
||||||
Core.create({
|
|
||||||
debug: true,
|
|
||||||
enableEntitySystems: true,
|
|
||||||
debugConfig: {
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: 'ws://localhost:8080',
|
|
||||||
autoReconnect: true,
|
|
||||||
debugFrameRate: 30,
|
|
||||||
channels: {
|
|
||||||
entities: true,
|
|
||||||
systems: true,
|
|
||||||
performance: true,
|
|
||||||
components: true,
|
|
||||||
scenes: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('✅ ECS调试模式已启用');
|
|
||||||
} else {
|
|
||||||
Core.create({
|
|
||||||
debug: false,
|
|
||||||
enableEntitySystems: true
|
|
||||||
});
|
|
||||||
console.log('ℹ️ ECS调试模式已禁用');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 创建游戏场景
|
|
||||||
const gameScene = new GameScene();
|
|
||||||
|
|
||||||
// 3. 设置为当前场景(会自动调用scene.begin())
|
|
||||||
Core.setScene(gameScene);
|
|
||||||
|
|
||||||
this.isInitialized = true;
|
|
||||||
// ECS框架初始化完成
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('ECS框架初始化失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧更新ECS框架
|
|
||||||
*/
|
|
||||||
update(deltaTime: number) {
|
|
||||||
if (this.isInitialized) {
|
|
||||||
// 更新ECS核心系统
|
|
||||||
Core.update(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件销毁时清理ECS
|
|
||||||
*/
|
|
||||||
onDestroy() {
|
|
||||||
if (this.isInitialized) {
|
|
||||||
// ECS框架清理
|
|
||||||
this.isInitialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "b89656f0-6320-4b6d-81cd-447bf811230c",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
# ECS框架启动模板
|
|
||||||
|
|
||||||
欢迎使用ECS框架!这是一个最基础的启动模板,帮助您快速开始ECS项目开发。
|
|
||||||
|
|
||||||
## 📁 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
ecs/
|
|
||||||
├── components/ # 组件目录(请在此添加您的组件)
|
|
||||||
├── systems/ # 系统目录(请在此添加您的系统)
|
|
||||||
├── scenes/ # 场景目录
|
|
||||||
│ └── GameScene.ts # 主游戏场景
|
|
||||||
├── ECSManager.ts # ECS管理器组件
|
|
||||||
└── README.md # 本文档
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 快速开始
|
|
||||||
|
|
||||||
### 1. 启动ECS框架
|
|
||||||
|
|
||||||
ECS框架已经配置完成!您只需要:
|
|
||||||
|
|
||||||
1. 在Cocos Creator中打开您的场景
|
|
||||||
2. 创建一个空节点(例如命名为"ECSManager")
|
|
||||||
3. 将 `ECSManager` 组件添加到该节点
|
|
||||||
4. 运行场景,ECS框架将自动启动
|
|
||||||
|
|
||||||
### 2. 查看控制台输出
|
|
||||||
|
|
||||||
如果一切正常,您将在控制台看到:
|
|
||||||
|
|
||||||
```
|
|
||||||
🎮 正在初始化ECS框架...
|
|
||||||
🔧 ECS调试模式已启用,可在Cocos Creator扩展面板中查看调试信息
|
|
||||||
🎯 游戏场景已创建
|
|
||||||
✅ ECS框架初始化成功!
|
|
||||||
🚀 游戏场景已启动
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 使用调试面板
|
|
||||||
|
|
||||||
ECS框架已启用调试功能,您可以:
|
|
||||||
|
|
||||||
1. 在Cocos Creator编辑器菜单中选择 "扩展" → "ECS Framework" → "调试面板"
|
|
||||||
2. 调试面板将显示实时的ECS运行状态:
|
|
||||||
- 实体数量和状态
|
|
||||||
- 系统执行信息
|
|
||||||
- 性能监控数据
|
|
||||||
- 组件统计信息
|
|
||||||
|
|
||||||
**注意**:调试功能会消耗一定性能,正式发布时建议关闭调试模式。
|
|
||||||
|
|
||||||
## 📚 下一步开发
|
|
||||||
|
|
||||||
### 创建您的第一个组件
|
|
||||||
|
|
||||||
在 `components/` 目录下创建组件:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// components/PositionComponent.ts
|
|
||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Vec3 } from 'cc';
|
|
||||||
|
|
||||||
export class PositionComponent extends Component {
|
|
||||||
public position: Vec3 = new Vec3();
|
|
||||||
|
|
||||||
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
|
||||||
super();
|
|
||||||
this.position.set(x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 创建您的第一个系统
|
|
||||||
|
|
||||||
在 `systems/` 目录下创建系统:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// systems/MovementSystem.ts
|
|
||||||
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
|
||||||
import { PositionComponent } from '../components/PositionComponent';
|
|
||||||
|
|
||||||
export class MovementSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.empty().all(PositionComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
for (const entity of entities) {
|
|
||||||
const position = entity.getComponent(PositionComponent);
|
|
||||||
if (position) {
|
|
||||||
// TODO: 在这里编写移动逻辑
|
|
||||||
console.log(`实体 ${entity.name} 位置: ${position.position}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 在场景中注册系统
|
|
||||||
|
|
||||||
在 `scenes/GameScene.ts` 的 `initialize()` 方法中添加:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { MovementSystem } from '../systems/MovementSystem';
|
|
||||||
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
this.name = "MainGameScene";
|
|
||||||
|
|
||||||
// 添加系统
|
|
||||||
this.addEntityProcessor(new MovementSystem());
|
|
||||||
|
|
||||||
// 创建测试实体
|
|
||||||
const testEntity = this.createEntity("TestEntity");
|
|
||||||
testEntity.addComponent(new PositionComponent(0, 0, 0));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔗 学习资源
|
|
||||||
|
|
||||||
- [ECS框架完整文档](https://github.com/esengine/ecs-framework)
|
|
||||||
- [ECS概念详解](https://github.com/esengine/ecs-framework/blob/master/docs/concepts-explained.md)
|
|
||||||
- [新手教程](https://github.com/esengine/ecs-framework/blob/master/docs/beginner-tutorials.md)
|
|
||||||
- [组件设计指南](https://github.com/esengine/ecs-framework/blob/master/docs/component-design-guide.md)
|
|
||||||
- [系统开发指南](https://github.com/esengine/ecs-framework/blob/master/docs/system-guide.md)
|
|
||||||
|
|
||||||
## 💡 开发提示
|
|
||||||
|
|
||||||
1. **组件只存储数据**:避免在组件中编写复杂逻辑
|
|
||||||
2. **系统处理逻辑**:所有业务逻辑应该在系统中实现
|
|
||||||
3. **使用Matcher过滤实体**:系统通过Matcher指定需要处理的实体类型
|
|
||||||
4. **性能优化**:大量实体时考虑使用位掩码查询和组件索引
|
|
||||||
|
|
||||||
## ❓ 常见问题
|
|
||||||
|
|
||||||
### Q: 如何创建实体?
|
|
||||||
A: 在场景中使用 `this.createEntity("实体名称")`
|
|
||||||
|
|
||||||
### Q: 如何给实体添加组件?
|
|
||||||
A: 使用 `entity.addComponent(new YourComponent())`
|
|
||||||
|
|
||||||
### Q: 如何获取实体的组件?
|
|
||||||
A: 使用 `entity.getComponent(YourComponent)`
|
|
||||||
|
|
||||||
### Q: 如何删除实体?
|
|
||||||
A: 使用 `entity.destroy()` 或 `this.destroyEntity(entity)`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
🎮 **开始您的ECS开发之旅吧!**
|
|
||||||
|
|
||||||
如有问题,请查阅官方文档或提交Issue。
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.0.1",
|
|
||||||
"importer": "text",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "ca94b460-6c6a-4f72-9ec1-ab5fcd2e0e0a",
|
|
||||||
"files": [
|
|
||||||
".json"
|
|
||||||
],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "3c7bd2b3-6781-482c-be41-21f3dde0e2ab",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Vec3 } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI组件 - 复杂的AI行为和状态管理
|
|
||||||
*/
|
|
||||||
export class AIComponent extends Component {
|
|
||||||
/** AI状态 */
|
|
||||||
public currentState: 'idle' | 'patrol' | 'chase' | 'attack' | 'flee' | 'dead' = 'idle';
|
|
||||||
|
|
||||||
/** 目标ID(避免循环引用) */
|
|
||||||
public targetId: number | null = null;
|
|
||||||
|
|
||||||
/** AI伙伴ID列表(避免循环引用) */
|
|
||||||
public allyIds: number[] = [];
|
|
||||||
|
|
||||||
/** 敌人ID列表 */
|
|
||||||
public enemyIds: number[] = [];
|
|
||||||
|
|
||||||
/** 复杂的AI配置 */
|
|
||||||
public config: {
|
|
||||||
personality: {
|
|
||||||
aggression: number; // 攻击性 0-1
|
|
||||||
curiosity: number; // 好奇心 0-1
|
|
||||||
loyalty: number; // 忠诚度 0-1
|
|
||||||
intelligence: number; // 智力 0-1
|
|
||||||
};
|
|
||||||
capabilities: {
|
|
||||||
sightRange: number;
|
|
||||||
hearingRange: number;
|
|
||||||
movementSpeed: number;
|
|
||||||
attackDamage: number;
|
|
||||||
health: number;
|
|
||||||
};
|
|
||||||
behaviorTree: {
|
|
||||||
rootNode: BehaviorNode;
|
|
||||||
blackboard: Map<string, any>;
|
|
||||||
executionHistory: BehaviorExecution[];
|
|
||||||
};
|
|
||||||
memory: {
|
|
||||||
lastSeenEnemyPosition: Vec3 | null;
|
|
||||||
lastSeenEnemyTime: number;
|
|
||||||
knownLocations: Array<{
|
|
||||||
position: Vec3;
|
|
||||||
type: 'safe' | 'danger' | 'resource' | 'patrol';
|
|
||||||
confidence: number;
|
|
||||||
lastVisited: number;
|
|
||||||
}>;
|
|
||||||
relationships: Map<number, {
|
|
||||||
entityId: number;
|
|
||||||
relationship: 'ally' | 'enemy' | 'neutral';
|
|
||||||
trustLevel: number;
|
|
||||||
lastInteraction: number;
|
|
||||||
interactionHistory: Array<{
|
|
||||||
type: 'friendly' | 'hostile' | 'neutral';
|
|
||||||
timestamp: number;
|
|
||||||
impact: number;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 状态机 */
|
|
||||||
public stateMachine: {
|
|
||||||
states: Map<string, AIState>;
|
|
||||||
transitions: Map<string, Array<{
|
|
||||||
targetState: string;
|
|
||||||
condition: () => boolean;
|
|
||||||
priority: number;
|
|
||||||
}>>;
|
|
||||||
stateHistory: Array<{
|
|
||||||
state: string;
|
|
||||||
enterTime: number;
|
|
||||||
exitTime: number;
|
|
||||||
data: any;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 感知系统 */
|
|
||||||
public perception: {
|
|
||||||
visibleEntities: Array<{
|
|
||||||
entityId: number;
|
|
||||||
position: Vec3;
|
|
||||||
distance: number;
|
|
||||||
angle: number;
|
|
||||||
lastSeen: number;
|
|
||||||
componentId?: number; // 使用组件ID避免循环引用
|
|
||||||
}>;
|
|
||||||
audibleSounds: Array<{
|
|
||||||
source: Vec3;
|
|
||||||
volume: number;
|
|
||||||
type: string;
|
|
||||||
timestamp: number;
|
|
||||||
}>;
|
|
||||||
tacticleInfo: Array<{
|
|
||||||
entityId: number;
|
|
||||||
contactPoint: Vec3;
|
|
||||||
force: number;
|
|
||||||
timestamp: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// 初始化AI配置
|
|
||||||
this.config = {
|
|
||||||
personality: {
|
|
||||||
aggression: Math.random(),
|
|
||||||
curiosity: Math.random(),
|
|
||||||
loyalty: Math.random(),
|
|
||||||
intelligence: Math.random()
|
|
||||||
},
|
|
||||||
capabilities: {
|
|
||||||
sightRange: 100 + Math.random() * 100,
|
|
||||||
hearingRange: 50 + Math.random() * 50,
|
|
||||||
movementSpeed: 80 + Math.random() * 40,
|
|
||||||
attackDamage: 10 + Math.random() * 20,
|
|
||||||
health: 80 + Math.random() * 40
|
|
||||||
},
|
|
||||||
behaviorTree: {
|
|
||||||
rootNode: new BehaviorNode('root'),
|
|
||||||
blackboard: new Map(),
|
|
||||||
executionHistory: []
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
lastSeenEnemyPosition: null,
|
|
||||||
lastSeenEnemyTime: 0,
|
|
||||||
knownLocations: [],
|
|
||||||
relationships: new Map()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化状态机
|
|
||||||
this.stateMachine = {
|
|
||||||
states: new Map(),
|
|
||||||
transitions: new Map(),
|
|
||||||
stateHistory: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化感知系统
|
|
||||||
this.perception = {
|
|
||||||
visibleEntities: [],
|
|
||||||
audibleSounds: [],
|
|
||||||
tacticleInfo: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initializeStateMachine();
|
|
||||||
this.initializeBehaviorTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化状态机
|
|
||||||
*/
|
|
||||||
private initializeStateMachine(): void {
|
|
||||||
// 添加基本状态
|
|
||||||
this.stateMachine.states.set('idle', new AIState('idle', this));
|
|
||||||
this.stateMachine.states.set('patrol', new AIState('patrol', this));
|
|
||||||
this.stateMachine.states.set('chase', new AIState('chase', this));
|
|
||||||
this.stateMachine.states.set('attack', new AIState('attack', this));
|
|
||||||
this.stateMachine.states.set('flee', new AIState('flee', this));
|
|
||||||
|
|
||||||
// 设置状态转换
|
|
||||||
this.stateMachine.transitions.set('idle', [
|
|
||||||
{ targetState: 'patrol', condition: () => Math.random() > 0.8, priority: 1 },
|
|
||||||
{ targetState: 'chase', condition: () => this.perception.visibleEntities.length > 0, priority: 3 }
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.stateMachine.transitions.set('patrol', [
|
|
||||||
{ targetState: 'idle', condition: () => Math.random() > 0.9, priority: 1 },
|
|
||||||
{ targetState: 'chase', condition: () => this.hasVisibleEnemies(), priority: 3 }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化行为树
|
|
||||||
*/
|
|
||||||
private initializeBehaviorTree(): void {
|
|
||||||
const root = this.config.behaviorTree.rootNode;
|
|
||||||
|
|
||||||
// 构建简单的行为树结构
|
|
||||||
const selectorNode = new BehaviorNode('selector');
|
|
||||||
const sequenceNode = new BehaviorNode('sequence');
|
|
||||||
const conditionNode = new BehaviorNode('condition');
|
|
||||||
const actionNode = new BehaviorNode('action');
|
|
||||||
|
|
||||||
root.addChild(selectorNode);
|
|
||||||
selectorNode.addChild(sequenceNode);
|
|
||||||
sequenceNode.addChild(conditionNode);
|
|
||||||
sequenceNode.addChild(actionNode);
|
|
||||||
|
|
||||||
// 设置黑板数据
|
|
||||||
this.config.behaviorTree.blackboard.set('lastPatrolPoint', new Vec3());
|
|
||||||
this.config.behaviorTree.blackboard.set('alertLevel', 0);
|
|
||||||
this.config.behaviorTree.blackboard.set('energy', 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加盟友(避免循环引用)
|
|
||||||
*/
|
|
||||||
public addAlly(allyEntityId: number): void {
|
|
||||||
if (!this.allyIds.includes(allyEntityId)) {
|
|
||||||
this.allyIds.push(allyEntityId);
|
|
||||||
|
|
||||||
// 更新关系记录
|
|
||||||
this.config.memory.relationships.set(allyEntityId, {
|
|
||||||
entityId: allyEntityId,
|
|
||||||
relationship: 'ally',
|
|
||||||
trustLevel: 0.8,
|
|
||||||
lastInteraction: Date.now(),
|
|
||||||
interactionHistory: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置目标(避免循环引用)
|
|
||||||
*/
|
|
||||||
public setTarget(targetEntityId: number): void {
|
|
||||||
this.targetId = targetEntityId;
|
|
||||||
// 不再需要双向引用
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新感知信息
|
|
||||||
*/
|
|
||||||
public updatePerception(deltaTime: number): void {
|
|
||||||
// 清理过期的感知信息
|
|
||||||
const currentTime = Date.now();
|
|
||||||
this.perception.visibleEntities = this.perception.visibleEntities.filter(
|
|
||||||
entity => currentTime - entity.lastSeen < 5000
|
|
||||||
);
|
|
||||||
|
|
||||||
this.perception.audibleSounds = this.perception.audibleSounds.filter(
|
|
||||||
sound => currentTime - sound.timestamp < 2000
|
|
||||||
);
|
|
||||||
|
|
||||||
// 更新记忆中的位置信息
|
|
||||||
this.config.memory.knownLocations.forEach(location => {
|
|
||||||
location.confidence *= 0.999; // 随时间衰减可信度
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否有可见敌人
|
|
||||||
*/
|
|
||||||
private hasVisibleEnemies(): boolean {
|
|
||||||
return this.perception.visibleEntities.some(entity =>
|
|
||||||
this.config.memory.relationships.get(entity.entityId)?.relationship === 'enemy'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置组件
|
|
||||||
*/
|
|
||||||
public reset(): void {
|
|
||||||
// 清理ID数组(不再需要处理循环引用)
|
|
||||||
this.allyIds = [];
|
|
||||||
this.enemyIds = [];
|
|
||||||
this.targetId = null;
|
|
||||||
this.currentState = 'idle';
|
|
||||||
|
|
||||||
this.config.behaviorTree.blackboard.clear();
|
|
||||||
this.config.memory.relationships.clear();
|
|
||||||
this.perception.visibleEntities = [];
|
|
||||||
this.perception.audibleSounds = [];
|
|
||||||
this.perception.tacticleInfo = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行为树节点
|
|
||||||
*/
|
|
||||||
class BehaviorNode {
|
|
||||||
public name: string;
|
|
||||||
public children: BehaviorNode[] = [];
|
|
||||||
public parent: BehaviorNode | null = null;
|
|
||||||
public data: Map<string, any> = new Map();
|
|
||||||
|
|
||||||
constructor(name: string) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addChild(child: BehaviorNode): void {
|
|
||||||
this.children.push(child);
|
|
||||||
child.parent = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行为执行记录
|
|
||||||
*/
|
|
||||||
interface BehaviorExecution {
|
|
||||||
nodeName: string;
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
result: 'success' | 'failure' | 'running';
|
|
||||||
data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI状态
|
|
||||||
*/
|
|
||||||
class AIState {
|
|
||||||
public name: string;
|
|
||||||
public owner: AIComponent;
|
|
||||||
public enterTime: number = 0;
|
|
||||||
public data: Map<string, any> = new Map();
|
|
||||||
|
|
||||||
constructor(name: string, owner: AIComponent) {
|
|
||||||
this.name = name;
|
|
||||||
this.owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enter(): void {
|
|
||||||
this.enterTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
public exit(): void {
|
|
||||||
// 记录状态历史
|
|
||||||
this.owner.stateMachine.stateHistory.push({
|
|
||||||
state: this.name,
|
|
||||||
enterTime: this.enterTime,
|
|
||||||
exitTime: Date.now(),
|
|
||||||
data: Object.fromEntries(this.data)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "cc0d3d0d-0c12-4007-8568-11b2cafdfb8f",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命值组件
|
|
||||||
* 管理实体的生命值、最大生命值等
|
|
||||||
*/
|
|
||||||
export class Health extends Component {
|
|
||||||
/** 当前生命值 */
|
|
||||||
public currentHealth: number = 100;
|
|
||||||
|
|
||||||
/** 最大生命值 */
|
|
||||||
public maxHealth: number = 100;
|
|
||||||
|
|
||||||
/** 是否死亡 */
|
|
||||||
public isDead: boolean = false;
|
|
||||||
|
|
||||||
/** 生命值回复速度 (每秒) */
|
|
||||||
public regenRate: number = 0;
|
|
||||||
|
|
||||||
constructor(maxHealth: number = 100) {
|
|
||||||
super();
|
|
||||||
this.maxHealth = maxHealth;
|
|
||||||
this.currentHealth = maxHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 受到伤害
|
|
||||||
*/
|
|
||||||
public takeDamage(damage: number): void {
|
|
||||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
|
||||||
this.isDead = this.currentHealth <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 治疗
|
|
||||||
*/
|
|
||||||
public heal(amount: number): void {
|
|
||||||
if (!this.isDead) {
|
|
||||||
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复活
|
|
||||||
*/
|
|
||||||
public revive(healthPercent: number = 1.0): void {
|
|
||||||
this.isDead = false;
|
|
||||||
this.currentHealth = Math.floor(this.maxHealth * Math.max(0, Math.min(1, healthPercent)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取生命值百分比
|
|
||||||
*/
|
|
||||||
public getHealthPercent(): number {
|
|
||||||
return this.maxHealth > 0 ? this.currentHealth / this.maxHealth : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置组件
|
|
||||||
*/
|
|
||||||
public reset(): void {
|
|
||||||
this.currentHealth = this.maxHealth;
|
|
||||||
this.isDead = false;
|
|
||||||
this.regenRate = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "90369635-a6cb-4313-adf1-64117b50f2bc",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,413 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络组件 - 模拟复杂的网络连接和数据同步(已移除循环引用)
|
|
||||||
*/
|
|
||||||
export class NetworkComponent extends Component {
|
|
||||||
/** 网络ID */
|
|
||||||
public networkId: string = '';
|
|
||||||
|
|
||||||
/** 连接状态 */
|
|
||||||
public connectionState: 'disconnected' | 'connecting' | 'connected' | 'error' = 'disconnected';
|
|
||||||
|
|
||||||
/** 网络连接信息 */
|
|
||||||
public connection: {
|
|
||||||
sessionId: string;
|
|
||||||
serverId: string;
|
|
||||||
roomId: string;
|
|
||||||
playerId: string;
|
|
||||||
ping: number;
|
|
||||||
packetLoss: number;
|
|
||||||
bandwidth: number;
|
|
||||||
lastHeartbeat: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 同步数据 */
|
|
||||||
public syncData: {
|
|
||||||
dirtyFlags: Set<string>;
|
|
||||||
lastSyncTime: number;
|
|
||||||
syncHistory: Array<{
|
|
||||||
timestamp: number;
|
|
||||||
dataSize: number;
|
|
||||||
properties: string[];
|
|
||||||
success: boolean;
|
|
||||||
}>;
|
|
||||||
queuedUpdates: Array<{
|
|
||||||
property: string;
|
|
||||||
value: any;
|
|
||||||
timestamp: number;
|
|
||||||
priority: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 网络统计 */
|
|
||||||
public networkStats: {
|
|
||||||
totalBytesSent: number;
|
|
||||||
totalBytesReceived: number;
|
|
||||||
packetsPerSecond: number;
|
|
||||||
averageLatency: number;
|
|
||||||
latencyHistory: number[];
|
|
||||||
connectionQuality: 'excellent' | 'good' | 'fair' | 'poor';
|
|
||||||
errorCount: number;
|
|
||||||
reconnectCount: number;
|
|
||||||
lastErrorTime: number;
|
|
||||||
errorLog: Array<{
|
|
||||||
timestamp: number;
|
|
||||||
errorType: string;
|
|
||||||
message: string;
|
|
||||||
stack?: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 连接的玩家ID列表(避免循环引用) */
|
|
||||||
public connectedPlayerIds: Set<string> = new Set();
|
|
||||||
|
|
||||||
/** 群组成员ID(避免循环引用) */
|
|
||||||
public groupMemberIds: string[] = [];
|
|
||||||
|
|
||||||
/** 群组领导者ID(避免循环引用) */
|
|
||||||
public groupLeaderId: string | null = null;
|
|
||||||
|
|
||||||
/** 复杂的网络配置 */
|
|
||||||
public config: {
|
|
||||||
autoReconnect: boolean;
|
|
||||||
maxReconnectAttempts: number;
|
|
||||||
heartbeatInterval: number;
|
|
||||||
syncFrequency: number;
|
|
||||||
compressionEnabled: boolean;
|
|
||||||
encryptionEnabled: boolean;
|
|
||||||
priorityLevels: Map<string, number>;
|
|
||||||
filters: Array<{
|
|
||||||
property: string;
|
|
||||||
condition: (value: any) => boolean;
|
|
||||||
action: 'allow' | 'deny' | 'transform';
|
|
||||||
transformer?: (value: any) => any;
|
|
||||||
}>;
|
|
||||||
bufferSettings: {
|
|
||||||
maxBufferSize: number;
|
|
||||||
flushInterval: number;
|
|
||||||
compressionThreshold: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 消息队列 */
|
|
||||||
public messageQueue: {
|
|
||||||
incoming: Array<{
|
|
||||||
senderId: string;
|
|
||||||
messageType: string;
|
|
||||||
data: any;
|
|
||||||
timestamp: number;
|
|
||||||
processed: boolean;
|
|
||||||
}>;
|
|
||||||
outgoing: Array<{
|
|
||||||
targetId: string;
|
|
||||||
messageType: string;
|
|
||||||
data: any;
|
|
||||||
priority: number;
|
|
||||||
attempts: number;
|
|
||||||
maxAttempts: number;
|
|
||||||
}>;
|
|
||||||
processing: Map<string, {
|
|
||||||
messageId: string;
|
|
||||||
startTime: number;
|
|
||||||
expectedDuration: number;
|
|
||||||
status: 'processing' | 'completed' | 'failed';
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 复杂的网络缓存系统 */
|
|
||||||
public cacheSystem: {
|
|
||||||
playerCache: Map<string, {
|
|
||||||
playerId: string;
|
|
||||||
lastSeen: number;
|
|
||||||
cachedData: any;
|
|
||||||
cacheExpiry: number;
|
|
||||||
}>;
|
|
||||||
messageCache: Map<string, {
|
|
||||||
messageId: string;
|
|
||||||
content: any;
|
|
||||||
timestamp: number;
|
|
||||||
recipients: string[];
|
|
||||||
}>;
|
|
||||||
syncCache: Map<string, {
|
|
||||||
propertyPath: string;
|
|
||||||
value: any;
|
|
||||||
lastUpdated: number;
|
|
||||||
version: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(networkId: string = '') {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.networkId = networkId || this.generateNetworkId();
|
|
||||||
|
|
||||||
this.connection = {
|
|
||||||
sessionId: '',
|
|
||||||
serverId: '',
|
|
||||||
roomId: '',
|
|
||||||
playerId: '',
|
|
||||||
ping: 0,
|
|
||||||
packetLoss: 0,
|
|
||||||
bandwidth: 0,
|
|
||||||
lastHeartbeat: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
this.syncData = {
|
|
||||||
dirtyFlags: new Set(),
|
|
||||||
lastSyncTime: 0,
|
|
||||||
syncHistory: [],
|
|
||||||
queuedUpdates: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.networkStats = {
|
|
||||||
totalBytesSent: 0,
|
|
||||||
totalBytesReceived: 0,
|
|
||||||
packetsPerSecond: 0,
|
|
||||||
averageLatency: 0,
|
|
||||||
latencyHistory: [],
|
|
||||||
connectionQuality: 'excellent',
|
|
||||||
errorCount: 0,
|
|
||||||
reconnectCount: 0,
|
|
||||||
lastErrorTime: 0,
|
|
||||||
errorLog: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.config = {
|
|
||||||
autoReconnect: true,
|
|
||||||
maxReconnectAttempts: 5,
|
|
||||||
heartbeatInterval: 1000,
|
|
||||||
syncFrequency: 60,
|
|
||||||
compressionEnabled: true,
|
|
||||||
encryptionEnabled: false,
|
|
||||||
priorityLevels: new Map([
|
|
||||||
['critical', 10],
|
|
||||||
['high', 7],
|
|
||||||
['medium', 5],
|
|
||||||
['low', 2]
|
|
||||||
]),
|
|
||||||
filters: [],
|
|
||||||
bufferSettings: {
|
|
||||||
maxBufferSize: 1024 * 1024, // 1MB
|
|
||||||
flushInterval: 100,
|
|
||||||
compressionThreshold: 1024
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.messageQueue = {
|
|
||||||
incoming: [],
|
|
||||||
outgoing: [],
|
|
||||||
processing: new Map()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.cacheSystem = {
|
|
||||||
playerCache: new Map(),
|
|
||||||
messageCache: new Map(),
|
|
||||||
syncCache: new Map()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成网络ID
|
|
||||||
*/
|
|
||||||
private generateNetworkId(): string {
|
|
||||||
return 'net_' + Math.random().toString(36).substring(2, 15) +
|
|
||||||
Math.random().toString(36).substring(2, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接到其他网络组件(避免循环引用)
|
|
||||||
*/
|
|
||||||
public connectToPlayer(playerNetworkId: string): void {
|
|
||||||
if (!this.connectedPlayerIds.has(playerNetworkId)) {
|
|
||||||
this.connectedPlayerIds.add(playerNetworkId);
|
|
||||||
|
|
||||||
// 记录连接事件
|
|
||||||
this.logNetworkEvent('player_connected', {
|
|
||||||
playerId: playerNetworkId,
|
|
||||||
timestamp: Date.now()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加入群组(避免循环引用)
|
|
||||||
*/
|
|
||||||
public joinGroup(memberIds: string[], leaderId?: string): void {
|
|
||||||
this.groupMemberIds = [...memberIds];
|
|
||||||
this.groupLeaderId = leaderId || null;
|
|
||||||
|
|
||||||
// 更新缓存
|
|
||||||
memberIds.forEach(memberId => {
|
|
||||||
this.cacheSystem.playerCache.set(memberId, {
|
|
||||||
playerId: memberId,
|
|
||||||
lastSeen: Date.now(),
|
|
||||||
cachedData: {},
|
|
||||||
cacheExpiry: Date.now() + 300000 // 5分钟缓存
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息
|
|
||||||
*/
|
|
||||||
public sendMessage(targetId: string, messageType: string, data: any, priority: number = 5): void {
|
|
||||||
const message = {
|
|
||||||
targetId,
|
|
||||||
messageType,
|
|
||||||
data: this.processOutgoingData(data),
|
|
||||||
priority,
|
|
||||||
attempts: 0,
|
|
||||||
maxAttempts: 3
|
|
||||||
};
|
|
||||||
|
|
||||||
this.messageQueue.outgoing.push(message);
|
|
||||||
this.messageQueue.outgoing.sort((a, b) => b.priority - a.priority);
|
|
||||||
|
|
||||||
// 更新统计
|
|
||||||
this.networkStats.totalBytesSent += JSON.stringify(data).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理传出数据
|
|
||||||
*/
|
|
||||||
private processOutgoingData(data: any): any {
|
|
||||||
let processedData = data;
|
|
||||||
|
|
||||||
// 应用过滤器
|
|
||||||
this.config.filters.forEach(filter => {
|
|
||||||
if (filter.condition(processedData)) {
|
|
||||||
if (filter.action === 'transform' && filter.transformer) {
|
|
||||||
processedData = filter.transformer(processedData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 压缩数据
|
|
||||||
if (this.config.compressionEnabled) {
|
|
||||||
processedData = this.compressData(processedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 压缩数据(模拟)
|
|
||||||
*/
|
|
||||||
private compressData(data: any): any {
|
|
||||||
// 模拟压缩算法
|
|
||||||
const serialized = JSON.stringify(data);
|
|
||||||
if (serialized.length > this.config.bufferSettings.compressionThreshold) {
|
|
||||||
// 模拟压缩
|
|
||||||
return {
|
|
||||||
compressed: true,
|
|
||||||
originalSize: serialized.length,
|
|
||||||
compressedSize: Math.floor(serialized.length * 0.6),
|
|
||||||
data: serialized.substring(0, Math.floor(serialized.length * 0.6))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记属性为脏
|
|
||||||
*/
|
|
||||||
public markDirty(property: string): void {
|
|
||||||
this.syncData.dirtyFlags.add(property);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新网络统计
|
|
||||||
*/
|
|
||||||
public updateNetworkStats(deltaTime: number): void {
|
|
||||||
// 更新延迟历史
|
|
||||||
if (this.networkStats.latencyHistory.length > 100) {
|
|
||||||
this.networkStats.latencyHistory.shift();
|
|
||||||
}
|
|
||||||
this.networkStats.latencyHistory.push(this.connection.ping);
|
|
||||||
|
|
||||||
// 计算平均延迟
|
|
||||||
this.networkStats.averageLatency = this.networkStats.latencyHistory.reduce((a, b) => a + b, 0) /
|
|
||||||
this.networkStats.latencyHistory.length;
|
|
||||||
|
|
||||||
// 更新连接质量
|
|
||||||
if (this.networkStats.averageLatency < 50) {
|
|
||||||
this.networkStats.connectionQuality = 'excellent';
|
|
||||||
} else if (this.networkStats.averageLatency < 100) {
|
|
||||||
this.networkStats.connectionQuality = 'good';
|
|
||||||
} else if (this.networkStats.averageLatency < 200) {
|
|
||||||
this.networkStats.connectionQuality = 'fair';
|
|
||||||
} else {
|
|
||||||
this.networkStats.connectionQuality = 'poor';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新包率
|
|
||||||
this.networkStats.packetsPerSecond = this.messageQueue.outgoing.length / deltaTime;
|
|
||||||
|
|
||||||
// 清理过期缓存
|
|
||||||
this.cleanupExpiredCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理过期缓存
|
|
||||||
*/
|
|
||||||
private cleanupExpiredCache(): void {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
// 清理玩家缓存
|
|
||||||
for (const [key, value] of this.cacheSystem.playerCache) {
|
|
||||||
if (value.cacheExpiry < now) {
|
|
||||||
this.cacheSystem.playerCache.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理消息缓存
|
|
||||||
for (const [key, value] of this.cacheSystem.messageCache) {
|
|
||||||
if (value.timestamp < now - 600000) { // 10分钟过期
|
|
||||||
this.cacheSystem.messageCache.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录网络事件
|
|
||||||
*/
|
|
||||||
private logNetworkEvent(eventType: string, data: any): void {
|
|
||||||
this.networkStats.errorLog.push({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
errorType: eventType,
|
|
||||||
message: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 限制日志大小
|
|
||||||
if (this.networkStats.errorLog.length > 1000) {
|
|
||||||
this.networkStats.errorLog = this.networkStats.errorLog.slice(-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置组件
|
|
||||||
*/
|
|
||||||
public reset(): void {
|
|
||||||
// 清理ID列表(不再需要处理循环引用)
|
|
||||||
this.connectedPlayerIds.clear();
|
|
||||||
this.groupMemberIds = [];
|
|
||||||
this.groupLeaderId = null;
|
|
||||||
this.connectionState = 'disconnected';
|
|
||||||
|
|
||||||
this.syncData.dirtyFlags.clear();
|
|
||||||
this.syncData.syncHistory = [];
|
|
||||||
this.syncData.queuedUpdates = [];
|
|
||||||
|
|
||||||
this.messageQueue.incoming = [];
|
|
||||||
this.messageQueue.outgoing = [];
|
|
||||||
this.messageQueue.processing.clear();
|
|
||||||
|
|
||||||
this.cacheSystem.playerCache.clear();
|
|
||||||
this.cacheSystem.messageCache.clear();
|
|
||||||
this.cacheSystem.syncCache.clear();
|
|
||||||
|
|
||||||
this.networkStats.errorLog = [];
|
|
||||||
this.networkStats.latencyHistory = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "d9263549-7b26-4b4f-9a15-b82e7af5fbd5",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,346 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Node, Vec3, Color, Sprite, Label } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Node组件 - 包含Cocos Creator节点引用(已移除循环引用)
|
|
||||||
*/
|
|
||||||
export class NodeComponent extends Component {
|
|
||||||
/** Cocos Creator节点引用 */
|
|
||||||
public node: Node | null = null;
|
|
||||||
|
|
||||||
/** 子节点列表 */
|
|
||||||
public children: Node[] = [];
|
|
||||||
|
|
||||||
/** 节点配置信息 */
|
|
||||||
public nodeConfig: {
|
|
||||||
name: string;
|
|
||||||
layer: number;
|
|
||||||
tag: string;
|
|
||||||
userData: Record<string, any>;
|
|
||||||
transformData: {
|
|
||||||
position: Vec3;
|
|
||||||
rotation: Vec3;
|
|
||||||
scale: Vec3;
|
|
||||||
};
|
|
||||||
renderData: {
|
|
||||||
color: Color;
|
|
||||||
opacity: number;
|
|
||||||
visible: boolean;
|
|
||||||
};
|
|
||||||
parentId: number | null; // 避免循环引用:使用父节点实体ID
|
|
||||||
childIds: number[]; // 避免循环引用:使用子节点实体ID列表
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 渲染组件引用 */
|
|
||||||
public sprite: Sprite | null = null;
|
|
||||||
public label: Label | null = null;
|
|
||||||
|
|
||||||
/** 复杂嵌套对象 */
|
|
||||||
public complexData: {
|
|
||||||
statistics: {
|
|
||||||
frameCount: number;
|
|
||||||
lastUpdateTime: number;
|
|
||||||
performance: {
|
|
||||||
avgRenderTime: number;
|
|
||||||
maxRenderTime: number;
|
|
||||||
renderHistory: number[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
cache: {
|
|
||||||
textureCache: Map<string, any>;
|
|
||||||
materialCache: Map<string, any>;
|
|
||||||
shaderCache: Map<string, any>;
|
|
||||||
};
|
|
||||||
hierarchy: {
|
|
||||||
parentId: number | null; // 避免循环引用:使用ID
|
|
||||||
rootId: number | null; // 避免循环引用:使用ID
|
|
||||||
depth: number;
|
|
||||||
siblingIndex: number;
|
|
||||||
};
|
|
||||||
animation: {
|
|
||||||
isPlaying: boolean;
|
|
||||||
currentFrame: number;
|
|
||||||
totalFrames: number;
|
|
||||||
loopCount: number;
|
|
||||||
animationQueue: Array<{
|
|
||||||
name: string;
|
|
||||||
duration: number;
|
|
||||||
delay: number;
|
|
||||||
easing: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
interaction: {
|
|
||||||
isInteractable: boolean;
|
|
||||||
touchEnabled: boolean;
|
|
||||||
hitTestResults: Array<{
|
|
||||||
position: Vec3;
|
|
||||||
timestamp: number;
|
|
||||||
result: boolean;
|
|
||||||
}>;
|
|
||||||
boundingBox: {
|
|
||||||
min: Vec3;
|
|
||||||
max: Vec3;
|
|
||||||
center: Vec3;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 复杂的渲染状态 */
|
|
||||||
public renderState: {
|
|
||||||
layerInfo: {
|
|
||||||
currentLayer: number;
|
|
||||||
layerStack: number[];
|
|
||||||
sortingOrder: number;
|
|
||||||
cullingMask: number;
|
|
||||||
};
|
|
||||||
materials: Array<{
|
|
||||||
materialId: string;
|
|
||||||
properties: Map<string, any>;
|
|
||||||
textures: Map<string, any>;
|
|
||||||
shaderParams: Record<string, any>;
|
|
||||||
}>;
|
|
||||||
lightingData: {
|
|
||||||
ambientColor: Color;
|
|
||||||
diffuseColor: Color;
|
|
||||||
specularColor: Color;
|
|
||||||
lightDirection: Vec3;
|
|
||||||
shadowData: {
|
|
||||||
castShadows: boolean;
|
|
||||||
receiveShadows: boolean;
|
|
||||||
shadowQuality: 'low' | 'medium' | 'high';
|
|
||||||
shadowDistance: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(name: string = "DefaultNode") {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.nodeConfig = {
|
|
||||||
name: name,
|
|
||||||
layer: 0,
|
|
||||||
tag: "default",
|
|
||||||
userData: {},
|
|
||||||
transformData: {
|
|
||||||
position: new Vec3(),
|
|
||||||
rotation: new Vec3(),
|
|
||||||
scale: new Vec3(1, 1, 1)
|
|
||||||
},
|
|
||||||
renderData: {
|
|
||||||
color: new Color(255, 255, 255, 255),
|
|
||||||
opacity: 1.0,
|
|
||||||
visible: true
|
|
||||||
},
|
|
||||||
parentId: null,
|
|
||||||
childIds: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.complexData = {
|
|
||||||
statistics: {
|
|
||||||
frameCount: 0,
|
|
||||||
lastUpdateTime: 0,
|
|
||||||
performance: {
|
|
||||||
avgRenderTime: 0,
|
|
||||||
maxRenderTime: 0,
|
|
||||||
renderHistory: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cache: {
|
|
||||||
textureCache: new Map(),
|
|
||||||
materialCache: new Map(),
|
|
||||||
shaderCache: new Map()
|
|
||||||
},
|
|
||||||
hierarchy: {
|
|
||||||
parentId: null,
|
|
||||||
rootId: null,
|
|
||||||
depth: 0,
|
|
||||||
siblingIndex: 0
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
isPlaying: false,
|
|
||||||
currentFrame: 0,
|
|
||||||
totalFrames: 60,
|
|
||||||
loopCount: 0,
|
|
||||||
animationQueue: []
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
isInteractable: true,
|
|
||||||
touchEnabled: true,
|
|
||||||
hitTestResults: [],
|
|
||||||
boundingBox: {
|
|
||||||
min: new Vec3(-1, -1, -1),
|
|
||||||
max: new Vec3(1, 1, 1),
|
|
||||||
center: new Vec3()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.renderState = {
|
|
||||||
layerInfo: {
|
|
||||||
currentLayer: 0,
|
|
||||||
layerStack: [0],
|
|
||||||
sortingOrder: 0,
|
|
||||||
cullingMask: 0xFFFFFFFF
|
|
||||||
},
|
|
||||||
materials: [],
|
|
||||||
lightingData: {
|
|
||||||
ambientColor: new Color(128, 128, 128, 255),
|
|
||||||
diffuseColor: new Color(255, 255, 255, 255),
|
|
||||||
specularColor: new Color(255, 255, 255, 255),
|
|
||||||
lightDirection: new Vec3(0, -1, 0),
|
|
||||||
shadowData: {
|
|
||||||
castShadows: true,
|
|
||||||
receiveShadows: true,
|
|
||||||
shadowQuality: 'medium',
|
|
||||||
shadowDistance: 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置父节点组件(避免循环引用)
|
|
||||||
*/
|
|
||||||
public setParent(parentEntityId: number): void {
|
|
||||||
this.nodeConfig.parentId = parentEntityId;
|
|
||||||
this.complexData.hierarchy.parentId = parentEntityId;
|
|
||||||
// 深度需要通过其他方式计算,避免引用
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加子节点
|
|
||||||
*/
|
|
||||||
public addChild(childEntityId: number): void {
|
|
||||||
if (!this.nodeConfig.childIds.includes(childEntityId)) {
|
|
||||||
this.nodeConfig.childIds.push(childEntityId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新性能统计
|
|
||||||
*/
|
|
||||||
public updatePerformance(renderTime: number): void {
|
|
||||||
this.complexData.statistics.frameCount++;
|
|
||||||
this.complexData.statistics.lastUpdateTime = Date.now();
|
|
||||||
|
|
||||||
const perf = this.complexData.statistics.performance;
|
|
||||||
perf.renderHistory.push(renderTime);
|
|
||||||
|
|
||||||
// 保持历史记录在合理范围内
|
|
||||||
if (perf.renderHistory.length > 100) {
|
|
||||||
perf.renderHistory.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算平均值和最大值
|
|
||||||
perf.avgRenderTime = perf.renderHistory.reduce((a, b) => a + b, 0) / perf.renderHistory.length;
|
|
||||||
perf.maxRenderTime = Math.max(perf.maxRenderTime, renderTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新动画状态
|
|
||||||
*/
|
|
||||||
public updateAnimation(deltaTime: number): void {
|
|
||||||
if (this.complexData.animation.isPlaying) {
|
|
||||||
this.complexData.animation.currentFrame++;
|
|
||||||
|
|
||||||
if (this.complexData.animation.currentFrame >= this.complexData.animation.totalFrames) {
|
|
||||||
this.complexData.animation.currentFrame = 0;
|
|
||||||
this.complexData.animation.loopCount++;
|
|
||||||
|
|
||||||
// 处理动画队列
|
|
||||||
if (this.complexData.animation.animationQueue.length > 0) {
|
|
||||||
const nextAnim = this.complexData.animation.animationQueue.shift();
|
|
||||||
if (nextAnim) {
|
|
||||||
this.complexData.animation.totalFrames = Math.floor(nextAnim.duration * 60); // 假设60FPS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加材质
|
|
||||||
*/
|
|
||||||
public addMaterial(materialId: string, properties: Record<string, any>): void {
|
|
||||||
this.renderState.materials.push({
|
|
||||||
materialId,
|
|
||||||
properties: new Map(Object.entries(properties)),
|
|
||||||
textures: new Map(),
|
|
||||||
shaderParams: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新包围盒
|
|
||||||
*/
|
|
||||||
public updateBoundingBox(): void {
|
|
||||||
if (this.node) {
|
|
||||||
const worldPos = this.node.getWorldPosition();
|
|
||||||
const scale = this.node.getScale();
|
|
||||||
|
|
||||||
this.complexData.interaction.boundingBox.center = new Vec3(worldPos.x, worldPos.y, worldPos.z);
|
|
||||||
this.complexData.interaction.boundingBox.min = new Vec3(
|
|
||||||
worldPos.x - scale.x * 0.5,
|
|
||||||
worldPos.y - scale.y * 0.5,
|
|
||||||
worldPos.z - scale.z * 0.5
|
|
||||||
);
|
|
||||||
this.complexData.interaction.boundingBox.max = new Vec3(
|
|
||||||
worldPos.x + scale.x * 0.5,
|
|
||||||
worldPos.y + scale.y * 0.5,
|
|
||||||
worldPos.z + scale.z * 0.5
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行点击测试
|
|
||||||
*/
|
|
||||||
public hitTest(point: Vec3): boolean {
|
|
||||||
const bbox = this.complexData.interaction.boundingBox;
|
|
||||||
const result = point.x >= bbox.min.x && point.x <= bbox.max.x &&
|
|
||||||
point.y >= bbox.min.y && point.y <= bbox.max.y &&
|
|
||||||
point.z >= bbox.min.z && point.z <= bbox.max.z;
|
|
||||||
|
|
||||||
// 记录测试结果
|
|
||||||
this.complexData.interaction.hitTestResults.push({
|
|
||||||
position: new Vec3(point.x, point.y, point.z),
|
|
||||||
timestamp: Date.now(),
|
|
||||||
result
|
|
||||||
});
|
|
||||||
|
|
||||||
// 限制历史记录大小
|
|
||||||
if (this.complexData.interaction.hitTestResults.length > 50) {
|
|
||||||
this.complexData.interaction.hitTestResults.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置组件
|
|
||||||
*/
|
|
||||||
public reset(): void {
|
|
||||||
this.node = null;
|
|
||||||
this.children = [];
|
|
||||||
this.sprite = null;
|
|
||||||
this.label = null;
|
|
||||||
|
|
||||||
// 清理ID列表(不再需要处理循环引用)
|
|
||||||
this.nodeConfig.parentId = null;
|
|
||||||
this.nodeConfig.childIds = [];
|
|
||||||
this.complexData.hierarchy.parentId = null;
|
|
||||||
this.complexData.hierarchy.rootId = null;
|
|
||||||
this.complexData.hierarchy.depth = 0;
|
|
||||||
|
|
||||||
this.complexData.cache.textureCache.clear();
|
|
||||||
this.complexData.cache.materialCache.clear();
|
|
||||||
this.complexData.cache.shaderCache.clear();
|
|
||||||
|
|
||||||
this.complexData.animation.isPlaying = false;
|
|
||||||
this.complexData.animation.currentFrame = 0;
|
|
||||||
this.complexData.animation.animationQueue = [];
|
|
||||||
|
|
||||||
this.complexData.interaction.hitTestResults = [];
|
|
||||||
this.renderState.materials = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "28e7e8cd-591e-4fde-bb14-d668724a6201",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Color } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染组件
|
|
||||||
* 存储实体的渲染相关信息
|
|
||||||
*/
|
|
||||||
export class Renderer extends Component {
|
|
||||||
/** 颜色 */
|
|
||||||
public color: Color = new Color(255, 255, 255, 255);
|
|
||||||
|
|
||||||
/** 是否可见 */
|
|
||||||
public visible: boolean = true;
|
|
||||||
|
|
||||||
/** 渲染层级 */
|
|
||||||
public layer: number = 0;
|
|
||||||
|
|
||||||
/** 精灵名称或纹理路径 */
|
|
||||||
public spriteName: string = '';
|
|
||||||
|
|
||||||
/** 大小 */
|
|
||||||
public size: { width: number, height: number } = { width: 32, height: 32 };
|
|
||||||
|
|
||||||
/** 透明度 (0-1) */
|
|
||||||
public alpha: number = 1.0;
|
|
||||||
|
|
||||||
constructor(spriteName: string = '', color?: Color) {
|
|
||||||
super();
|
|
||||||
this.spriteName = spriteName;
|
|
||||||
if (color) {
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置颜色
|
|
||||||
*/
|
|
||||||
public setColor(r: number, g: number, b: number, a: number = 255): void {
|
|
||||||
this.color.set(r, g, b, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置透明度
|
|
||||||
*/
|
|
||||||
public setAlpha(alpha: number): void {
|
|
||||||
this.alpha = Math.max(0, Math.min(1, alpha));
|
|
||||||
this.color.a = Math.floor(this.alpha * 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置大小
|
|
||||||
*/
|
|
||||||
public setSize(width: number, height: number): void {
|
|
||||||
this.size.width = width;
|
|
||||||
this.size.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示/隐藏
|
|
||||||
*/
|
|
||||||
public setVisible(visible: boolean): void {
|
|
||||||
this.visible = visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置组件
|
|
||||||
*/
|
|
||||||
public reset(): void {
|
|
||||||
this.color.set(255, 255, 255, 255);
|
|
||||||
this.visible = true;
|
|
||||||
this.layer = 0;
|
|
||||||
this.spriteName = '';
|
|
||||||
this.size = { width: 32, height: 32 };
|
|
||||||
this.alpha = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "bf51f134-6ea5-4a26-8b15-0ed20fe1d605",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Vec3 } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 变换组件
|
|
||||||
* 存储实体的位置、旋转和缩放信息
|
|
||||||
*/
|
|
||||||
export class Transform extends Component {
|
|
||||||
/** 位置 */
|
|
||||||
public position: Vec3 = new Vec3(0, 0, 0);
|
|
||||||
|
|
||||||
/** 旋转 (度数) */
|
|
||||||
public rotation: Vec3 = new Vec3(0, 0, 0);
|
|
||||||
|
|
||||||
/** 缩放 */
|
|
||||||
public scale: Vec3 = new Vec3(1, 1, 1);
|
|
||||||
|
|
||||||
/** 移动速度 */
|
|
||||||
public speed: number = 100;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置位置
|
|
||||||
*/
|
|
||||||
public setPosition(x: number, y: number, z: number = 0): void {
|
|
||||||
this.position.set(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动
|
|
||||||
*/
|
|
||||||
public move(deltaX: number, deltaY: number, deltaZ: number = 0): void {
|
|
||||||
this.position.x += deltaX;
|
|
||||||
this.position.y += deltaY;
|
|
||||||
this.position.z += deltaZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置组件
|
|
||||||
*/
|
|
||||||
public reset(): void {
|
|
||||||
this.position.set(0, 0, 0);
|
|
||||||
this.rotation.set(0, 0, 0);
|
|
||||||
this.scale.set(1, 1, 1);
|
|
||||||
this.speed = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "09de6e5b-7bb7-4de8-8038-67be5ae955bc",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Vec3 } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 速度组件
|
|
||||||
* 存储实体的移动速度和方向
|
|
||||||
*/
|
|
||||||
export class Velocity extends Component {
|
|
||||||
/** 速度向量 */
|
|
||||||
public velocity: Vec3 = new Vec3(0, 0, 0);
|
|
||||||
|
|
||||||
/** 最大速度 */
|
|
||||||
public maxSpeed: number = 200;
|
|
||||||
|
|
||||||
/** 摩擦力 (0-1, 1表示无摩擦) */
|
|
||||||
public friction: number = 0.98;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置速度
|
|
||||||
*/
|
|
||||||
public setVelocity(x: number, y: number, z: number = 0): void {
|
|
||||||
this.velocity.set(x, y, z);
|
|
||||||
this.clampToMaxSpeed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加速度
|
|
||||||
*/
|
|
||||||
public addVelocity(x: number, y: number, z: number = 0): void {
|
|
||||||
this.velocity.x += x;
|
|
||||||
this.velocity.y += y;
|
|
||||||
this.velocity.z += z;
|
|
||||||
this.clampToMaxSpeed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用摩擦力
|
|
||||||
*/
|
|
||||||
public applyFriction(): void {
|
|
||||||
this.velocity.multiplyScalar(this.friction);
|
|
||||||
|
|
||||||
// 当速度很小时直接设为0,避免无限减小
|
|
||||||
if (this.velocity.length() < 0.1) {
|
|
||||||
this.velocity.set(0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 限制到最大速度
|
|
||||||
*/
|
|
||||||
private clampToMaxSpeed(): void {
|
|
||||||
const currentSpeed = this.velocity.length();
|
|
||||||
if (currentSpeed > this.maxSpeed) {
|
|
||||||
this.velocity.normalize().multiplyScalar(this.maxSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前速度大小
|
|
||||||
*/
|
|
||||||
public getSpeed(): number {
|
|
||||||
return this.velocity.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置组件
|
|
||||||
*/
|
|
||||||
public reset(): void {
|
|
||||||
this.velocity.set(0, 0, 0);
|
|
||||||
this.maxSpeed = 200;
|
|
||||||
this.friction = 0.98;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "10c40371-267a-4016-a8b5-9f803e68e72b",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// 导出所有组件
|
|
||||||
export { Transform } from './Transform';
|
|
||||||
export { Health } from './Health';
|
|
||||||
export { Velocity } from './Velocity';
|
|
||||||
export { Renderer } from './Renderer';
|
|
||||||
export { NodeComponent } from './NodeComponent';
|
|
||||||
export { AIComponent } from './AIComponent';
|
|
||||||
export { NetworkComponent } from './NetworkComponent';
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "dfc46d23-7ad6-4a21-914c-35d948185f93",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "39da4804-e61e-440e-b73d-544963bd3901",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,321 +0,0 @@
|
|||||||
import { Scene } from '@esengine/ecs-framework';
|
|
||||||
import { Color, Node } from 'cc';
|
|
||||||
import { MovementSystem, HealthSystem, RandomMovementSystem, AISystem, NetworkSystem, NodeRenderSystem } from '../systems';
|
|
||||||
import { Transform, Health, Velocity, Renderer, NodeComponent, AIComponent, NetworkComponent } from '../components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 游戏场景
|
|
||||||
*
|
|
||||||
* 这是您的主游戏场景。在这里可以:
|
|
||||||
* - 添加游戏系统
|
|
||||||
* - 创建初始实体
|
|
||||||
* - 设置场景参数
|
|
||||||
*/
|
|
||||||
export class GameScene extends Scene {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景初始化
|
|
||||||
* 在场景创建时调用,用于设置基础配置
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
|
|
||||||
// 设置场景名称
|
|
||||||
this.name = "MainGameScene";
|
|
||||||
|
|
||||||
console.log('🎯 游戏场景已创建');
|
|
||||||
|
|
||||||
// 添加游戏系统
|
|
||||||
this.addEntityProcessor(new MovementSystem());
|
|
||||||
this.addEntityProcessor(new HealthSystem());
|
|
||||||
this.addEntityProcessor(new RandomMovementSystem());
|
|
||||||
// this.addEntityProcessor(new AISystem());
|
|
||||||
// this.addEntityProcessor(new NetworkSystem());
|
|
||||||
// this.addEntityProcessor(new NodeRenderSystem());
|
|
||||||
|
|
||||||
// 创建大量复杂的测试实体
|
|
||||||
this.createComplexTestEntities();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建复杂的测试实体(1000+个)
|
|
||||||
*/
|
|
||||||
private createComplexTestEntities(): void {
|
|
||||||
console.log('🚀 开始创建大量复杂测试实体...');
|
|
||||||
|
|
||||||
// 存储创建的AI和网络组件用于建立循环引用
|
|
||||||
const aiComponents: AIComponent[] = [];
|
|
||||||
const networkComponents: NetworkComponent[] = [];
|
|
||||||
const nodeComponents: NodeComponent[] = [];
|
|
||||||
|
|
||||||
// 1. 创建玩家实体(具有所有组件类型)
|
|
||||||
console.log('创建玩家实体...');
|
|
||||||
const player = this.createComplexEntity("Player", "player", new Color(0, 255, 0, 255), 0, 0, true, true, true);
|
|
||||||
if (player) {
|
|
||||||
const playerAI = player.getComponent(AIComponent);
|
|
||||||
const playerNetwork = player.getComponent(NetworkComponent);
|
|
||||||
const playerNode = player.getComponent(NodeComponent);
|
|
||||||
|
|
||||||
if (playerAI) aiComponents.push(playerAI);
|
|
||||||
if (playerNetwork) networkComponents.push(playerNetwork);
|
|
||||||
if (playerNode) nodeComponents.push(playerNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 创建AI智能体(500个)
|
|
||||||
console.log('创建AI智能体...');
|
|
||||||
for (let i = 0; i < 500; i++) {
|
|
||||||
const entityName = `AI_Agent_${i}`;
|
|
||||||
const x = (Math.random() - 0.5) * 2000;
|
|
||||||
const y = (Math.random() - 0.5) * 2000;
|
|
||||||
const color = new Color(
|
|
||||||
Math.floor(Math.random() * 255),
|
|
||||||
Math.floor(Math.random() * 255),
|
|
||||||
Math.floor(Math.random() * 255),
|
|
||||||
255
|
|
||||||
);
|
|
||||||
|
|
||||||
const entity = this.createComplexEntity(entityName, "ai_agent", color, x, y, true, true, Math.random() > 0.5);
|
|
||||||
|
|
||||||
if (entity) {
|
|
||||||
const ai = entity.getComponent(AIComponent);
|
|
||||||
const network = entity.getComponent(NetworkComponent);
|
|
||||||
const node = entity.getComponent(NodeComponent);
|
|
||||||
|
|
||||||
if (ai) {
|
|
||||||
aiComponents.push(ai);
|
|
||||||
// 设置随机AI个性
|
|
||||||
ai.config.personality.aggression = Math.random();
|
|
||||||
ai.config.personality.curiosity = Math.random();
|
|
||||||
ai.config.personality.loyalty = Math.random();
|
|
||||||
ai.config.personality.intelligence = Math.random();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (network) {
|
|
||||||
networkComponents.push(network);
|
|
||||||
// 随机设置网络状态
|
|
||||||
if (Math.random() > 0.2) {
|
|
||||||
network.connectionState = 'connected';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
nodeComponents.push(node);
|
|
||||||
// 设置随机节点属性
|
|
||||||
node.nodeConfig.layer = Math.floor(Math.random() * 10);
|
|
||||||
node.nodeConfig.tag = `layer_${node.nodeConfig.layer}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 创建网络节点(300个)
|
|
||||||
console.log('创建网络节点...');
|
|
||||||
for (let i = 0; i < 300; i++) {
|
|
||||||
const entityName = `Network_Node_${i}`;
|
|
||||||
const x = (Math.random() - 0.5) * 1500;
|
|
||||||
const y = (Math.random() - 0.5) * 1500;
|
|
||||||
const color = new Color(0, 150, 255, 200);
|
|
||||||
|
|
||||||
const entity = this.createComplexEntity(entityName, "network_node", color, x, y, false, true, true);
|
|
||||||
|
|
||||||
if (entity) {
|
|
||||||
const network = entity.getComponent(NetworkComponent);
|
|
||||||
const node = entity.getComponent(NodeComponent);
|
|
||||||
|
|
||||||
if (network) {
|
|
||||||
networkComponents.push(network);
|
|
||||||
network.connectionState = 'connected';
|
|
||||||
network.config.syncFrequency = 30 + Math.random() * 30; // 30-60Hz
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
nodeComponents.push(node);
|
|
||||||
// 创建复杂的层次结构
|
|
||||||
node.nodeConfig.layer = Math.floor(i / 10); // 每10个一层
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 创建简单移动实体(200个)
|
|
||||||
console.log('创建简单移动实体...');
|
|
||||||
for (let i = 0; i < 200; i++) {
|
|
||||||
const entityName = `Simple_Mover_${i}`;
|
|
||||||
const x = (Math.random() - 0.5) * 1000;
|
|
||||||
const y = (Math.random() - 0.5) * 1000;
|
|
||||||
const color = new Color(255, 255, 255, 150);
|
|
||||||
|
|
||||||
this.createComplexEntity(entityName, "simple_mover", color, x, y, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 建立循环引用和复杂关系
|
|
||||||
console.log('建立实体间的复杂关系...');
|
|
||||||
this.establishComplexRelationships(aiComponents, networkComponents, nodeComponents);
|
|
||||||
|
|
||||||
const totalEntities = this.entities.count;
|
|
||||||
console.log(`✅ 创建完成!总共创建了 ${totalEntities} 个实体`);
|
|
||||||
console.log(` - AI组件: ${aiComponents.length} 个`);
|
|
||||||
console.log(` - 网络组件: ${networkComponents.length} 个`);
|
|
||||||
console.log(` - 节点组件: ${nodeComponents.length} 个`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建复杂实体的辅助方法
|
|
||||||
*/
|
|
||||||
private createComplexEntity(
|
|
||||||
name: string,
|
|
||||||
type: string,
|
|
||||||
color: Color,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
hasAI: boolean,
|
|
||||||
hasNetwork: boolean,
|
|
||||||
hasNode: boolean
|
|
||||||
): any {
|
|
||||||
const entity = this.createEntity(name);
|
|
||||||
|
|
||||||
// 基础组件
|
|
||||||
const transform = new Transform();
|
|
||||||
transform.setPosition(x, y);
|
|
||||||
transform.speed = 50 + Math.random() * 100;
|
|
||||||
entity.addComponent(transform);
|
|
||||||
|
|
||||||
const health = new Health(50 + Math.random() * 100);
|
|
||||||
health.regenRate = Math.random() * 10;
|
|
||||||
entity.addComponent(health);
|
|
||||||
|
|
||||||
const velocity = new Velocity();
|
|
||||||
velocity.maxSpeed = 80 + Math.random() * 120;
|
|
||||||
velocity.friction = 0.95 + Math.random() * 0.04;
|
|
||||||
entity.addComponent(velocity);
|
|
||||||
|
|
||||||
const renderer = new Renderer(type, color.clone());
|
|
||||||
renderer.alpha = 0.5 + Math.random() * 0.5;
|
|
||||||
renderer.layer = Math.floor(Math.random() * 5);
|
|
||||||
entity.addComponent(renderer);
|
|
||||||
|
|
||||||
// 复杂组件
|
|
||||||
if (hasAI) {
|
|
||||||
const ai = new AIComponent();
|
|
||||||
entity.addComponent(ai);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNetwork) {
|
|
||||||
const network = new NetworkComponent(`${type}_${name}`);
|
|
||||||
entity.addComponent(network);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNode) {
|
|
||||||
const node = new NodeComponent(name);
|
|
||||||
entity.addComponent(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 建立实体间的复杂关系
|
|
||||||
*/
|
|
||||||
private establishComplexRelationships(
|
|
||||||
aiComponents: AIComponent[],
|
|
||||||
networkComponents: NetworkComponent[],
|
|
||||||
nodeComponents: NodeComponent[]
|
|
||||||
): void {
|
|
||||||
// 建立AI之间的盟友/敌人关系(避免循环引用)
|
|
||||||
for (let i = 0; i < Math.min(aiComponents.length, 100); i++) {
|
|
||||||
const ai = aiComponents[i];
|
|
||||||
|
|
||||||
// 随机添加盟友(使用实体ID)
|
|
||||||
const allyCount = Math.floor(Math.random() * 5);
|
|
||||||
for (let j = 0; j < allyCount; j++) {
|
|
||||||
const randomIndex = Math.floor(Math.random() * aiComponents.length);
|
|
||||||
const ally = aiComponents[randomIndex];
|
|
||||||
if (ally !== ai && !ai.allyIds.includes(ally.entity.id)) {
|
|
||||||
ai.addAlly(ally.entity.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 随机设置目标(使用实体ID)
|
|
||||||
if (Math.random() > 0.7) {
|
|
||||||
const randomIndex = Math.floor(Math.random() * aiComponents.length);
|
|
||||||
const target = aiComponents[randomIndex];
|
|
||||||
if (target !== ai) {
|
|
||||||
ai.setTarget(target.entity.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 建立网络连接(避免循环引用)
|
|
||||||
for (let i = 0; i < Math.min(networkComponents.length, 50); i++) {
|
|
||||||
const network = networkComponents[i];
|
|
||||||
|
|
||||||
// 连接到其他网络组件(使用网络ID)
|
|
||||||
const connectionCount = Math.floor(Math.random() * 8);
|
|
||||||
for (let j = 0; j < connectionCount; j++) {
|
|
||||||
const randomIndex = Math.floor(Math.random() * networkComponents.length);
|
|
||||||
const other = networkComponents[randomIndex];
|
|
||||||
if (other !== network) {
|
|
||||||
network.connectToPlayer(other.networkId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建群组(使用网络ID)
|
|
||||||
if (Math.random() > 0.8) {
|
|
||||||
const groupSize = Math.floor(Math.random() * 10) + 2;
|
|
||||||
const groupMemberIds: string[] = [network.networkId];
|
|
||||||
|
|
||||||
for (let k = 0; k < groupSize - 1; k++) {
|
|
||||||
const randomIndex = Math.floor(Math.random() * networkComponents.length);
|
|
||||||
const member = networkComponents[randomIndex];
|
|
||||||
if (!groupMemberIds.includes(member.networkId)) {
|
|
||||||
groupMemberIds.push(member.networkId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
network.joinGroup(groupMemberIds, network.networkId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 建立节点层次结构(避免循环引用)
|
|
||||||
for (let i = 0; i < Math.min(nodeComponents.length, 30); i++) {
|
|
||||||
const parent = nodeComponents[i];
|
|
||||||
|
|
||||||
// 添加一些子节点(使用实体ID)
|
|
||||||
const childCount = Math.floor(Math.random() * 5);
|
|
||||||
for (let j = 0; j < childCount; j++) {
|
|
||||||
const childIndex = Math.floor(Math.random() * nodeComponents.length);
|
|
||||||
const child = nodeComponents[childIndex];
|
|
||||||
|
|
||||||
if (child !== parent && !parent.nodeConfig.childIds.includes(child.entity.id)) {
|
|
||||||
parent.addChild(child.entity.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🔗 复杂关系建立完成!');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景开始运行
|
|
||||||
* 在场景开始时调用,用于执行启动逻辑
|
|
||||||
*/
|
|
||||||
public onStart(): void {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
console.log('🚀 游戏场景已启动');
|
|
||||||
|
|
||||||
// TODO: 在这里添加场景启动逻辑
|
|
||||||
// 例如:创建UI、播放音乐、初始化游戏状态等
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景卸载
|
|
||||||
* 在场景结束时调用,用于清理资源
|
|
||||||
*/
|
|
||||||
public unload(): void {
|
|
||||||
console.log('🛑 游戏场景已结束');
|
|
||||||
|
|
||||||
// TODO: 在这里添加清理逻辑
|
|
||||||
// 例如:清理缓存、释放资源等
|
|
||||||
|
|
||||||
super.unload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "dbf17c21-6f4a-4f87-8568-7ac5a2ec10cd",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "f8f0d97b-46f6-49ff-9fe5-b31eee989963",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,317 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { AIComponent, Transform, Health } from '../components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI系统 - 处理AI行为和状态机
|
|
||||||
*/
|
|
||||||
export class AISystem extends EntitySystem {
|
|
||||||
|
|
||||||
/** 系统处理的实体计数器 */
|
|
||||||
private processedEntityCount: number = 0;
|
|
||||||
|
|
||||||
/** 状态转换计数器 */
|
|
||||||
private stateTransitionCount: number = 0;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 处理具有AI组件的实体
|
|
||||||
super(Matcher.empty().all(AIComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有实体
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
const deltaTime = Time.deltaTime;
|
|
||||||
const currentTime = Time.totalTime;
|
|
||||||
|
|
||||||
this.processedEntityCount = entities.length;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.processEntity(entity, deltaTime, currentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量处理AI间的交互
|
|
||||||
this.processAIInteractions(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单个实体
|
|
||||||
*/
|
|
||||||
private processEntity(entity: Entity, deltaTime: number, currentTime: number): void {
|
|
||||||
const ai = entity.getComponent(AIComponent);
|
|
||||||
const transform = entity.getComponent(Transform);
|
|
||||||
const health = entity.getComponent(Health);
|
|
||||||
|
|
||||||
if (!ai) return;
|
|
||||||
|
|
||||||
// 更新感知系统
|
|
||||||
ai.updatePerception(deltaTime);
|
|
||||||
|
|
||||||
// 处理状态机
|
|
||||||
this.updateStateMachine(ai, deltaTime);
|
|
||||||
|
|
||||||
// 更新行为树
|
|
||||||
this.updateBehaviorTree(ai, deltaTime);
|
|
||||||
|
|
||||||
// 处理AI能力(如果有Transform和Health组件)
|
|
||||||
if (transform && health) {
|
|
||||||
this.updateAICapabilities(ai, transform, health, deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理记忆衰减
|
|
||||||
this.updateMemory(ai, deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新状态机
|
|
||||||
*/
|
|
||||||
private updateStateMachine(ai: AIComponent, deltaTime: number): void {
|
|
||||||
const currentStateName = ai.currentState;
|
|
||||||
const transitions = ai.stateMachine.transitions.get(currentStateName);
|
|
||||||
|
|
||||||
if (transitions) {
|
|
||||||
// 按优先级排序转换条件
|
|
||||||
const sortedTransitions = transitions.sort((a, b) => b.priority - a.priority);
|
|
||||||
|
|
||||||
for (const transition of sortedTransitions) {
|
|
||||||
if (transition.condition()) {
|
|
||||||
// 执行状态转换
|
|
||||||
const currentState = ai.stateMachine.states.get(currentStateName);
|
|
||||||
const newState = ai.stateMachine.states.get(transition.targetState);
|
|
||||||
|
|
||||||
if (currentState && newState) {
|
|
||||||
currentState.exit();
|
|
||||||
ai.currentState = transition.targetState as any;
|
|
||||||
newState.enter();
|
|
||||||
this.stateTransitionCount++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新行为树
|
|
||||||
*/
|
|
||||||
private updateBehaviorTree(ai: AIComponent, deltaTime: number): void {
|
|
||||||
const behaviorTree = ai.config.behaviorTree;
|
|
||||||
const blackboard = behaviorTree.blackboard;
|
|
||||||
|
|
||||||
// 更新黑板数据
|
|
||||||
blackboard.set('deltaTime', deltaTime);
|
|
||||||
blackboard.set('currentTime', Date.now());
|
|
||||||
|
|
||||||
// 模拟行为树执行
|
|
||||||
const executionResult = this.executeBehaviorNode(behaviorTree.rootNode, ai);
|
|
||||||
|
|
||||||
// 记录执行历史
|
|
||||||
behaviorTree.executionHistory.push({
|
|
||||||
nodeName: behaviorTree.rootNode.name,
|
|
||||||
startTime: Date.now(),
|
|
||||||
endTime: Date.now() + Math.random() * 10,
|
|
||||||
result: executionResult,
|
|
||||||
data: { deltaTime }
|
|
||||||
});
|
|
||||||
|
|
||||||
// 保持历史记录在合理范围内
|
|
||||||
if (behaviorTree.executionHistory.length > 50) {
|
|
||||||
behaviorTree.executionHistory.shift();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行行为树节点(模拟)
|
|
||||||
*/
|
|
||||||
private executeBehaviorNode(node: any, ai: AIComponent): 'success' | 'failure' | 'running' {
|
|
||||||
// 简单的行为树执行模拟
|
|
||||||
switch (node.name) {
|
|
||||||
case 'root':
|
|
||||||
case 'selector':
|
|
||||||
// 选择器节点:尝试执行子节点直到一个成功
|
|
||||||
for (const child of node.children) {
|
|
||||||
const result = this.executeBehaviorNode(child, ai);
|
|
||||||
if (result === 'success' || result === 'running') {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 'failure';
|
|
||||||
|
|
||||||
case 'sequence':
|
|
||||||
// 序列节点:按顺序执行所有子节点
|
|
||||||
for (const child of node.children) {
|
|
||||||
const result = this.executeBehaviorNode(child, ai);
|
|
||||||
if (result === 'failure' || result === 'running') {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 'success';
|
|
||||||
|
|
||||||
case 'condition':
|
|
||||||
// 条件节点:检查AI状态
|
|
||||||
return ai.config.personality.intelligence > 0.5 ? 'success' : 'failure';
|
|
||||||
|
|
||||||
case 'action':
|
|
||||||
// 动作节点:执行AI行为
|
|
||||||
return Math.random() > 0.3 ? 'success' : 'running';
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新AI能力
|
|
||||||
*/
|
|
||||||
private updateAICapabilities(ai: AIComponent, transform: Transform, health: Health, deltaTime: number): void {
|
|
||||||
const capabilities = ai.config.capabilities;
|
|
||||||
|
|
||||||
// 根据健康状况调整能力
|
|
||||||
const healthRatio = health.currentHealth / health.maxHealth;
|
|
||||||
const effectiveSpeed = capabilities.movementSpeed * healthRatio;
|
|
||||||
|
|
||||||
// 更新移动速度
|
|
||||||
transform.speed = effectiveSpeed;
|
|
||||||
|
|
||||||
// 根据个性调整行为
|
|
||||||
if (ai.config.personality.aggression > 0.7 && healthRatio > 0.5) {
|
|
||||||
// 高攻击性且健康状况良好时更主动
|
|
||||||
ai.currentState = 'chase';
|
|
||||||
} else if (healthRatio < 0.3) {
|
|
||||||
// 生命值低时逃跑
|
|
||||||
ai.currentState = 'flee';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新记忆系统
|
|
||||||
*/
|
|
||||||
private updateMemory(ai: AIComponent, deltaTime: number): void {
|
|
||||||
const memory = ai.config.memory;
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
// 衰减已知位置的可信度
|
|
||||||
memory.knownLocations.forEach(location => {
|
|
||||||
const timeSinceVisit = currentTime - location.lastVisited;
|
|
||||||
const decayFactor = Math.exp(-timeSinceVisit / 30000); // 30秒衰减率
|
|
||||||
location.confidence *= decayFactor;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 移除可信度过低的位置
|
|
||||||
memory.knownLocations = memory.knownLocations.filter(location => location.confidence > 0.1);
|
|
||||||
|
|
||||||
// 衰减关系信任度
|
|
||||||
memory.relationships.forEach(relation => {
|
|
||||||
const timeSinceInteraction = currentTime - relation.lastInteraction;
|
|
||||||
if (timeSinceInteraction > 60000) { // 60秒没有交互
|
|
||||||
relation.trustLevel *= 0.99; // 缓慢衰减
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理AI间的交互
|
|
||||||
*/
|
|
||||||
private processAIInteractions(entities: Entity[]): void {
|
|
||||||
const aiEntities = entities.filter(e => e.getComponent(AIComponent));
|
|
||||||
|
|
||||||
for (let i = 0; i < aiEntities.length; i++) {
|
|
||||||
for (let j = i + 1; j < aiEntities.length; j++) {
|
|
||||||
this.processAIPair(aiEntities[i], aiEntities[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理两个AI实体间的交互
|
|
||||||
*/
|
|
||||||
private processAIPair(entity1: Entity, entity2: Entity): void {
|
|
||||||
const ai1 = entity1.getComponent(AIComponent);
|
|
||||||
const ai2 = entity2.getComponent(AIComponent);
|
|
||||||
const transform1 = entity1.getComponent(Transform);
|
|
||||||
const transform2 = entity2.getComponent(Transform);
|
|
||||||
|
|
||||||
if (!ai1 || !ai2 || !transform1 || !transform2) return;
|
|
||||||
|
|
||||||
// 计算距离
|
|
||||||
const distance = Math.sqrt(
|
|
||||||
Math.pow(transform1.position.x - transform2.position.x, 2) +
|
|
||||||
Math.pow(transform1.position.y - transform2.position.y, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 视线范围内的交互
|
|
||||||
if (distance <= ai1.config.capabilities.sightRange) {
|
|
||||||
this.handleVisualContact(ai1, ai2, entity1, entity2, distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 听力范围内的交互
|
|
||||||
if (distance <= ai1.config.capabilities.hearingRange) {
|
|
||||||
this.handleAudioContact(ai1, ai2, distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建盟友关系(随机)
|
|
||||||
if (Math.random() < 0.001 && !ai1.allyIds.includes(entity2.id)) { // 0.1%概率每帧
|
|
||||||
ai1.addAlly(entity2.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理视觉接触
|
|
||||||
*/
|
|
||||||
private handleVisualContact(ai1: AIComponent, ai2: AIComponent, entity1: Entity, entity2: Entity, distance: number): void {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
// 添加到可见实体列表
|
|
||||||
const existingEntry = ai1.perception.visibleEntities.find(e => e.entityId === entity2.id);
|
|
||||||
if (existingEntry) {
|
|
||||||
existingEntry.distance = distance;
|
|
||||||
existingEntry.lastSeen = currentTime;
|
|
||||||
existingEntry.componentId = ai2.id; // 使用组件ID避免循环引用
|
|
||||||
} else {
|
|
||||||
ai1.perception.visibleEntities.push({
|
|
||||||
entityId: entity2.id,
|
|
||||||
position: entity2.getComponent(Transform)!.position.clone(),
|
|
||||||
distance: distance,
|
|
||||||
angle: Math.atan2(
|
|
||||||
entity2.getComponent(Transform)!.position.y - entity1.getComponent(Transform)!.position.y,
|
|
||||||
entity2.getComponent(Transform)!.position.x - entity1.getComponent(Transform)!.position.x
|
|
||||||
),
|
|
||||||
lastSeen: currentTime,
|
|
||||||
componentId: ai2.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理音频接触
|
|
||||||
*/
|
|
||||||
private handleAudioContact(ai1: AIComponent, ai2: AIComponent, distance: number): void {
|
|
||||||
const soundVolume = 1.0 - (distance / ai1.config.capabilities.hearingRange);
|
|
||||||
|
|
||||||
ai1.perception.audibleSounds.push({
|
|
||||||
source: ai2.entity.getComponent(Transform)!.position.clone(),
|
|
||||||
volume: soundVolume,
|
|
||||||
type: 'movement',
|
|
||||||
timestamp: Date.now()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统初始化时调用
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log('🤖 AI系统已启动');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统统计信息
|
|
||||||
*/
|
|
||||||
public getSystemStats(): any {
|
|
||||||
return {
|
|
||||||
processedEntities: this.processedEntityCount,
|
|
||||||
stateTransitions: this.stateTransitionCount,
|
|
||||||
systemName: 'AISystem'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "449fa887-eece-424d-ae1d-7082454fac3f",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { Health } from '../components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命值系统
|
|
||||||
* 处理生命值回复、死亡检测等逻辑
|
|
||||||
*/
|
|
||||||
export class HealthSystem extends EntitySystem {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 处理具有Health组件的实体
|
|
||||||
super(Matcher.empty().all(Health));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有实体
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
const deltaTime = Time.deltaTime;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.processEntity(entity, deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单个实体
|
|
||||||
*/
|
|
||||||
private processEntity(entity: Entity, deltaTime: number): void {
|
|
||||||
const health = entity.getComponent(Health);
|
|
||||||
|
|
||||||
if (!health) return;
|
|
||||||
|
|
||||||
// 如果实体已死亡,跳过处理
|
|
||||||
if (health.isDead) return;
|
|
||||||
|
|
||||||
// 处理生命值回复
|
|
||||||
if (health.regenRate > 0) {
|
|
||||||
const regenAmount = health.regenRate * deltaTime;
|
|
||||||
health.heal(regenAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否需要标记为死亡
|
|
||||||
if (health.currentHealth <= 0 && !health.isDead) {
|
|
||||||
health.isDead = true;
|
|
||||||
this.onEntityDied(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当实体死亡时调用
|
|
||||||
*/
|
|
||||||
private onEntityDied(entity: Entity): void {
|
|
||||||
console.log(`💀 实体 ${entity.name} 已死亡`);
|
|
||||||
|
|
||||||
// 这里可以添加死亡相关的逻辑
|
|
||||||
// 比如播放死亡动画、掉落物品等
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统初始化时调用
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log('❤️ 生命值系统已启动');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "074b3e3a-351e-4d95-b502-5a7dab8efc8d",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { Transform, Velocity } from '../components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动系统
|
|
||||||
* 处理具有Transform和Velocity组件的实体移动
|
|
||||||
*/
|
|
||||||
export class MovementSystem extends EntitySystem {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 使用Matcher设置系统处理的组件类型
|
|
||||||
super(Matcher.empty().all(Transform, Velocity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有实体
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
const deltaTime = Time.deltaTime;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.processEntity(entity, deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单个实体
|
|
||||||
*/
|
|
||||||
private processEntity(entity: Entity, deltaTime: number): void {
|
|
||||||
const transform = entity.getComponent(Transform);
|
|
||||||
const velocity = entity.getComponent(Velocity);
|
|
||||||
|
|
||||||
if (!transform || !velocity) return;
|
|
||||||
|
|
||||||
// 应用摩擦力
|
|
||||||
velocity.applyFriction();
|
|
||||||
|
|
||||||
// 根据速度更新位置
|
|
||||||
const deltaX = velocity.velocity.x * deltaTime;
|
|
||||||
const deltaY = velocity.velocity.y * deltaTime;
|
|
||||||
const deltaZ = velocity.velocity.z * deltaTime;
|
|
||||||
|
|
||||||
transform.move(deltaX, deltaY, deltaZ);
|
|
||||||
|
|
||||||
// 简单的边界检查 (假设游戏世界是 -500 到 500)
|
|
||||||
const bounds = 500;
|
|
||||||
if (transform.position.x > bounds) {
|
|
||||||
transform.position.x = bounds;
|
|
||||||
velocity.velocity.x = -Math.abs(velocity.velocity.x) * 0.5; // 反弹并减速
|
|
||||||
} else if (transform.position.x < -bounds) {
|
|
||||||
transform.position.x = -bounds;
|
|
||||||
velocity.velocity.x = Math.abs(velocity.velocity.x) * 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transform.position.y > bounds) {
|
|
||||||
transform.position.y = bounds;
|
|
||||||
velocity.velocity.y = -Math.abs(velocity.velocity.y) * 0.5;
|
|
||||||
} else if (transform.position.y < -bounds) {
|
|
||||||
transform.position.y = -bounds;
|
|
||||||
velocity.velocity.y = Math.abs(velocity.velocity.y) * 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统初始化时调用
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log('🏃 移动系统已启动');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "5e556e6d-ddd5-415c-b074-3cbdb59ed503",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,448 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { NetworkComponent } from '../components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络系统 - 处理网络同步和连接管理
|
|
||||||
*/
|
|
||||||
export class NetworkSystem extends EntitySystem {
|
|
||||||
|
|
||||||
/** 网络统计 */
|
|
||||||
private networkStats = {
|
|
||||||
totalEntities: 0,
|
|
||||||
connectedEntities: 0,
|
|
||||||
totalMessagesSent: 0,
|
|
||||||
totalMessagesReceived: 0,
|
|
||||||
averagePing: 0,
|
|
||||||
networkTraffic: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 消息处理队列 */
|
|
||||||
private globalMessageQueue: Array<{
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
messageType: string;
|
|
||||||
data: any;
|
|
||||||
timestamp: number;
|
|
||||||
priority: number;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 处理具有网络组件的实体
|
|
||||||
super(Matcher.empty().all(NetworkComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有实体
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
const deltaTime = Time.deltaTime;
|
|
||||||
|
|
||||||
this.networkStats.totalEntities = entities.length;
|
|
||||||
this.networkStats.connectedEntities = entities.filter(e =>
|
|
||||||
e.getComponent(NetworkComponent)?.connectionState === 'connected'
|
|
||||||
).length;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.processEntity(entity, deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理全局消息队列
|
|
||||||
this.processGlobalMessages();
|
|
||||||
|
|
||||||
// 更新网络统计
|
|
||||||
this.updateGlobalNetworkStats(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单个实体
|
|
||||||
*/
|
|
||||||
private processEntity(entity: Entity, deltaTime: number): void {
|
|
||||||
const network = entity.getComponent(NetworkComponent);
|
|
||||||
|
|
||||||
if (!network) return;
|
|
||||||
|
|
||||||
// 更新网络统计
|
|
||||||
network.updateNetworkStats(deltaTime);
|
|
||||||
|
|
||||||
// 处理连接状态
|
|
||||||
this.updateConnectionState(network, deltaTime);
|
|
||||||
|
|
||||||
// 处理消息队列
|
|
||||||
this.processEntityMessages(network, entity);
|
|
||||||
|
|
||||||
// 处理数据同步
|
|
||||||
this.processSynchronization(network, deltaTime);
|
|
||||||
|
|
||||||
// 处理群组通信
|
|
||||||
this.processGroupCommunication(network);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新连接状态
|
|
||||||
*/
|
|
||||||
private updateConnectionState(network: NetworkComponent, deltaTime: number): void {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
switch (network.connectionState) {
|
|
||||||
case 'disconnected':
|
|
||||||
// 尝试连接
|
|
||||||
if (network.config.autoReconnect &&
|
|
||||||
network.networkStats.reconnectCount < network.config.maxReconnectAttempts) {
|
|
||||||
network.connectionState = 'connecting';
|
|
||||||
network.connection.lastHeartbeat = currentTime;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'connecting':
|
|
||||||
// 模拟连接过程
|
|
||||||
if (Math.random() > 0.1) { // 90% 成功率
|
|
||||||
network.connectionState = 'connected';
|
|
||||||
network.connection.sessionId = this.generateSessionId();
|
|
||||||
network.connection.serverId = 'server_001';
|
|
||||||
network.connection.lastHeartbeat = currentTime;
|
|
||||||
} else if (currentTime - network.connection.lastHeartbeat > 5000) {
|
|
||||||
// 连接超时
|
|
||||||
network.connectionState = 'error';
|
|
||||||
network.networkStats.errorCount++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'connected':
|
|
||||||
// 维持连接心跳
|
|
||||||
if (currentTime - network.connection.lastHeartbeat > network.config.heartbeatInterval) {
|
|
||||||
this.sendHeartbeat(network);
|
|
||||||
network.connection.lastHeartbeat = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟网络质量变化
|
|
||||||
network.connection.ping = Math.random() * 100 + 20; // 20-120ms
|
|
||||||
network.connection.packetLoss = Math.random() * 0.05; // 0-5%
|
|
||||||
network.connection.bandwidth = 1000 + Math.random() * 500; // 1000-1500 Kbps
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'error':
|
|
||||||
// 错误状态,尝试重连
|
|
||||||
if (network.config.autoReconnect &&
|
|
||||||
network.networkStats.reconnectCount < network.config.maxReconnectAttempts) {
|
|
||||||
network.connectionState = 'disconnected';
|
|
||||||
network.networkStats.reconnectCount++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理实体消息
|
|
||||||
*/
|
|
||||||
private processEntityMessages(network: NetworkComponent, entity: Entity): void {
|
|
||||||
// 处理传出消息
|
|
||||||
const outgoingMessages = network.messageQueue.outgoing.slice();
|
|
||||||
network.messageQueue.outgoing = [];
|
|
||||||
|
|
||||||
for (const message of outgoingMessages) {
|
|
||||||
if (this.sendMessage(network, message)) {
|
|
||||||
this.networkStats.totalMessagesSent++;
|
|
||||||
network.networkStats.totalBytesSent += this.estimateMessageSize(message);
|
|
||||||
} else {
|
|
||||||
// 发送失败,重新加入队列
|
|
||||||
message.attempts++;
|
|
||||||
if (message.attempts < message.maxAttempts) {
|
|
||||||
network.messageQueue.outgoing.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理传入消息
|
|
||||||
this.processIncomingMessages(network, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息
|
|
||||||
*/
|
|
||||||
private sendMessage(network: NetworkComponent, message: any): boolean {
|
|
||||||
if (network.connectionState !== 'connected') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟网络延迟和丢包
|
|
||||||
const shouldDelay = Math.random() < 0.3; // 30% 概率有延迟
|
|
||||||
const shouldDrop = Math.random() < network.connection.packetLoss;
|
|
||||||
|
|
||||||
if (shouldDrop) {
|
|
||||||
network.networkStats.errorCount++;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加到全局消息队列
|
|
||||||
this.globalMessageQueue.push({
|
|
||||||
from: network.networkId,
|
|
||||||
to: message.targetId,
|
|
||||||
messageType: message.messageType,
|
|
||||||
data: message.data,
|
|
||||||
timestamp: Date.now() + (shouldDelay ? Math.random() * 200 : 0),
|
|
||||||
priority: message.priority
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理传入消息
|
|
||||||
*/
|
|
||||||
private processIncomingMessages(network: NetworkComponent, entity: Entity): void {
|
|
||||||
// 从全局队列中获取发给此实体的消息
|
|
||||||
const incomingMessages = this.globalMessageQueue.filter(msg =>
|
|
||||||
msg.to === network.networkId && msg.timestamp <= Date.now()
|
|
||||||
);
|
|
||||||
|
|
||||||
// 从全局队列中移除这些消息
|
|
||||||
this.globalMessageQueue = this.globalMessageQueue.filter(msg =>
|
|
||||||
!(msg.to === network.networkId && msg.timestamp <= Date.now())
|
|
||||||
);
|
|
||||||
|
|
||||||
// 处理消息
|
|
||||||
for (const message of incomingMessages) {
|
|
||||||
network.messageQueue.incoming.push({
|
|
||||||
senderId: message.from,
|
|
||||||
messageType: message.messageType,
|
|
||||||
data: message.data,
|
|
||||||
timestamp: message.timestamp,
|
|
||||||
processed: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.networkStats.totalMessagesReceived++;
|
|
||||||
network.networkStats.totalBytesReceived += this.estimateMessageSize(message);
|
|
||||||
|
|
||||||
// 立即处理某些类型的消息
|
|
||||||
this.handleSpecialMessages(network, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理特殊消息类型
|
|
||||||
*/
|
|
||||||
private handleSpecialMessages(network: NetworkComponent, message: any): void {
|
|
||||||
switch (message.messageType) {
|
|
||||||
case 'player_join_group':
|
|
||||||
// 处理加入群组消息
|
|
||||||
const groupData = message.data;
|
|
||||||
if (groupData.members && Array.isArray(groupData.members)) {
|
|
||||||
// 查找对应的网络组件并建立连接
|
|
||||||
groupData.members.forEach((memberId: string) => {
|
|
||||||
// 直接使用成员ID建立连接
|
|
||||||
network.connectToPlayer(memberId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'heartbeat':
|
|
||||||
// 心跳响应
|
|
||||||
network.connection.ping = Date.now() - message.data.timestamp;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'sync_request':
|
|
||||||
// 同步请求
|
|
||||||
this.handleSyncRequest(network, message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理数据同步
|
|
||||||
*/
|
|
||||||
private processSynchronization(network: NetworkComponent, deltaTime: number): void {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
const syncInterval = 1000 / network.config.syncFrequency; // 转换为毫秒
|
|
||||||
|
|
||||||
if (currentTime - network.syncData.lastSyncTime >= syncInterval) {
|
|
||||||
this.performSynchronization(network);
|
|
||||||
network.syncData.lastSyncTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理排队的更新
|
|
||||||
this.processQueuedUpdates(network);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行同步
|
|
||||||
*/
|
|
||||||
private performSynchronization(network: NetworkComponent): void {
|
|
||||||
if (network.syncData.dirtyFlags.size === 0) {
|
|
||||||
return; // 没有需要同步的数据
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncData = {
|
|
||||||
networkId: network.networkId,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
properties: Array.from(network.syncData.dirtyFlags),
|
|
||||||
checksum: this.calculateChecksum(network)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送同步数据给连接的玩家
|
|
||||||
network.connectedPlayerIds.forEach(playerId => {
|
|
||||||
network.sendMessage(playerId, 'sync_data', syncData, 7);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 记录同步历史
|
|
||||||
network.syncData.syncHistory.push({
|
|
||||||
timestamp: syncData.timestamp,
|
|
||||||
dataSize: this.estimateMessageSize(syncData),
|
|
||||||
properties: syncData.properties,
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 清理脏标记
|
|
||||||
network.syncData.dirtyFlags.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理排队的更新
|
|
||||||
*/
|
|
||||||
private processQueuedUpdates(network: NetworkComponent): void {
|
|
||||||
// 按优先级和时间戳排序
|
|
||||||
network.syncData.queuedUpdates.sort((a, b) => {
|
|
||||||
if (a.priority !== b.priority) {
|
|
||||||
return b.priority - a.priority; // 高优先级优先
|
|
||||||
}
|
|
||||||
return a.timestamp - b.timestamp; // 时间戳早的优先
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理前10个更新
|
|
||||||
const updatesToProcess = network.syncData.queuedUpdates.splice(0, 10);
|
|
||||||
for (const update of updatesToProcess) {
|
|
||||||
network.markDirty(update.property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理群组通信
|
|
||||||
*/
|
|
||||||
private processGroupCommunication(network: NetworkComponent): void {
|
|
||||||
if (network.groupMemberIds.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 群组消息广播
|
|
||||||
if (Math.random() < 0.01) { // 1% 概率发送群组消息
|
|
||||||
const groupMessage = {
|
|
||||||
type: 'group_update',
|
|
||||||
data: {
|
|
||||||
sender: network.networkId,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
groupSize: network.groupMemberIds.length,
|
|
||||||
status: network.connectionState
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
network.groupMemberIds.forEach(memberId => {
|
|
||||||
if (memberId !== network.networkId) {
|
|
||||||
network.sendMessage(memberId, 'group_message', groupMessage, 5);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理全局消息
|
|
||||||
*/
|
|
||||||
private processGlobalMessages(): void {
|
|
||||||
// 移除过期消息
|
|
||||||
const currentTime = Date.now();
|
|
||||||
this.globalMessageQueue = this.globalMessageQueue.filter(msg =>
|
|
||||||
currentTime - msg.timestamp < 30000 // 30秒过期
|
|
||||||
);
|
|
||||||
|
|
||||||
// 按优先级排序
|
|
||||||
this.globalMessageQueue.sort((a, b) => b.priority - a.priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新全局网络统计
|
|
||||||
*/
|
|
||||||
private updateGlobalNetworkStats(entities: Entity[]): void {
|
|
||||||
let totalPing = 0;
|
|
||||||
let connectedCount = 0;
|
|
||||||
let totalTraffic = 0;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
const network = entity.getComponent(NetworkComponent);
|
|
||||||
if (network && network.connectionState === 'connected') {
|
|
||||||
totalPing += network.connection.ping;
|
|
||||||
connectedCount++;
|
|
||||||
totalTraffic += network.networkStats.totalBytesSent + network.networkStats.totalBytesReceived;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.networkStats.averagePing = connectedCount > 0 ? totalPing / connectedCount : 0;
|
|
||||||
this.networkStats.networkTraffic = totalTraffic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 辅助方法
|
|
||||||
*/
|
|
||||||
private generateSessionId(): string {
|
|
||||||
return 'session_' + Math.random().toString(36).substring(2, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
private estimateMessageSize(message: any): number {
|
|
||||||
return JSON.stringify(message).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateChecksum(network: NetworkComponent): string {
|
|
||||||
// 简单的校验和计算
|
|
||||||
const data = JSON.stringify({
|
|
||||||
networkId: network.networkId,
|
|
||||||
connectionState: network.connectionState
|
|
||||||
});
|
|
||||||
return btoa(data).substring(0, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sendHeartbeat(network: NetworkComponent): void {
|
|
||||||
network.sendMessage('server', 'heartbeat', { timestamp: Date.now() }, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private findNetworkComponentById(networkId: string): NetworkComponent | null {
|
|
||||||
// 这里应该有一个全局的网络组件注册表
|
|
||||||
// 为了简化,我们返回null
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleSyncRequest(network: NetworkComponent, message: any): void {
|
|
||||||
// 处理同步请求
|
|
||||||
const response = {
|
|
||||||
requestId: message.data.requestId,
|
|
||||||
data: this.gatherSyncData(network),
|
|
||||||
timestamp: Date.now()
|
|
||||||
};
|
|
||||||
|
|
||||||
network.sendMessage(message.from, 'sync_response', response, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private gatherSyncData(network: NetworkComponent): any {
|
|
||||||
return {
|
|
||||||
networkId: network.networkId,
|
|
||||||
connectionState: network.connectionState,
|
|
||||||
ping: network.connection.ping,
|
|
||||||
groupSize: network.groupMemberIds.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统初始化时调用
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log('🌐 网络系统已启动');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统统计信息
|
|
||||||
*/
|
|
||||||
public getSystemStats(): any {
|
|
||||||
return {
|
|
||||||
...this.networkStats,
|
|
||||||
globalMessageQueueSize: this.globalMessageQueue.length,
|
|
||||||
systemName: 'NetworkSystem'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "09cb67c9-12ef-48e0-949d-c8edf2c7ae22",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,475 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { NodeComponent, Transform, Renderer } from '../components';
|
|
||||||
import { Node, Vec3, Color } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点渲染系统 - 处理NodeComponent和Cocos Creator节点的同步
|
|
||||||
*/
|
|
||||||
export class NodeRenderSystem extends EntitySystem {
|
|
||||||
|
|
||||||
/** 渲染统计 */
|
|
||||||
private renderStats = {
|
|
||||||
totalNodes: 0,
|
|
||||||
visibleNodes: 0,
|
|
||||||
renderCalls: 0,
|
|
||||||
averageRenderTime: 0,
|
|
||||||
totalRenderTime: 0,
|
|
||||||
frameCount: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 节点池 */
|
|
||||||
private nodePool: Node[] = [];
|
|
||||||
|
|
||||||
/** 性能监控 */
|
|
||||||
private performanceMonitor = {
|
|
||||||
frameStartTime: 0,
|
|
||||||
renderTimeHistory: [] as number[],
|
|
||||||
cullCount: 0,
|
|
||||||
frustumCullCount: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 处理具有NodeComponent的实体
|
|
||||||
super(Matcher.empty().all(NodeComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有实体
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
this.performanceMonitor.frameStartTime = performance.now();
|
|
||||||
|
|
||||||
this.renderStats.totalNodes = entities.length;
|
|
||||||
this.renderStats.visibleNodes = 0;
|
|
||||||
this.renderStats.renderCalls = 0;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.processEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理节点层次结构
|
|
||||||
this.updateNodeHierarchy(entities);
|
|
||||||
|
|
||||||
// 更新性能统计
|
|
||||||
this.updatePerformanceStats();
|
|
||||||
|
|
||||||
// 清理过期的性能缓存
|
|
||||||
this.cleanupPerformanceCache(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单个实体
|
|
||||||
*/
|
|
||||||
private processEntity(entity: Entity): void {
|
|
||||||
const nodeComponent = entity.getComponent(NodeComponent);
|
|
||||||
const transform = entity.getComponent(Transform);
|
|
||||||
const renderer = entity.getComponent(Renderer);
|
|
||||||
|
|
||||||
if (!nodeComponent) return;
|
|
||||||
|
|
||||||
const renderStartTime = performance.now();
|
|
||||||
|
|
||||||
// 确保有对应的Cocos Creator节点
|
|
||||||
this.ensureNode(nodeComponent, entity);
|
|
||||||
|
|
||||||
// 同步Transform数据
|
|
||||||
if (transform && nodeComponent.node) {
|
|
||||||
this.syncTransform(nodeComponent, transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同步渲染数据
|
|
||||||
if (renderer && nodeComponent.node) {
|
|
||||||
this.syncRenderer(nodeComponent, renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新节点配置
|
|
||||||
this.updateNodeConfig(nodeComponent);
|
|
||||||
|
|
||||||
// 执行视锥体剔除
|
|
||||||
const isVisible = this.performCulling(nodeComponent);
|
|
||||||
if (isVisible) {
|
|
||||||
this.renderStats.visibleNodes++;
|
|
||||||
this.performRender(nodeComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新性能统计
|
|
||||||
const renderTime = performance.now() - renderStartTime;
|
|
||||||
nodeComponent.updatePerformance(renderTime);
|
|
||||||
|
|
||||||
this.renderStats.renderCalls++;
|
|
||||||
this.renderStats.totalRenderTime += renderTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 确保节点存在
|
|
||||||
*/
|
|
||||||
private ensureNode(nodeComponent: NodeComponent, entity: Entity): void {
|
|
||||||
if (!nodeComponent.node) {
|
|
||||||
// 从对象池中获取节点或创建新节点
|
|
||||||
nodeComponent.node = this.getNodeFromPool() || new Node(nodeComponent.nodeConfig.name);
|
|
||||||
|
|
||||||
// 初始化节点
|
|
||||||
this.initializeNode(nodeComponent.node, nodeComponent, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从对象池获取节点
|
|
||||||
*/
|
|
||||||
private getNodeFromPool(): Node | null {
|
|
||||||
return this.nodePool.pop() || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化节点
|
|
||||||
*/
|
|
||||||
private initializeNode(node: Node, nodeComponent: NodeComponent, entity: Entity): void {
|
|
||||||
const config = nodeComponent.nodeConfig;
|
|
||||||
|
|
||||||
// 设置基本属性
|
|
||||||
node.name = config.name;
|
|
||||||
node.layer = config.layer;
|
|
||||||
node.active = config.renderData.visible;
|
|
||||||
|
|
||||||
// 设置变换
|
|
||||||
node.setPosition(config.transformData.position);
|
|
||||||
node.setRotationFromEuler(config.transformData.rotation);
|
|
||||||
node.setScale(config.transformData.scale);
|
|
||||||
|
|
||||||
// 设置渲染属性
|
|
||||||
const opacity = Math.floor(config.renderData.opacity * 255);
|
|
||||||
// 这里可以设置更多Cocos Creator特定的属性
|
|
||||||
|
|
||||||
// 添加用户数据
|
|
||||||
config.userData.entityId = entity.id;
|
|
||||||
config.userData.componentId = nodeComponent.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步Transform数据
|
|
||||||
*/
|
|
||||||
private syncTransform(nodeComponent: NodeComponent, transform: Transform): void {
|
|
||||||
const node = nodeComponent.node!;
|
|
||||||
const config = nodeComponent.nodeConfig;
|
|
||||||
|
|
||||||
// 更新配置中的变换数据
|
|
||||||
config.transformData.position.set(transform.position);
|
|
||||||
config.transformData.rotation.set(transform.rotation);
|
|
||||||
config.transformData.scale.set(transform.scale);
|
|
||||||
|
|
||||||
// 同步到Cocos Creator节点
|
|
||||||
node.setPosition(transform.position);
|
|
||||||
node.setRotationFromEuler(transform.rotation);
|
|
||||||
node.setScale(transform.scale);
|
|
||||||
|
|
||||||
// 更新缓存数据
|
|
||||||
nodeComponent.complexData.cache.textureCache.set('lastPosition', transform.position.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步渲染数据
|
|
||||||
*/
|
|
||||||
private syncRenderer(nodeComponent: NodeComponent, renderer: Renderer): void {
|
|
||||||
const node = nodeComponent.node!;
|
|
||||||
const config = nodeComponent.nodeConfig;
|
|
||||||
|
|
||||||
// 更新配置中的渲染数据
|
|
||||||
config.renderData.color.set(renderer.color);
|
|
||||||
config.renderData.opacity = renderer.alpha;
|
|
||||||
config.renderData.visible = renderer.visible && renderer.alpha > 0;
|
|
||||||
|
|
||||||
// 同步到Cocos Creator节点
|
|
||||||
node.active = config.renderData.visible;
|
|
||||||
|
|
||||||
// 更新材质缓存
|
|
||||||
nodeComponent.complexData.cache.materialCache.set('currentColor', renderer.color.clone());
|
|
||||||
nodeComponent.complexData.cache.materialCache.set('alpha', renderer.alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新节点配置
|
|
||||||
*/
|
|
||||||
private updateNodeConfig(nodeComponent: NodeComponent): void {
|
|
||||||
const config = nodeComponent.nodeConfig;
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
// 更新统计信息
|
|
||||||
nodeComponent.complexData.statistics.frameCount++;
|
|
||||||
nodeComponent.complexData.statistics.lastUpdateTime = currentTime;
|
|
||||||
|
|
||||||
// 更新用户数据
|
|
||||||
config.userData.lastFrameUpdate = currentTime;
|
|
||||||
config.userData.frameCount = nodeComponent.complexData.statistics.frameCount;
|
|
||||||
|
|
||||||
// 动态调整配置
|
|
||||||
if (Math.random() < 0.01) { // 1% 概率调整
|
|
||||||
config.renderData.opacity *= (0.95 + Math.random() * 0.1); // 轻微透明度变化
|
|
||||||
config.renderData.opacity = Math.max(0.1, Math.min(1.0, config.renderData.opacity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行视锥体剔除
|
|
||||||
*/
|
|
||||||
private performCulling(nodeComponent: NodeComponent): boolean {
|
|
||||||
if (!nodeComponent.node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = nodeComponent.nodeConfig;
|
|
||||||
|
|
||||||
// 简单的可见性检查
|
|
||||||
if (!config.renderData.visible || config.renderData.opacity <= 0) {
|
|
||||||
this.performanceMonitor.cullCount++;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 距离剔除
|
|
||||||
const position = config.transformData.position;
|
|
||||||
const distance = position.length();
|
|
||||||
if (distance > 1000) { // 超过1000单位距离的对象被剔除
|
|
||||||
this.performanceMonitor.frustumCullCount++;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 层级剔除
|
|
||||||
if (config.layer < 0) {
|
|
||||||
this.performanceMonitor.cullCount++;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行渲染
|
|
||||||
*/
|
|
||||||
private performRender(nodeComponent: NodeComponent): void {
|
|
||||||
if (!nodeComponent.node) return;
|
|
||||||
|
|
||||||
const renderStartTime = performance.now();
|
|
||||||
|
|
||||||
// 模拟复杂的渲染过程
|
|
||||||
this.simulateRenderingWork(nodeComponent);
|
|
||||||
|
|
||||||
// 更新子节点
|
|
||||||
this.updateChildNodes(nodeComponent);
|
|
||||||
|
|
||||||
// 更新着色器缓存
|
|
||||||
this.updateShaderCache(nodeComponent);
|
|
||||||
|
|
||||||
const renderTime = performance.now() - renderStartTime;
|
|
||||||
|
|
||||||
// 更新性能统计
|
|
||||||
const perf = nodeComponent.complexData.statistics.performance;
|
|
||||||
perf.renderHistory.push(renderTime);
|
|
||||||
|
|
||||||
if (perf.renderHistory.length > 100) {
|
|
||||||
perf.renderHistory.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
perf.avgRenderTime = perf.renderHistory.reduce((a, b) => a + b, 0) / perf.renderHistory.length;
|
|
||||||
perf.maxRenderTime = Math.max(perf.maxRenderTime, renderTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟渲染工作
|
|
||||||
*/
|
|
||||||
private simulateRenderingWork(nodeComponent: NodeComponent): void {
|
|
||||||
const complexity = nodeComponent.complexData.cache.materialCache.size +
|
|
||||||
nodeComponent.complexData.cache.textureCache.size;
|
|
||||||
|
|
||||||
// 模拟基于复杂度的计算工作
|
|
||||||
let iterations = Math.min(complexity * 10, 1000);
|
|
||||||
let result = 0;
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
result += Math.sin(i * 0.001) * Math.cos(i * 0.002);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储计算结果到缓存
|
|
||||||
nodeComponent.complexData.cache.shaderCache.set('computeResult', result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新子节点
|
|
||||||
*/
|
|
||||||
private updateChildNodes(nodeComponent: NodeComponent): void {
|
|
||||||
if (nodeComponent.children.length === 0) return;
|
|
||||||
|
|
||||||
const parentNode = nodeComponent.node!;
|
|
||||||
|
|
||||||
// 同步子节点
|
|
||||||
for (let i = 0; i < nodeComponent.children.length; i++) {
|
|
||||||
const childNode = nodeComponent.children[i];
|
|
||||||
if (childNode && childNode.parent !== parentNode) {
|
|
||||||
parentNode.addChild(childNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新层次结构数据
|
|
||||||
nodeComponent.complexData.hierarchy.siblingIndex = parentNode.getSiblingIndex();
|
|
||||||
|
|
||||||
// 更新子组件的层次深度(需要通过实体管理器查找)
|
|
||||||
// 这里省略了复杂的查找逻辑,避免循环引用
|
|
||||||
if (nodeComponent.nodeConfig.childIds.length > 0) {
|
|
||||||
// 实际项目中应该通过实体管理器查找子实体并更新深度
|
|
||||||
// 为了示例简化,我们只更新自己的深度
|
|
||||||
nodeComponent.complexData.hierarchy.depth = Math.max(0, nodeComponent.complexData.hierarchy.depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新着色器缓存
|
|
||||||
*/
|
|
||||||
private updateShaderCache(nodeComponent: NodeComponent): void {
|
|
||||||
const shaderCache = nodeComponent.complexData.cache.shaderCache;
|
|
||||||
|
|
||||||
// 模拟着色器参数更新
|
|
||||||
const currentTime = Date.now();
|
|
||||||
shaderCache.set('time', currentTime);
|
|
||||||
shaderCache.set('frameCount', nodeComponent.complexData.statistics.frameCount);
|
|
||||||
|
|
||||||
// 清理过期的着色器缓存
|
|
||||||
if (shaderCache.size > 50) {
|
|
||||||
const keys = Array.from(shaderCache.keys());
|
|
||||||
const oldestKey = keys[0];
|
|
||||||
shaderCache.delete(oldestKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新节点层次结构
|
|
||||||
*/
|
|
||||||
private updateNodeHierarchy(entities: Entity[]): void {
|
|
||||||
// 构建层次结构映射
|
|
||||||
const nodeMap = new Map<number, NodeComponent>();
|
|
||||||
|
|
||||||
entities.forEach(entity => {
|
|
||||||
const nodeComponent = entity.getComponent(NodeComponent);
|
|
||||||
if (nodeComponent) {
|
|
||||||
nodeMap.set(entity.id, nodeComponent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新层次关系(使用ID避免循环引用)
|
|
||||||
nodeMap.forEach((nodeComponent, entityId) => {
|
|
||||||
// 更新根节点ID
|
|
||||||
if (!nodeComponent.complexData.hierarchy.parentId) {
|
|
||||||
nodeComponent.complexData.hierarchy.rootId = entityId;
|
|
||||||
} else {
|
|
||||||
// 查找根节点ID(简化版本,避免深度遍历)
|
|
||||||
let currentParentId = nodeComponent.complexData.hierarchy.parentId;
|
|
||||||
let depth = 0;
|
|
||||||
|
|
||||||
// 限制深度以避免无限循环
|
|
||||||
while (currentParentId && depth < 10) {
|
|
||||||
const parentNode = nodeMap.get(currentParentId);
|
|
||||||
if (parentNode && parentNode.complexData.hierarchy.parentId) {
|
|
||||||
currentParentId = parentNode.complexData.hierarchy.parentId;
|
|
||||||
depth++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeComponent.complexData.hierarchy.rootId = currentParentId || entityId;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新性能统计
|
|
||||||
*/
|
|
||||||
private updatePerformanceStats(): void {
|
|
||||||
const frameTime = performance.now() - this.performanceMonitor.frameStartTime;
|
|
||||||
|
|
||||||
this.performanceMonitor.renderTimeHistory.push(frameTime);
|
|
||||||
if (this.performanceMonitor.renderTimeHistory.length > 60) {
|
|
||||||
this.performanceMonitor.renderTimeHistory.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.renderStats.frameCount++;
|
|
||||||
if (this.renderStats.renderCalls > 0) {
|
|
||||||
this.renderStats.averageRenderTime = this.renderStats.totalRenderTime / this.renderStats.renderCalls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理性能缓存
|
|
||||||
*/
|
|
||||||
private cleanupPerformanceCache(entities: Entity[]): void {
|
|
||||||
entities.forEach(entity => {
|
|
||||||
const nodeComponent = entity.getComponent(NodeComponent);
|
|
||||||
if (nodeComponent) {
|
|
||||||
const caches = nodeComponent.complexData.cache;
|
|
||||||
|
|
||||||
// 清理纹理缓存
|
|
||||||
if (caches.textureCache.size > 100) {
|
|
||||||
const keys = Array.from(caches.textureCache.keys());
|
|
||||||
const toDelete = keys.slice(0, 20); // 删除最旧的20个
|
|
||||||
toDelete.forEach(key => caches.textureCache.delete(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理材质缓存
|
|
||||||
if (caches.materialCache.size > 50) {
|
|
||||||
const keys = Array.from(caches.materialCache.keys());
|
|
||||||
const toDelete = keys.slice(0, 10); // 删除最旧的10个
|
|
||||||
toDelete.forEach(key => caches.materialCache.delete(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回收节点到对象池
|
|
||||||
*/
|
|
||||||
public recycleNode(node: Node): void {
|
|
||||||
if (this.nodePool.length < 100) { // 限制对象池大小
|
|
||||||
node.removeFromParent();
|
|
||||||
node.destroyAllChildren();
|
|
||||||
this.nodePool.push(node);
|
|
||||||
} else {
|
|
||||||
node.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统初始化时调用
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log('🎨 节点渲染系统已启动');
|
|
||||||
|
|
||||||
// 预热对象池
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
this.nodePool.push(new Node(`PooledNode_${i}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当实体被移除时
|
|
||||||
*/
|
|
||||||
protected onRemoved(entity: Entity): void {
|
|
||||||
const nodeComponent = entity.getComponent(NodeComponent);
|
|
||||||
if (nodeComponent && nodeComponent.node) {
|
|
||||||
this.recycleNode(nodeComponent.node);
|
|
||||||
nodeComponent.node = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统统计信息
|
|
||||||
*/
|
|
||||||
public getSystemStats(): any {
|
|
||||||
return {
|
|
||||||
...this.renderStats,
|
|
||||||
cullCount: this.performanceMonitor.cullCount,
|
|
||||||
frustumCullCount: this.performanceMonitor.frustumCullCount,
|
|
||||||
nodePoolSize: this.nodePool.length,
|
|
||||||
averageFrameTime: this.performanceMonitor.renderTimeHistory.length > 0
|
|
||||||
? this.performanceMonitor.renderTimeHistory.reduce((a, b) => a + b, 0) / this.performanceMonitor.renderTimeHistory.length
|
|
||||||
: 0,
|
|
||||||
systemName: 'NodeRenderSystem'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "b4593488-685b-4e52-800b-b2a2990305d6",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { Transform, Velocity } from '../components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 随机移动系统
|
|
||||||
* 让实体随机改变移动方向
|
|
||||||
*/
|
|
||||||
export class RandomMovementSystem extends EntitySystem {
|
|
||||||
|
|
||||||
/** 每个实体的下次方向改变时间 */
|
|
||||||
private nextDirectionChangeTime: Map<number, number> = new Map();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 处理具有Transform和Velocity组件的实体
|
|
||||||
super(Matcher.empty().all(Transform, Velocity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有实体
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
const currentTime = Time.totalTime;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
this.processEntity(entity, currentTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单个实体
|
|
||||||
*/
|
|
||||||
private processEntity(entity: Entity, currentTime: number): void {
|
|
||||||
const velocity = entity.getComponent(Velocity);
|
|
||||||
|
|
||||||
if (!velocity) return;
|
|
||||||
|
|
||||||
// 检查是否需要改变方向
|
|
||||||
const nextChangeTime = this.nextDirectionChangeTime.get(entity.id) || 0;
|
|
||||||
|
|
||||||
if (currentTime >= nextChangeTime) {
|
|
||||||
// 随机生成新的移动方向
|
|
||||||
const angle = Math.random() * Math.PI * 2; // 0-360度
|
|
||||||
const speed = 50 + Math.random() * 100; // 50-150的随机速度
|
|
||||||
|
|
||||||
const newVelocityX = Math.cos(angle) * speed;
|
|
||||||
const newVelocityY = Math.sin(angle) * speed;
|
|
||||||
|
|
||||||
velocity.setVelocity(newVelocityX, newVelocityY);
|
|
||||||
|
|
||||||
// 设置下次改变方向的时间(1-3秒后)
|
|
||||||
const nextInterval = 1 + Math.random() * 2;
|
|
||||||
this.nextDirectionChangeTime.set(entity.id, currentTime + nextInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当实体被添加到系统时
|
|
||||||
*/
|
|
||||||
protected onAdded(entity: Entity): void {
|
|
||||||
// 为新实体设置初始方向改变时间
|
|
||||||
const initialDelay = Math.random() * 2; // 0-2秒的初始延迟
|
|
||||||
this.nextDirectionChangeTime.set(entity.id, Time.totalTime + initialDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当实体从系统中移除时
|
|
||||||
*/
|
|
||||||
protected onRemoved(entity: Entity): void {
|
|
||||||
// 清理实体的时间记录
|
|
||||||
this.nextDirectionChangeTime.delete(entity.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统初始化时调用
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log('🎲 随机移动系统已启动');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "ed1688a1-b44f-4588-ae6a-080a6af38a94",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// 导出所有系统
|
|
||||||
export { MovementSystem } from './MovementSystem';
|
|
||||||
export { HealthSystem } from './HealthSystem';
|
|
||||||
export { RandomMovementSystem } from './RandomMovementSystem';
|
|
||||||
export { AISystem } from './AISystem';
|
|
||||||
export { NetworkSystem } from './NetworkSystem';
|
|
||||||
export { NodeRenderSystem } from './NodeRenderSystem';
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "586d2e9b-054b-457b-b44c-dafda0a73b6e",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "0ce4b9fe-436f-4735-8ef3-b90bead65200",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "ab8a85e4-962f-49ac-843a-b57d534f27c5",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { _decorator, Component, Label, Button } from 'cc';
|
|
||||||
import { DataBinding, BindingType, BindingMode } from '@esengine/mvvm-ui-framework';
|
|
||||||
import { TestViewModel } from './TestViewModel';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
@ccclass('TestView')
|
|
||||||
export class TestView extends Component {
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
testLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
testButton: Button = null!;
|
|
||||||
|
|
||||||
private viewModel: TestViewModel;
|
|
||||||
private dataBinding: DataBinding;
|
|
||||||
private bindingId: string = '';
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
console.log('TestView onLoad');
|
|
||||||
|
|
||||||
// 初始化数据绑定系统
|
|
||||||
this.dataBinding = DataBinding.getInstance();
|
|
||||||
|
|
||||||
// 创建 ViewModel
|
|
||||||
this.viewModel = new TestViewModel();
|
|
||||||
console.log('创建 ViewModel:', this.viewModel);
|
|
||||||
|
|
||||||
// 手动添加观察者来测试
|
|
||||||
this.viewModel.addObserver('testValue', (newValue, oldValue, property) => {
|
|
||||||
console.log(`属性 ${property} 变化: ${oldValue} -> ${newValue}`);
|
|
||||||
if (this.testLabel) {
|
|
||||||
this.testLabel.string = '测试值: ' + newValue;
|
|
||||||
console.log('手动更新 Label 文本:', this.testLabel.string);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置数据绑定
|
|
||||||
this.setupDataBinding();
|
|
||||||
|
|
||||||
// 设置按钮事件
|
|
||||||
this.setupButtonEvent();
|
|
||||||
|
|
||||||
// 测试初始值
|
|
||||||
console.log('初始值:', this.viewModel.testValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupDataBinding(): void {
|
|
||||||
if (this.testLabel) {
|
|
||||||
console.log('设置数据绑定');
|
|
||||||
console.log('Label 对象:', this.testLabel);
|
|
||||||
console.log('Label 初始文本:', this.testLabel.string);
|
|
||||||
|
|
||||||
this.bindingId = this.dataBinding.bind(this.viewModel, this.testLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.FORMAT,
|
|
||||||
source: 'testValue',
|
|
||||||
target: 'string',
|
|
||||||
format: '测试值: {0}'
|
|
||||||
});
|
|
||||||
console.log('绑定ID:', this.bindingId);
|
|
||||||
|
|
||||||
// 手动测试一下绑定是否工作
|
|
||||||
this.testLabel.string = '测试值: ' + this.viewModel.testValue;
|
|
||||||
console.log('手动设置后的文本:', this.testLabel.string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupButtonEvent(): void {
|
|
||||||
if (this.testButton) {
|
|
||||||
this.testButton.node.on(Button.EventType.CLICK, () => {
|
|
||||||
console.log('按钮点击');
|
|
||||||
this.viewModel.addValue();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
if (this.bindingId) {
|
|
||||||
this.dataBinding.unbind(this.bindingId);
|
|
||||||
}
|
|
||||||
if (this.viewModel) {
|
|
||||||
this.viewModel.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "667e05c4-fdf7-4b08-8c65-57e085465210",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import 'reflect-metadata';
|
|
||||||
import { ViewModel, observable } from '@esengine/mvvm-ui-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 简单的测试 ViewModel
|
|
||||||
*/
|
|
||||||
export class TestViewModel extends ViewModel {
|
|
||||||
|
|
||||||
public get name(): string {
|
|
||||||
return 'TestViewModel';
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable
|
|
||||||
testValue: number = 0;
|
|
||||||
|
|
||||||
public addValue(): void {
|
|
||||||
console.log('添加值之前:', this.testValue);
|
|
||||||
console.log('notifyObservers 方法存在吗?', typeof this.notifyObservers);
|
|
||||||
|
|
||||||
// 检查属性描述符
|
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(this, 'testValue') ||
|
|
||||||
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), 'testValue');
|
|
||||||
console.log('testValue 属性描述符:', descriptor);
|
|
||||||
|
|
||||||
// 检查私有属性
|
|
||||||
console.log('_testValue 私有属性:', (this as any)._testValue);
|
|
||||||
|
|
||||||
this.testValue += 1;
|
|
||||||
console.log('添加值之后:', this.testValue);
|
|
||||||
console.log('_testValue 私有属性 (之后):', (this as any)._testValue);
|
|
||||||
|
|
||||||
// 手动触发通知测试
|
|
||||||
if (this.notifyObservers) {
|
|
||||||
console.log('手动触发通知');
|
|
||||||
this.notifyObservers('testValue', this.testValue, this.testValue - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "28999657-2992-4293-839b-101ae666b364",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
import { _decorator, Component, Node, Label, Button } from 'cc';
|
|
||||||
import { DataBinding, BindingType, BindingMode } from '@esengine/mvvm-ui-framework';
|
|
||||||
import { UserInfoViewModel } from './UserInfoViewModel';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户信息视图组件
|
|
||||||
* 展示如何使用 MVVM 框架进行 Label 数据绑定
|
|
||||||
*/
|
|
||||||
@ccclass('UserInfoView')
|
|
||||||
export class UserInfoView extends Component {
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
userNameLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
levelLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
scoreLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
coinsLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
onlineStatusLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
displayNameLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
totalAssetsLabel: Label = null!;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
addScoreButton: Button = null!;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
addCoinsButton: Button = null!;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
levelUpButton: Button = null!;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
toggleOnlineButton: Button = null!;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
resetButton: Button = null!;
|
|
||||||
|
|
||||||
private viewModel: UserInfoViewModel;
|
|
||||||
private dataBinding: DataBinding;
|
|
||||||
private bindingIds: string[] = [];
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
// 初始化数据绑定系统
|
|
||||||
this.dataBinding = DataBinding.getInstance();
|
|
||||||
|
|
||||||
// 创建 ViewModel
|
|
||||||
this.viewModel = new UserInfoViewModel();
|
|
||||||
|
|
||||||
// 设置数据绑定
|
|
||||||
this.setupDataBindings();
|
|
||||||
|
|
||||||
// 设置按钮事件
|
|
||||||
this.setupButtonEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置数据绑定
|
|
||||||
*/
|
|
||||||
private setupDataBindings(): void {
|
|
||||||
// 用户名绑定
|
|
||||||
if (this.userNameLabel) {
|
|
||||||
const bindingId = this.dataBinding.bind(this.viewModel, this.userNameLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.REPLACE,
|
|
||||||
source: 'userName',
|
|
||||||
target: 'string'
|
|
||||||
});
|
|
||||||
this.bindingIds.push(bindingId);
|
|
||||||
|
|
||||||
|
|
||||||
this.dataBinding.bind(this.viewModel, this.coinsLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.REPLACE,
|
|
||||||
source: 'price',
|
|
||||||
target: 'string',
|
|
||||||
converter: 'currency', // 货币转换器
|
|
||||||
converterParams: ['USD', 2], // 美元,2位小数
|
|
||||||
format: '价格: {0}'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dataBinding.registerConverter('currency', {
|
|
||||||
convert: (value: number) => `${(value * 100).toFixed(1)}%`,
|
|
||||||
convertBack: (value: string) => parseFloat(value) / 100
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等级绑定(使用格式化)
|
|
||||||
if (this.levelLabel) {
|
|
||||||
const bindingId = this.dataBinding.bind(this.viewModel, this.levelLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.FORMAT,
|
|
||||||
source: 'level',
|
|
||||||
target: 'string',
|
|
||||||
format: '等级: {0}'
|
|
||||||
});
|
|
||||||
this.bindingIds.push(bindingId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分数绑定(使用数字转换器)
|
|
||||||
if (this.scoreLabel) {
|
|
||||||
const bindingId = this.dataBinding.bind(this.viewModel, this.scoreLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.FORMAT,
|
|
||||||
source: 'score',
|
|
||||||
target: 'string',
|
|
||||||
converter: 'number',
|
|
||||||
format: '分数: {0}'
|
|
||||||
});
|
|
||||||
this.bindingIds.push(bindingId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 金币绑定(使用数字转换器)
|
|
||||||
if (this.coinsLabel) {
|
|
||||||
const bindingId = this.dataBinding.bind(this.viewModel, this.coinsLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.FORMAT,
|
|
||||||
source: 'coins',
|
|
||||||
target: 'string',
|
|
||||||
converter: 'number',
|
|
||||||
format: '金币: {0}'
|
|
||||||
});
|
|
||||||
this.bindingIds.push(bindingId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在线状态绑定
|
|
||||||
if (this.onlineStatusLabel) {
|
|
||||||
const bindingId = this.dataBinding.bind(this.viewModel, this.onlineStatusLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.FORMAT,
|
|
||||||
source: 'onlineStatusText',
|
|
||||||
target: 'string',
|
|
||||||
format: '状态: {0}'
|
|
||||||
});
|
|
||||||
this.bindingIds.push(bindingId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示名称绑定(计算属性)
|
|
||||||
if (this.displayNameLabel) {
|
|
||||||
const bindingId = this.dataBinding.bind(this.viewModel, this.displayNameLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.REPLACE,
|
|
||||||
source: 'displayName',
|
|
||||||
target: 'string'
|
|
||||||
});
|
|
||||||
this.bindingIds.push(bindingId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 总资产绑定(计算属性)
|
|
||||||
if (this.totalAssetsLabel) {
|
|
||||||
const bindingId = this.dataBinding.bind(this.viewModel, this.totalAssetsLabel, {
|
|
||||||
type: BindingType.ONE_WAY,
|
|
||||||
mode: BindingMode.FORMAT,
|
|
||||||
source: 'totalAssets',
|
|
||||||
target: 'string',
|
|
||||||
converter: 'number',
|
|
||||||
format: '总资产: {0}'
|
|
||||||
});
|
|
||||||
this.bindingIds.push(bindingId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置按钮事件
|
|
||||||
*/
|
|
||||||
private setupButtonEvents(): void {
|
|
||||||
// 增加分数按钮 - 直接调用方法
|
|
||||||
if (this.addScoreButton) {
|
|
||||||
this.addScoreButton.node.on(Button.EventType.CLICK, () => {
|
|
||||||
this.viewModel.executeCommand('addScore');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 增加金币按钮 - 使用命令系统,支持 canExecute 检查
|
|
||||||
if (this.addCoinsButton) {
|
|
||||||
this.addCoinsButton.node.on(Button.EventType.CLICK, () => {
|
|
||||||
// 检查命令是否可以执行
|
|
||||||
if (this.viewModel.canExecuteCommand('addCoins')) {
|
|
||||||
this.viewModel.executeCommand('addCoins');
|
|
||||||
} else {
|
|
||||||
console.log('等级太低,无法获得金币!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 升级按钮 - 使用命令系统,支持 canExecute 检查
|
|
||||||
if (this.levelUpButton) {
|
|
||||||
this.levelUpButton.node.on(Button.EventType.CLICK, () => {
|
|
||||||
// 检查命令是否可以执行
|
|
||||||
if (this.viewModel.canExecuteCommand('levelUp')) {
|
|
||||||
this.viewModel.executeCommand('levelUp');
|
|
||||||
} else {
|
|
||||||
console.log('分数不足,无法升级!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换在线状态按钮 - 直接调用方法
|
|
||||||
if (this.toggleOnlineButton) {
|
|
||||||
this.toggleOnlineButton.node.on(Button.EventType.CLICK, () => {
|
|
||||||
this.viewModel.toggleOnlineStatus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置按钮 - 直接调用方法
|
|
||||||
if (this.resetButton) {
|
|
||||||
this.resetButton.node.on(Button.EventType.CLICK, () => {
|
|
||||||
this.viewModel.resetUserData();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动更新用户信息(演示用)
|
|
||||||
*/
|
|
||||||
public updateUserInfo(): void {
|
|
||||||
// 可以通过代码直接修改 ViewModel 的属性
|
|
||||||
// 绑定的 Label 会自动更新
|
|
||||||
this.viewModel.userName = '测试用户' + Math.floor(Math.random() * 1000);
|
|
||||||
this.viewModel.level = Math.floor(Math.random() * 50) + 1;
|
|
||||||
this.viewModel.score = Math.floor(Math.random() * 10000);
|
|
||||||
this.viewModel.coins = Math.floor(Math.random() * 1000);
|
|
||||||
this.viewModel.isOnline = Math.random() > 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前 ViewModel
|
|
||||||
*/
|
|
||||||
public getViewModel(): UserInfoViewModel {
|
|
||||||
return this.viewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
// 清理绑定
|
|
||||||
for (const bindingId of this.bindingIds) {
|
|
||||||
this.dataBinding.unbind(bindingId);
|
|
||||||
}
|
|
||||||
this.bindingIds = [];
|
|
||||||
|
|
||||||
// 销毁 ViewModel
|
|
||||||
if (this.viewModel) {
|
|
||||||
this.viewModel.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "0a14765b-2165-4fba-b286-00ba485caa11",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import { ViewModel, observable, computed, command, viewModel } from '@esengine/mvvm-ui-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户信息视图模型
|
|
||||||
* 展示如何使用 MVVM 框架进行数据绑定
|
|
||||||
*/
|
|
||||||
@viewModel
|
|
||||||
export class UserInfoViewModel extends ViewModel {
|
|
||||||
|
|
||||||
public get name(): string {
|
|
||||||
return 'UserInfoViewModel';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户名 - 使用 @observable 装饰器自动处理数据绑定
|
|
||||||
*/
|
|
||||||
@observable
|
|
||||||
userName: string = '未知用户';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户等级
|
|
||||||
*/
|
|
||||||
level: number = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户分数
|
|
||||||
*/
|
|
||||||
@observable
|
|
||||||
score: number = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户金币
|
|
||||||
*/
|
|
||||||
@observable
|
|
||||||
coins: number = 100;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否在线
|
|
||||||
*/
|
|
||||||
@observable
|
|
||||||
isOnline: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算属性:用户显示名称(格式化)
|
|
||||||
*/
|
|
||||||
@computed(['userName', 'level'])
|
|
||||||
get displayName(): string {
|
|
||||||
return `${this.userName} (Lv.${this.level})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算属性:在线状态文本
|
|
||||||
*/
|
|
||||||
@computed(['isOnline'])
|
|
||||||
get onlineStatusText(): string {
|
|
||||||
return this.isOnline ? '在线' : '离线';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算属性:总资产(分数 + 金币)
|
|
||||||
*/
|
|
||||||
@computed(['score', 'coins'])
|
|
||||||
get totalAssets(): number {
|
|
||||||
return this.score + this.coins;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加分数 - 使用 @command 装饰器,可以通过 executeCommand('addScore') 调用
|
|
||||||
*/
|
|
||||||
@command()
|
|
||||||
public addScore(amount: number = 10): void {
|
|
||||||
this.score += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加金币 - 带有 canExecute 逻辑的命令
|
|
||||||
*/
|
|
||||||
@command('canAddCoins')
|
|
||||||
public addCoins(amount: number = 5): void {
|
|
||||||
this.coins += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否可以增加金币(例如:等级必须大于 1)
|
|
||||||
*/
|
|
||||||
public canAddCoins(): boolean {
|
|
||||||
return this.level > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 升级 - 带有复杂 canExecute 逻辑的命令
|
|
||||||
*/
|
|
||||||
@command('canLevelUp')
|
|
||||||
public levelUp(): void {
|
|
||||||
this.level += 1;
|
|
||||||
this.score += this.level * 100; // 升级奖励
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否可以升级(例如:需要足够的分数)
|
|
||||||
*/
|
|
||||||
public canLevelUp(): boolean {
|
|
||||||
return this.score >= this.level * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 切换在线状态
|
|
||||||
*/
|
|
||||||
@command()
|
|
||||||
public toggleOnlineStatus(): void {
|
|
||||||
this.isOnline = !this.isOnline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置用户数据
|
|
||||||
*/
|
|
||||||
public resetUserData(): void {
|
|
||||||
this.batchUpdate({
|
|
||||||
userName: '新用户',
|
|
||||||
level: 1,
|
|
||||||
score: 0,
|
|
||||||
coins: 100,
|
|
||||||
isOnline: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "0e90a89e-1c64-4c47-ab61-9093f9964678",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "21b8d75a-82be-4b5a-8ecf-765558907857",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { ViewModel, observable, computed, command } from '@esengine/mvvm-ui-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 游戏状态视图模型
|
|
||||||
*/
|
|
||||||
export class GameStateViewModel extends ViewModel {
|
|
||||||
|
|
||||||
public get name(): string {
|
|
||||||
return 'GameStateViewModel';
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable
|
|
||||||
currentLevel: number = 1;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
health: number = 100;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
mana: number = 50;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
experience: number = 0;
|
|
||||||
|
|
||||||
@computed(['health'])
|
|
||||||
get healthPercent(): number {
|
|
||||||
return (this.health / 100) * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed(['experience', 'currentLevel'])
|
|
||||||
get experienceToNextLevel(): number {
|
|
||||||
return (this.currentLevel * 100) - this.experience;
|
|
||||||
}
|
|
||||||
|
|
||||||
@command()
|
|
||||||
public levelUp(): void {
|
|
||||||
this.currentLevel += 1;
|
|
||||||
this.health = 100;
|
|
||||||
this.mana += 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
@command()
|
|
||||||
public takeDamage(damage: number): void {
|
|
||||||
this.health = Math.max(0, this.health - damage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "8fa14e6f-46cd-4cb4-9ac1-0a1919e260a5",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "2c09b280-bf73-4d95-9b1f-d4d915442980",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { ViewModel, observable, computed, command } from '@esengine/mvvm-ui-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商店视图模型
|
|
||||||
*/
|
|
||||||
export class ShopViewModel extends ViewModel {
|
|
||||||
|
|
||||||
public get name(): string {
|
|
||||||
return 'ShopViewModel';
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable
|
|
||||||
selectedCategory: string = 'weapons';
|
|
||||||
|
|
||||||
@observable
|
|
||||||
playerGold: number = 1000;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
cartItems: any[] = [];
|
|
||||||
|
|
||||||
@computed(['cartItems'])
|
|
||||||
get totalPrice(): number {
|
|
||||||
return this.cartItems.reduce((total, item) => total + item.price, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed(['playerGold', 'totalPrice'])
|
|
||||||
get canPurchase(): boolean {
|
|
||||||
return this.playerGold >= this.totalPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
@command()
|
|
||||||
public addToCart(item: any): void {
|
|
||||||
this.cartItems.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@command('canPurchase')
|
|
||||||
public purchase(): void {
|
|
||||||
this.playerGold -= this.totalPrice;
|
|
||||||
this.cartItems = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "148fe394-03cd-45a1-9bc0-5cb24440d8db",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "62abfd02-b9f5-41d2-9822-2c777af21e27",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { ViewModel, observable, computed, command } from '@esengine/mvvm-ui-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户配置文件视图模型
|
|
||||||
*/
|
|
||||||
export class UserProfileViewModel extends ViewModel {
|
|
||||||
|
|
||||||
public get name(): string {
|
|
||||||
return 'UserProfileViewModel';
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable
|
|
||||||
avatar: string = '';
|
|
||||||
|
|
||||||
@observable
|
|
||||||
nickname: string = '';
|
|
||||||
|
|
||||||
@observable
|
|
||||||
email: string = '';
|
|
||||||
|
|
||||||
@observable
|
|
||||||
phone: string = '';
|
|
||||||
|
|
||||||
@computed(['nickname', 'email'])
|
|
||||||
get displayInfo(): string {
|
|
||||||
return `${this.nickname} (${this.email})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
@command()
|
|
||||||
public updateProfile(): void {
|
|
||||||
console.log('更新用户配置文件');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "05648650-5963-4847-8789-7bdc6ea7f43c",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
Submodule extensions/cocos/cocos-ecs/extensions/behaviour-tree deleted from d4fd74fb94
Submodule extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension deleted from ea76db5749
Submodule extensions/cocos/cocos-ecs/extensions/cocos-terrain-gen deleted from fb7d5bbb01
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user