rts-demo
This commit is contained in:
122
extensions/cocos/cocos-ecs/README_BehaviorTree_Demo.md
Normal file
122
extensions/cocos/cocos-ecs/README_BehaviorTree_Demo.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# 行为树 RTS 演示项目
|
||||||
|
|
||||||
|
这是一个展示`@esengine/ai`行为树系统在RTS游戏中应用的完整演示项目。
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
该演示项目展示了如何在Cocos Creator 3D环境中使用行为树系统创建智能的RTS单位AI。项目包含三种不同类型的单位,每种都有独特的行为模式和AI策略。
|
||||||
|
|
||||||
|
## 单位类型
|
||||||
|
|
||||||
|
### 1. 工人 (Worker)
|
||||||
|
- **文件**: `worker-ai.btree`, `worker-ai.bt.json`
|
||||||
|
- **特点**: 专注于资源收集、建造和修理
|
||||||
|
- **行为优先级**: 紧急情况 > 命令执行 > 空闲行为
|
||||||
|
- **主要功能**:
|
||||||
|
- 资源收集和运输
|
||||||
|
- 建筑建造
|
||||||
|
- 结构修理
|
||||||
|
- 自动寻找附近资源
|
||||||
|
- 生命值过低时撤退
|
||||||
|
|
||||||
|
### 2. 士兵 (Soldier)
|
||||||
|
- **文件**: `soldier-ai.btree`, `soldier-ai.bt.json`
|
||||||
|
- **特点**: 专注于战斗、防御和战术行动
|
||||||
|
- **行为优先级**: 战斗 > 命令执行 > 防御行为
|
||||||
|
- **主要功能**:
|
||||||
|
- 近战和远程攻击
|
||||||
|
- 敌人检测和交战
|
||||||
|
- 战术移动和撤退
|
||||||
|
- 区域防御和巡逻
|
||||||
|
- 队形保持
|
||||||
|
|
||||||
|
### 3. 侦察兵 (Scout)
|
||||||
|
- **文件**: `scout-ai.btree`, `scout-ai.bt.json`
|
||||||
|
- **特点**: 专注于探索、侦察和信息收集
|
||||||
|
- **行为优先级**: 生存 > 命令执行 > 自主侦察
|
||||||
|
- **主要功能**:
|
||||||
|
- 区域探索和侦察
|
||||||
|
- 威胁检测和规避
|
||||||
|
- 隐蔽移动和撤退
|
||||||
|
- 情报收集和报告
|
||||||
|
- 自主探索未知区域
|
||||||
|
|
||||||
|
## 技术特点
|
||||||
|
|
||||||
|
### 行为树设计
|
||||||
|
- **选择器(Selector)**: 根据优先级选择行为分支
|
||||||
|
- **序列器(Sequence)**: 按顺序执行一系列行为
|
||||||
|
- **条件装饰器(Conditional Decorator)**: 基于条件执行子节点
|
||||||
|
- **事件动作(Event Action)**: 执行具体的游戏行为
|
||||||
|
- **等待动作(Wait Action)**: 添加时间延迟
|
||||||
|
|
||||||
|
### 黑板系统
|
||||||
|
每个单位都有独立的黑板变量,用于存储和共享状态信息:
|
||||||
|
- 单位属性(生命值、移动速度、攻击力等)
|
||||||
|
- 状态标志(是否移动、是否有目标等)
|
||||||
|
- 位置信息(当前位置、目标位置等)
|
||||||
|
- 特殊能力(隐蔽、战斗模式等)
|
||||||
|
|
||||||
|
### 文件格式
|
||||||
|
|
||||||
|
#### .btree 文件(编辑器格式)
|
||||||
|
- 包含完整的编辑器状态和元数据
|
||||||
|
- 包含节点位置、连接关系、属性配置
|
||||||
|
- 包含黑板变量定义和初始值
|
||||||
|
- 支持编辑器的撤销/重做、剪贴板等功能
|
||||||
|
|
||||||
|
#### .bt.json 文件(运行时格式)
|
||||||
|
- 精简的运行时格式,只包含游戏所需的核心数据
|
||||||
|
- 优化的数据结构,适合快速加载和执行
|
||||||
|
- 移除了编辑器相关的元数据
|
||||||
|
|
||||||
|
## 代码结构
|
||||||
|
|
||||||
|
### 核心组件
|
||||||
|
- **RTSDemo.ts**: 主控制器,管理整个演示场景
|
||||||
|
- **UnitController.ts**: 单位控制器,管理单位的基本行为和状态
|
||||||
|
- **BehaviorTreeManager.ts**: 行为树管理器,集成`@esengine/ai`系统
|
||||||
|
- **RTSCameraController.ts**: RTS相机控制器
|
||||||
|
- **UIController.ts**: 用户界面控制器
|
||||||
|
|
||||||
|
### 关键特性
|
||||||
|
- **纯Cocos Creator实现**: 不依赖其他框架,专注展示行为树功能
|
||||||
|
- **实时状态同步**: 单位状态自动同步到行为树黑板
|
||||||
|
- **命令系统**: 支持移动、攻击、收集、巡逻等RTS命令
|
||||||
|
- **可视化调试**: 实时显示单位状态和行为信息
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 在行为树编辑器中
|
||||||
|
1. 打开对应的`.btree`文件进行编辑
|
||||||
|
2. 修改节点逻辑、添加新行为或调整优先级
|
||||||
|
3. 导出为`.bt.json`文件供游戏运行时使用
|
||||||
|
|
||||||
|
### 2. 在游戏中
|
||||||
|
1. 运行场景,观察不同单位的AI行为
|
||||||
|
2. 使用UI按钮发布命令,测试单位响应
|
||||||
|
3. 观察单位在不同情况下的行为切换
|
||||||
|
|
||||||
|
### 3. 扩展开发
|
||||||
|
- 添加新的行为节点类型
|
||||||
|
- 创建更复杂的AI策略
|
||||||
|
- 集成更多游戏机制(资源系统、建筑系统等)
|
||||||
|
|
||||||
|
## 演示价值
|
||||||
|
|
||||||
|
这个项目完整展示了:
|
||||||
|
1. **行为树在RTS游戏中的实际应用**
|
||||||
|
2. **不同单位类型的AI差异化设计**
|
||||||
|
3. **复杂游戏逻辑的行为树实现方式**
|
||||||
|
4. **黑板系统在状态管理中的作用**
|
||||||
|
5. **行为树编辑器与游戏运行时的集成**
|
||||||
|
|
||||||
|
## 技术亮点
|
||||||
|
|
||||||
|
- **模块化设计**: 每个单位类型独立的行为树设计
|
||||||
|
- **优先级系统**: 清晰的行为优先级和决策逻辑
|
||||||
|
- **状态管理**: 完善的黑板变量系统
|
||||||
|
- **实时调试**: 支持运行时状态监控和调试
|
||||||
|
- **可扩展性**: 易于添加新的单位类型和行为
|
||||||
|
|
||||||
|
这个演示项目可以作为学习行为树系统的完整案例,也可以作为实际RTS游戏开发的起点。
|
||||||
165
extensions/cocos/cocos-ecs/README_BehaviorTree_Game_Demo.md
Normal file
165
extensions/cocos/cocos-ecs/README_BehaviorTree_Game_Demo.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# 行为树游戏演示文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本演示展示了两个真实游戏场景的行为树AI系统:
|
||||||
|
|
||||||
|
1. **RPG角色AI** (`rpg-character-ai.btree`) - 智能RPG角色行为系统
|
||||||
|
2. **RTS单位AI** (`rts-unit-ai.btree`) - 智能RTS单位行为系统
|
||||||
|
|
||||||
|
这些行为树使用编辑器格式(`.btree`),可以在行为树编辑器中打开、编辑和可视化。
|
||||||
|
|
||||||
|
## 行为树详解
|
||||||
|
|
||||||
|
### 1. RPG角色AI (`rpg-character-ai.btree`)
|
||||||
|
|
||||||
|
**优先级系统**:生存 > 战斗 > 任务 > 探索 > 休息
|
||||||
|
|
||||||
|
#### 主要行为模式:
|
||||||
|
|
||||||
|
1. **生存行为**(最高优先级)
|
||||||
|
- 触发条件:生命值 < 30
|
||||||
|
- 行为:使用治疗物品 → 逃跑到安全区域
|
||||||
|
- 确保角色在危险时优先保命
|
||||||
|
|
||||||
|
2. **战斗行为**
|
||||||
|
- 触发条件:发现敌人
|
||||||
|
- 智能攻击选择:
|
||||||
|
- 远程攻击:距离 > 5 时使用弓箭
|
||||||
|
- 近战攻击:距离 ≤ 2 时使用剑
|
||||||
|
- 魔法攻击:魔法值 > 20 时释放法术
|
||||||
|
|
||||||
|
3. **任务行为**
|
||||||
|
- 触发条件:有活跃任务
|
||||||
|
- 任务类型:收集物品、与NPC对话、前往指定地点
|
||||||
|
|
||||||
|
4. **探索行为**
|
||||||
|
- 无紧急事务时进行
|
||||||
|
- 活动:寻找宝藏、收集资源
|
||||||
|
|
||||||
|
5. **休息行为**(最低优先级)
|
||||||
|
- 默认行为:恢复生命值、修理装备
|
||||||
|
|
||||||
|
#### 黑板变量:
|
||||||
|
- `hasEnemy`: 是否发现敌人
|
||||||
|
- `hasActiveQuest`: 是否有活跃任务
|
||||||
|
- `character.health`: 角色生命值
|
||||||
|
- `character.mana`: 角色魔法值
|
||||||
|
- `character.distanceToEnemy`: 与敌人的距离
|
||||||
|
|
||||||
|
### 2. RTS单位AI (`rts-unit-ai.btree`)
|
||||||
|
|
||||||
|
**优先级系统**:紧急防御 > 战斗 > 建造 > 资源收集 > 巡逻
|
||||||
|
|
||||||
|
#### 主要行为模式:
|
||||||
|
|
||||||
|
1. **紧急防御**(最高优先级)
|
||||||
|
- 触发条件:基地受到攻击
|
||||||
|
- 行为:立即返回基地 → 参与基地防御
|
||||||
|
- 确保基地安全是最高优先级
|
||||||
|
|
||||||
|
2. **战斗行为**
|
||||||
|
- 触发条件:发现敌方目标
|
||||||
|
- 行为:锁定目标 → 攻击目标
|
||||||
|
- 主动寻找并消灭敌人
|
||||||
|
|
||||||
|
3. **建造行为**
|
||||||
|
- 触发条件:有建造任务
|
||||||
|
- 行为:前往建造地点 → 执行建造
|
||||||
|
- 按指令建造建筑物
|
||||||
|
|
||||||
|
4. **资源收集行为**
|
||||||
|
- 默认经济行为
|
||||||
|
- 行为:寻找资源 → 收集资源 → 运送回基地
|
||||||
|
- 为基地提供经济支持
|
||||||
|
|
||||||
|
5. **巡逻行为**(最低优先级)
|
||||||
|
- 空闲时的默认行为
|
||||||
|
- 行为:沿路线巡逻 → 扫描周围区域
|
||||||
|
- 保持警戒状态
|
||||||
|
|
||||||
|
#### 黑板变量:
|
||||||
|
- `baseUnderAttack`: 基地是否受到攻击
|
||||||
|
- `hasTarget`: 是否发现敌方目标
|
||||||
|
- `hasBuildOrder`: 是否有建造任务
|
||||||
|
|
||||||
|
## 演示脚本
|
||||||
|
|
||||||
|
### RealisticGameDemo.ts
|
||||||
|
|
||||||
|
演示脚本展示了RPG角色AI的完整功能:
|
||||||
|
|
||||||
|
#### 功能特性:
|
||||||
|
- **实时状态显示**:生命值、魔法值、敌人状态、任务状态
|
||||||
|
- **交互式事件触发**:
|
||||||
|
- 低生命值按钮:模拟角色受伤
|
||||||
|
- 敌人按钮:切换敌人发现状态
|
||||||
|
- 任务按钮:切换任务状态
|
||||||
|
- 重置按钮:恢复初始状态
|
||||||
|
- **实时日志系统**:显示AI决策和行为执行过程
|
||||||
|
- **智能行为响应**:AI根据状态变化自动调整行为
|
||||||
|
|
||||||
|
#### 事件处理器:
|
||||||
|
- 治疗物品使用:恢复30点生命值
|
||||||
|
- 逃跑行为:移除敌人威胁,增加距离
|
||||||
|
- 各种攻击方式:消耗不同的魔法值
|
||||||
|
- 任务相关行为:模拟真实游戏交互
|
||||||
|
- 探索和休息:提供持续的角色发展
|
||||||
|
|
||||||
|
## 使用指南
|
||||||
|
|
||||||
|
### 1. 在编辑器中打开
|
||||||
|
|
||||||
|
1. 启动Cocos Creator
|
||||||
|
2. 打开行为树编辑器扩展
|
||||||
|
3. 加载 `assets/resources/rpg-character-ai.btree` 或 `rts-unit-ai.btree`
|
||||||
|
4. 可视化查看和编辑行为树结构
|
||||||
|
|
||||||
|
### 2. 在游戏中运行
|
||||||
|
|
||||||
|
1. 将 `RealisticGameDemo` 组件添加到场景节点
|
||||||
|
2. 配置UI组件:
|
||||||
|
- 状态标签:显示角色当前状态
|
||||||
|
- 日志标签:显示行为执行日志
|
||||||
|
- 控制按钮:触发不同的游戏事件
|
||||||
|
3. 运行场景,观察AI行为
|
||||||
|
|
||||||
|
### 3. 自定义和扩展
|
||||||
|
|
||||||
|
#### 添加新的行为节点:
|
||||||
|
1. 在编辑器中添加新的事件动作节点
|
||||||
|
2. 在演示脚本中注册对应的事件处理器
|
||||||
|
3. 实现具体的行为逻辑
|
||||||
|
|
||||||
|
#### 修改优先级:
|
||||||
|
1. 在编辑器中调整选择器节点的子节点顺序
|
||||||
|
2. 更高位置的节点具有更高优先级
|
||||||
|
|
||||||
|
#### 添加新的条件:
|
||||||
|
1. 在黑板中定义新的变量
|
||||||
|
2. 为条件装饰器配置相应的条件检查
|
||||||
|
3. 在代码中更新黑板变量值
|
||||||
|
|
||||||
|
## 实际游戏应用
|
||||||
|
|
||||||
|
### RPG游戏中的应用:
|
||||||
|
- **NPC AI**:为游戏中的NPC提供智能行为
|
||||||
|
- **伙伴AI**:玩家队友的自动战斗和支援行为
|
||||||
|
- **敌人AI**:敌人的智能战斗和逃跑策略
|
||||||
|
- **宠物AI**:宠物的跟随、战斗和辅助行为
|
||||||
|
|
||||||
|
### RTS游戏中的应用:
|
||||||
|
- **单位AI**:军事单位的自动化行为
|
||||||
|
- **工人AI**:资源收集和建造单位的智能调度
|
||||||
|
- **防御AI**:自动防御系统的智能响应
|
||||||
|
- **经济AI**:自动化的经济发展策略
|
||||||
|
|
||||||
|
## 学习要点
|
||||||
|
|
||||||
|
1. **优先级设计**:合理的优先级确保AI在复杂情况下做出正确决策
|
||||||
|
2. **状态管理**:通过黑板系统有效管理AI状态
|
||||||
|
3. **事件驱动**:使用事件系统实现松耦合的行为实现
|
||||||
|
4. **条件判断**:智能的条件系统让AI能够适应不同情况
|
||||||
|
5. **模块化设计**:每个行为模块独立,便于维护和扩展
|
||||||
|
|
||||||
|
这些行为树演示了如何构建商业级游戏AI系统,为开发者提供了完整的参考实现。
|
||||||
859
extensions/cocos/cocos-ecs/assets/resources/scout-ai.bt.json
Normal file
859
extensions/cocos/cocos-ecs/assets/resources/scout-ai.bt.json
Normal file
@@ -0,0 +1,859 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "root",
|
||||||
|
"type": "root",
|
||||||
|
"name": "侦察兵AI根节点",
|
||||||
|
"children": [
|
||||||
|
"main-selector"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "主选择器",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "根据优先级选择行为:生存 > 命令执行 > 自主侦察",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"survival-sequence",
|
||||||
|
"command-sequence",
|
||||||
|
"autonomous-sequence"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "survival-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "生存序列",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "处理威胁和生存相关行为",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"threat-detection",
|
||||||
|
"survival-selector"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "threat-detection",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "威胁检测",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "检测周围是否有威胁",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"survival-selector"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "threatDetected",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "survival-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "生存选择器",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "选择合适的生存策略",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"stealth-escape",
|
||||||
|
"evasive-maneuver",
|
||||||
|
"emergency-retreat"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stealth-escape",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "隐蔽脱离",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "使用隐蔽能力脱离危险",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"can-stealth-check",
|
||||||
|
"activate-stealth",
|
||||||
|
"stealth-retreat"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "can-stealth-check",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "隐蔽能力检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"activate-stealth"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "stealthAvailable",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "activate-stealth",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "激活隐蔽",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "activate-stealth",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "激活隐蔽模式",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stealth-retreat",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "隐蔽撤退",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "stealth-retreat",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "在隐蔽状态下撤退",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evasive-maneuver",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "规避机动",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "evasive-maneuver",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "执行规避机动动作",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency-retreat",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "紧急撤退",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "emergency-retreat",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "快速撤退到安全区域",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "command-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "命令执行",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "执行玩家发布的侦察命令",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"has-command-check",
|
||||||
|
"command-selector"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "has-command-check",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "命令检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "检查是否有待执行的命令",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"command-selector"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "hasTarget",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "command-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "命令选择器",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "根据命令类型选择执行方式",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"scout-command",
|
||||||
|
"move-command",
|
||||||
|
"observe-command",
|
||||||
|
"patrol-command"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "scout-command",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "侦察命令",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "执行区域侦察任务",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"is-scout-command",
|
||||||
|
"scout-sequence"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "is-scout-command",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "侦察命令检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"scout-sequence"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "scout"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "scout-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "侦察序列",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "完整的侦察流程",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"approach-carefully",
|
||||||
|
"scan-area",
|
||||||
|
"report-findings"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "approach-carefully",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "谨慎接近",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "approach-carefully",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "谨慎接近目标区域",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "scan-area",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "扫描区域",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "scan-area",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "扫描并记录区域信息",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "report-findings",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "报告发现",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "report-findings",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "向指挥部报告侦察结果",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-command",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "移动命令",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "执行移动到目标位置",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"is-move-command",
|
||||||
|
"stealth-move"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "is-move-command",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "移动命令检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"stealth-move"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "move"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stealth-move",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "隐蔽移动",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "stealth-move",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "以隐蔽方式移动到目标位置",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "observe-command",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "观察命令",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "执行目标观察任务",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"is-observe-command",
|
||||||
|
"observe-target"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "is-observe-command",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "观察命令检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"observe-target"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "observe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "observe-target",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "观察目标",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "observe-target",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "持续观察指定目标",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-command",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "巡逻命令",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "执行区域巡逻",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"is-patrol-command",
|
||||||
|
"reconnaissance-patrol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "is-patrol-command",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "巡逻命令检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"reconnaissance-patrol"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "patrol"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reconnaissance-patrol",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "侦察巡逻",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "reconnaissance-patrol",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "在指定区域进行侦察巡逻",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "autonomous-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "自主行为",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "没有命令时的自主侦察行为",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"autonomous-selector"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "autonomous-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "自主选择器",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "选择自主行为模式",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"explore-unknown",
|
||||||
|
"monitor-area",
|
||||||
|
"return-to-base"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "explore-unknown",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "探索未知区域",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "自主探索未知区域",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"has-unknown-area",
|
||||||
|
"explore-action"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "has-unknown-area",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "未知区域检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"explore-action"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "hasUnknownArea",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "explore-action",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "探索行动",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "explore-unknown-area",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "探索最近的未知区域",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "monitor-area",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "监控区域",
|
||||||
|
"properties": {
|
||||||
|
"abortType": "None",
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "监控重要区域",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"has-monitoring-target",
|
||||||
|
"monitor-action"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "has-monitoring-target",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "监控目标检查",
|
||||||
|
"properties": {
|
||||||
|
"conditionType": "custom",
|
||||||
|
"executeWhenTrue": true,
|
||||||
|
"executeWhenFalse": false,
|
||||||
|
"checkInterval": 0,
|
||||||
|
"abortType": "None"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
"monitor-action"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "hasMonitoringTarget",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "monitor-action",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "监控行动",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "monitor-strategic-area",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "监控战略重要区域",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "return-to-base",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "返回基地",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "return-to-base",
|
||||||
|
"parameters": "{}",
|
||||||
|
"timeout": 0,
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "string",
|
||||||
|
"value": "返回基地待命",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blackboard": [
|
||||||
|
{
|
||||||
|
"name": "unitType",
|
||||||
|
"type": "string",
|
||||||
|
"value": "scout",
|
||||||
|
"description": "单位类型"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentHealth",
|
||||||
|
"type": "number",
|
||||||
|
"value": 80,
|
||||||
|
"description": "当前生命值"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxHealth",
|
||||||
|
"type": "number",
|
||||||
|
"value": 80,
|
||||||
|
"description": "最大生命值"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "threatDetected",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否检测到威胁"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stealthAvailable",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true,
|
||||||
|
"description": "隐蔽能力是否可用"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentCommand",
|
||||||
|
"type": "string",
|
||||||
|
"value": "idle",
|
||||||
|
"description": "当前执行的命令"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasTarget",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否有目标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "targetPosition",
|
||||||
|
"type": "object",
|
||||||
|
"value": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"description": "目标位置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasUnknownArea",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true,
|
||||||
|
"description": "是否有未探索的区域"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasMonitoringTarget",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否有需要监控的目标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "visionRange",
|
||||||
|
"type": "number",
|
||||||
|
"value": 8,
|
||||||
|
"description": "视野范围"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "moveSpeed",
|
||||||
|
"type": "number",
|
||||||
|
"value": 5,
|
||||||
|
"description": "移动速度"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stealthDuration",
|
||||||
|
"type": "number",
|
||||||
|
"value": 10,
|
||||||
|
"description": "隐蔽持续时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "detectionRadius",
|
||||||
|
"type": "number",
|
||||||
|
"value": 6,
|
||||||
|
"description": "威胁检测半径"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastReportTime",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0,
|
||||||
|
"description": "上次报告时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exploredAreas",
|
||||||
|
"type": "array",
|
||||||
|
"value": [],
|
||||||
|
"description": "已探索区域列表"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "intelligenceData",
|
||||||
|
"type": "object",
|
||||||
|
"value": {},
|
||||||
|
"description": "收集的情报数据"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "behavior-tree",
|
||||||
|
"created": "2025-06-24T09:20:19.582Z",
|
||||||
|
"version": "1.0",
|
||||||
|
"exportType": "clean"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.1",
|
||||||
|
"importer": "json",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "1fb4b238-4ceb-4daa-817a-ac745bf598ca",
|
||||||
|
"files": [
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
2598
extensions/cocos/cocos-ecs/assets/resources/scout-ai.btree
Normal file
2598
extensions/cocos/cocos-ecs/assets/resources/scout-ai.btree
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.0",
|
||||||
|
"importer": "*",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "96872e28-7488-4d80-906b-edcc3511c84b",
|
||||||
|
"files": [
|
||||||
|
".btree",
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
483
extensions/cocos/cocos-ecs/assets/resources/soldier-ai.bt.json
Normal file
483
extensions/cocos/cocos-ecs/assets/resources/soldier-ai.bt.json
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
{
|
||||||
|
"id": "soldier-ai",
|
||||||
|
"name": "士兵AI行为树",
|
||||||
|
"description": "士兵单位的战斗AI系统,包含攻击、防御、巡逻等军事行为",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "ECS Framework",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "root",
|
||||||
|
"type": "root",
|
||||||
|
"name": "士兵AI根节点",
|
||||||
|
"description": "士兵AI的根节点,管理所有战斗行为",
|
||||||
|
"children": ["main-selector"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "主行为选择器",
|
||||||
|
"description": "按优先级选择士兵行为:撤退 > 战斗 > 巡逻 > 待命",
|
||||||
|
"children": ["retreat-behavior", "combat-behavior", "patrol-behavior", "standby-behavior"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "retreat-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "撤退行为",
|
||||||
|
"description": "生命值过低时撤退",
|
||||||
|
"children": ["retreat-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "shouldRetreat",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "retreat-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "撤退序列",
|
||||||
|
"description": "执行撤退的完整流程",
|
||||||
|
"children": ["set-retreat-state", "call-for-help", "retreat-move"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-retreat-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置撤退状态",
|
||||||
|
"description": "将士兵状态设置为撤退",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentState",
|
||||||
|
"value": "retreating"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "call-for-help",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "呼叫支援",
|
||||||
|
"description": "向附近友军发出支援请求",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "call-for-help"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "retreat-move",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "撤退移动",
|
||||||
|
"description": "快速移动到安全区域",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "retreat-to-safety"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "combat-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "战斗行为",
|
||||||
|
"description": "发现敌人时进入战斗状态",
|
||||||
|
"children": ["combat-selector"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "hasEnemy",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "combat-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "战斗选择器",
|
||||||
|
"description": "根据距离选择战斗策略",
|
||||||
|
"children": ["ranged-attack", "close-combat", "chase-enemy"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ranged-attack",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "远程攻击",
|
||||||
|
"description": "在远程攻击范围内时使用远程武器",
|
||||||
|
"children": ["ranged-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "isInRangedRange",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ranged-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "远程攻击序列",
|
||||||
|
"description": "执行远程攻击的完整流程",
|
||||||
|
"children": ["aim-target", "ranged-cooldown-check", "fire-ranged"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aim-target",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "瞄准目标",
|
||||||
|
"description": "瞄准敌方目标",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "aim-at-target"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ranged-cooldown-check",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "远程冷却检查",
|
||||||
|
"description": "检查远程攻击冷却时间",
|
||||||
|
"children": ["execute-ranged"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "canRangedAttack",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "execute-ranged",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "执行远程攻击",
|
||||||
|
"description": "发射远程武器",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "ranged-attack"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fire-ranged",
|
||||||
|
"type": "log-action",
|
||||||
|
"name": "远程攻击日志",
|
||||||
|
"description": "记录远程攻击",
|
||||||
|
"properties": {
|
||||||
|
"message": "士兵执行远程攻击"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "close-combat",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "近战攻击",
|
||||||
|
"description": "在近战范围内时使用近战武器",
|
||||||
|
"children": ["melee-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "isInMeleeRange",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "melee-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "近战攻击序列",
|
||||||
|
"description": "执行近战攻击的完整流程",
|
||||||
|
"children": ["melee-cooldown-check", "execute-melee", "melee-log"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "melee-cooldown-check",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "近战冷却检查",
|
||||||
|
"description": "检查近战攻击冷却时间",
|
||||||
|
"children": ["melee-attack"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "canMeleeAttack",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "melee-attack",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "近战攻击",
|
||||||
|
"description": "执行近战攻击",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "melee-attack"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "execute-melee",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "更新近战冷却",
|
||||||
|
"description": "重置近战攻击冷却时间",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "lastMeleeTime",
|
||||||
|
"value": "currentTime"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "melee-log",
|
||||||
|
"type": "log-action",
|
||||||
|
"name": "近战攻击日志",
|
||||||
|
"description": "记录近战攻击",
|
||||||
|
"properties": {
|
||||||
|
"message": "士兵执行近战攻击"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chase-enemy",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "追击敌人",
|
||||||
|
"description": "追击敌方目标",
|
||||||
|
"children": ["set-chase-state", "move-to-enemy"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-chase-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置追击状态",
|
||||||
|
"description": "将士兵状态设置为追击",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentState",
|
||||||
|
"value": "chasing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-to-enemy",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "移动到敌人",
|
||||||
|
"description": "快速移动到敌人位置",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "move-to-target"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "巡逻行为",
|
||||||
|
"description": "没有敌人时执行巡逻",
|
||||||
|
"children": ["patrol-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "patrol"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "巡逻序列",
|
||||||
|
"description": "执行巡逻的完整流程",
|
||||||
|
"children": ["set-patrol-state", "patrol-move", "patrol-scan", "patrol-wait"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-patrol-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置巡逻状态",
|
||||||
|
"description": "将士兵状态设置为巡逻",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentState",
|
||||||
|
"value": "patrolling"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-move",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "巡逻移动",
|
||||||
|
"description": "沿巡逻路线移动",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "patrol-move"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-scan",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "扫描敌人",
|
||||||
|
"description": "扫描周围是否有敌人",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "scan-for-enemies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-wait",
|
||||||
|
"type": "wait-action",
|
||||||
|
"name": "巡逻等待",
|
||||||
|
"description": "在巡逻点等待",
|
||||||
|
"properties": {
|
||||||
|
"waitTime": 2.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "standby-behavior",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "待命行为",
|
||||||
|
"description": "默认的待命状态",
|
||||||
|
"children": ["set-standby-state", "standby-wait"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-standby-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置待命状态",
|
||||||
|
"description": "将士兵状态设置为待命",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentState",
|
||||||
|
"value": "standby"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "standby-wait",
|
||||||
|
"type": "wait-action",
|
||||||
|
"name": "待命等待",
|
||||||
|
"description": "待命状态下等待",
|
||||||
|
"properties": {
|
||||||
|
"waitTime": 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blackboard": [
|
||||||
|
{
|
||||||
|
"name": "currentState",
|
||||||
|
"type": "string",
|
||||||
|
"value": "standby",
|
||||||
|
"description": "士兵当前状态(standby、patrolling、chasing、attacking、retreating)",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentCommand",
|
||||||
|
"type": "string",
|
||||||
|
"value": "patrol",
|
||||||
|
"description": "当前接收的命令(patrol、attack、defend、move、standby)",
|
||||||
|
"group": "命令"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasEnemy",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否发现敌人",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "enemyDistance",
|
||||||
|
"type": "number",
|
||||||
|
"value": 999,
|
||||||
|
"description": "与最近敌人的距离",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isInMeleeRange",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否在近战攻击范围内",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isInRangedRange",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否在远程攻击范围内",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "canMeleeAttack",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true,
|
||||||
|
"description": "是否可以进行近战攻击(冷却检查)",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "canRangedAttack",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true,
|
||||||
|
"description": "是否可以进行远程攻击(冷却检查)",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastMeleeTime",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0,
|
||||||
|
"description": "上次近战攻击时间",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastRangedTime",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0,
|
||||||
|
"description": "上次远程攻击时间",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meleeCooldown",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1.0,
|
||||||
|
"description": "近战攻击冷却时间",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rangedCooldown",
|
||||||
|
"type": "number",
|
||||||
|
"value": 2.0,
|
||||||
|
"description": "远程攻击冷却时间",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meleeRange",
|
||||||
|
"type": "number",
|
||||||
|
"value": 2.0,
|
||||||
|
"description": "近战攻击范围",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rangedRange",
|
||||||
|
"type": "number",
|
||||||
|
"value": 8.0,
|
||||||
|
"description": "远程攻击范围",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shouldRetreat",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否应该撤退(生命值低或寡不敌众)",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentHealth",
|
||||||
|
"type": "number",
|
||||||
|
"value": 100,
|
||||||
|
"description": "当前生命值",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxHealth",
|
||||||
|
"type": "number",
|
||||||
|
"value": 100,
|
||||||
|
"description": "最大生命值",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "armor",
|
||||||
|
"type": "number",
|
||||||
|
"value": 10,
|
||||||
|
"description": "护甲值",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "moveSpeed",
|
||||||
|
"type": "number",
|
||||||
|
"value": 4.0,
|
||||||
|
"description": "移动速度",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "patrolRadius",
|
||||||
|
"type": "number",
|
||||||
|
"value": 10.0,
|
||||||
|
"description": "巡逻半径",
|
||||||
|
"group": "属性"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"created": "2024-01-01T00:00:00.000Z",
|
||||||
|
"nodeCount": 35,
|
||||||
|
"editorVersion": "1.0.0",
|
||||||
|
"description": "士兵AI展示了完整的战斗单位行为模式,包括多种攻击方式、战术撤退、巡逻警戒等军事行为的智能决策。"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.1",
|
||||||
|
"importer": "json",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "a71fbedb-9435-4637-b7a5-e0fb8b99d3c1",
|
||||||
|
"files": [
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
1915
extensions/cocos/cocos-ecs/assets/resources/soldier-ai.btree
Normal file
1915
extensions/cocos/cocos-ecs/assets/resources/soldier-ai.btree
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.0",
|
||||||
|
"importer": "*",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "843934be-5d8e-49b3-93e5-34999ca6f50e",
|
||||||
|
"files": [
|
||||||
|
".btree",
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
380
extensions/cocos/cocos-ecs/assets/resources/worker-ai.bt.json
Normal file
380
extensions/cocos/cocos-ecs/assets/resources/worker-ai.bt.json
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
{
|
||||||
|
"id": "worker-ai",
|
||||||
|
"name": "工人AI行为树",
|
||||||
|
"description": "工人单位的智能行为系统,包含资源收集、建造、修理等功能",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "ECS Framework",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "root",
|
||||||
|
"type": "root",
|
||||||
|
"name": "工人AI根节点",
|
||||||
|
"description": "工人AI的根节点,管理所有工人行为",
|
||||||
|
"children": ["main-selector"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "主行为选择器",
|
||||||
|
"description": "按优先级选择工人行为:逃跑 > 执行命令 > 空闲",
|
||||||
|
"children": ["flee-behavior", "command-behavior", "idle-behavior"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flee-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "逃跑行为",
|
||||||
|
"description": "生命值低时逃跑到安全区域",
|
||||||
|
"children": ["flee-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "isLowHealth",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flee-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "逃跑序列",
|
||||||
|
"description": "执行逃跑行为的完整序列",
|
||||||
|
"children": ["set-flee-state", "move-to-safety", "heal-self"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-flee-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置逃跑状态",
|
||||||
|
"description": "将工人状态设置为逃跑",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentState",
|
||||||
|
"value": "fleeing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-to-safety",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "移动到安全区域",
|
||||||
|
"description": "移动到最近的安全建筑",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "move-to-safety"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heal-self",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "自我治疗",
|
||||||
|
"description": "使用治疗物品恢复生命值",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "heal-self"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "command-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "命令行为",
|
||||||
|
"description": "有命令时执行相应行为",
|
||||||
|
"children": ["command-selector"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "hasTarget",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "command-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "命令选择器",
|
||||||
|
"description": "根据命令类型选择行为",
|
||||||
|
"children": ["gather-behavior", "build-behavior", "repair-behavior", "move-behavior"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gather-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "收集行为",
|
||||||
|
"description": "收集资源的行为",
|
||||||
|
"children": ["gather-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "gather"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gather-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "收集序列",
|
||||||
|
"description": "完整的资源收集流程",
|
||||||
|
"children": ["move-to-resource", "collect-resource", "return-to-base"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-to-resource",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "移动到资源",
|
||||||
|
"description": "移动到目标资源点",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "move-to-target"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collect-resource",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "收集资源",
|
||||||
|
"description": "执行资源收集动作",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "gather-resource"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "return-to-base",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "返回基地",
|
||||||
|
"description": "将资源运回基地",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "return-to-base"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "build-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "建造行为",
|
||||||
|
"description": "建造建筑的行为",
|
||||||
|
"children": ["build-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "build-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "建造序列",
|
||||||
|
"description": "完整的建造流程",
|
||||||
|
"children": ["move-to-build-site", "construct-building"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-to-build-site",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "移动到建筑工地",
|
||||||
|
"description": "移动到指定的建筑位置",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "move-to-target"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "construct-building",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "建造建筑",
|
||||||
|
"description": "执行建筑建造动作",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "construct-building"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "repair-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "修理行为",
|
||||||
|
"description": "修理损坏建筑的行为",
|
||||||
|
"children": ["repair-sequence"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "repair"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "repair-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "修理序列",
|
||||||
|
"description": "完整的修理流程",
|
||||||
|
"children": ["move-to-building", "repair-building"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-to-building",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "移动到建筑",
|
||||||
|
"description": "移动到需要修理的建筑",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "move-to-target"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "repair-building",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "修理建筑",
|
||||||
|
"description": "执行建筑修理动作",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "repair-building"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "移动行为",
|
||||||
|
"description": "单纯的移动行为",
|
||||||
|
"children": ["simple-move"],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentCommand",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": "move"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "simple-move",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "简单移动",
|
||||||
|
"description": "移动到指定位置",
|
||||||
|
"properties": {
|
||||||
|
"eventName": "move-to-target"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "idle-behavior",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "空闲行为",
|
||||||
|
"description": "没有任务时的默认行为",
|
||||||
|
"children": ["set-idle-state", "idle-wait"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-idle-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置空闲状态",
|
||||||
|
"description": "将工人状态设置为空闲",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "currentState",
|
||||||
|
"value": "idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "idle-wait",
|
||||||
|
"type": "wait-action",
|
||||||
|
"name": "空闲等待",
|
||||||
|
"description": "空闲时等待一段时间",
|
||||||
|
"properties": {
|
||||||
|
"waitTime": 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blackboard": [
|
||||||
|
{
|
||||||
|
"name": "currentState",
|
||||||
|
"type": "string",
|
||||||
|
"value": "idle",
|
||||||
|
"description": "工人当前状态(idle、working、fleeing、gathering、building、repairing)",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentCommand",
|
||||||
|
"type": "string",
|
||||||
|
"value": "idle",
|
||||||
|
"description": "当前接收的命令(idle、move、gather、build、repair、attack)",
|
||||||
|
"group": "命令"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasTarget",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否有目标任务",
|
||||||
|
"group": "目标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "targetPosition",
|
||||||
|
"type": "object",
|
||||||
|
"value": {"x": 0, "y": 0, "z": 0},
|
||||||
|
"description": "目标位置坐标",
|
||||||
|
"group": "目标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentHealth",
|
||||||
|
"type": "number",
|
||||||
|
"value": 100,
|
||||||
|
"description": "当前生命值",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxHealth",
|
||||||
|
"type": "number",
|
||||||
|
"value": 100,
|
||||||
|
"description": "最大生命值",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "healthPercentage",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1.0,
|
||||||
|
"description": "生命值百分比",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isLowHealth",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false,
|
||||||
|
"description": "是否生命值较低(低于30%)",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "moveSpeed",
|
||||||
|
"type": "number",
|
||||||
|
"value": 3.0,
|
||||||
|
"description": "移动速度",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gatherSpeed",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1.0,
|
||||||
|
"description": "资源收集速度",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "buildSpeed",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1.0,
|
||||||
|
"description": "建造速度",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "carryCapacity",
|
||||||
|
"type": "number",
|
||||||
|
"value": 10,
|
||||||
|
"description": "携带容量",
|
||||||
|
"group": "属性"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentLoad",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0,
|
||||||
|
"description": "当前携带量",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "resourceType",
|
||||||
|
"type": "string",
|
||||||
|
"value": "none",
|
||||||
|
"description": "携带的资源类型",
|
||||||
|
"group": "状态"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"created": "2024-01-01T00:00:00.000Z",
|
||||||
|
"nodeCount": 30,
|
||||||
|
"editorVersion": "1.0.0",
|
||||||
|
"description": "工人AI展示了完整的RTS单位行为模式,包括生存、工作、建造等多种行为的优先级管理和状态切换。"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"ver": "2.0.1",
|
||||||
|
"importer": "json",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "59141420-59cc-425f-b519-1073df8b93f8",
|
||||||
|
"files": [
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
2028
extensions/cocos/cocos-ecs/assets/resources/worker-ai.btree
Normal file
2028
extensions/cocos/cocos-ecs/assets/resources/worker-ai.btree
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.0",
|
||||||
|
"importer": "*",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "f7fcacf2-eab6-4bef-b7b4-55d13707c90c",
|
||||||
|
"files": [
|
||||||
|
".btree",
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
"_shutter": 7,
|
"_shutter": 7,
|
||||||
"_iso": 0,
|
"_iso": 0,
|
||||||
"_screenScale": 1,
|
"_screenScale": 1,
|
||||||
"_visibility": 1116733440,
|
"_visibility": 1822425087,
|
||||||
"_targetTexture": null,
|
"_targetTexture": null,
|
||||||
"_postProcess": null,
|
"_postProcess": null,
|
||||||
"_usePostProcess": false,
|
"_usePostProcess": false,
|
||||||
|
|||||||
236
extensions/cocos/cocos-ecs/assets/scripts/RTSDemo.ts
Normal file
236
extensions/cocos/cocos-ecs/assets/scripts/RTSDemo.ts
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
import { _decorator, Component, Node, Vec3, instantiate, Prefab, Camera } from 'cc';
|
||||||
|
import { UnitController } from './components/UnitController';
|
||||||
|
import { RTSCameraController } from './controllers/RTSCameraController';
|
||||||
|
import { UIController } from './controllers/UIController';
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RTS演示项目主控制器
|
||||||
|
* 展示行为树在3D RTS游戏中的应用
|
||||||
|
*/
|
||||||
|
@ccclass('RTSDemo')
|
||||||
|
export class RTSDemo extends Component {
|
||||||
|
|
||||||
|
@property(Prefab)
|
||||||
|
unitPrefab: Prefab = null!;
|
||||||
|
|
||||||
|
@property(Prefab)
|
||||||
|
buildingPrefab: Prefab = null!;
|
||||||
|
|
||||||
|
@property(Prefab)
|
||||||
|
resourcePrefab: Prefab = null!;
|
||||||
|
|
||||||
|
@property(Node)
|
||||||
|
gameWorld: Node = null!;
|
||||||
|
|
||||||
|
@property(Camera)
|
||||||
|
mainCamera: Camera = null!;
|
||||||
|
|
||||||
|
@property(Node)
|
||||||
|
uiRoot: Node = null!;
|
||||||
|
|
||||||
|
private cameraController: RTSCameraController = null!;
|
||||||
|
private uiController: UIController = null!;
|
||||||
|
|
||||||
|
// 游戏状态
|
||||||
|
private units: Node[] = [];
|
||||||
|
private buildings: Node[] = [];
|
||||||
|
private resources: Node[] = [];
|
||||||
|
private selectedUnits: Node[] = [];
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
console.log('RTS Demo 初始化开始...');
|
||||||
|
this.initializeControllers();
|
||||||
|
this.setupScene();
|
||||||
|
console.log('RTS Demo 初始化完成!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化控制器
|
||||||
|
*/
|
||||||
|
private initializeControllers() {
|
||||||
|
// 相机控制器
|
||||||
|
this.cameraController = this.mainCamera.getComponent(RTSCameraController) ||
|
||||||
|
this.mainCamera.addComponent(RTSCameraController);
|
||||||
|
|
||||||
|
// UI控制器
|
||||||
|
this.uiController = this.uiRoot.getComponent(UIController) ||
|
||||||
|
this.uiRoot.addComponent(UIController);
|
||||||
|
|
||||||
|
// 设置UI回调
|
||||||
|
this.uiController.onUnitSelected = this.onUnitSelected.bind(this);
|
||||||
|
this.uiController.onCommandIssued = this.onCommandIssued.bind(this);
|
||||||
|
|
||||||
|
console.log('控制器初始化完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置场景
|
||||||
|
*/
|
||||||
|
private setupScene() {
|
||||||
|
this.createUnits();
|
||||||
|
this.createBuildings();
|
||||||
|
this.createResources();
|
||||||
|
|
||||||
|
// 设置初始相机位置
|
||||||
|
this.mainCamera.node.setPosition(0, 20, 15);
|
||||||
|
this.mainCamera.node.lookAt(Vec3.ZERO);
|
||||||
|
|
||||||
|
console.log('场景设置完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建单位
|
||||||
|
*/
|
||||||
|
private createUnits() {
|
||||||
|
const unitTypes = [
|
||||||
|
{ name: 'Worker', behaviorTree: 'worker-ai', color: 'blue' },
|
||||||
|
{ name: 'Soldier', behaviorTree: 'soldier-ai', color: 'red' },
|
||||||
|
{ name: 'Scout', behaviorTree: 'scout-ai', color: 'green' }
|
||||||
|
];
|
||||||
|
|
||||||
|
unitTypes.forEach((type, typeIndex) => {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const unit = instantiate(this.unitPrefab);
|
||||||
|
unit.name = `${type.name}_${i + 1}`;
|
||||||
|
|
||||||
|
// 设置位置
|
||||||
|
const angle = (i / 3) * Math.PI * 2;
|
||||||
|
const radius = 3 + typeIndex * 2;
|
||||||
|
const x = Math.cos(angle) * radius;
|
||||||
|
const z = Math.sin(angle) * radius;
|
||||||
|
unit.setPosition(x, 0, z);
|
||||||
|
|
||||||
|
// 添加到场景
|
||||||
|
this.gameWorld.addChild(unit);
|
||||||
|
this.units.push(unit);
|
||||||
|
|
||||||
|
// 配置单位组件
|
||||||
|
const unitController = unit.getComponent(UnitController) || unit.addComponent(UnitController);
|
||||||
|
unitController.setup({
|
||||||
|
unitType: type.name.toLowerCase(),
|
||||||
|
behaviorTreeName: type.behaviorTree,
|
||||||
|
maxHealth: 100,
|
||||||
|
moveSpeed: 3,
|
||||||
|
attackRange: 2,
|
||||||
|
attackDamage: 25,
|
||||||
|
color: type.color
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`创建单位: ${unit.name} at (${x.toFixed(1)}, 0, ${z.toFixed(1)})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建建筑
|
||||||
|
*/
|
||||||
|
private createBuildings() {
|
||||||
|
const buildingPositions = [
|
||||||
|
{ pos: new Vec3(-10, 0, -10), name: 'MainBase' },
|
||||||
|
{ pos: new Vec3(10, 0, 10), name: 'Barracks' },
|
||||||
|
{ pos: new Vec3(-8, 0, 8), name: 'ResourceCenter' }
|
||||||
|
];
|
||||||
|
|
||||||
|
buildingPositions.forEach((building, index) => {
|
||||||
|
const buildingNode = instantiate(this.buildingPrefab);
|
||||||
|
buildingNode.name = building.name;
|
||||||
|
buildingNode.setPosition(building.pos);
|
||||||
|
|
||||||
|
this.gameWorld.addChild(buildingNode);
|
||||||
|
this.buildings.push(buildingNode);
|
||||||
|
|
||||||
|
console.log(`创建建筑: ${building.name} at ${building.pos}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建资源
|
||||||
|
*/
|
||||||
|
private createResources() {
|
||||||
|
const resourcePositions = [
|
||||||
|
new Vec3(5, 0, -5),
|
||||||
|
new Vec3(-5, 0, 5),
|
||||||
|
new Vec3(8, 0, -8),
|
||||||
|
new Vec3(-8, 0, -5),
|
||||||
|
new Vec3(6, 0, 6)
|
||||||
|
];
|
||||||
|
|
||||||
|
resourcePositions.forEach((pos, index) => {
|
||||||
|
const resource = instantiate(this.resourcePrefab);
|
||||||
|
resource.name = `Resource_${index + 1}`;
|
||||||
|
resource.setPosition(pos);
|
||||||
|
|
||||||
|
this.gameWorld.addChild(resource);
|
||||||
|
this.resources.push(resource);
|
||||||
|
|
||||||
|
console.log(`创建资源: ${resource.name} at ${pos}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位选择回调
|
||||||
|
*/
|
||||||
|
private onUnitSelected(units: Node[]) {
|
||||||
|
// 取消之前的选择
|
||||||
|
this.selectedUnits.forEach(unit => {
|
||||||
|
const unitController = unit.getComponent(UnitController);
|
||||||
|
if (unitController) {
|
||||||
|
unitController.setSelected(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置新选择
|
||||||
|
this.selectedUnits = units;
|
||||||
|
this.selectedUnits.forEach(unit => {
|
||||||
|
const unitController = unit.getComponent(UnitController);
|
||||||
|
if (unitController) {
|
||||||
|
unitController.setSelected(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`选择了 ${units.length} 个单位`);
|
||||||
|
this.uiController.setSelectedUnitsCount(units.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令发布回调
|
||||||
|
*/
|
||||||
|
private onCommandIssued(command: string, target?: Vec3 | Node) {
|
||||||
|
if (this.selectedUnits.length === 0) {
|
||||||
|
console.log('没有选择单位');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedUnits.forEach(unit => {
|
||||||
|
const unitController = unit.getComponent(UnitController);
|
||||||
|
if (unitController) {
|
||||||
|
unitController.issueCommand(command, target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`发布命令: ${command} 给 ${this.selectedUnits.length} 个单位`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有单位
|
||||||
|
*/
|
||||||
|
getAllUnits(): Node[] {
|
||||||
|
return [...this.units];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有建筑
|
||||||
|
*/
|
||||||
|
getAllBuildings(): Node[] {
|
||||||
|
return [...this.buildings];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有资源
|
||||||
|
*/
|
||||||
|
getAllResources(): Node[] {
|
||||||
|
return [...this.resources];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "4.0.24",
|
"ver": "4.0.24",
|
||||||
"importer": "typescript",
|
"importer": "typescript",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "0f20d48a-7b30-4081-a9de-709432b6737b",
|
"uuid": "c3386f4a-9bef-416f-b770-fcec91cedbc4",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "1.2.0",
|
"ver": "1.2.0",
|
||||||
"importer": "directory",
|
"importer": "directory",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "3d8cbc91-5bc5-4d17-b53a-01fda26e4660",
|
"uuid": "d946c8cb-cba5-46eb-8949-5a327bdd4367",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { Node, resources, JsonAsset } from 'cc';
|
||||||
|
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, TaskStatus, BehaviorTreeJSONConfig } from '@esengine/ai';
|
||||||
|
import { ECSComponent } from './UnitComponent';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为树组件 - ECS组件,管理单个实体的行为树
|
||||||
|
*/
|
||||||
|
export class BehaviorTreeComponent extends ECSComponent {
|
||||||
|
public behaviorTreeFile: string = '';
|
||||||
|
public cocosNode: Node | null = null;
|
||||||
|
|
||||||
|
private behaviorTree: BehaviorTree<any> | null = null;
|
||||||
|
private blackboard: Blackboard | null = null;
|
||||||
|
private context: any = null;
|
||||||
|
private isLoaded: boolean = false;
|
||||||
|
private isRunning: boolean = false;
|
||||||
|
private lastTickTime: number = 0;
|
||||||
|
private tickInterval: number = 0.1; // 行为树更新间隔(秒)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化行为树
|
||||||
|
*/
|
||||||
|
async initialize() {
|
||||||
|
if (!this.behaviorTreeFile) {
|
||||||
|
console.error('行为树文件路径未设置');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.loadBehaviorTree();
|
||||||
|
this.setupBlackboard();
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.isRunning = true;
|
||||||
|
console.log(`行为树组件初始化成功: ${this.behaviorTreeFile}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`行为树组件初始化失败: ${this.behaviorTreeFile}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载行为树文件
|
||||||
|
*/
|
||||||
|
private async loadBehaviorTree(): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 移除.btree扩展名,使用.bt.json
|
||||||
|
const jsonPath = this.behaviorTreeFile.replace('.btree', '.bt.json');
|
||||||
|
|
||||||
|
resources.load(jsonPath, JsonAsset, (err, asset) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`加载行为树文件失败: ${jsonPath}`, err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const treeData = asset.json as BehaviorTreeJSONConfig;
|
||||||
|
this.buildBehaviorTree(treeData);
|
||||||
|
resolve();
|
||||||
|
} catch (buildError) {
|
||||||
|
console.error(`构建行为树失败: ${jsonPath}`, buildError);
|
||||||
|
reject(buildError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建行为树
|
||||||
|
*/
|
||||||
|
private buildBehaviorTree(treeData: BehaviorTreeJSONConfig) {
|
||||||
|
// 创建基础执行上下文
|
||||||
|
const baseContext = {
|
||||||
|
cocosNode: this.cocosNode,
|
||||||
|
unitComponent: this
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用@esengine/ai的BehaviorTreeBuilder构建行为树
|
||||||
|
// 这会自动创建黑板并设置所有配置
|
||||||
|
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(treeData, baseContext);
|
||||||
|
this.behaviorTree = result.tree;
|
||||||
|
this.blackboard = result.blackboard;
|
||||||
|
this.context = result.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置黑板
|
||||||
|
*/
|
||||||
|
private setupBlackboard() {
|
||||||
|
if (!this.blackboard || !this.cocosNode) return;
|
||||||
|
|
||||||
|
// 设置基础信息
|
||||||
|
this.blackboard.setValue('entityName', this.cocosNode.name);
|
||||||
|
this.blackboard.setValue('currentTime', Date.now() / 1000);
|
||||||
|
this.blackboard.setValue('deltaTime', 0.016);
|
||||||
|
this.blackboard.setValue('worldPosition', this.cocosNode.worldPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新行为树
|
||||||
|
*/
|
||||||
|
update(deltaTime: number) {
|
||||||
|
if (!this.isLoaded || !this.isRunning || !this.behaviorTree || !this.context) return;
|
||||||
|
|
||||||
|
// 控制更新频率
|
||||||
|
this.lastTickTime += deltaTime;
|
||||||
|
if (this.lastTickTime < this.tickInterval) return;
|
||||||
|
|
||||||
|
this.lastTickTime = 0;
|
||||||
|
|
||||||
|
// 更新黑板中的时间信息
|
||||||
|
if (this.blackboard) {
|
||||||
|
this.blackboard.setValue('deltaTime', deltaTime);
|
||||||
|
this.blackboard.setValue('currentTime', Date.now() / 1000);
|
||||||
|
if (this.cocosNode) {
|
||||||
|
this.blackboard.setValue('worldPosition', this.cocosNode.worldPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行行为树
|
||||||
|
try {
|
||||||
|
this.behaviorTree.tick();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`行为树执行错误:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取黑板
|
||||||
|
*/
|
||||||
|
getBlackboard(): Blackboard | null {
|
||||||
|
return this.blackboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停行为树
|
||||||
|
*/
|
||||||
|
pause() {
|
||||||
|
this.isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复行为树
|
||||||
|
*/
|
||||||
|
resume() {
|
||||||
|
if (this.isLoaded) {
|
||||||
|
this.isRunning = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止行为树
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
this.isRunning = false;
|
||||||
|
if (this.behaviorTree) {
|
||||||
|
this.behaviorTree.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "4.0.24",
|
"ver": "4.0.24",
|
||||||
"importer": "typescript",
|
"importer": "typescript",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "0c14be41-df1d-4bdc-88fd-b2e504ecdee6",
|
"uuid": "8efd182b-9891-4903-bef2-eb07b5184263",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
import { _decorator, Component, resources, JsonAsset } from 'cc';
|
||||||
|
import { BehaviorTree, BehaviorTreeBuilder, Blackboard, BehaviorTreeJSONConfig } from '@esengine/ai';
|
||||||
|
import { UnitController } from './UnitController';
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行上下文接口
|
||||||
|
*/
|
||||||
|
interface GameExecutionContext {
|
||||||
|
blackboard?: Blackboard;
|
||||||
|
unitController: UnitController;
|
||||||
|
gameObject: any;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为树管理器 - 使用@esengine/ai包管理行为树
|
||||||
|
*/
|
||||||
|
@ccclass('BehaviorTreeManager')
|
||||||
|
export class BehaviorTreeManager extends Component {
|
||||||
|
|
||||||
|
@property
|
||||||
|
debugMode: boolean = true;
|
||||||
|
|
||||||
|
@property
|
||||||
|
tickInterval: number = 0.1; // 行为树更新间隔(秒)
|
||||||
|
|
||||||
|
private behaviorTree: BehaviorTree<GameExecutionContext> | null = null;
|
||||||
|
private blackboard: Blackboard | null = null;
|
||||||
|
private context: GameExecutionContext | null = null;
|
||||||
|
private isLoaded: boolean = false;
|
||||||
|
private isRunning: boolean = false;
|
||||||
|
private lastTickTime: number = 0;
|
||||||
|
private unitController: UnitController | null = null;
|
||||||
|
private currentBehaviorTreeName: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化行为树
|
||||||
|
*/
|
||||||
|
async initializeBehaviorTree(behaviorTreeName: string, unitController: UnitController) {
|
||||||
|
this.currentBehaviorTreeName = behaviorTreeName;
|
||||||
|
this.unitController = unitController;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.loadBehaviorTree(behaviorTreeName);
|
||||||
|
this.setupBlackboard();
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.isRunning = true;
|
||||||
|
console.log(`行为树初始化成功: ${behaviorTreeName}`);
|
||||||
|
} 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.context = {
|
||||||
|
blackboard: this.blackboard,
|
||||||
|
unitController: this.unitController!,
|
||||||
|
gameObject: this.node
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从JSON数据创建行为树
|
||||||
|
const buildResult = BehaviorTreeBuilder.fromBehaviorTreeConfig(behaviorTreeData, this.context);
|
||||||
|
this.behaviorTree = buildResult.tree as BehaviorTree<GameExecutionContext>;
|
||||||
|
this.blackboard = buildResult.blackboard;
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error(`创建行为树失败: ${jsonPath}`, parseError);
|
||||||
|
reject(parseError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置黑板基础信息
|
||||||
|
*/
|
||||||
|
private setupBlackboard() {
|
||||||
|
if (!this.unitController || !this.blackboard) return;
|
||||||
|
|
||||||
|
// 设置单位基础信息
|
||||||
|
this.blackboard.setValue('entityName', this.node.name);
|
||||||
|
this.blackboard.setValue('unitType', this.unitController.unitType);
|
||||||
|
this.blackboard.setValue('maxHealth', this.unitController.maxHealth);
|
||||||
|
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
|
||||||
|
this.blackboard.setValue('moveSpeed', this.unitController.moveSpeed);
|
||||||
|
this.blackboard.setValue('attackRange', this.unitController.attackRange);
|
||||||
|
this.blackboard.setValue('attackDamage', this.unitController.attackDamage);
|
||||||
|
this.blackboard.setValue('attackCooldown', this.unitController.attackCooldown);
|
||||||
|
|
||||||
|
// 设置时间信息
|
||||||
|
this.blackboard.setValue('currentTime', Date.now() / 1000);
|
||||||
|
this.blackboard.setValue('deltaTime', 0.016);
|
||||||
|
this.blackboard.setValue('worldPosition', this.node.worldPosition);
|
||||||
|
|
||||||
|
// 设置初始状态
|
||||||
|
this.blackboard.setValue('currentCommand', 'idle');
|
||||||
|
this.blackboard.setValue('hasTarget', false);
|
||||||
|
this.blackboard.setValue('isSelected', false);
|
||||||
|
|
||||||
|
// 设置单位控制器引用,供行为树节点使用
|
||||||
|
this.blackboard.setValue('unitController', this.unitController);
|
||||||
|
this.blackboard.setValue('gameObject', this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新黑板值
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 更新黑板中的时间信息
|
||||||
|
this.blackboard.setValue('deltaTime', deltaTime);
|
||||||
|
this.blackboard.setValue('currentTime', Date.now() / 1000);
|
||||||
|
this.blackboard.setValue('worldPosition', this.node.worldPosition);
|
||||||
|
|
||||||
|
// 更新单位状态信息
|
||||||
|
if (this.unitController) {
|
||||||
|
this.blackboard.setValue('currentHealth', this.unitController.currentHealth);
|
||||||
|
this.blackboard.setValue('healthPercentage', this.unitController.currentHealth / this.unitController.maxHealth);
|
||||||
|
this.blackboard.setValue('isLowHealth', this.unitController.currentHealth < this.unitController.maxHealth * 0.3);
|
||||||
|
this.blackboard.setValue('currentCommand', this.unitController.currentCommand);
|
||||||
|
this.blackboard.setValue('isSelected', this.unitController.isSelected);
|
||||||
|
this.blackboard.setValue('targetPosition', this.unitController.targetPosition);
|
||||||
|
this.blackboard.setValue('targetNode', this.unitController.targetNode);
|
||||||
|
|
||||||
|
// 更新距离信息
|
||||||
|
if (this.unitController.targetPosition) {
|
||||||
|
const distance = this.node.worldPosition.subtract(this.unitController.targetPosition).length();
|
||||||
|
this.blackboard.setValue('distanceToTarget', distance);
|
||||||
|
this.blackboard.setValue('isInAttackRange', distance <= this.unitController.attackRange);
|
||||||
|
this.blackboard.setValue('isCloseToTarget', distance <= 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态标志
|
||||||
|
this.blackboard.setValue('isIdle', this.unitController.currentCommand === 'idle');
|
||||||
|
this.blackboard.setValue('isMoving', this.unitController.currentCommand === 'move');
|
||||||
|
this.blackboard.setValue('isAttacking', this.unitController.currentCommand === 'attack');
|
||||||
|
this.blackboard.setValue('isGathering', this.unitController.currentCommand === 'gather');
|
||||||
|
this.blackboard.setValue('isPatrolling', this.unitController.currentCommand === 'patrol');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行行为树
|
||||||
|
try {
|
||||||
|
this.behaviorTree.tick(deltaTime);
|
||||||
|
if (this.debugMode && Math.random() < 0.01) { // 1%的概率打印调试信息
|
||||||
|
console.log(`行为树执行完成, 单位: ${this.node.name}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`行为树执行错误:`, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "4.0.24",
|
"ver": "4.0.24",
|
||||||
"importer": "typescript",
|
"importer": "typescript",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "3c3bf485-e1ab-44a4-8758-6594edc4c575",
|
"uuid": "891c88fc-282d-4791-a961-8d85244bfee7",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
import { _decorator, Component, Node, Vec3, Material, MeshRenderer, Color, tween } from 'cc';
|
||||||
|
import { BehaviorTreeComponent } from './BehaviorTreeComponent';
|
||||||
|
|
||||||
|
// 简化的ECS组件基类
|
||||||
|
export class ECSComponent {
|
||||||
|
public entity: any = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化的Entity类
|
||||||
|
export class Entity {
|
||||||
|
public name: string = '';
|
||||||
|
private components: Map<any, any> = new Map();
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
addComponent(component: any) {
|
||||||
|
this.components.set(component.constructor, component);
|
||||||
|
component.entity = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponent<T>(componentClass: any): T | null {
|
||||||
|
return this.components.get(componentClass) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasComponent(componentClass: any): boolean {
|
||||||
|
return this.components.has(componentClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化的Core类
|
||||||
|
export class Core {
|
||||||
|
static entityManager = {
|
||||||
|
entities: [] as Entity[],
|
||||||
|
createEntity: (name: string) => {
|
||||||
|
const entity = new Entity(name);
|
||||||
|
Core.entityManager.entities.push(entity);
|
||||||
|
return entity;
|
||||||
|
},
|
||||||
|
destroyEntity: (entity: Entity) => {
|
||||||
|
const index = Core.entityManager.entities.indexOf(entity);
|
||||||
|
if (index !== -1) {
|
||||||
|
Core.entityManager.entities.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static create(config?: any) {
|
||||||
|
console.log('ECS Core initialized with config:', config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static update(deltaTime: number) {
|
||||||
|
// 简化的更新逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位配置接口
|
||||||
|
*/
|
||||||
|
export interface UnitConfig {
|
||||||
|
unitType: string;
|
||||||
|
behaviorTreeFile: string;
|
||||||
|
maxHealth: number;
|
||||||
|
moveSpeed: number;
|
||||||
|
attackRange: number;
|
||||||
|
attackDamage: number;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位状态组件
|
||||||
|
*/
|
||||||
|
export class UnitStateComponent extends ECSComponent {
|
||||||
|
public unitType: string = '';
|
||||||
|
public maxHealth: number = 100;
|
||||||
|
public currentHealth: number = 100;
|
||||||
|
public moveSpeed: number = 3;
|
||||||
|
public attackRange: number = 2;
|
||||||
|
public attackDamage: number = 25;
|
||||||
|
public isSelected: boolean = false;
|
||||||
|
public currentCommand: string = 'idle';
|
||||||
|
public targetPosition: Vec3 = Vec3.ZERO.clone();
|
||||||
|
public targetNode: Node | null = null;
|
||||||
|
public lastAttackTime: number = 0;
|
||||||
|
public attackCooldown: number = 1.5;
|
||||||
|
public color: string = 'white';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位组件 - Cocos Creator组件,管理单位的可视化和ECS实体
|
||||||
|
*/
|
||||||
|
@ccclass('UnitComponent')
|
||||||
|
export class UnitComponent extends Component {
|
||||||
|
|
||||||
|
@property
|
||||||
|
showDebugInfo: boolean = true;
|
||||||
|
|
||||||
|
private entity: Entity | null = null;
|
||||||
|
private unitState: UnitStateComponent | null = null;
|
||||||
|
private behaviorTreeComponent: BehaviorTreeComponent | null = null;
|
||||||
|
private meshRenderer: MeshRenderer | null = null;
|
||||||
|
private originalMaterial: Material | null = null;
|
||||||
|
private selectionMaterial: Material | null = null;
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.meshRenderer = this.getComponent(MeshRenderer);
|
||||||
|
if (this.meshRenderer && this.meshRenderer.material) {
|
||||||
|
this.originalMaterial = this.meshRenderer.material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单位配置
|
||||||
|
*/
|
||||||
|
setup(config: UnitConfig) {
|
||||||
|
// 创建ECS实体
|
||||||
|
this.entity = Core.entityManager.createEntity(`Unit_${this.node.name}`);
|
||||||
|
|
||||||
|
// 添加单位状态组件
|
||||||
|
this.unitState = new UnitStateComponent();
|
||||||
|
this.unitState.unitType = config.unitType;
|
||||||
|
this.unitState.maxHealth = config.maxHealth;
|
||||||
|
this.unitState.currentHealth = config.maxHealth;
|
||||||
|
this.unitState.moveSpeed = config.moveSpeed;
|
||||||
|
this.unitState.attackRange = config.attackRange;
|
||||||
|
this.unitState.attackDamage = config.attackDamage;
|
||||||
|
this.unitState.color = config.color;
|
||||||
|
this.entity.addComponent(this.unitState);
|
||||||
|
|
||||||
|
// 添加行为树组件
|
||||||
|
this.behaviorTreeComponent = new BehaviorTreeComponent();
|
||||||
|
this.behaviorTreeComponent.behaviorTreeFile = config.behaviorTreeFile;
|
||||||
|
this.behaviorTreeComponent.cocosNode = this.node;
|
||||||
|
this.entity.addComponent(this.behaviorTreeComponent);
|
||||||
|
|
||||||
|
// 设置材质颜色
|
||||||
|
this.setUnitColor(config.color);
|
||||||
|
|
||||||
|
console.log(`单位 ${this.node.name} 设置完成 - 类型: ${config.unitType}, 行为树: ${config.behaviorTreeFile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单位颜色
|
||||||
|
*/
|
||||||
|
private setUnitColor(colorName: string) {
|
||||||
|
if (!this.meshRenderer || !this.meshRenderer.material) return;
|
||||||
|
|
||||||
|
const colorMap: { [key: string]: Color } = {
|
||||||
|
'red': Color.RED,
|
||||||
|
'green': Color.GREEN,
|
||||||
|
'blue': Color.BLUE,
|
||||||
|
'yellow': Color.YELLOW,
|
||||||
|
'white': Color.WHITE,
|
||||||
|
'cyan': Color.CYAN,
|
||||||
|
'magenta': Color.MAGENTA
|
||||||
|
};
|
||||||
|
|
||||||
|
const color = colorMap[colorName] || Color.WHITE;
|
||||||
|
this.meshRenderer.material.setProperty('mainColor', color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置选择状态
|
||||||
|
*/
|
||||||
|
setSelected(selected: boolean) {
|
||||||
|
if (!this.unitState) return;
|
||||||
|
|
||||||
|
this.unitState.isSelected = selected;
|
||||||
|
|
||||||
|
// 视觉效果
|
||||||
|
if (selected) {
|
||||||
|
this.showSelectionEffect();
|
||||||
|
} else {
|
||||||
|
this.hideSelectionEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示选择效果
|
||||||
|
*/
|
||||||
|
private showSelectionEffect() {
|
||||||
|
// 添加选择圈效果
|
||||||
|
tween(this.node)
|
||||||
|
.to(0.3, { scale: new Vec3(1.1, 1.1, 1.1) })
|
||||||
|
.to(0.3, { scale: Vec3.ONE })
|
||||||
|
.union()
|
||||||
|
.repeatForever()
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏选择效果
|
||||||
|
*/
|
||||||
|
private hideSelectionEffect() {
|
||||||
|
// 停止所有缩放动画
|
||||||
|
tween(this.node).stop();
|
||||||
|
this.node.setScale(Vec3.ONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布命令
|
||||||
|
*/
|
||||||
|
issueCommand(command: string, target?: Vec3 | Node) {
|
||||||
|
if (!this.unitState || !this.behaviorTreeComponent) return;
|
||||||
|
|
||||||
|
this.unitState.currentCommand = command;
|
||||||
|
|
||||||
|
// 设置目标
|
||||||
|
if (target instanceof Vec3) {
|
||||||
|
this.unitState.targetPosition = target.clone();
|
||||||
|
this.unitState.targetNode = null;
|
||||||
|
} else if (target instanceof Node) {
|
||||||
|
this.unitState.targetPosition = target.worldPosition.clone();
|
||||||
|
this.unitState.targetNode = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过黑板更新行为树状态
|
||||||
|
const blackboard = this.behaviorTreeComponent.getBlackboard();
|
||||||
|
if (blackboard) {
|
||||||
|
blackboard.setValue('currentCommand', command);
|
||||||
|
blackboard.setValue('hasTarget', target !== undefined);
|
||||||
|
blackboard.setValue('targetPosition', this.unitState.targetPosition);
|
||||||
|
|
||||||
|
if (target instanceof Node) {
|
||||||
|
blackboard.setValue('targetType', target.name.includes('Resource') ? 'resource' :
|
||||||
|
target.name.includes('Building') ? 'building' : 'unit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`单位 ${this.node.name} 接收命令: ${command}`, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单位状态
|
||||||
|
*/
|
||||||
|
getUnitState(): UnitStateComponent | null {
|
||||||
|
return this.unitState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取行为树组件
|
||||||
|
*/
|
||||||
|
getBehaviorTreeComponent(): BehaviorTreeComponent | null {
|
||||||
|
return this.behaviorTreeComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 受到伤害
|
||||||
|
*/
|
||||||
|
takeDamage(damage: number) {
|
||||||
|
if (!this.unitState) return;
|
||||||
|
|
||||||
|
this.unitState.currentHealth = Math.max(0, this.unitState.currentHealth - damage);
|
||||||
|
|
||||||
|
// 更新黑板
|
||||||
|
const blackboard = this.behaviorTreeComponent?.getBlackboard();
|
||||||
|
if (blackboard) {
|
||||||
|
blackboard.setValue('currentHealth', this.unitState.currentHealth);
|
||||||
|
blackboard.setValue('healthPercentage', this.unitState.currentHealth / this.unitState.maxHealth);
|
||||||
|
blackboard.setValue('isLowHealth', this.unitState.currentHealth < this.unitState.maxHealth * 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视觉效果
|
||||||
|
this.showDamageEffect();
|
||||||
|
|
||||||
|
if (this.unitState.currentHealth <= 0) {
|
||||||
|
this.die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示受伤效果
|
||||||
|
*/
|
||||||
|
private showDamageEffect() {
|
||||||
|
if (!this.meshRenderer || !this.meshRenderer.material) return;
|
||||||
|
|
||||||
|
// 闪红效果
|
||||||
|
const originalColor = this.meshRenderer.material.getProperty('mainColor') as Color;
|
||||||
|
this.meshRenderer.material.setProperty('mainColor', Color.RED);
|
||||||
|
|
||||||
|
this.scheduleOnce(() => {
|
||||||
|
if (this.meshRenderer && this.meshRenderer.material) {
|
||||||
|
this.meshRenderer.material.setProperty('mainColor', originalColor);
|
||||||
|
}
|
||||||
|
}, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位死亡
|
||||||
|
*/
|
||||||
|
private die() {
|
||||||
|
console.log(`单位 ${this.node.name} 死亡`);
|
||||||
|
|
||||||
|
// 从ECS系统中移除实体
|
||||||
|
if (this.entity) {
|
||||||
|
Core.entityManager.destroyEntity(this.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放死亡动画后销毁节点
|
||||||
|
tween(this.node)
|
||||||
|
.to(0.5, { scale: Vec3.ZERO })
|
||||||
|
.call(() => {
|
||||||
|
this.node.destroy();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime: number) {
|
||||||
|
if (!this.unitState || !this.behaviorTreeComponent) return;
|
||||||
|
|
||||||
|
// 更新黑板中的时间相关变量
|
||||||
|
const blackboard = this.behaviorTreeComponent.getBlackboard();
|
||||||
|
if (blackboard) {
|
||||||
|
blackboard.setValue('deltaTime', deltaTime);
|
||||||
|
blackboard.setValue('currentTime', Date.now() / 1000);
|
||||||
|
blackboard.setValue('worldPosition', this.node.worldPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试信息显示
|
||||||
|
if (this.showDebugInfo) {
|
||||||
|
this.updateDebugInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新调试信息
|
||||||
|
*/
|
||||||
|
private updateDebugInfo() {
|
||||||
|
// 可以在这里添加调试信息的显示逻辑
|
||||||
|
// 比如在单位上方显示状态文本等
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy() {
|
||||||
|
// 清理ECS实体
|
||||||
|
if (this.entity) {
|
||||||
|
Core.entityManager.destroyEntity(this.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止所有动画
|
||||||
|
tween(this.node).stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "4.0.24",
|
"ver": "4.0.24",
|
||||||
"importer": "typescript",
|
"importer": "typescript",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "c82e7909-01e6-49ca-b68d-9ee98837e238",
|
"uuid": "61885e67-b7a2-4e51-857e-ccbea4fdea03",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
import { _decorator, Component, Node, Vec3, Material, MeshRenderer, Color, tween } from 'cc';
|
||||||
|
import { BehaviorTreeManager } from './BehaviorTreeManager';
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位配置接口
|
||||||
|
*/
|
||||||
|
export interface UnitConfig {
|
||||||
|
unitType: string;
|
||||||
|
behaviorTreeName: string;
|
||||||
|
maxHealth: number;
|
||||||
|
moveSpeed: number;
|
||||||
|
attackRange: number;
|
||||||
|
attackDamage: number;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位控制器 - 纯Cocos Creator组件,管理单位的行为和状态
|
||||||
|
*/
|
||||||
|
@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 = 3;
|
||||||
|
public attackRange: number = 2;
|
||||||
|
public attackDamage: number = 25;
|
||||||
|
public isSelected: boolean = false;
|
||||||
|
public currentCommand: string = 'idle';
|
||||||
|
public targetPosition: Vec3 = Vec3.ZERO.clone();
|
||||||
|
public targetNode: Node | null = null;
|
||||||
|
public lastAttackTime: number = 0;
|
||||||
|
public attackCooldown: number = 1.5;
|
||||||
|
public color: string = 'white';
|
||||||
|
|
||||||
|
private behaviorTreeManager: BehaviorTreeManager | null = null;
|
||||||
|
private meshRenderer: MeshRenderer | null = null;
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.meshRenderer = this.getComponent(MeshRenderer);
|
||||||
|
|
||||||
|
// 创建行为树管理器
|
||||||
|
this.behaviorTreeManager = this.addComponent(BehaviorTreeManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单位配置
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 初始化行为树
|
||||||
|
if (this.behaviorTreeManager) {
|
||||||
|
this.behaviorTreeManager.initializeBehaviorTree(config.behaviorTreeName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`单位 ${this.node.name} 设置完成 - 类型: ${config.unitType}, 行为树: ${config.behaviorTreeName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单位颜色
|
||||||
|
*/
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`单位 ${this.node.name} 接收命令: ${command}`, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 受到伤害
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动到目标位置
|
||||||
|
*/
|
||||||
|
moveToTarget(targetPos: Vec3, speed?: number): boolean {
|
||||||
|
const currentPos = this.node.worldPosition;
|
||||||
|
const distance = currentPos.subtract(targetPos).length();
|
||||||
|
|
||||||
|
if (distance < 0.5) {
|
||||||
|
return true; // 已到达目标
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的移动逻辑
|
||||||
|
const direction = targetPos.subtract(currentPos).normalize();
|
||||||
|
const moveSpeed = speed || this.moveSpeed;
|
||||||
|
const deltaTime = 1/60; // 假设60fps
|
||||||
|
|
||||||
|
const newPosition = currentPos.add(direction.multiplyScalar(moveSpeed * deltaTime));
|
||||||
|
this.node.setWorldPosition(newPosition);
|
||||||
|
|
||||||
|
return false; // 还在移动中
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 攻击目标
|
||||||
|
*/
|
||||||
|
attackTarget(): boolean {
|
||||||
|
const currentTime = Date.now() / 1000;
|
||||||
|
|
||||||
|
if (currentTime - this.lastAttackTime < this.attackCooldown) {
|
||||||
|
return false; // 冷却中
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行攻击
|
||||||
|
console.log(`${this.node.name} 执行攻击`);
|
||||||
|
this.lastAttackTime = currentTime;
|
||||||
|
|
||||||
|
// 更新行为树黑板
|
||||||
|
if (this.behaviorTreeManager) {
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('lastAttackTime', currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 攻击成功
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime: number) {
|
||||||
|
// 更新行为树黑板中的时间相关变量
|
||||||
|
if (this.behaviorTreeManager) {
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('deltaTime', deltaTime);
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('currentTime', Date.now() / 1000);
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('worldPosition', this.node.worldPosition);
|
||||||
|
|
||||||
|
// 更新距离信息
|
||||||
|
if (this.targetPosition) {
|
||||||
|
const distance = this.node.worldPosition.subtract(this.targetPosition).length();
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('distanceToTarget', distance);
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('isInAttackRange', distance <= this.attackRange);
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('isCloseToTarget', distance <= 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态标志
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('isIdle', this.currentCommand === 'idle');
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('isMoving', this.currentCommand === 'move');
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('isAttacking', this.currentCommand === 'attack');
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('isGathering', this.currentCommand === 'gather');
|
||||||
|
this.behaviorTreeManager.updateBlackboardValue('isPatrolling', this.currentCommand === 'patrol');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试信息显示
|
||||||
|
if (this.showDebugInfo) {
|
||||||
|
this.updateDebugInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新调试信息
|
||||||
|
*/
|
||||||
|
private updateDebugInfo() {
|
||||||
|
// 可以在这里添加调试信息的显示逻辑
|
||||||
|
// 比如在单位上方显示状态文本等
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy() {
|
||||||
|
// 停止所有动画
|
||||||
|
tween(this.node).stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "4ac64480-2d09-4de6-a22c-add022790676",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"ver": "1.2.0",
|
"ver": "1.2.0",
|
||||||
"importer": "directory",
|
"importer": "directory",
|
||||||
"imported": true,
|
"imported": true,
|
||||||
"uuid": "6f9217a1-dff6-4460-b5da-eb01cf29c03c",
|
"uuid": "84a4754f-3fad-4031-8aeb-e699584cfb92",
|
||||||
"files": [],
|
"files": [],
|
||||||
"subMetas": {},
|
"subMetas": {},
|
||||||
"userData": {}
|
"userData": {}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
import { _decorator, Component, Node, Camera, Vec3, input, Input, EventMouse, EventTouch, KeyCode, Quat } from 'cc';
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RTS相机控制器
|
||||||
|
* 提供RTS游戏常用的相机控制功能
|
||||||
|
*/
|
||||||
|
@ccclass('RTSCameraController')
|
||||||
|
export class RTSCameraController extends Component {
|
||||||
|
|
||||||
|
@property
|
||||||
|
moveSpeed: number = 10;
|
||||||
|
|
||||||
|
@property
|
||||||
|
rotateSpeed: number = 60;
|
||||||
|
|
||||||
|
@property
|
||||||
|
zoomSpeed: number = 5;
|
||||||
|
|
||||||
|
@property
|
||||||
|
minZoom: number = 5;
|
||||||
|
|
||||||
|
@property
|
||||||
|
maxZoom: number = 30;
|
||||||
|
|
||||||
|
@property
|
||||||
|
boundarySize: number = 50;
|
||||||
|
|
||||||
|
private camera: Camera = null!;
|
||||||
|
private isMouseDown: boolean = false;
|
||||||
|
private lastMousePosition: Vec3 = Vec3.ZERO.clone();
|
||||||
|
private currentZoom: number = 15;
|
||||||
|
|
||||||
|
// 键盘输入状态
|
||||||
|
private keyStates: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.camera = this.getComponent(Camera)!;
|
||||||
|
this.currentZoom = this.node.position.y;
|
||||||
|
|
||||||
|
// 注册输入事件
|
||||||
|
input.on(Input.EventType.MOUSE_DOWN, this.onMouseDown, this);
|
||||||
|
input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this);
|
||||||
|
input.on(Input.EventType.MOUSE_MOVE, this.onMouseMove, this);
|
||||||
|
input.on(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
|
||||||
|
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||||
|
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy() {
|
||||||
|
// 取消注册输入事件
|
||||||
|
input.off(Input.EventType.MOUSE_DOWN, this.onMouseDown, this);
|
||||||
|
input.off(Input.EventType.MOUSE_UP, this.onMouseUp, this);
|
||||||
|
input.off(Input.EventType.MOUSE_MOVE, this.onMouseMove, this);
|
||||||
|
input.off(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
|
||||||
|
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||||
|
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鼠标按下事件
|
||||||
|
*/
|
||||||
|
private onMouseDown(event: EventMouse) {
|
||||||
|
if (event.getButton() === EventMouse.BUTTON_MIDDLE) {
|
||||||
|
this.isMouseDown = true;
|
||||||
|
this.lastMousePosition.set(event.getLocationX(), event.getLocationY(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鼠标抬起事件
|
||||||
|
*/
|
||||||
|
private onMouseUp(event: EventMouse) {
|
||||||
|
if (event.getButton() === EventMouse.BUTTON_MIDDLE) {
|
||||||
|
this.isMouseDown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鼠标移动事件
|
||||||
|
*/
|
||||||
|
private onMouseMove(event: EventMouse) {
|
||||||
|
if (this.isMouseDown) {
|
||||||
|
const deltaX = event.getLocationX() - this.lastMousePosition.x;
|
||||||
|
const deltaY = event.getLocationY() - this.lastMousePosition.y;
|
||||||
|
|
||||||
|
// 相机平移
|
||||||
|
const moveVector = new Vec3(-deltaX * 0.01, 0, deltaY * 0.01);
|
||||||
|
this.node.translate(moveVector);
|
||||||
|
|
||||||
|
this.lastMousePosition.set(event.getLocationX(), event.getLocationY(), 0);
|
||||||
|
|
||||||
|
// 限制相机边界
|
||||||
|
this.clampCameraBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鼠标滚轮事件
|
||||||
|
*/
|
||||||
|
private onMouseWheel(event: EventMouse) {
|
||||||
|
const scrollY = event.getScrollY();
|
||||||
|
this.currentZoom -= scrollY * this.zoomSpeed * 0.1;
|
||||||
|
this.currentZoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.currentZoom));
|
||||||
|
|
||||||
|
const pos = this.node.position;
|
||||||
|
this.node.setPosition(pos.x, this.currentZoom, pos.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘按下事件
|
||||||
|
*/
|
||||||
|
private onKeyDown(event: any) {
|
||||||
|
this.keyStates[event.keyCode] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘抬起事件
|
||||||
|
*/
|
||||||
|
private onKeyUp(event: any) {
|
||||||
|
this.keyStates[event.keyCode] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新相机移动
|
||||||
|
*/
|
||||||
|
update(deltaTime: number) {
|
||||||
|
this.handleKeyboardMovement(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理键盘移动
|
||||||
|
*/
|
||||||
|
private handleKeyboardMovement(deltaTime: number) {
|
||||||
|
const moveDistance = this.moveSpeed * deltaTime;
|
||||||
|
let moveVector = Vec3.ZERO.clone();
|
||||||
|
|
||||||
|
// WASD 移动
|
||||||
|
if (this.keyStates[KeyCode.KEY_W] || this.keyStates[KeyCode.ARROW_UP]) {
|
||||||
|
moveVector.z -= moveDistance;
|
||||||
|
}
|
||||||
|
if (this.keyStates[KeyCode.KEY_S] || this.keyStates[KeyCode.ARROW_DOWN]) {
|
||||||
|
moveVector.z += moveDistance;
|
||||||
|
}
|
||||||
|
if (this.keyStates[KeyCode.KEY_A] || this.keyStates[KeyCode.ARROW_LEFT]) {
|
||||||
|
moveVector.x -= moveDistance;
|
||||||
|
}
|
||||||
|
if (this.keyStates[KeyCode.KEY_D] || this.keyStates[KeyCode.ARROW_RIGHT]) {
|
||||||
|
moveVector.x += moveDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用移动
|
||||||
|
if (!moveVector.equals(Vec3.ZERO)) {
|
||||||
|
this.node.translate(moveVector);
|
||||||
|
this.clampCameraBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// QE 旋转
|
||||||
|
if (this.keyStates[KeyCode.KEY_Q]) {
|
||||||
|
const rotateAngle = this.rotateSpeed * deltaTime * Math.PI / 180;
|
||||||
|
const currentRotation = this.node.rotation.clone();
|
||||||
|
const yRotation = Quat.fromAxisAngle(new Quat(), Vec3.UP, rotateAngle);
|
||||||
|
Quat.multiply(currentRotation, currentRotation, yRotation);
|
||||||
|
this.node.rotation = currentRotation;
|
||||||
|
}
|
||||||
|
if (this.keyStates[KeyCode.KEY_E]) {
|
||||||
|
const rotateAngle = -this.rotateSpeed * deltaTime * Math.PI / 180;
|
||||||
|
const currentRotation = this.node.rotation.clone();
|
||||||
|
const yRotation = Quat.fromAxisAngle(new Quat(), Vec3.UP, rotateAngle);
|
||||||
|
Quat.multiply(currentRotation, currentRotation, yRotation);
|
||||||
|
this.node.rotation = currentRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限制相机边界
|
||||||
|
*/
|
||||||
|
private clampCameraBounds() {
|
||||||
|
const pos = this.node.position;
|
||||||
|
const clampedX = Math.max(-this.boundarySize, Math.min(this.boundarySize, pos.x));
|
||||||
|
const clampedZ = Math.max(-this.boundarySize, Math.min(this.boundarySize, pos.z));
|
||||||
|
|
||||||
|
this.node.setPosition(clampedX, pos.y, clampedZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置相机位置
|
||||||
|
*/
|
||||||
|
setCameraPosition(position: Vec3) {
|
||||||
|
this.node.setPosition(position);
|
||||||
|
this.clampCameraBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚焦到目标位置
|
||||||
|
*/
|
||||||
|
focusOnTarget(target: Vec3, duration: number = 1.0) {
|
||||||
|
const targetPos = new Vec3(target.x, this.currentZoom, target.z);
|
||||||
|
// 这里可以添加缓动动画
|
||||||
|
this.setCameraPosition(targetPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "dabc6540-6e9f-450a-9d22-b9af94c20d6d",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import { _decorator, Component, Node, Vec3, Label, Button, EventHandler } from 'cc';
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI控制器 - 管理RTS演示的用户界面
|
||||||
|
*/
|
||||||
|
@ccclass('UIController')
|
||||||
|
export class UIController extends Component {
|
||||||
|
|
||||||
|
@property(Label)
|
||||||
|
selectedUnitsLabel: Label = null!;
|
||||||
|
|
||||||
|
@property(Button)
|
||||||
|
moveButton: Button = null!;
|
||||||
|
|
||||||
|
@property(Button)
|
||||||
|
attackButton: Button = null!;
|
||||||
|
|
||||||
|
@property(Button)
|
||||||
|
gatherButton: Button = null!;
|
||||||
|
|
||||||
|
@property(Button)
|
||||||
|
patrolButton: Button = null!;
|
||||||
|
|
||||||
|
@property(Label)
|
||||||
|
infoLabel: Label = null!;
|
||||||
|
|
||||||
|
// 回调函数
|
||||||
|
public onUnitSelected: ((units: Node[]) => void) | null = null;
|
||||||
|
public onCommandIssued: ((command: string, target?: Vec3 | Node) => void) | null = null;
|
||||||
|
|
||||||
|
private selectedUnitsCount: number = 0;
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.setupUI();
|
||||||
|
this.updateSelectedUnitsDisplay();
|
||||||
|
this.updateInfoDisplay('RTS 行为树演示 - 点击单位选择,然后发布命令');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置UI事件
|
||||||
|
*/
|
||||||
|
private setupUI() {
|
||||||
|
// 移动命令按钮
|
||||||
|
if (this.moveButton) {
|
||||||
|
const moveHandler = new EventHandler();
|
||||||
|
moveHandler.target = this.node;
|
||||||
|
moveHandler.component = 'UIController';
|
||||||
|
moveHandler.handler = 'onMoveCommand';
|
||||||
|
this.moveButton.clickEvents.push(moveHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 攻击命令按钮
|
||||||
|
if (this.attackButton) {
|
||||||
|
const attackHandler = new EventHandler();
|
||||||
|
attackHandler.target = this.node;
|
||||||
|
attackHandler.component = 'UIController';
|
||||||
|
attackHandler.handler = 'onAttackCommand';
|
||||||
|
this.attackButton.clickEvents.push(attackHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集命令按钮
|
||||||
|
if (this.gatherButton) {
|
||||||
|
const gatherHandler = new EventHandler();
|
||||||
|
gatherHandler.target = this.node;
|
||||||
|
gatherHandler.component = 'UIController';
|
||||||
|
gatherHandler.handler = 'onGatherCommand';
|
||||||
|
this.gatherButton.clickEvents.push(gatherHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 巡逻命令按钮
|
||||||
|
if (this.patrolButton) {
|
||||||
|
const patrolHandler = new EventHandler();
|
||||||
|
patrolHandler.target = this.node;
|
||||||
|
patrolHandler.component = 'UIController';
|
||||||
|
patrolHandler.handler = 'onPatrolCommand';
|
||||||
|
this.patrolButton.clickEvents.push(patrolHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置选中单位数量
|
||||||
|
*/
|
||||||
|
setSelectedUnitsCount(count: number) {
|
||||||
|
this.selectedUnitsCount = count;
|
||||||
|
this.updateSelectedUnitsDisplay();
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新选中单位显示
|
||||||
|
*/
|
||||||
|
private updateSelectedUnitsDisplay() {
|
||||||
|
if (this.selectedUnitsLabel) {
|
||||||
|
this.selectedUnitsLabel.string = `选中单位: ${this.selectedUnitsCount}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新按钮状态
|
||||||
|
*/
|
||||||
|
private updateButtonStates() {
|
||||||
|
const hasSelection = this.selectedUnitsCount > 0;
|
||||||
|
|
||||||
|
if (this.moveButton) {
|
||||||
|
this.moveButton.interactable = hasSelection;
|
||||||
|
}
|
||||||
|
if (this.attackButton) {
|
||||||
|
this.attackButton.interactable = hasSelection;
|
||||||
|
}
|
||||||
|
if (this.gatherButton) {
|
||||||
|
this.gatherButton.interactable = hasSelection;
|
||||||
|
}
|
||||||
|
if (this.patrolButton) {
|
||||||
|
this.patrolButton.interactable = hasSelection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新信息显示
|
||||||
|
*/
|
||||||
|
updateInfoDisplay(message: string) {
|
||||||
|
if (this.infoLabel) {
|
||||||
|
this.infoLabel.string = message;
|
||||||
|
}
|
||||||
|
console.log(`[UI] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动命令
|
||||||
|
*/
|
||||||
|
onMoveCommand() {
|
||||||
|
if (this.selectedUnitsCount === 0) {
|
||||||
|
this.updateInfoDisplay('请先选择单位');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机目标位置
|
||||||
|
const targetPos = new Vec3(
|
||||||
|
(Math.random() - 0.5) * 20,
|
||||||
|
0,
|
||||||
|
(Math.random() - 0.5) * 20
|
||||||
|
);
|
||||||
|
|
||||||
|
this.onCommandIssued?.('move', targetPos);
|
||||||
|
this.updateInfoDisplay(`发布移动命令到位置 (${targetPos.x.toFixed(1)}, ${targetPos.z.toFixed(1)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 攻击命令
|
||||||
|
*/
|
||||||
|
onAttackCommand() {
|
||||||
|
if (this.selectedUnitsCount === 0) {
|
||||||
|
this.updateInfoDisplay('请先选择单位');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机攻击位置
|
||||||
|
const targetPos = new Vec3(
|
||||||
|
(Math.random() - 0.5) * 15,
|
||||||
|
0,
|
||||||
|
(Math.random() - 0.5) * 15
|
||||||
|
);
|
||||||
|
|
||||||
|
this.onCommandIssued?.('attack', targetPos);
|
||||||
|
this.updateInfoDisplay(`发布攻击命令到位置 (${targetPos.x.toFixed(1)}, ${targetPos.z.toFixed(1)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集命令
|
||||||
|
*/
|
||||||
|
onGatherCommand() {
|
||||||
|
if (this.selectedUnitsCount === 0) {
|
||||||
|
this.updateInfoDisplay('请先选择单位');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onCommandIssued?.('gather');
|
||||||
|
this.updateInfoDisplay('发布收集资源命令');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 巡逻命令
|
||||||
|
*/
|
||||||
|
onPatrolCommand() {
|
||||||
|
if (this.selectedUnitsCount === 0) {
|
||||||
|
this.updateInfoDisplay('请先选择单位');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onCommandIssued?.('patrol');
|
||||||
|
this.updateInfoDisplay('发布巡逻命令');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示单位信息
|
||||||
|
*/
|
||||||
|
showUnitInfo(unitName: string, unitType: string, health: number, maxHealth: number) {
|
||||||
|
const healthPercent = Math.round((health / maxHealth) * 100);
|
||||||
|
this.updateInfoDisplay(`${unitName} (${unitType}) - 生命值: ${health}/${maxHealth} (${healthPercent}%)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示行为树状态
|
||||||
|
*/
|
||||||
|
showBehaviorTreeStatus(unitName: string, currentBehavior: string) {
|
||||||
|
this.updateInfoDisplay(`${unitName} 当前行为: ${currentBehavior}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示错误信息
|
||||||
|
*/
|
||||||
|
showError(message: string) {
|
||||||
|
this.updateInfoDisplay(`错误: ${message}`);
|
||||||
|
console.error(`[UI Error] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示成功信息
|
||||||
|
*/
|
||||||
|
showSuccess(message: string) {
|
||||||
|
this.updateInfoDisplay(`成功: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "00e982f3-dcf3-44b0-9b63-5e2877c1971e",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
import { _decorator, Component, Node, Label, Button, resources, JsonAsset } from 'cc';
|
|
||||||
import {
|
|
||||||
BehaviorTreeBuilder,
|
|
||||||
BehaviorTree,
|
|
||||||
Blackboard
|
|
||||||
} from '@esengine/ai';
|
|
||||||
import type { BehaviorTreeJSONConfig } from '@esengine/ai';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行为树测试示例
|
|
||||||
* 使用 @esengine/ai 包的 BehaviorTreeBuilder API 自动加载和初始化行为树
|
|
||||||
*
|
|
||||||
* 特性:
|
|
||||||
* - 自动从JSON配置文件加载行为树
|
|
||||||
* - 自动初始化黑板变量
|
|
||||||
* - 支持行为树的启动、停止、暂停、恢复
|
|
||||||
* - 实时显示黑板变量状态
|
|
||||||
*/
|
|
||||||
@ccclass('BehaviorTreeExample')
|
|
||||||
export class BehaviorTreeExample extends Component {
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
statusLabel: Label = null;
|
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
logLabel: Label = null;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
startButton: Button = null;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
stopButton: Button = null;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
pauseButton: Button = null;
|
|
||||||
|
|
||||||
@property(Button)
|
|
||||||
resumeButton: Button = null;
|
|
||||||
|
|
||||||
private behaviorTree: BehaviorTree<any> = null;
|
|
||||||
private blackboard: Blackboard = null;
|
|
||||||
private isRunning: boolean = false;
|
|
||||||
private isPaused: boolean = false;
|
|
||||||
private logs: string[] = [];
|
|
||||||
private executionContext: any = null;
|
|
||||||
private updateTimer: number = 0;
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
this.setupUI();
|
|
||||||
this.loadBehaviorTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupUI() {
|
|
||||||
if (this.startButton) {
|
|
||||||
this.startButton.node.on('click', this.startBehaviorTree, this);
|
|
||||||
}
|
|
||||||
if (this.stopButton) {
|
|
||||||
this.stopButton.node.on('click', this.stopBehaviorTree, this);
|
|
||||||
}
|
|
||||||
if (this.pauseButton) {
|
|
||||||
this.pauseButton.node.on('click', this.pauseBehaviorTree, this);
|
|
||||||
}
|
|
||||||
if (this.resumeButton) {
|
|
||||||
this.resumeButton.node.on('click', this.resumeBehaviorTree, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateStatus('初始化中...');
|
|
||||||
this.updateLog('行为树测试组件已加载');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadBehaviorTree() {
|
|
||||||
try {
|
|
||||||
this.updateStatus('加载行为树配置...');
|
|
||||||
|
|
||||||
// 从resources目录加载simple-example.bt.json文件
|
|
||||||
resources.load('simple-example.bt', JsonAsset, (err, jsonAsset: JsonAsset) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('加载行为树配置失败:', err);
|
|
||||||
this.updateStatus('加载失败: ' + err.message);
|
|
||||||
this.updateLog('❌ 加载simple-example.bt.json失败: ' + err.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const config = jsonAsset.json as BehaviorTreeJSONConfig;
|
|
||||||
this.setupBehaviorTree(config);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解析行为树配置失败:', error);
|
|
||||||
this.updateStatus('解析失败: ' + error.message);
|
|
||||||
this.updateLog('❌ 解析行为树配置失败: ' + error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('行为树加载过程出错:', error);
|
|
||||||
this.updateStatus('加载出错: ' + error.message);
|
|
||||||
this.updateLog('❌ 行为树加载过程出错: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupBehaviorTree(config: BehaviorTreeJSONConfig) {
|
|
||||||
try {
|
|
||||||
// 创建执行上下文
|
|
||||||
this.executionContext = {
|
|
||||||
node: this.node,
|
|
||||||
component: this,
|
|
||||||
// 添加日志方法供行为树节点使用
|
|
||||||
log: (message: string, level: string = 'info') => {
|
|
||||||
this.updateLog(`🤖 [${level.toUpperCase()}] ${message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 🎯 使用 @esengine/ai 的 BehaviorTreeBuilder API - 一行代码完成所有初始化!
|
|
||||||
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(config, this.executionContext);
|
|
||||||
|
|
||||||
this.behaviorTree = result.tree;
|
|
||||||
this.blackboard = result.blackboard;
|
|
||||||
this.executionContext = result.context;
|
|
||||||
|
|
||||||
this.updateStatus('行为树加载完成,准备执行');
|
|
||||||
this.updateLog('✅ 行为树创建成功(使用 @esengine/ai 包)');
|
|
||||||
this.updateLog(`📊 节点总数: ${config.nodes ? config.nodes.length : 0}`);
|
|
||||||
this.updateLog(`📋 变量总数: ${config.blackboard ? config.blackboard.length : 0}`);
|
|
||||||
|
|
||||||
// 显示黑板变量初始状态
|
|
||||||
this.logBlackboardStatus();
|
|
||||||
|
|
||||||
// 启用开始按钮
|
|
||||||
if (this.startButton) {
|
|
||||||
this.startButton.interactable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('设置行为树失败:', error);
|
|
||||||
this.updateStatus('设置失败: ' + error.message);
|
|
||||||
this.updateLog('❌ 行为树设置失败: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private startBehaviorTree() {
|
|
||||||
if (!this.behaviorTree) {
|
|
||||||
this.updateLog('❌ 行为树未准备好');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isRunning = true;
|
|
||||||
this.isPaused = false;
|
|
||||||
// 重置行为树状态
|
|
||||||
this.behaviorTree.reset();
|
|
||||||
this.updateStatus('执行中...');
|
|
||||||
this.updateLog('🚀 开始执行行为树(自动初始化黑板)');
|
|
||||||
|
|
||||||
// 更新按钮状态
|
|
||||||
if (this.startButton) this.startButton.interactable = false;
|
|
||||||
if (this.stopButton) this.stopButton.interactable = true;
|
|
||||||
if (this.pauseButton) this.pauseButton.interactable = true;
|
|
||||||
if (this.resumeButton) this.resumeButton.interactable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private stopBehaviorTree() {
|
|
||||||
this.isRunning = false;
|
|
||||||
this.isPaused = false;
|
|
||||||
this.updateStatus('已停止');
|
|
||||||
this.updateLog('⏹️ 行为树执行已停止');
|
|
||||||
|
|
||||||
// 重置行为树状态
|
|
||||||
if (this.behaviorTree) {
|
|
||||||
this.behaviorTree.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新按钮状态
|
|
||||||
if (this.startButton) this.startButton.interactable = true;
|
|
||||||
if (this.stopButton) this.stopButton.interactable = false;
|
|
||||||
if (this.pauseButton) this.pauseButton.interactable = false;
|
|
||||||
if (this.resumeButton) this.resumeButton.interactable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private pauseBehaviorTree() {
|
|
||||||
this.isPaused = true;
|
|
||||||
this.updateStatus('已暂停');
|
|
||||||
this.updateLog('⏸️ 行为树执行已暂停');
|
|
||||||
|
|
||||||
// 更新按钮状态
|
|
||||||
if (this.pauseButton) this.pauseButton.interactable = false;
|
|
||||||
if (this.resumeButton) this.resumeButton.interactable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resumeBehaviorTree() {
|
|
||||||
this.isPaused = false;
|
|
||||||
this.updateStatus('执行中...');
|
|
||||||
this.updateLog('▶️ 行为树执行已恢复');
|
|
||||||
|
|
||||||
// 更新按钮状态
|
|
||||||
if (this.pauseButton) this.pauseButton.interactable = true;
|
|
||||||
if (this.resumeButton) this.resumeButton.interactable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaTime: number) {
|
|
||||||
if (!this.isRunning || this.isPaused || !this.behaviorTree) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateTimer += deltaTime;
|
|
||||||
|
|
||||||
// 每帧执行行为树
|
|
||||||
try {
|
|
||||||
this.behaviorTree.tick(deltaTime);
|
|
||||||
|
|
||||||
// 每2秒输出一次状态信息
|
|
||||||
if (this.updateTimer >= 2.0) {
|
|
||||||
this.updateTimer = 0;
|
|
||||||
this.logBlackboardStatus();
|
|
||||||
|
|
||||||
// 检查行为树是否处于活动状态
|
|
||||||
const isActive = this.behaviorTree.isActive();
|
|
||||||
if (!isActive) {
|
|
||||||
this.updateLog('✅ 行为树执行完成');
|
|
||||||
// 注意:这里只是演示,实际上行为树会持续运行
|
|
||||||
// 如果需要检查完成状态,需要通过黑板变量或其他逻辑判断
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('行为树执行出错:', error);
|
|
||||||
this.updateLog('❌ 执行出错: ' + error.message);
|
|
||||||
this.stopBehaviorTree();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private logBlackboardStatus() {
|
|
||||||
if (!this.blackboard) return;
|
|
||||||
|
|
||||||
// 获取所有黑板变量的当前值
|
|
||||||
const variables = ['state', 'lastAction', 'defendActive', 'health', 'energy'];
|
|
||||||
const status = variables.map(name => {
|
|
||||||
const value = this.blackboard.getValue(name, 'undefined');
|
|
||||||
return `${name}:${value}`;
|
|
||||||
}).join(', ');
|
|
||||||
|
|
||||||
this.updateLog(`📊 状态: ${status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateStatus(status: string) {
|
|
||||||
if (this.statusLabel) {
|
|
||||||
this.statusLabel.string = status;
|
|
||||||
}
|
|
||||||
console.log('[BehaviorTree] 状态:', status);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateLog(message: string) {
|
|
||||||
this.logs.push(`[${new Date().toLocaleTimeString()}] ${message}`);
|
|
||||||
|
|
||||||
// 只保留最新的25条日志
|
|
||||||
if (this.logs.length > 25) {
|
|
||||||
this.logs.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.logLabel) {
|
|
||||||
this.logLabel.string = this.logs.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[BehaviorTree]', message);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
this.stopBehaviorTree();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
import { _decorator, Component, Node, Label, Button, Canvas, UITransform, Widget, Layout, Sprite, Color } from 'cc';
|
|
||||||
import { BehaviorTreeExample } from './BehaviorTreeExample';
|
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行为树测试场景
|
|
||||||
* 自动创建UI界面用于测试行为树
|
|
||||||
*/
|
|
||||||
@ccclass('BehaviorTreeTestScene')
|
|
||||||
export class BehaviorTreeTestScene extends Component {
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
this.createTestUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private createTestUI() {
|
|
||||||
// 创建Canvas
|
|
||||||
const canvasNode = new Node('BehaviorTreeTestCanvas');
|
|
||||||
canvasNode.addComponent(Canvas);
|
|
||||||
canvasNode.addComponent(UITransform);
|
|
||||||
canvasNode.parent = this.node;
|
|
||||||
|
|
||||||
// 创建背景
|
|
||||||
const backgroundNode = new Node('Background');
|
|
||||||
const backgroundTransform = backgroundNode.addComponent(UITransform);
|
|
||||||
backgroundTransform.setContentSize(1920, 1080);
|
|
||||||
const backgroundSprite = backgroundNode.addComponent(Sprite);
|
|
||||||
backgroundSprite.color = new Color(40, 40, 40, 255);
|
|
||||||
backgroundNode.parent = canvasNode;
|
|
||||||
|
|
||||||
// 设置Widget组件使背景充满整个画布
|
|
||||||
const backgroundWidget = backgroundNode.addComponent(Widget);
|
|
||||||
backgroundWidget.isAlignTop = true;
|
|
||||||
backgroundWidget.isAlignBottom = true;
|
|
||||||
backgroundWidget.isAlignLeft = true;
|
|
||||||
backgroundWidget.isAlignRight = true;
|
|
||||||
|
|
||||||
// 创建主容器
|
|
||||||
const mainContainer = new Node('MainContainer');
|
|
||||||
const mainTransform = mainContainer.addComponent(UITransform);
|
|
||||||
mainTransform.setContentSize(1600, 900);
|
|
||||||
mainContainer.parent = canvasNode;
|
|
||||||
|
|
||||||
// 设置主容器居中
|
|
||||||
const mainWidget = mainContainer.addComponent(Widget);
|
|
||||||
mainWidget.isAlignHorizontalCenter = true;
|
|
||||||
mainWidget.isAlignVerticalCenter = true;
|
|
||||||
|
|
||||||
// 设置主容器的Layout
|
|
||||||
const mainLayout = mainContainer.addComponent(Layout);
|
|
||||||
mainLayout.type = Layout.Type.VERTICAL;
|
|
||||||
mainLayout.spacingY = 20;
|
|
||||||
mainLayout.paddingTop = 50;
|
|
||||||
mainLayout.paddingBottom = 50;
|
|
||||||
mainLayout.paddingLeft = 50;
|
|
||||||
mainLayout.paddingRight = 50;
|
|
||||||
|
|
||||||
// 创建标题
|
|
||||||
const titleNode = this.createText('简化API行为树测试器', 48, Color.WHITE);
|
|
||||||
titleNode.parent = mainContainer;
|
|
||||||
|
|
||||||
// 创建状态显示区域
|
|
||||||
const statusContainer = new Node('StatusContainer');
|
|
||||||
const statusTransform = statusContainer.addComponent(UITransform);
|
|
||||||
statusTransform.setContentSize(1500, 80);
|
|
||||||
statusContainer.parent = mainContainer;
|
|
||||||
|
|
||||||
const statusLayout = statusContainer.addComponent(Layout);
|
|
||||||
statusLayout.type = Layout.Type.HORIZONTAL;
|
|
||||||
statusLayout.spacingX = 20;
|
|
||||||
|
|
||||||
// 状态标签
|
|
||||||
const statusLabelNode = this.createText('状态: 初始化中...', 24, Color.YELLOW);
|
|
||||||
statusLabelNode.parent = statusContainer;
|
|
||||||
|
|
||||||
// 创建控制按钮区域
|
|
||||||
const buttonContainer = new Node('ButtonContainer');
|
|
||||||
const buttonTransform = buttonContainer.addComponent(UITransform);
|
|
||||||
buttonTransform.setContentSize(1500, 80);
|
|
||||||
buttonContainer.parent = mainContainer;
|
|
||||||
|
|
||||||
const buttonLayout = buttonContainer.addComponent(Layout);
|
|
||||||
buttonLayout.type = Layout.Type.HORIZONTAL;
|
|
||||||
buttonLayout.spacingX = 20;
|
|
||||||
|
|
||||||
// 创建控制按钮
|
|
||||||
const startButton = this.createButton('开始执行', Color.GREEN);
|
|
||||||
const stopButton = this.createButton('停止', Color.RED);
|
|
||||||
const pauseButton = this.createButton('暂停', Color.YELLOW);
|
|
||||||
const resumeButton = this.createButton('恢复', Color.BLUE);
|
|
||||||
|
|
||||||
startButton.parent = buttonContainer;
|
|
||||||
stopButton.parent = buttonContainer;
|
|
||||||
pauseButton.parent = buttonContainer;
|
|
||||||
resumeButton.parent = buttonContainer;
|
|
||||||
|
|
||||||
// 创建说明区域
|
|
||||||
const infoContainer = new Node('InfoContainer');
|
|
||||||
const infoTransform = infoContainer.addComponent(UITransform);
|
|
||||||
infoTransform.setContentSize(1500, 60);
|
|
||||||
infoContainer.parent = mainContainer;
|
|
||||||
|
|
||||||
const infoNode = this.createText('使用新的简化API - 一行代码完成所有初始化!', 18, Color.CYAN);
|
|
||||||
infoNode.parent = infoContainer;
|
|
||||||
|
|
||||||
// 创建日志显示区域
|
|
||||||
const logContainer = new Node('LogContainer');
|
|
||||||
const logTransform = logContainer.addComponent(UITransform);
|
|
||||||
logTransform.setContentSize(1500, 600);
|
|
||||||
logContainer.parent = mainContainer;
|
|
||||||
|
|
||||||
// 日志背景
|
|
||||||
const logBackground = new Node('LogBackground');
|
|
||||||
const logBgTransform = logBackground.addComponent(UITransform);
|
|
||||||
logBgTransform.setContentSize(1500, 600);
|
|
||||||
const logBgSprite = logBackground.addComponent(Sprite);
|
|
||||||
logBgSprite.color = new Color(20, 20, 20, 255);
|
|
||||||
logBackground.parent = logContainer;
|
|
||||||
|
|
||||||
// 日志文本
|
|
||||||
const logLabelNode = this.createText('等待行为树配置加载...', 16, Color.CYAN);
|
|
||||||
const logLabel = logLabelNode.getComponent(Label);
|
|
||||||
logLabel.overflow = Label.Overflow.RESIZE_HEIGHT;
|
|
||||||
logLabel.verticalAlign = Label.VerticalAlign.TOP;
|
|
||||||
logLabel.horizontalAlign = Label.HorizontalAlign.LEFT;
|
|
||||||
const logTransformComp = logLabelNode.getComponent(UITransform);
|
|
||||||
logTransformComp.setContentSize(1450, 550);
|
|
||||||
logLabelNode.parent = logContainer;
|
|
||||||
|
|
||||||
// 设置日志文本位置
|
|
||||||
const logWidget = logLabelNode.addComponent(Widget);
|
|
||||||
logWidget.isAlignTop = true;
|
|
||||||
logWidget.isAlignLeft = true;
|
|
||||||
logWidget.top = 25;
|
|
||||||
logWidget.left = 25;
|
|
||||||
|
|
||||||
// 添加BehaviorTreeExample组件
|
|
||||||
const behaviorTreeExample = this.node.addComponent(BehaviorTreeExample);
|
|
||||||
behaviorTreeExample.statusLabel = statusLabelNode.getComponent(Label);
|
|
||||||
behaviorTreeExample.logLabel = logLabel;
|
|
||||||
behaviorTreeExample.startButton = startButton.getComponent(Button);
|
|
||||||
behaviorTreeExample.stopButton = stopButton.getComponent(Button);
|
|
||||||
behaviorTreeExample.pauseButton = pauseButton.getComponent(Button);
|
|
||||||
behaviorTreeExample.resumeButton = resumeButton.getComponent(Button);
|
|
||||||
|
|
||||||
// 初始化按钮状态
|
|
||||||
stopButton.getComponent(Button).interactable = false;
|
|
||||||
pauseButton.getComponent(Button).interactable = false;
|
|
||||||
resumeButton.getComponent(Button).interactable = false;
|
|
||||||
startButton.getComponent(Button).interactable = false; // 等待行为树配置加载完成
|
|
||||||
|
|
||||||
console.log('行为树测试UI创建完成');
|
|
||||||
}
|
|
||||||
|
|
||||||
private createText(text: string, fontSize: number, color: Color): Node {
|
|
||||||
const textNode = new Node('Text');
|
|
||||||
const textTransform = textNode.addComponent(UITransform);
|
|
||||||
|
|
||||||
const label = textNode.addComponent(Label);
|
|
||||||
label.string = text;
|
|
||||||
label.fontSize = fontSize;
|
|
||||||
label.color = color;
|
|
||||||
label.lineHeight = fontSize + 4;
|
|
||||||
|
|
||||||
// 根据文本内容调整大小
|
|
||||||
const estimatedWidth = Math.max(200, text.length * fontSize * 0.6);
|
|
||||||
textTransform.setContentSize(estimatedWidth, fontSize + 10);
|
|
||||||
|
|
||||||
return textNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createButton(text: string, color: Color): Node {
|
|
||||||
const buttonNode = new Node('Button');
|
|
||||||
const buttonTransform = buttonNode.addComponent(UITransform);
|
|
||||||
buttonTransform.setContentSize(180, 60);
|
|
||||||
|
|
||||||
// 按钮背景
|
|
||||||
const buttonSprite = buttonNode.addComponent(Sprite);
|
|
||||||
buttonSprite.color = color;
|
|
||||||
|
|
||||||
// 按钮组件
|
|
||||||
const button = buttonNode.addComponent(Button);
|
|
||||||
button.target = buttonNode;
|
|
||||||
|
|
||||||
// 按钮文本
|
|
||||||
const labelNode = new Node('Label');
|
|
||||||
const labelTransform = labelNode.addComponent(UITransform);
|
|
||||||
labelTransform.setContentSize(170, 50);
|
|
||||||
|
|
||||||
const label = labelNode.addComponent(Label);
|
|
||||||
label.string = text;
|
|
||||||
label.fontSize = 20;
|
|
||||||
label.color = Color.WHITE;
|
|
||||||
label.horizontalAlign = Label.HorizontalAlign.CENTER;
|
|
||||||
label.verticalAlign = Label.VerticalAlign.CENTER;
|
|
||||||
|
|
||||||
labelNode.parent = buttonNode;
|
|
||||||
|
|
||||||
return buttonNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import { Core } from '@esengine/ecs-framework';
|
|
||||||
import { Component, _decorator } from 'cc';
|
|
||||||
import { ExampleGameScene } from './scenes/ExampleGameScene';
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
console.log('🎮 正在初始化ECS框架...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 创建Core实例,启用调试功能
|
|
||||||
if (this.debugMode) {
|
|
||||||
Core.create({
|
|
||||||
debugConfig: {
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: 'ws://localhost:8080/ecs-debug',
|
|
||||||
autoReconnect: true,
|
|
||||||
updateInterval: 1000,
|
|
||||||
channels: {
|
|
||||||
entities: true,
|
|
||||||
systems: true,
|
|
||||||
performance: true,
|
|
||||||
components: true,
|
|
||||||
scenes: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('🔧 ECS调试模式已启用,可在Cocos Creator扩展面板中查看调试信息');
|
|
||||||
} else {
|
|
||||||
Core.create(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 创建游戏场景
|
|
||||||
const gameScene = new ExampleGameScene();
|
|
||||||
|
|
||||||
// 3. 设置为当前场景(会自动调用scene.begin())
|
|
||||||
Core.scene = gameScene;
|
|
||||||
|
|
||||||
this.isInitialized = true;
|
|
||||||
console.log('✅ ECS框架初始化成功!');
|
|
||||||
console.log('📖 请查看 assets/scripts/ecs/README.md 了解如何添加组件和系统');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ ECS框架初始化失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧更新ECS框架
|
|
||||||
*/
|
|
||||||
update(deltaTime: number) {
|
|
||||||
if (this.isInitialized) {
|
|
||||||
// 更新ECS核心系统
|
|
||||||
Core.update(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件销毁时清理ECS
|
|
||||||
*/
|
|
||||||
onDestroy() {
|
|
||||||
if (this.isInitialized) {
|
|
||||||
console.log('🧹 清理ECS框架...');
|
|
||||||
// ECS框架会自动处理场景清理
|
|
||||||
this.isInitialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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": "0932496e-f7fe-4cb9-86e2-ebd7d2a3d047",
|
|
||||||
"files": [
|
|
||||||
".json"
|
|
||||||
],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命值组件 - 管理实体的生命值和相关状态
|
|
||||||
*
|
|
||||||
* 展示游戏逻辑组件的设计:
|
|
||||||
* 1. 包含生命值的核心数据
|
|
||||||
* 2. 提供简单的查询方法
|
|
||||||
* 3. 复杂的伤害处理逻辑留给系统处理
|
|
||||||
*/
|
|
||||||
export class HealthComponent extends Component {
|
|
||||||
/** 最大生命值 */
|
|
||||||
public maxHealth: number;
|
|
||||||
/** 当前生命值 */
|
|
||||||
public currentHealth: number;
|
|
||||||
/** 生命值回复速度(每秒回复量) */
|
|
||||||
public regenRate: number = 0;
|
|
||||||
/** 最后受到伤害的时间(用于延迟回血等机制) */
|
|
||||||
public lastDamageTime: number = 0;
|
|
||||||
/** 是否无敌 */
|
|
||||||
public invincible: boolean = false;
|
|
||||||
/** 无敌持续时间 */
|
|
||||||
public invincibleDuration: number = 0;
|
|
||||||
|
|
||||||
constructor(maxHealth: number = 100, regenRate: number = 0) {
|
|
||||||
super();
|
|
||||||
this.maxHealth = maxHealth;
|
|
||||||
this.currentHealth = maxHealth;
|
|
||||||
this.regenRate = regenRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否死亡
|
|
||||||
*/
|
|
||||||
isDead(): boolean {
|
|
||||||
return this.currentHealth <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否满血
|
|
||||||
*/
|
|
||||||
isFullHealth(): boolean {
|
|
||||||
return this.currentHealth >= this.maxHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取生命值百分比(0-1)
|
|
||||||
*/
|
|
||||||
getHealthPercentage(): number {
|
|
||||||
return this.currentHealth / this.maxHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查生命值是否低于指定百分比
|
|
||||||
*/
|
|
||||||
isHealthBelowPercentage(percentage: number): boolean {
|
|
||||||
return this.getHealthPercentage() < percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置生命值(不超过最大值)
|
|
||||||
*/
|
|
||||||
setHealth(health: number) {
|
|
||||||
this.currentHealth = Math.max(0, Math.min(health, this.maxHealth));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加生命值(治疗)
|
|
||||||
*/
|
|
||||||
heal(amount: number) {
|
|
||||||
this.currentHealth = Math.min(this.currentHealth + amount, this.maxHealth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 减少生命值(受伤)
|
|
||||||
* 注意:这里只修改数据,具体的伤害逻辑(如死亡处理)应该在系统中实现
|
|
||||||
*/
|
|
||||||
takeDamage(damage: number) {
|
|
||||||
if (this.invincible) return;
|
|
||||||
|
|
||||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
|
||||||
this.lastDamageTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置无敌状态
|
|
||||||
*/
|
|
||||||
setInvincible(duration: number) {
|
|
||||||
this.invincible = true;
|
|
||||||
this.invincibleDuration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置到满血状态
|
|
||||||
*/
|
|
||||||
reset() {
|
|
||||||
this.currentHealth = this.maxHealth;
|
|
||||||
this.invincible = false;
|
|
||||||
this.invincibleDuration = 0;
|
|
||||||
this.lastDamageTime = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Vec2 } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 玩家输入组件 - 存储玩家的输入状态
|
|
||||||
*
|
|
||||||
* 标记组件示例:
|
|
||||||
* 1. 标识这是一个玩家控制的实体
|
|
||||||
* 2. 存储输入状态数据
|
|
||||||
* 3. 输入处理逻辑在InputSystem中实现
|
|
||||||
*/
|
|
||||||
export class PlayerInputComponent extends Component {
|
|
||||||
/** 移动输入方向(-1到1) */
|
|
||||||
public moveDirection: Vec2 = new Vec2();
|
|
||||||
/** 按键状态 */
|
|
||||||
public keys: { [key: string]: boolean } = {};
|
|
||||||
/** 鼠标位置 */
|
|
||||||
public mousePosition: Vec2 = new Vec2();
|
|
||||||
/** 鼠标按键状态 */
|
|
||||||
public mouseButtons: { left: boolean; right: boolean; middle: boolean } = {
|
|
||||||
left: false,
|
|
||||||
right: false,
|
|
||||||
middle: false
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 是否启用输入 */
|
|
||||||
public inputEnabled: boolean = true;
|
|
||||||
/** 输入敏感度 */
|
|
||||||
public sensitivity: number = 1.0;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置移动方向
|
|
||||||
*/
|
|
||||||
setMoveDirection(x: number, y: number) {
|
|
||||||
this.moveDirection.set(x, y);
|
|
||||||
// 标准化方向向量(对角线移动不应该更快)
|
|
||||||
if (this.moveDirection.lengthSqr() > 1) {
|
|
||||||
this.moveDirection.normalize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置按键状态
|
|
||||||
*/
|
|
||||||
setKey(key: string, pressed: boolean) {
|
|
||||||
this.keys[key] = pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查按键是否按下
|
|
||||||
*/
|
|
||||||
isKeyPressed(key: string): boolean {
|
|
||||||
return this.keys[key] || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否有移动输入
|
|
||||||
*/
|
|
||||||
hasMovementInput(): boolean {
|
|
||||||
return this.moveDirection.lengthSqr() > 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取标准化的移动方向
|
|
||||||
*/
|
|
||||||
getNormalizedMoveDirection(): Vec2 {
|
|
||||||
const result = new Vec2(this.moveDirection);
|
|
||||||
if (result.lengthSqr() > 0) {
|
|
||||||
result.normalize();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置鼠标位置
|
|
||||||
*/
|
|
||||||
setMousePosition(x: number, y: number) {
|
|
||||||
this.mousePosition.set(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置鼠标按键状态
|
|
||||||
*/
|
|
||||||
setMouseButton(button: 'left' | 'right' | 'middle', pressed: boolean) {
|
|
||||||
this.mouseButtons[button] = pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查鼠标按键是否按下
|
|
||||||
*/
|
|
||||||
isMouseButtonPressed(button: 'left' | 'right' | 'middle'): boolean {
|
|
||||||
return this.mouseButtons[button];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除所有输入状态
|
|
||||||
*/
|
|
||||||
clearInput() {
|
|
||||||
this.moveDirection.set(0, 0);
|
|
||||||
this.keys = {};
|
|
||||||
this.mouseButtons.left = false;
|
|
||||||
this.mouseButtons.right = false;
|
|
||||||
this.mouseButtons.middle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 禁用输入
|
|
||||||
*/
|
|
||||||
disableInput() {
|
|
||||||
this.inputEnabled = false;
|
|
||||||
this.clearInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启用输入
|
|
||||||
*/
|
|
||||||
enableInput() {
|
|
||||||
this.inputEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "ab10dc4c-c8a3-4fd2-83d6-433d4195966b",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Vec3 } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 位置组件 - 存储实体的空间位置信息
|
|
||||||
*
|
|
||||||
* 这是最基础的组件示例,展示了ECS组件的设计原则:
|
|
||||||
* 1. 主要存储数据,少量辅助方法
|
|
||||||
* 2. 单一职责:只负责位置相关的数据
|
|
||||||
* 3. 可复用:任何需要位置信息的实体都可以使用
|
|
||||||
*/
|
|
||||||
export class PositionComponent extends Component {
|
|
||||||
/** 3D位置坐标 */
|
|
||||||
public position: Vec3 = new Vec3();
|
|
||||||
/** 上一帧的位置(用于计算移动距离) */
|
|
||||||
public lastPosition: Vec3 = new Vec3();
|
|
||||||
|
|
||||||
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
|
||||||
super();
|
|
||||||
this.position.set(x, y, z);
|
|
||||||
this.lastPosition.set(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置位置
|
|
||||||
*/
|
|
||||||
setPosition(x: number, y: number, z: number = 0) {
|
|
||||||
this.lastPosition.set(this.position);
|
|
||||||
this.position.set(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动位置
|
|
||||||
*/
|
|
||||||
move(deltaX: number, deltaY: number, deltaZ: number = 0) {
|
|
||||||
this.lastPosition.set(this.position);
|
|
||||||
this.position.x += deltaX;
|
|
||||||
this.position.y += deltaY;
|
|
||||||
this.position.z += deltaZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算到另一个位置的距离
|
|
||||||
*/
|
|
||||||
distanceTo(other: PositionComponent): number {
|
|
||||||
return Vec3.distance(this.position, other.position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取本帧移动的距离
|
|
||||||
*/
|
|
||||||
getMovementDistance(): number {
|
|
||||||
return Vec3.distance(this.position, this.lastPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否在指定范围内
|
|
||||||
*/
|
|
||||||
isWithinRange(target: PositionComponent, range: number): boolean {
|
|
||||||
return this.distanceTo(target) <= range;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "e6ee57d6-d0eb-43f2-a601-9b7a2812de66",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { Vec3 } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 速度组件 - 存储实体的运动速度信息
|
|
||||||
*
|
|
||||||
* 设计原则展示:
|
|
||||||
* 1. 与PositionComponent分离:遵循单一职责原则
|
|
||||||
* 2. 包含速度限制:避免无限加速
|
|
||||||
* 3. 提供常用的速度操作方法
|
|
||||||
*/
|
|
||||||
export class VelocityComponent extends Component {
|
|
||||||
/** 当前速度向量 */
|
|
||||||
public velocity: Vec3 = new Vec3();
|
|
||||||
/** 最大速度限制 */
|
|
||||||
public maxSpeed: number = 100;
|
|
||||||
/** 阻尼系数(0-1,1为无阻尼) */
|
|
||||||
public damping: number = 1.0;
|
|
||||||
|
|
||||||
constructor(x: number = 0, y: number = 0, z: number = 0, maxSpeed: number = 100) {
|
|
||||||
super();
|
|
||||||
this.velocity.set(x, y, z);
|
|
||||||
this.maxSpeed = maxSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置速度
|
|
||||||
*/
|
|
||||||
setVelocity(x: number, y: number, z: number = 0) {
|
|
||||||
this.velocity.set(x, y, z);
|
|
||||||
this.clampToMaxSpeed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加速度(加速度效果)
|
|
||||||
*/
|
|
||||||
addVelocity(x: number, y: number, z: number = 0) {
|
|
||||||
this.velocity.x += x;
|
|
||||||
this.velocity.y += y;
|
|
||||||
this.velocity.z += z;
|
|
||||||
this.clampToMaxSpeed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用阻尼
|
|
||||||
*/
|
|
||||||
applyDamping(deltaTime: number) {
|
|
||||||
if (this.damping < 1.0) {
|
|
||||||
const dampingFactor = Math.pow(this.damping, deltaTime);
|
|
||||||
this.velocity.multiplyScalar(dampingFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 限制速度不超过最大值
|
|
||||||
*/
|
|
||||||
private clampToMaxSpeed() {
|
|
||||||
const speed = this.velocity.length();
|
|
||||||
if (speed > this.maxSpeed) {
|
|
||||||
this.velocity.normalize();
|
|
||||||
this.velocity.multiplyScalar(this.maxSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前速度大小
|
|
||||||
*/
|
|
||||||
getSpeed(): number {
|
|
||||||
return this.velocity.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取速度方向(单位向量)
|
|
||||||
*/
|
|
||||||
getDirection(): Vec3 {
|
|
||||||
const result = new Vec3();
|
|
||||||
Vec3.normalize(result, this.velocity);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止移动
|
|
||||||
*/
|
|
||||||
stop() {
|
|
||||||
this.velocity.set(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否在移动
|
|
||||||
*/
|
|
||||||
isMoving(): boolean {
|
|
||||||
return this.velocity.lengthSqr() > 0.01; // 避免浮点数精度问题
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "784d7c28-2b72-427c-8b04-da0fcf775acf",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "6a2d6231-acf9-47b8-a020-d45a7433a95d",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "80dcbaf5-21f7-4bb1-aff4-2cdbb0b5d364",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "879b4e07-dd6b-4445-adb2-a970b97c6d6f",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
import { Scene } from '@esengine/ecs-framework';
|
|
||||||
import { Entity } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
// 导入组件
|
|
||||||
import { PositionComponent } from '../components/PositionComponent';
|
|
||||||
import { VelocityComponent } from '../components/VelocityComponent';
|
|
||||||
import { HealthComponent } from '../components/HealthComponent';
|
|
||||||
import { PlayerInputComponent } from '../components/PlayerInputComponent';
|
|
||||||
|
|
||||||
// 导入系统
|
|
||||||
import { MovementSystem } from '../systems/MovementSystem';
|
|
||||||
import { PlayerInputSystem } from '../systems/PlayerInputSystem';
|
|
||||||
import { HealthSystem } from '../systems/HealthSystem';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 示例游戏场景 - 完整的ECS应用示例
|
|
||||||
*
|
|
||||||
* 这个场景展示了:
|
|
||||||
* 1. 如何创建和配置各种实体
|
|
||||||
* 2. 如何添加和组织系统
|
|
||||||
* 3. 如何实现完整的游戏逻辑
|
|
||||||
* 4. 如何进行调试和监控
|
|
||||||
*/
|
|
||||||
export class ExampleGameScene extends Scene {
|
|
||||||
// 场景中的重要实体引用
|
|
||||||
private player: Entity | null;
|
|
||||||
private enemies: Entity[];
|
|
||||||
private gameConfig: {
|
|
||||||
maxEnemies: number;
|
|
||||||
enemySpawnInterval: number;
|
|
||||||
gameArea: { width: number; height: number };
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
// 在构造函数中初始化属性
|
|
||||||
this.player = null;
|
|
||||||
this.enemies = [];
|
|
||||||
this.gameConfig = {
|
|
||||||
maxEnemies: 5,
|
|
||||||
enemySpawnInterval: 3000, // 3秒生成一个敌人
|
|
||||||
gameArea: { width: 800, height: 600 }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景初始化(构造时调用)
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
this.name = "ExampleGameScene";
|
|
||||||
console.log("📋 ExampleGameScene 构造完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景开始时的回调(所有构造函数执行完毕后调用)
|
|
||||||
*/
|
|
||||||
public onStart(): void {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
console.log("🎮 开始初始化示例游戏场景...");
|
|
||||||
|
|
||||||
// 1. 添加系统(注意顺序很重要)
|
|
||||||
this.setupSystems();
|
|
||||||
|
|
||||||
// 2. 创建游戏实体
|
|
||||||
this.createGameEntities();
|
|
||||||
|
|
||||||
// 3. 设置定时器和事件
|
|
||||||
this.setupGameLogic();
|
|
||||||
|
|
||||||
console.log("✅ 示例游戏场景初始化完成!");
|
|
||||||
this.printSceneInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置游戏系统
|
|
||||||
*/
|
|
||||||
private setupSystems(): void {
|
|
||||||
console.log("🔧 添加游戏系统...");
|
|
||||||
|
|
||||||
// 输入系统(最先处理输入)
|
|
||||||
this.addEntityProcessor(new PlayerInputSystem());
|
|
||||||
|
|
||||||
// 移动系统(处理所有移动逻辑)
|
|
||||||
this.addEntityProcessor(new MovementSystem());
|
|
||||||
|
|
||||||
// 生命值系统(处理生命值、死亡等)
|
|
||||||
this.addEntityProcessor(new HealthSystem());
|
|
||||||
|
|
||||||
console.log("✅ 系统添加完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建游戏实体
|
|
||||||
*/
|
|
||||||
private createGameEntities(): void {
|
|
||||||
console.log("🏗️ 创建游戏实体...");
|
|
||||||
|
|
||||||
// 创建玩家
|
|
||||||
this.createPlayer();
|
|
||||||
|
|
||||||
// 创建初始敌人
|
|
||||||
this.createInitialEnemies();
|
|
||||||
|
|
||||||
// 创建环境实体(可选)
|
|
||||||
this.createEnvironmentEntities();
|
|
||||||
|
|
||||||
console.log("✅ 实体创建完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建玩家实体
|
|
||||||
*/
|
|
||||||
private createPlayer(): void {
|
|
||||||
this.player = this.createEntity("Player");
|
|
||||||
|
|
||||||
// 添加玩家组件
|
|
||||||
this.player.addComponent(new PositionComponent(0, 0, 0));
|
|
||||||
this.player.addComponent(new VelocityComponent(0, 0, 0, 250)); // 最大速度250
|
|
||||||
this.player.addComponent(new HealthComponent(100, 5)); // 100血,每秒回5血
|
|
||||||
this.player.addComponent(new PlayerInputComponent());
|
|
||||||
|
|
||||||
console.log("🎯 玩家创建完成 - 使用WASD或方向键移动,空格键攻击");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建初始敌人
|
|
||||||
*/
|
|
||||||
private createInitialEnemies(): void {
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
this.createEnemy(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建单个敌人
|
|
||||||
*/
|
|
||||||
private createEnemy(index: number): Entity {
|
|
||||||
const enemy = this.createEntity(`Enemy_${index}`);
|
|
||||||
|
|
||||||
// 随机位置
|
|
||||||
const x = (Math.random() - 0.5) * this.gameConfig.gameArea.width;
|
|
||||||
const y = (Math.random() - 0.5) * this.gameConfig.gameArea.height;
|
|
||||||
|
|
||||||
// 随机速度
|
|
||||||
const velocityX = (Math.random() - 0.5) * 100;
|
|
||||||
const velocityY = (Math.random() - 0.5) * 100;
|
|
||||||
|
|
||||||
// 添加敌人组件
|
|
||||||
enemy.addComponent(new PositionComponent(x, y, 0));
|
|
||||||
enemy.addComponent(new VelocityComponent(velocityX, velocityY, 0, 150));
|
|
||||||
enemy.addComponent(new HealthComponent(50, 0)); // 50血,不回血
|
|
||||||
|
|
||||||
// 添加到敌人列表
|
|
||||||
this.enemies.push(enemy);
|
|
||||||
|
|
||||||
return enemy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建环境实体(演示不同类型的实体)
|
|
||||||
*/
|
|
||||||
private createEnvironmentEntities(): void {
|
|
||||||
// 创建一些静态的环境对象
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
const obstacle = this.createEntity(`Obstacle_${i}`);
|
|
||||||
|
|
||||||
const x = (Math.random() - 0.5) * this.gameConfig.gameArea.width * 0.8;
|
|
||||||
const y = (Math.random() - 0.5) * this.gameConfig.gameArea.height * 0.8;
|
|
||||||
|
|
||||||
// 只有位置,没有速度和生命值
|
|
||||||
obstacle.addComponent(new PositionComponent(x, y, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("🌲 环境实体创建完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置游戏逻辑和定时器
|
|
||||||
*/
|
|
||||||
private setupGameLogic(): void {
|
|
||||||
console.log("⚙️ 设置游戏逻辑...");
|
|
||||||
|
|
||||||
// 敌人生成定时器
|
|
||||||
this.setupEnemySpawner();
|
|
||||||
|
|
||||||
// 游戏状态监控
|
|
||||||
this.setupGameMonitoring();
|
|
||||||
|
|
||||||
console.log("✅ 游戏逻辑设置完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置敌人生成器
|
|
||||||
*/
|
|
||||||
private setupEnemySpawner(): void {
|
|
||||||
setInterval(() => {
|
|
||||||
if (this.enemies.length < this.gameConfig.maxEnemies) {
|
|
||||||
const newEnemy = this.createEnemy(this.enemies.length);
|
|
||||||
}
|
|
||||||
}, this.gameConfig.enemySpawnInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置游戏监控
|
|
||||||
*/
|
|
||||||
private setupGameMonitoring(): void {
|
|
||||||
// 每10秒清理已死亡的敌人引用
|
|
||||||
setInterval(() => {
|
|
||||||
this.cleanupDeadEnemies();
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印游戏状态(按需调用)
|
|
||||||
*/
|
|
||||||
private printGameStatus(): void {
|
|
||||||
const totalEntities = this.entities.count;
|
|
||||||
const aliveEnemies = this.enemies.filter(e => !e.isDestroyed).length;
|
|
||||||
|
|
||||||
console.log("📊 游戏状态报告:");
|
|
||||||
console.log(` - 总实体数: ${totalEntities}`);
|
|
||||||
console.log(` - 存活敌人: ${aliveEnemies}`);
|
|
||||||
|
|
||||||
if (this.player && !this.player.isDestroyed) {
|
|
||||||
const playerHealth = this.player.getComponent(HealthComponent);
|
|
||||||
const playerPos = this.player.getComponent(PositionComponent);
|
|
||||||
console.log(` - 玩家生命值: ${playerHealth?.currentHealth}/${playerHealth?.maxHealth}`);
|
|
||||||
console.log(` - 玩家位置: (${playerPos?.position.x.toFixed(1)}, ${playerPos?.position.y.toFixed(1)})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理已死亡的敌人引用
|
|
||||||
*/
|
|
||||||
private cleanupDeadEnemies(): void {
|
|
||||||
const initialCount = this.enemies.length;
|
|
||||||
this.enemies = this.enemies.filter(enemy => !enemy.isDestroyed);
|
|
||||||
const removedCount = initialCount - this.enemies.length;
|
|
||||||
|
|
||||||
if (removedCount > 0) {
|
|
||||||
console.log(`🧹 清理了 ${removedCount} 个已死亡的敌人引用`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印场景信息
|
|
||||||
*/
|
|
||||||
private printSceneInfo(): void {
|
|
||||||
console.log("\n📋 场景信息:");
|
|
||||||
console.log(` 场景名: ${this.name}`);
|
|
||||||
console.log(` 实体数: ${this.entities.count}`);
|
|
||||||
console.log(` 系统数: ${this.entityProcessors.count}`);
|
|
||||||
console.log(` 玩家: ${this.player?.name || '未创建'}`);
|
|
||||||
console.log(` 敌人: ${this.enemies.length} 个`);
|
|
||||||
console.log("\n🎮 控制说明:");
|
|
||||||
console.log(" - WASD 或 方向键: 移动");
|
|
||||||
console.log(" - 空格: 攻击/行动");
|
|
||||||
console.log(" - ESC: 暂停");
|
|
||||||
console.log("\n💡 学习要点:");
|
|
||||||
console.log(" 1. 观察控制台输出,了解ECS运行过程");
|
|
||||||
console.log(" 2. 打开调试面板查看性能数据");
|
|
||||||
console.log(" 3. 尝试修改组件参数观察变化");
|
|
||||||
console.log(" 4. 查看代码学习ECS设计模式\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取玩家实体(供其他系统使用)
|
|
||||||
*/
|
|
||||||
public getPlayer(): Entity | null {
|
|
||||||
return this.player;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有敌人(供其他系统使用)
|
|
||||||
*/
|
|
||||||
public getEnemies(): Entity[] {
|
|
||||||
return this.enemies.filter(enemy => !enemy.isDestroyed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 游戏重置方法
|
|
||||||
*/
|
|
||||||
public resetGame(): void {
|
|
||||||
console.log("🔄 重置游戏...");
|
|
||||||
|
|
||||||
// 销毁所有实体
|
|
||||||
if (this.player) {
|
|
||||||
this.player.destroy();
|
|
||||||
this.player = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.enemies.forEach(enemy => enemy.destroy());
|
|
||||||
this.enemies = [];
|
|
||||||
|
|
||||||
// 重新创建实体
|
|
||||||
this.createGameEntities();
|
|
||||||
|
|
||||||
console.log("✅ 游戏重置完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景卸载时调用
|
|
||||||
*/
|
|
||||||
public unload(): void {
|
|
||||||
console.log("🧹 清理示例游戏场景...");
|
|
||||||
|
|
||||||
// 清理引用
|
|
||||||
this.player = null;
|
|
||||||
this.enemies = [];
|
|
||||||
|
|
||||||
super.unload();
|
|
||||||
console.log("✅ 场景清理完成");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "da87facc-89a0-47da-a0ef-423255200a51",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { Scene } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 游戏场景
|
|
||||||
*
|
|
||||||
* 这是您的主游戏场景。在这里可以:
|
|
||||||
* - 添加游戏系统
|
|
||||||
* - 创建初始实体
|
|
||||||
* - 设置场景参数
|
|
||||||
*/
|
|
||||||
export class GameScene extends Scene {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景初始化
|
|
||||||
* 在场景创建时调用,用于设置基础配置
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
|
|
||||||
// 设置场景名称
|
|
||||||
this.name = "MainGameScene";
|
|
||||||
|
|
||||||
console.log('🎯 游戏场景已创建');
|
|
||||||
|
|
||||||
// TODO: 在这里添加您的游戏系统
|
|
||||||
// 例如:this.addEntityProcessor(new MovementSystem());
|
|
||||||
|
|
||||||
// TODO: 在这里创建初始实体
|
|
||||||
// 例如:this.createEntity("Player");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景开始运行
|
|
||||||
* 在场景开始时调用,用于执行启动逻辑
|
|
||||||
*/
|
|
||||||
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": "8fee85be-2224-4200-a898-d3ae2406fb1d",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "1.2.0",
|
|
||||||
"importer": "directory",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "a5e3a8c9-3d0b-4a36-9d20-6f70f1380131",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { HealthComponent } from '../components/HealthComponent';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命值系统 - 处理生命值相关的逻辑
|
|
||||||
*
|
|
||||||
* 展示生命值管理:
|
|
||||||
* 1. 自动回血
|
|
||||||
* 2. 无敌状态管理
|
|
||||||
* 3. 死亡处理
|
|
||||||
* 4. 事件触发
|
|
||||||
*/
|
|
||||||
export class HealthSystem extends EntitySystem {
|
|
||||||
/** 回血延迟时间(受伤后多久开始回血,毫秒) */
|
|
||||||
private regenDelay: number = 3000;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 只处理拥有HealthComponent的实体
|
|
||||||
super(Matcher.empty().all(HealthComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log("HealthSystem 已初始化 - 开始处理生命值逻辑");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧处理:更新生命值相关逻辑
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
for (const entity of entities) {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
|
|
||||||
// 处理无敌状态
|
|
||||||
this.processInvincibility(health);
|
|
||||||
|
|
||||||
// 处理生命值回复
|
|
||||||
this.processHealthRegeneration(entity, health);
|
|
||||||
|
|
||||||
// 检查死亡状态
|
|
||||||
this.checkDeathStatus(entity, health);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理无敌状态
|
|
||||||
*/
|
|
||||||
private processInvincibility(health: HealthComponent): void {
|
|
||||||
if (health.invincible && health.invincibleDuration > 0) {
|
|
||||||
health.invincibleDuration -= Time.deltaTime;
|
|
||||||
|
|
||||||
// 无敌时间结束
|
|
||||||
if (health.invincibleDuration <= 0) {
|
|
||||||
health.invincible = false;
|
|
||||||
health.invincibleDuration = 0;
|
|
||||||
console.log("无敌状态结束");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理生命值回复
|
|
||||||
*/
|
|
||||||
private processHealthRegeneration(entity: Entity, health: HealthComponent): void {
|
|
||||||
// 如果已经满血或者没有回复速度,则不处理
|
|
||||||
if (health.isFullHealth() || health.regenRate <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否超过了回血延迟时间
|
|
||||||
const currentTime = Date.now();
|
|
||||||
if (currentTime - health.lastDamageTime < this.regenDelay) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算回血量
|
|
||||||
const regenAmount = health.regenRate * Time.deltaTime;
|
|
||||||
const oldHealth = health.currentHealth;
|
|
||||||
|
|
||||||
// 执行回血
|
|
||||||
health.heal(regenAmount);
|
|
||||||
|
|
||||||
// 如果实际回了血,输出日志
|
|
||||||
if (health.currentHealth > oldHealth) {
|
|
||||||
console.log(`${entity.name} 回血: ${oldHealth.toFixed(1)} -> ${health.currentHealth.toFixed(1)} (${health.getHealthPercentage() * 100}%)`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查死亡状态
|
|
||||||
*/
|
|
||||||
private checkDeathStatus(entity: Entity, health: HealthComponent): void {
|
|
||||||
if (health.isDead()) {
|
|
||||||
this.handleEntityDeath(entity, health);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理实体死亡
|
|
||||||
*/
|
|
||||||
private handleEntityDeath(entity: Entity, health: HealthComponent): void {
|
|
||||||
console.log(`💀 ${entity.name} 已死亡!`);
|
|
||||||
|
|
||||||
// 触发死亡事件(如果有事件系统)
|
|
||||||
this.triggerDeathEvent(entity);
|
|
||||||
|
|
||||||
// 可以在这里添加死亡效果、掉落物品等逻辑
|
|
||||||
this.createDeathEffect(entity);
|
|
||||||
|
|
||||||
// 标记实体为死亡状态(而不是立即销毁)
|
|
||||||
// 这样其他系统可以处理死亡相关的逻辑
|
|
||||||
entity.addComponent(new DeadMarkerComponent());
|
|
||||||
|
|
||||||
// 可选:延迟销毁实体
|
|
||||||
setTimeout(() => {
|
|
||||||
if (entity && !entity.isDestroyed) {
|
|
||||||
entity.destroy();
|
|
||||||
console.log(`${entity.name} 已被销毁`);
|
|
||||||
}
|
|
||||||
}, 1000); // 1秒后销毁
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 触发死亡事件
|
|
||||||
*/
|
|
||||||
private triggerDeathEvent(entity: Entity): void {
|
|
||||||
// 如果项目中有事件系统,可以在这里发送死亡事件
|
|
||||||
console.log(`触发死亡事件: ${entity.name}`);
|
|
||||||
|
|
||||||
// 示例事件数据
|
|
||||||
const deathEventData = {
|
|
||||||
entityId: entity.id,
|
|
||||||
entityName: entity.name,
|
|
||||||
deathTime: Date.now(),
|
|
||||||
position: this.getEntityPosition(entity)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 这里可以调用事件系统发送事件
|
|
||||||
// eventBus.emit('entity:died', deathEventData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建死亡效果
|
|
||||||
*/
|
|
||||||
private createDeathEffect(entity: Entity): void {
|
|
||||||
console.log(`💥 为 ${entity.name} 创建死亡效果`);
|
|
||||||
|
|
||||||
// 在实际游戏中,这里可能会:
|
|
||||||
// 1. 播放死亡动画
|
|
||||||
// 2. 播放死亡音效
|
|
||||||
// 3. 创建粒子效果
|
|
||||||
// 4. 掉落物品
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实体位置(辅助方法)
|
|
||||||
*/
|
|
||||||
private getEntityPosition(entity: Entity): { x: number; y: number; z: number } {
|
|
||||||
// 尝试获取位置组件
|
|
||||||
const position = entity.getComponent(PositionComponent);
|
|
||||||
if (position) {
|
|
||||||
return {
|
|
||||||
x: position.position.x,
|
|
||||||
y: position.position.y,
|
|
||||||
z: position.position.z
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x: 0, y: 0, z: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 公共方法:对实体造成伤害
|
|
||||||
* 这个方法可以被其他系统调用
|
|
||||||
*/
|
|
||||||
public damageEntity(entity: Entity, damage: number, source?: Entity): boolean {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
if (!health || health.invincible) {
|
|
||||||
return false; // 无生命值组件或处于无敌状态
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldHealth = health.currentHealth;
|
|
||||||
health.takeDamage(damage);
|
|
||||||
|
|
||||||
console.log(`⚔️ ${entity.name} 受到 ${damage} 点伤害: ${oldHealth.toFixed(1)} -> ${health.currentHealth.toFixed(1)}`);
|
|
||||||
|
|
||||||
// 如果有伤害来源,可以记录或处理
|
|
||||||
if (source) {
|
|
||||||
console.log(`伤害来源: ${source.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 公共方法:治疗实体
|
|
||||||
*/
|
|
||||||
public healEntity(entity: Entity, healAmount: number): boolean {
|
|
||||||
const health = entity.getComponent(HealthComponent);
|
|
||||||
if (!health || health.isFullHealth()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldHealth = health.currentHealth;
|
|
||||||
health.heal(healAmount);
|
|
||||||
|
|
||||||
console.log(`💚 ${entity.name} 恢复 ${healAmount} 点生命值: ${oldHealth.toFixed(1)} -> ${health.currentHealth.toFixed(1)}`);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 死亡标记组件 - 标记已死亡的实体
|
|
||||||
* 这是一个简单的标记组件,用于标识死亡状态
|
|
||||||
*/
|
|
||||||
class DeadMarkerComponent extends Component {
|
|
||||||
public deathTime: number;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.deathTime = Date.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导入位置组件(用于获取实体位置)
|
|
||||||
import { PositionComponent } from '../components/PositionComponent';
|
|
||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "455c12d1-52a8-41ac-b1b5-0d2b93c079aa",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|
||||||
import { PositionComponent } from '../components/PositionComponent';
|
|
||||||
import { VelocityComponent } from '../components/VelocityComponent';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动系统 - 处理实体的移动逻辑
|
|
||||||
*
|
|
||||||
* EntitySystem示例:
|
|
||||||
* 1. 使用Matcher指定需要的组件(Position + Velocity)
|
|
||||||
* 2. 每帧更新所有移动实体的位置
|
|
||||||
* 3. 展示组件间的协作
|
|
||||||
*/
|
|
||||||
export class MovementSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
// 只处理同时拥有PositionComponent和VelocityComponent的实体
|
|
||||||
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧执行:更新所有移动实体的位置
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
for (const entity of entities) {
|
|
||||||
const position = entity.getComponent(PositionComponent);
|
|
||||||
const velocity = entity.getComponent(VelocityComponent);
|
|
||||||
|
|
||||||
// 基本移动:位置 = 当前位置 + 速度 * 时间
|
|
||||||
position.move(
|
|
||||||
velocity.velocity.x * Time.deltaTime,
|
|
||||||
velocity.velocity.y * Time.deltaTime,
|
|
||||||
velocity.velocity.z * Time.deltaTime
|
|
||||||
);
|
|
||||||
|
|
||||||
// 应用阻尼(摩擦力)
|
|
||||||
velocity.applyDamping(Time.deltaTime);
|
|
||||||
|
|
||||||
// 可选:添加边界检查
|
|
||||||
this.checkBoundaries(position, velocity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 边界检查(可选功能)
|
|
||||||
* 这个方法演示了如何在系统中实现额外的游戏逻辑
|
|
||||||
*/
|
|
||||||
private checkBoundaries(position: PositionComponent, velocity: VelocityComponent) {
|
|
||||||
const bounds = {
|
|
||||||
left: -400,
|
|
||||||
right: 400,
|
|
||||||
top: 300,
|
|
||||||
bottom: -300
|
|
||||||
};
|
|
||||||
|
|
||||||
// 检查X轴边界
|
|
||||||
if (position.position.x < bounds.left) {
|
|
||||||
position.position.x = bounds.left;
|
|
||||||
velocity.velocity.x = Math.abs(velocity.velocity.x); // 反弹
|
|
||||||
} else if (position.position.x > bounds.right) {
|
|
||||||
position.position.x = bounds.right;
|
|
||||||
velocity.velocity.x = -Math.abs(velocity.velocity.x); // 反弹
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查Y轴边界
|
|
||||||
if (position.position.y < bounds.bottom) {
|
|
||||||
position.position.y = bounds.bottom;
|
|
||||||
velocity.velocity.y = Math.abs(velocity.velocity.y); // 反弹
|
|
||||||
} else if (position.position.y > bounds.top) {
|
|
||||||
position.position.y = bounds.top;
|
|
||||||
velocity.velocity.y = -Math.abs(velocity.velocity.y); // 反弹
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统初始化时调用
|
|
||||||
* 可以在这里设置系统级别的配置
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log("MovementSystem 已初始化 - 开始处理实体移动");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统统计信息(用于调试)
|
|
||||||
*/
|
|
||||||
public getStats(): { processedEntities: number; totalMovement: number } {
|
|
||||||
let totalMovement = 0;
|
|
||||||
const entities = this.entities;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
const position = entity.getComponent(PositionComponent);
|
|
||||||
totalMovement += position.getMovementDistance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
processedEntities: entities.length,
|
|
||||||
totalMovement: totalMovement
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "a8712467-efe0-46ec-a246-a9fa07d203d9",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
|
||||||
import { PlayerInputComponent } from '../components/PlayerInputComponent';
|
|
||||||
import { VelocityComponent } from '../components/VelocityComponent';
|
|
||||||
import { input, Input, EventKeyboard, KeyCode } from 'cc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 玩家输入系统 - 处理玩家输入并转换为游戏行为
|
|
||||||
*
|
|
||||||
* 展示系统的职责:
|
|
||||||
* 1. 收集输入事件
|
|
||||||
* 2. 更新输入组件状态
|
|
||||||
* 3. 根据输入修改其他组件(如速度)
|
|
||||||
*/
|
|
||||||
export class PlayerInputSystem extends EntitySystem {
|
|
||||||
private moveSpeed: number = 200; // 移动速度
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 只处理拥有PlayerInputComponent的实体
|
|
||||||
super(Matcher.empty().all(PlayerInputComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
public initialize(): void {
|
|
||||||
super.initialize();
|
|
||||||
console.log("PlayerInputSystem 已初始化 - 开始监听玩家输入");
|
|
||||||
|
|
||||||
// 注册键盘事件监听器
|
|
||||||
this.setupInputListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置输入事件监听器
|
|
||||||
*/
|
|
||||||
private setupInputListeners(): void {
|
|
||||||
// 键盘按下事件
|
|
||||||
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
|
||||||
// 键盘抬起事件
|
|
||||||
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 键盘按下处理
|
|
||||||
*/
|
|
||||||
private onKeyDown(event: EventKeyboard): void {
|
|
||||||
const keyCode = event.keyCode;
|
|
||||||
const keyName = this.getKeyName(keyCode);
|
|
||||||
|
|
||||||
// 更新所有玩家实体的输入状态
|
|
||||||
for (const entity of this.entities) {
|
|
||||||
const playerInput = entity.getComponent(PlayerInputComponent);
|
|
||||||
if (playerInput && playerInput.inputEnabled) {
|
|
||||||
playerInput.setKey(keyName, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 键盘抬起处理
|
|
||||||
*/
|
|
||||||
private onKeyUp(event: EventKeyboard): void {
|
|
||||||
const keyCode = event.keyCode;
|
|
||||||
const keyName = this.getKeyName(keyCode);
|
|
||||||
|
|
||||||
// 更新所有玩家实体的输入状态
|
|
||||||
for (const entity of this.entities) {
|
|
||||||
const playerInput = entity.getComponent(PlayerInputComponent);
|
|
||||||
if (playerInput && playerInput.inputEnabled) {
|
|
||||||
playerInput.setKey(keyName, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧处理:根据输入状态更新实体行为
|
|
||||||
*/
|
|
||||||
protected process(entities: Entity[]): void {
|
|
||||||
for (const entity of entities) {
|
|
||||||
const playerInput = entity.getComponent(PlayerInputComponent);
|
|
||||||
|
|
||||||
if (!playerInput || !playerInput.inputEnabled) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理移动输入
|
|
||||||
this.processMovementInput(entity, playerInput);
|
|
||||||
|
|
||||||
// 处理其他输入(如攻击、跳跃等)
|
|
||||||
this.processActionInput(entity, playerInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理移动输入
|
|
||||||
*/
|
|
||||||
private processMovementInput(entity: Entity, playerInput: PlayerInputComponent): void {
|
|
||||||
const velocity = entity.getComponent(VelocityComponent);
|
|
||||||
if (!velocity) return;
|
|
||||||
|
|
||||||
// 根据按键状态计算移动方向
|
|
||||||
let moveX = 0;
|
|
||||||
let moveY = 0;
|
|
||||||
|
|
||||||
if (playerInput.isKeyPressed('A') || playerInput.isKeyPressed('ArrowLeft')) {
|
|
||||||
moveX -= 1;
|
|
||||||
}
|
|
||||||
if (playerInput.isKeyPressed('D') || playerInput.isKeyPressed('ArrowRight')) {
|
|
||||||
moveX += 1;
|
|
||||||
}
|
|
||||||
if (playerInput.isKeyPressed('W') || playerInput.isKeyPressed('ArrowUp')) {
|
|
||||||
moveY += 1;
|
|
||||||
}
|
|
||||||
if (playerInput.isKeyPressed('S') || playerInput.isKeyPressed('ArrowDown')) {
|
|
||||||
moveY -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新输入组件的移动方向
|
|
||||||
playerInput.setMoveDirection(moveX, moveY);
|
|
||||||
|
|
||||||
// 将输入转换为速度
|
|
||||||
const normalizedDirection = playerInput.getNormalizedMoveDirection();
|
|
||||||
velocity.setVelocity(
|
|
||||||
normalizedDirection.x * this.moveSpeed * playerInput.sensitivity,
|
|
||||||
normalizedDirection.y * this.moveSpeed * playerInput.sensitivity,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理动作输入(攻击、技能等)
|
|
||||||
*/
|
|
||||||
private processActionInput(entity: Entity, playerInput: PlayerInputComponent): void {
|
|
||||||
// 空格键 - 跳跃或攻击
|
|
||||||
if (playerInput.isKeyPressed('Space')) {
|
|
||||||
console.log(`玩家 ${entity.name} 执行动作:攻击/跳跃`);
|
|
||||||
// 这里可以触发攻击组件或添加跳跃效果
|
|
||||||
}
|
|
||||||
|
|
||||||
// ESC键 - 暂停游戏
|
|
||||||
if (playerInput.isKeyPressed('Escape')) {
|
|
||||||
console.log("玩家请求暂停游戏");
|
|
||||||
// 可以发送暂停事件给游戏管理系统
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将键码转换为字符串
|
|
||||||
*/
|
|
||||||
private getKeyName(keyCode: KeyCode): string {
|
|
||||||
const keyMap: { [key: number]: string } = {
|
|
||||||
[KeyCode.KEY_A]: 'A',
|
|
||||||
[KeyCode.KEY_D]: 'D',
|
|
||||||
[KeyCode.KEY_S]: 'S',
|
|
||||||
[KeyCode.KEY_W]: 'W',
|
|
||||||
[KeyCode.ARROW_LEFT]: 'ArrowLeft',
|
|
||||||
[KeyCode.ARROW_RIGHT]: 'ArrowRight',
|
|
||||||
[KeyCode.ARROW_UP]: 'ArrowUp',
|
|
||||||
[KeyCode.ARROW_DOWN]: 'ArrowDown',
|
|
||||||
[KeyCode.SPACE]: 'Space',
|
|
||||||
[KeyCode.ESCAPE]: 'Escape'
|
|
||||||
};
|
|
||||||
|
|
||||||
return keyMap[keyCode] || `Key_${keyCode}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统清理
|
|
||||||
*/
|
|
||||||
public onDestroy(): void {
|
|
||||||
// 移除事件监听器
|
|
||||||
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
|
||||||
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
|
|
||||||
console.log("PlayerInputSystem 已清理");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ver": "4.0.24",
|
|
||||||
"importer": "typescript",
|
|
||||||
"imported": true,
|
|
||||||
"uuid": "7b69a39f-926a-4260-94ba-e15e31b324b5",
|
|
||||||
"files": [],
|
|
||||||
"subMetas": {},
|
|
||||||
"userData": {}
|
|
||||||
}
|
|
||||||
214
extensions/cocos/cocos-ecs/enemy-ai.bt.json
Normal file
214
extensions/cocos/cocos-ecs/enemy-ai.bt.json
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "root",
|
||||||
|
"type": "root",
|
||||||
|
"name": "敌人AI根节点",
|
||||||
|
"description": "敌人AI行为树的根节点,控制敌人的所有行为逻辑。包含攻击、追击和巡逻三种主要行为模式。",
|
||||||
|
"children": [
|
||||||
|
"main-selector"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main-selector",
|
||||||
|
"type": "selector",
|
||||||
|
"name": "主要行为选择器",
|
||||||
|
"description": "选择器节点:按优先级执行子节点(攻击>追击>巡逻)。这确保敌人在有目标时优先战斗,没有目标时进行巡逻。",
|
||||||
|
"children": [
|
||||||
|
"attack-behavior",
|
||||||
|
"chase-behavior",
|
||||||
|
"patrol-behavior"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "attack-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "攻击行为",
|
||||||
|
"description": "条件装饰器:只有当发现目标且距离足够近时才进行攻击。这确保敌人只在有效攻击范围内战斗。",
|
||||||
|
"children": [
|
||||||
|
"attack-sequence"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "hasTarget",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "attack-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "攻击序列",
|
||||||
|
"description": "序列节点:按顺序执行攻击相关的所有步骤,确保攻击逻辑的完整性。",
|
||||||
|
"children": [
|
||||||
|
"set-attack-state",
|
||||||
|
"attack-cooldown-check",
|
||||||
|
"perform-attack",
|
||||||
|
"reset-attack-cooldown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-attack-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置攻击状态",
|
||||||
|
"description": "黑板赋值节点:将敌人状态设置为'attacking',便于其他系统了解敌人当前行为。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "attack-cooldown-check",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "攻击冷却检查",
|
||||||
|
"description": "条件装饰器:检查攻击冷却是否结束。只有冷却时间为0时才能执行攻击,防止过于频繁的攻击。",
|
||||||
|
"children": [
|
||||||
|
"attack-action"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "attackCooldown",
|
||||||
|
"compareOperator": "lessEqual",
|
||||||
|
"compareValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "attack-action",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "执行攻击",
|
||||||
|
"description": "事件动作节点:触发实际的攻击事件,造成伤害并播放攻击动画和音效。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "perform-attack",
|
||||||
|
"type": "log-action",
|
||||||
|
"name": "攻击日志",
|
||||||
|
"description": "日志动作节点:记录攻击行为,用于调试和监控敌人AI的行为表现。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reset-attack-cooldown",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "重置攻击冷却",
|
||||||
|
"description": "黑板赋值节点:设置攻击冷却时间为1.5秒,控制攻击频率,避免过于频繁的攻击。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chase-behavior",
|
||||||
|
"type": "conditional-decorator",
|
||||||
|
"name": "追击行为",
|
||||||
|
"description": "条件装饰器:当发现目标但距离较远时进行追击。这是攻击和巡逻之间的中间状态。",
|
||||||
|
"children": [
|
||||||
|
"chase-sequence"
|
||||||
|
],
|
||||||
|
"condition": {
|
||||||
|
"type": "blackboard-value-comparison",
|
||||||
|
"properties": {
|
||||||
|
"variableName": "hasTarget",
|
||||||
|
"compareOperator": "equals",
|
||||||
|
"compareValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chase-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "追击序列",
|
||||||
|
"description": "序列节点:执行追击相关的行为,包括状态设置、移动和冷却更新。",
|
||||||
|
"children": [
|
||||||
|
"set-chase-state",
|
||||||
|
"move-to-target",
|
||||||
|
"update-attack-cooldown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-chase-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置追击状态",
|
||||||
|
"description": "黑板赋值节点:将敌人状态设置为'chasing',表示正在追击目标。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "move-to-target",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "向目标移动",
|
||||||
|
"description": "事件动作节点:触发移动事件,让敌人朝目标方向移动。移动速度比巡逻时更快。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "update-attack-cooldown",
|
||||||
|
"type": "math-blackboard-operation",
|
||||||
|
"name": "更新攻击冷却",
|
||||||
|
"description": "数学运算节点:减少攻击冷却时间,让敌人在追击过程中为下次攻击做准备。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-behavior",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "巡逻行为",
|
||||||
|
"description": "序列节点:当没有目标时执行巡逻行为。这是敌人的默认行为,确保敌人始终在活动。",
|
||||||
|
"children": [
|
||||||
|
"patrol-sequence"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-sequence",
|
||||||
|
"type": "sequence",
|
||||||
|
"name": "巡逻序列",
|
||||||
|
"description": "序列节点:执行完整的巡逻循环,包括状态设置、移动和等待。",
|
||||||
|
"children": [
|
||||||
|
"set-patrol-state",
|
||||||
|
"patrol-move",
|
||||||
|
"patrol-wait"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-patrol-state",
|
||||||
|
"type": "set-blackboard-value",
|
||||||
|
"name": "设置巡逻状态",
|
||||||
|
"description": "黑板赋值节点:将敌人状态设置为'patrolling',表示正在巡逻。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-move",
|
||||||
|
"type": "event-action",
|
||||||
|
"name": "巡逻移动",
|
||||||
|
"description": "事件动作节点:触发巡逻移动事件,让敌人在预设的巡逻路径上移动。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patrol-wait",
|
||||||
|
"type": "wait-action",
|
||||||
|
"name": "巡逻等待",
|
||||||
|
"description": "等待动作节点:在巡逻点停留2秒,模拟敌人观察周围环境。这让巡逻看起来更自然。"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blackboard": [
|
||||||
|
{
|
||||||
|
"name": "currentState",
|
||||||
|
"type": "string",
|
||||||
|
"value": "idle",
|
||||||
|
"description": "敌人的当前状态(idle、attacking、chasing、patrolling),用于状态跟踪和AI调试",
|
||||||
|
"group": "状态"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasTarget",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": "false",
|
||||||
|
"description": "是否发现了有效目标,决定敌人是否应该从巡逻切换到追击模式",
|
||||||
|
"group": "目标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attackCooldown",
|
||||||
|
"type": "number",
|
||||||
|
"value": "0",
|
||||||
|
"description": "攻击冷却倒计时(秒),控制攻击频率,0表示可以攻击,大于0表示正在冷却",
|
||||||
|
"group": "战斗"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastKnownTargetPosition",
|
||||||
|
"type": "string",
|
||||||
|
"value": "0,0",
|
||||||
|
"description": "最后已知的目标位置,用于敌人在失去目标后继续搜索一段时间",
|
||||||
|
"group": "目标"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "behavior-tree",
|
||||||
|
"created": "2025-06-24T03:38:29.653Z",
|
||||||
|
"version": "1.0",
|
||||||
|
"exportType": "clean"
|
||||||
|
}
|
||||||
|
}
|
||||||
Submodule extensions/cocos/cocos-ecs/extensions/behaviour-tree updated: 6575763836...714a3ab9ee
9
extensions/cocos/cocos-ecs/package-lock.json
generated
9
extensions/cocos/cocos-ecs/package-lock.json
generated
@@ -6,15 +6,14 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "cocos-ecs",
|
"name": "cocos-ecs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esengine/ai": "^2.0.7",
|
"@esengine/ai": "^2.0.10",
|
||||||
"@esengine/ecs-framework": "^2.1.22"
|
"@esengine/ecs-framework": "^2.1.22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esengine/ai": {
|
"node_modules/@esengine/ai": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.10.tgz",
|
||||||
"integrity": "sha512-Otlgq/1Mn71o9rnRR87+SHmZGCNkFsbJMxhkBjumWQsb9ZD716RilXqRX9psYSMLcFLge9EEVeqExSOIq688sQ==",
|
"integrity": "sha512-L0jWaYHDe8fw/XMpVniFqMIFDVzP9wib+HZKKq+NOXV3zWj+F3XpOgNrb7+4ZN5yR8rK9wjEh+PNkVdTkLOl8g==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esengine/ecs-framework": "^2.1.20"
|
"@esengine/ecs-framework": "^2.1.20"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esengine/ecs-framework": "^2.1.22",
|
"@esengine/ecs-framework": "^2.1.22",
|
||||||
"@esengine/ai": "^2.0.7"
|
"@esengine/ai": "^2.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2
thirdparty/BehaviourTree-ai
vendored
2
thirdparty/BehaviourTree-ai
vendored
Submodule thirdparty/BehaviourTree-ai updated: d4babd9aa2...a849240fdd
Reference in New Issue
Block a user