diff --git a/README.md b/README.md index 1475782..33db79e 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,20 @@ # 行为树 -> 一个简洁、高效的 TypeScript 行为树库。遵循"好品味"设计原则:简单数据结构,消除特殊情况,直接暴露问题。 - [![npm version](https://badge.fury.io/js/kunpocc-behaviortree.svg)](https://badge.fury.io/js/kunpocc-behaviortree) [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC) +一个简洁、高效的 TypeScript 行为树库。 +该库无任何依赖,理论上可用于所有js、ts项目。 +作者主要使用此库作为游戏AI的实现。 + +## 什么是行为树 +行为树(Behavior Tree)是一种用于描述和控制复杂行为逻辑的数据结构,最初在游戏AI领域大放异彩,现在已经广泛应用于机器人控制、自动化系统等领域。它简单、直观、可扩展,是真正解决实际问题的好工具。 + +行为树本质上是一个**有向无环图**,用树状结构来组织和执行行为逻辑。每个节点代表一个行为或决策,通过父子关系形成层次化的控制流。 + ## 特性 -- 🎯 **简洁设计**: 零废话,直接解决问题 - 🔧 **类型安全**: 完整 TypeScript 支持 -- 🚀 **高性能**: 优化的执行机制,最小开销 - 📦 **零依赖**: 纯净实现,无第三方依赖 - 🔄 **状态管理**: 分层黑板系统,数据隔离清晰 @@ -21,17 +26,20 @@ npm install kunpocc-behaviortree ``` -#### 内置demo +#### 内置demo (基于cocos creator 3.8.6) 项目根目录下的 `bt-demo`文件夹 -demo是基于`cocos creator3.8.6`制作的 +## GUI编辑器 +查看详情: [GUI编辑器](./docs/USED.md) + +![image](./image/image_tree.png) ## 核心概念 -### 状态类型 +#### 状态类型 ```typescript enum Status { SUCCESS, // 成功 @@ -40,14 +48,13 @@ enum Status { } ``` -### 节点类型 +#### 节点类型 - **组合节点**: 包含多个子节点 (Composite) - **装饰节点**: 有且只有一个子节点(Decorator) - **叶子节点**: 不能包含子节点 (LeafNode) - **条件节点**: 特殊的叶子节点 (Condition) - ## 装饰器 > **自行实现的节点,通过装饰器把数据暴露给行为树编辑器** @@ -75,14 +82,14 @@ enum Status { * 按顺序执行子节点,执行过程中子节点返回非SUCCESS,则返回子节点状态,全部成功返回SUCCESS ##### Parallel - 并行节点 -* 执行所有子节点,全部成功才成功 -* 并不是真正的并行,也有执行顺序 +* 依次执行所有子节点(从左到右),全部成功才成功 +* 注意:这里的"并行"是逻辑概念,实际是顺序执行 ##### RandomSelector - 随机选择节点 * 随机选择一个子节点执行 ##### ParallelAnySuccess - 并行任一成功 -* 同时执行所有子节点,任一成功就成功 +* 依次执行所有子节点(从左到右),任一成功就成功 @@ -136,6 +143,8 @@ enum Status { ##### WaitTime - 时间等待节点 + + ### 条件节点 (Condition) ##### Condition - 条件节点基类 @@ -154,36 +163,68 @@ enum Status { ## 黑板系统 -黑板系统提供分层数据存储,支持数据隔离和查找链: +### 黑板系统的作用 +黑板系统(Blackboard System)是行为树中的数据共享中心,类似于传统 AI 系统中的黑板概念。它解决了行为树中节点间数据传递和状态共享的核心问题: + +- **数据传递**:在不同节点间传递运行时数据 +- **状态管理**:维护游戏对象的状态信息 +- **解耦设计**:避免节点间直接依赖,提高代码可维护性 +- **上下文感知**:让节点能够感知周围环境和历史状态 + +### 数据隔离层次 + +黑板系统采用三层数据隔离设计,形成清晰的数据作用域: + +#### 1. 本地数据(Node Level) +- **作用域**:当前节点可见 +- **生命周期**:随节点创建和销毁 +- **用途**:节点内部状态、临时变量、私有数据 + +#### 2. 树级数据(Tree Level) +- **作用域**:整棵行为树内所有节点可见 +- **生命周期**:随行为树创建和销毁 +- **用途**:树内节点间共享的状态、任务进度、策略参数 + +#### 3. 全局数据(Global Level) +- **作用域**:所有行为树实例可见 +- **生命周期**:应用程序生命周期 +- **用途**:全局配置、静态数据、跨树共享状态 + + +### 黑板的使用 ```typescript -// 在节点中使用黑板 -new Action((node) => { - // 直接获取实体 - const entity = node.getEntity(); - - // 本地数据(仅当前节点可见) - node.set('local_count', 1); - const count = node.get('local_count'); - - // 树级数据(整棵树可见) - node.setRoot('tree_data', 'shared'); - const shared = node.getRoot('tree_data'); - - // 全局数据(所有树可见) - node.setGlobal('global_config', config); - const config = node.getGlobal('global_config'); - - return Status.SUCCESS; -}) +import * as BT from "kunpocc-behaviortree"; + +// 设置全局数据 所有行为树实例可见 +BT.globalBlackboard.set("playerPosition", {x: 100, y: 100}); + +// 在节点中使用黑板数据 +export class BTAction extends BT.LeafNode { + public tick(): BT.Status { + // 获取全局数据 + const playerPosition = this.getGlobal<{x: number, y: number}>("playerPosition"); + console.log(playerPosition); + + // 设置树级数据 + this.setRoot("isDead", true); + + // 获取树级数据 + const isDead = this.getRoot("isDead"); + console.log(isDead); + + // 设置节点数据 + this.set("finished", true); + + // 获取节点数据 + const finished = this.get("finished"); + console.log(finished); + + return BT.Status.SUCCESS; + } +} ``` - - -## 许可证 - -ISC License - 详见 [LICENSE](LICENSE) 文件 - ## 贡献 欢迎提交 Issue 和 Pull Request。请确保: diff --git a/bt-demo/assets/script/BTNode.ts b/bt-demo/assets/script/BTNode.ts index a3d6358..3572ada 100644 --- a/bt-demo/assets/script/BTNode.ts +++ b/bt-demo/assets/script/BTNode.ts @@ -7,33 +7,6 @@ import { sp } from "cc"; import { BT } from "./Header"; -@BT.ClassAction("BTTestNode", { name: "嵌套数据测试节点", group: "测试", desc: "测试节点" }) -export class BTTestNode extends BT.LeafNode { - @BT.prop({ - type: BT.ParamType.object, - properties: { - x: { type: BT.ParamType.int, min: 0 }, - y: { type: BT.ParamType.int, min: 0 } - } - }) - position: { x: number, y: number }; - - // 对象数组参数 - @BT.prop({ - type: BT.ParamType.array, - itemType: BT.ParamType.object, - itemProperties: { - name: { type: BT.ParamType.string }, - value: { type: BT.ParamType.int } - } - }) - configs: Array<{ name: string, value: number }>; - - public tick(): BT.Status { - return BT.Status.SUCCESS; - } -} - @BT.ClassAction("BTAnimation", { name: "播放动画", group: "动画", desc: "通过动画名播放动画,播放完成后返回成功" }) export class BTAnimation extends BT.LeafNode { @BT.prop({ type: BT.ParamType.string, description: "动画名" }) @@ -86,6 +59,52 @@ export class BTConditionRandom extends BT.Condition { } + +/************************ 下方是几个编辑器中测试用的节点,删除即可 *************************/ +@BT.ClassAction("BTTestNode2", { name: "空行为节点", group: "测试", desc: "测试节点" }) +export class BTTestNode2 extends BT.LeafNode { + public tick(): BT.Status { + return BT.Status.SUCCESS; + } +} + +@BT.ClassAction("BTTestNode", { name: "嵌套数据测试节点", group: "测试", desc: "测试节点" }) +export class BTTestNode extends BT.LeafNode { + @BT.prop({ + type: BT.ParamType.object, + properties: { + x: { type: BT.ParamType.int, min: 0 }, + y: { type: BT.ParamType.int, min: 0 } + } + }) + position: { x: number, y: number }; + + // 对象数组参数 + @BT.prop({ + type: BT.ParamType.array, + itemType: BT.ParamType.object, + itemProperties: { + name: { type: BT.ParamType.string }, + value: { type: BT.ParamType.int } + } + }) + configs: Array<{ name: string, value: number }>; + + public tick(): BT.Status { + return BT.Status.SUCCESS; + } +} + +/** 条件节点 */ +@BT.ClassCondition("BTConditionTest", { name: "测试条件节点", group: "基础条件节点", desc: "" }) +export class BTConditionRandomTest extends BT.Condition { + + public isEligible(): boolean { + return true; + } +} + + /** 条件装饰节点 */ @BT.ClassDecorator("BTCondition", { name: "条件装饰节点", group: "基础装饰节点", desc: "随机0-1的值,大于设置值返回成功,否则返回失败" }) export class BTCondition extends BT.ConditionDecorator { diff --git a/bt-demo/extensions-config/bt-editor/bttest.json b/bt-demo/extensions-config/bt-editor/bttest.json new file mode 100644 index 0000000..46e098e --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/bttest.json @@ -0,0 +1,331 @@ +{ + "name": "bttest", + "description": "死亡顺序节点", + "nodes": [ + { + "id": "1758261718850_lh2zeww5x", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -60, + "y": -220 + }, + "parameters": {}, + "children": [ + "1758523039812_tjcddh9ze", + "1758253809172_7ug7k3z91", + "1758363111204_lop2a6plc", + "1758523349295_96r7men3n" + ], + "alias": "根选择节点" + }, + { + "id": "1758253809172_7ug7k3z91", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -60, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758253982404_6rhda0crn", + "1758363223180_wgl2lftj9" + ], + "alias": "战斗顺序节点" + }, + { + "id": "1758253982404_6rhda0crn", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -260, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "攻击范围内" + }, + { + "id": "1758363111204_lop2a6plc", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 360, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758523389760_eimzn4sqi", + "1758523381506_arxf3pn6e" + ], + "alias": "巡逻顺序节点" + }, + { + "id": "1758363223180_wgl2lftj9", + "className": "Selector", + "name": "选择节点", + "position": { + "x": 20, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758371105178_0cdpe0b8s", + "1758371282480_wtl4l8yy4" + ], + "alias": "攻击选择" + }, + { + "id": "1758371105178_0cdpe0b8s", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -60, + "y": 60 + }, + "parameters": {}, + "children": [ + "1758371168774_oeixpztqv", + "1758371186379_nl05q6e4w" + ], + "alias": "技能攻击" + }, + { + "id": "1758371168774_oeixpztqv", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -120, + "y": 120 + }, + "parameters": {}, + "children": [], + "alias": "可以释放技能" + }, + { + "id": "1758371186379_nl05q6e4w", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 20, + "y": 140 + }, + "parameters": {}, + "children": [], + "alias": "释放技能" + }, + { + "id": "1758371282480_wtl4l8yy4", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 160, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "普通攻击" + }, + { + "id": "1758523039812_tjcddh9ze", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -540, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758523078993_5vt56w1fv", + "1758523095101_kc0taam2a", + "1758523118932_tv2q9zeij" + ], + "alias": "死亡顺序节点" + }, + { + "id": "1758523078993_5vt56w1fv", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -680, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "血量小于0" + }, + { + "id": "1758523095101_kc0taam2a", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -540, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "播放死亡动画" + }, + { + "id": "1758523118932_tv2q9zeij", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -400, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "删除实体" + }, + { + "id": "1758523349295_96r7men3n", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 580, + "y": -120 + }, + "parameters": {}, + "children": [], + "alias": "随便走走" + }, + { + "id": "1758523381506_arxf3pn6e", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 440, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "追击敌人" + }, + { + "id": "1758523389760_eimzn4sqi", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": 300, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "发现敌人" + } + ], + "connections": [ + { + "id": "conn_1758253994001_5wea6k553", + "sourceNodeId": "1758253809172_7ug7k3z91", + "targetNodeId": "1758253982404_6rhda0crn", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758261733816_q28lthyfv", + "sourceNodeId": "1758261718850_lh2zeww5x", + "targetNodeId": "1758253809172_7ug7k3z91", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758363162954_dj8hnv7wt", + "sourceNodeId": "1758261718850_lh2zeww5x", + "targetNodeId": "1758363111204_lop2a6plc", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758363226473_93afkajso", + "sourceNodeId": "1758253809172_7ug7k3z91", + "targetNodeId": "1758363223180_wgl2lftj9", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758371112294_k4do2tfeq", + "sourceNodeId": "1758363223180_wgl2lftj9", + "targetNodeId": "1758371105178_0cdpe0b8s", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758371171824_ltxuzvkkw", + "sourceNodeId": "1758371105178_0cdpe0b8s", + "targetNodeId": "1758371168774_oeixpztqv", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758371193389_atj5h7bca", + "sourceNodeId": "1758371105178_0cdpe0b8s", + "targetNodeId": "1758371186379_nl05q6e4w", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523042573_2gbahqv3s", + "sourceNodeId": "1758261718850_lh2zeww5x", + "targetNodeId": "1758523039812_tjcddh9ze", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523080725_xqtkpgq2z", + "sourceNodeId": "1758523039812_tjcddh9ze", + "targetNodeId": "1758523078993_5vt56w1fv", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523097500_azabfun5e", + "sourceNodeId": "1758523039812_tjcddh9ze", + "targetNodeId": "1758523095101_kc0taam2a", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523121341_2j7mu0ja3", + "sourceNodeId": "1758523039812_tjcddh9ze", + "targetNodeId": "1758523118932_tv2q9zeij", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523303417_xm4pwy7dz", + "sourceNodeId": "1758363223180_wgl2lftj9", + "targetNodeId": "1758371282480_wtl4l8yy4", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523351101_9nhuxfajs", + "sourceNodeId": "1758261718850_lh2zeww5x", + "targetNodeId": "1758523349295_96r7men3n", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523391171_weiqaojvf", + "sourceNodeId": "1758363111204_lop2a6plc", + "targetNodeId": "1758523389760_eimzn4sqi", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758523392747_mg2deaf3w", + "sourceNodeId": "1758363111204_lop2a6plc", + "targetNodeId": "1758523381506_arxf3pn6e", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 0.8315100681556811, + "canvasOffset": { + "x": 669.6848993184432, + "y": 527.9726510223352 + } +} \ No newline at end of file diff --git a/bt-demo/settings/v2/packages/information.json b/bt-demo/settings/v2/packages/information.json index 94848de..62fccb5 100644 --- a/bt-demo/settings/v2/packages/information.json +++ b/bt-demo/settings/v2/packages/information.json @@ -4,19 +4,19 @@ "customSplash": { "id": "customSplash", "label": "customSplash", - "enable": false, + "enable": true, "customSplash": { "complete": false, - "form": "https://creator-api.cocos.com/api/form/show?" + "form": "https://creator-api.cocos.com/api/form/show?sid=39d299030f31eb42b71bc53d67bdc54e" } }, "removeSplash": { "id": "removeSplash", "label": "removeSplash", - "enable": false, + "enable": true, "removeSplash": { "complete": false, - "form": "https://creator-api.cocos.com/api/form/show?" + "form": "https://creator-api.cocos.com/api/form/show?sid=39d299030f31eb42b71bc53d67bdc54e" } } } diff --git a/docs/USED.md b/docs/USED.md new file mode 100644 index 0000000..a89ca1d --- /dev/null +++ b/docs/USED.md @@ -0,0 +1,335 @@ +# 行为树使用指南 + +![image](../image/bt-gui.png) + +本指南将详细介绍如何使用 kunpocc-behaviortree 库和行为树编辑器。 + +## 一、开发环境 +- 引擎版本:Cocos Creator 3.8.6 +- 编程语言:TypeScript + + +- 支持引擎版本:Cocos Creator 3.7+ + + +## 二、安装 +1. 安装 kunpocc-behaviortree + ``` + npm install kunpocc-behaviortree + ``` + +2. 下载扩展插件(bt-editor) + +3. 项目脚本中引入库文件 + ```typescript + // 比如在项目代码目录下添加一个文件 header.ts + // 写上如下代码 + import * as BT from "kunpocc-behaviortree"; + export { BT }; + ``` + +4. 重启creator + +5. 启用插件 + * 在 Cocos Creator 中选择 `扩展` -> `扩展管理器` -> `已安装扩展` + * 找到 `bt-editor` 并启用 + +6. 打开扩展面板 + * 在 Cocos Creator 顶部菜单栏中选择 `kunpo` -> `行为树编辑器` + +7. 创建第一颗行为树 (见下方 `导出文件使用` 部分) + + + +## 三、自定义节点 (扩展编辑器节点池) + +### 节点装饰器 + +装饰器系统是连接自定义节点和编辑器的桥梁 +只有通过装饰器装饰的节点,才能在编辑器中的节点池中显示 + +#### @ClassAction - 行为节点装饰器 + +用于装饰执行具体逻辑的叶子节点。 + +```typescript +@BT.ClassAction("NodeName", { name: "显示名称", group: "节点分组", desc: "节点描述" }) +export class MyActionNode extends BT.LeafNode { + public tick(): BT.Status { + // 执行逻辑 + return BT.Status.SUCCESS; + } +} +``` + +#### @ClassCondition - 条件节点装饰器 + +用于装饰判断条件的节点。 + +```typescript +@BT.ClassCondition("ConditionName", { name: "条件名称", group: "条件分组", desc: "节点描述" }) +export class MyCondition extends BT.Condition { + protected isEligible(): boolean { + // 返回判断结果 + return true; + } +} +``` + +#### @ClassComposite - 组合节点装饰器 + +用于装饰控制子节点执行流程的组合节点。 + +```typescript +@BT.ClassComposite("CompositeName", { name: "组合名称", group: "组合分组", desc: "节点描述" }) +export class MyComposite extends BT.Composite { + public tick(dt: number): BT.Status { + // 控制子节点执行逻辑 + for (const child of this.children) { + const status = child._execute(dt); + if (status !== BT.Status.SUCCESS) { + return status; + } + } + return BT.Status.SUCCESS; + } +} +``` + +#### @ClassDecorator - 装饰节点装饰器 + +用于装饰修改单个子节点行为的装饰节点。 + +```typescript +@BT.ClassDecorator("DecoratorName", { name: "装饰名称", group: "装饰分组", desc: "节点描述" }) +export class MyDecorator extends BT.Decorator { + public tick(dt: number): BT.Status { + // 装饰逻辑,修改子节点行为 + return this.children[0]._execute(dt); + } +} +``` + +### 节点属性装饰器 (扩展节点参数) + +为节点添加可在编辑器中配置的参数。 + +```typescript +@BT.ClassAction("NodeName", { name: "显示名称", group: "节点分组", desc: "节点描述" }) +export class MyNode extends BT.LeafNode { + // 基础类型参数 + @BT.prop({ type: BT.ParamType.string, description: "动画名称", defaultValue: "idle" }) + private animationName: string = "idle"; + + @BT.prop({ type: BT.ParamType.float, description: "速度", min: 0, max: 10, step: 0.1, defaultValue: 1.0 }) + private speed: number = 1.0; + + @BT.prop({ type: BT.ParamType.bool, description: "是否循环" }) + private loop: boolean = false; + + // 对象参数 + @BT.prop({ + type: BT.ParamType.object, + description: "位置信息", + properties: { + x: { type: BT.ParamType.int, min: 0 }, + y: { type: BT.ParamType.int, min: 0 } + } + }) + private position: { x: number, y: number }; + + // 数组参数 + @BT.prop({ + type: BT.ParamType.array, + description: "巡逻点列表", + itemType: BT.ParamType.object, + itemProperties: { + x: { type: BT.ParamType.float }, + y: { type: BT.ParamType.float }, + name: { type: BT.ParamType.string } + } + }) + private patrolPoints: Array<{ x: number, y: number, name: string }>; +} +``` + +#### 参数类型详解 + +| 类型 | BT.ParamType | 描述 | 支持属性 | +|------|--------------|------|----------| +| 整数 | `int` | 整数类型 | `min`, `max`, `step`, `defaultValue` | +| 浮点数 | `float` | 浮点数类型 | `min`, `max`, `step`, `defaultValue` | +| 字符串 | `string` | 字符串类型 | `defaultValue` | +| 布尔 | `bool` | 布尔类型 | `defaultValue` | +| 对象 | `object` | 对象类型 | `properties` | +| 数组 | `array` | 数组类型 | `itemType`, `itemProperties` | + + +## 四、编辑器介绍 + +#### 快捷键 + +- **打开编辑器**: `Ctrl+Shift+K` (Windows) / `Cmd+Shift+K` (Mac) +- **导出配置**: `Ctrl+Shift+L` (Windows) / `Cmd+Shift+L` (Mac) + +#### 菜单访问 + +在 Cocos Creator 顶部菜单栏中选择 `kunpo` -> `行为树编辑器` + + +### 编辑器功能介绍 + +行为树编辑器提供了一个直观的可视化界面,让你可以轻松创建和管理复杂的行为树结构。 + +#### 核心功能 + +##### 可视化节点编辑 +- **拖拽创建**:从左侧节点库拖拽节点到画布中 +- **分组管理**:节点按功能分组显示,便于查找 +- **实时预览**:节点显示类型图标和描述信息 + +##### 属性参数配置 +- **智能表单**:根据`@prop`装饰器自动生成配置界面 +- **类型校验**:支持数字、字符串、布尔值、对象、数组等类型 +- **默认值**:自动填充装饰器中定义的默认值 +- **约束验证**:支持最小值、最大值、步长等约束条件 + +##### 连接线管理 +- **可视连接**:通过拖拽连接点创建父子关系 +- **自动布局**:连接线自动避让,保持界面整洁 +- **连接验证**:防止创建非法的节点连接关系 + +##### 画布操作 +- **缩放平移**:鼠标滚轮缩放,拖拽平移画布 +- **多选操作**:支持框选多个节点进行批量操作 + +##### 节点管理 +- **别名设置**:为节点设置有意义的别名,便于理解 +- **复制粘贴**:快速复制节点及其子树结构 +- **删除操作**:删除节点时自动清理相关连接 + +##### 编辑器界面布局 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 顶部工具栏 │ +│ [设置导出路径] [过滤行为树] [选择行为树▼] [导出配置] │ +├─────────────┬─────────────────────────────────────────────┬─────────────────┤ +│ │ │ │ +│ 节点库 │ │ 右侧面板 │ +│ (左侧) │ │ │ +│ │ ┌───────────────────────────────────────┐ │ ┌──────────────┐│ +│ • 行为节点 │ │ 画布工具栏 │ │ │ 行为树名称 │ │ +│ • 条件节点 │ │ [缩放] [重置] [清空] [复制] [粘贴] │ │ │ 行为树描述 │ │ +│ • 组合节点 │ └─────────────────────────────────────────┘ │ └──────────────┘ │ +│ • 装饰节点 │ │ │ +│ • 内置节点 │ ┌─────────────────────────────────────────┐ │ ┌──────────────┐ │ +│ │ │ │ │ │ │ │ +│ │ │ 画布操作区 │ │ │ 节点参数区 │ │ +│ │ │ │ │ │ │ │ +│ │ │ ┌─────┐ │ │ │ • 节点名称 │ │ +│ │ │ │根节点│ │ │ │ • 参数配置 │ │ +│ │ │ └──┬──┘ │ │ │ • 描述信息 │ │ +│ │ │ │ │ │ │ • 别名设置 │ │ +│ │ │ ┌──▼──┐ ┌──────┐ │ │ │ │ │ +│ │ │ │子节点│ │子节点│ │ │ └──────────────┘ │ +│ │ │ └─────┘ └──────┘ │ │ │ +│ │ │ │ │ ┌──────────────┐ │ +│ │ │ │ │ │ [删除行为树] │ │ +│ │ └────────────────────────────────────────┘ │ │ [创建新树] │ │ +│ │ │ └──────────────┘ │ +└────────────┴──────────────────────────────────────────────┴──────────────────┘ +``` + +### 导出文件使用 + +#### 在项目中使用导出配置 + +##### 1. 配置文件格式 + +```json +{ + "boss1": [ + { + "id": "1758206972710_bhxebhy7o", + "className": "Sequence", + "parameters": {}, + "children": [ + "1758090634327_mf36nwkdt" + ] + }, + { + "id": "1758090634327_mf36nwkdt", + "className": "Selector", + "parameters": {}, + "children": [ + "1758206988178_55b7kk5va" + ] + }, + { + "id": "1758206988178_55b7kk5va", + "className": "BTAnimation", + "parameters": { + "_name": "", + "_loop": false + }, + "children": [] + } + ] +} +``` + +##### 2. 配置文件放入项目资源目录 +将从编辑器导出的JSON文件放入项目资源目录 +自行加载配置数据 + +``` +assets/ +├── resources/ +│ └── config/ +│ ├── bt_config.json // 所有行为树的信息都在这个里边 +``` + +##### 3. 创建行为树实例 + +* BT.createBehaviorTree(config["boss1"], entity); + +* 函数详解 + +```typescript +// 工厂函数签名 +function createBehaviorTree(config: INodeConfig[], entity: T): BehaviorTree + +// 内部工作流程: +// 1. 验证配置格式 +// 2. 创建节点映射表 (id -> config) +// 3. 递归创建节点树 +// - 通过className查找构造函数 +// - 根据节点类型选择创建方式 +// - 设置节点参数 +// - 建立父子关系 +// 4. 返回行为树实例 +``` + +## 五、更新声明 + +## 0.0.1 (2025-09-23) +- 首版本 + +## 六、联系作者 + +* 邮箱: gong.xinhai@163.com +* 微信: G0900901 +* 扫码加微信: + + +## 七、版权声明 +此插件源代码可商业使用 + +商业授权范围仅限于在您自行开发的游戏作品中使用 + +不得进行任何形式的转售、租赁、传播等 + + +## 八、购买须知 +本产品为付费虚拟商品,一经购买成功概不退款,请在购买前谨慎确认购买内容。 \ No newline at end of file diff --git a/image/bt-gui.png b/image/bt-gui.png new file mode 100644 index 0000000..d069f42 Binary files /dev/null and b/image/bt-gui.png differ diff --git a/image/image_tree.png b/image/image_tree.png new file mode 100644 index 0000000..c75ad13 Binary files /dev/null and b/image/image_tree.png differ diff --git a/package.json b/package.json index 8ba9135..55becc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kunpocc-behaviortree", - "version": "0.1.1", + "version": "0.1.2", "description": "行为树", "main": "./dist/kunpocc-behaviortree.cjs", "module": "./dist/kunpocc-behaviortree.mjs", diff --git a/src/behaviortree/BTNode/Composite.ts b/src/behaviortree/BTNode/Composite.ts index e92ec25..0553d8c 100644 --- a/src/behaviortree/BTNode/Composite.ts +++ b/src/behaviortree/BTNode/Composite.ts @@ -87,10 +87,11 @@ export class Sequence extends Composite { } /** - * 并行节点 从上到下执行 全部执行一遍 + * 并行节点 从左到右依次执行所有子节点 + * 注意:这里的"并行"是逻辑概念,实际是顺序执行 * 返回优先级 FAILURE > RUNNING > SUCCESS */ -@BT.ClassComposite("Parallel", { name: "并行节点", group: "基础组合节点", desc: "同时执行所有子节点, 子节点状态: 任意失败则失败 > 任意执行中则执行中 > 全部成功则成功" }) +@BT.ClassComposite("Parallel", { name: "并行节点", group: "基础组合节点", desc: "依次执行所有子节点(从左到右), 子节点状态: 任意失败则失败 > 任意执行中则执行中 > 全部成功则成功" }) export class Parallel extends Composite { public tick(dt: number): Status { let result = Status.SUCCESS; @@ -161,10 +162,11 @@ export class RandomSelector extends Composite { } /** - * 并行节点 从上到下执行 全部执行一遍 + * 并行任意成功节点 从左到右依次执行所有子节点 + * 注意:这里的"并行"是逻辑概念,实际是顺序执行 * 返回优先级 SUCCESS > RUNNING > FAILURE */ -@BT.ClassComposite("ParallelAnySuccess", { name: "并行任意成功节点", group: "基础组合节点", desc: "任意一个成功则成功 > 任意一个执行中则执行中 > 全部失败则失败" }) +@BT.ClassComposite("ParallelAnySuccess", { name: "并行任意成功节点", group: "基础组合节点", desc: "依次执行所有子节点(从左到右), 任意一个成功则成功 > 任意一个执行中则执行中 > 全部失败则失败" }) export class ParallelAnySuccess extends Composite { public tick(dt: number): Status { let result = Status.FAILURE; diff --git a/src/index.ts b/src/index.ts index 0bc8571..279e201 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ /** 行为树 */ export { BehaviorTree } from "./behaviortree/BehaviorTree"; -export { Blackboard } from "./behaviortree/Blackboard"; +export { Blackboard, globalBlackboard } from "./behaviortree/Blackboard"; export * from "./behaviortree/BTNode/Action"; export { IBTNode } from "./behaviortree/BTNode/BTNode"; export * from "./behaviortree/BTNode/Composite";