4 Commits
0.1.0 ... 0.1.2

Author SHA1 Message Date
gongxh
80a2438f6e 更新文档 2025-09-23 16:07:04 +08:00
gongxh
50e29feeb8 行为树节点参数添加复合类型参数,更新demo 2025-09-18 22:56:35 +08:00
gongxh
f60bf869a1 修改内置节点的描述信息 2025-09-18 16:49:59 +08:00
gongxh
eb6934ce6a 重新导出行为树配置 2025-10-06 11:15:33 +08:00
17 changed files with 933 additions and 241 deletions

109
README.md
View File

@@ -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<Character>();
import * as BT from "kunpocc-behaviortree";
// 本地数据(仅当前节点可见
node.set('local_count', 1);
const count = node.get<number>('local_count');
// 设置全局数据 所有行为树实例可见
BT.globalBlackboard.set("playerPosition", {x: 100, y: 100});
// 树级数据(整棵树可见)
node.setRoot('tree_data', 'shared');
const shared = node.getRoot<string>('tree_data');
// 在节点中使用黑板数据
export class BTAction extends BT.LeafNode {
public tick(): BT.Status {
// 获取全局数据
const playerPosition = this.getGlobal<{x: number, y: number}>("playerPosition");
console.log(playerPosition);
// 全局数据(所有树可见)
node.setGlobal('global_config', config);
const config = node.getGlobal<Config>('global_config');
// 设置树级数据
this.setRoot("isDead", true);
return Status.SUCCESS;
})
// 获取树级数据
const isDead = this.getRoot<boolean>("isDead");
console.log(isDead);
// 设置节点数据
this.set<boolean>("finished", true);
// 获取节点数据
const finished = this.get<boolean>("finished");
console.log(finished);
return BT.Status.SUCCESS;
}
}
```
## 许可证
ISC License - 详见 [LICENSE](LICENSE) 文件
## 贡献
欢迎提交 Issue 和 Pull Request。请确保

View File

@@ -1,138 +1,8 @@
{
"bt-tree1": [
{
"id": "1759488688188_qejfcso50",
"className": "Selector",
"parameters": {},
"children": [
"1759488707759_2bmdm1fqt",
"1759488725107_v8u160t95",
"1759488737637_axpz9aqaz",
"1759482034741_cf3mqaqdj"
]
},
{
"id": "1759479318405_bptb8ltcp",
"className": "LimitTime",
"parameters": {
"_max": 2
},
"children": [
"1758089736854_t55n54hkh"
]
},
{
"id": "1759479295671_jflit2ek8",
"className": "LimitTime",
"parameters": {
"_max": 2
},
"children": [
"1758089659917_vjumiu9hy"
]
},
{
"id": "1758089659917_vjumiu9hy",
"className": "BTAnimation",
"parameters": {
"_name": "walk",
"_loop": true
},
"children": []
},
{
"id": "1758089736854_t55n54hkh",
"className": "BTAnimation",
"parameters": {
"_name": "run",
"_loop": true
},
"children": []
},
{
"id": "1758089757615_dp9tw9ka1",
"className": "BTAnimation",
"parameters": {
"_name": "jump",
"_loop": false
},
"children": []
},
{
"id": "1759478407706_w30m4btux",
"className": "BTAnimation",
"parameters": {
"_name": "idle",
"_loop": true
},
"children": []
},
{
"id": "1759481172259_xou25wj2n",
"className": "BTConditionRandom",
"parameters": {
"_value": 0.3
},
"children": []
},
{
"id": "1759481282875_5orqavi5y",
"className": "BTConditionRandom",
"parameters": {
"_value": 0.4
},
"children": []
},
{
"id": "1759481307863_ja6q4q9bz",
"className": "BTConditionRandom",
"parameters": {
"_value": 0.3
},
"children": []
},
{
"id": "1759482034741_cf3mqaqdj",
"className": "LimitTime",
"parameters": {
"_max": 3
},
"children": [
"1759478407706_w30m4btux"
]
},
{
"id": "1759488707759_2bmdm1fqt",
"className": "Sequence",
"parameters": {},
"children": [
"1759481172259_xou25wj2n",
"1759479295671_jflit2ek8"
]
},
{
"id": "1759488725107_v8u160t95",
"className": "Sequence",
"parameters": {},
"children": [
"1759481282875_5orqavi5y",
"1759479318405_bptb8ltcp"
]
},
{
"id": "1759488737637_axpz9aqaz",
"className": "Sequence",
"parameters": {},
"children": [
"1759481307863_ja6q4q9bz",
"1758089757615_dp9tw9ka1"
]
}
],
"bt-tree2": [
{
"id": "1757930589538_qisfksbwz",
"className": "MemSequence",
"id": "1758206972710_bhxebhy7o",
"className": "Sequence",
"parameters": {},
"children": [
"1758090634327_mf36nwkdt"
@@ -142,6 +12,17 @@
"id": "1758090634327_mf36nwkdt",
"className": "Selector",
"parameters": {},
"children": [
"1758206988178_55b7kk5va"
]
},
{
"id": "1758206988178_55b7kk5va",
"className": "BTAnimation",
"parameters": {
"_name": "",
"_loop": false
},
"children": []
}
]

View File

@@ -54,7 +54,53 @@ export class BTConditionRandom extends BT.Condition {
private _value: number = 0.5;
public isEligible(): boolean {
return Math.random() > this._value;
return Math.random() < this._value;
}
}
/************************ 下方是几个编辑器中测试用的节点,删除即可 *************************/
@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;
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "bt-tree1",
"description": "",
"name": "bt-tree2",
"description": "这是一个描述",
"nodes": [
{
"id": "1759488688188_qejfcso50",
@@ -15,16 +15,39 @@
"1759488707759_2bmdm1fqt",
"1759488725107_v8u160t95",
"1759488737637_axpz9aqaz",
"1759482034741_cf3mqaqdj"
"1759482034741_cf3mqaqdj",
"1758190139303_t5o7vv3ak"
],
"alias": "根节点"
"alias": "根选择节点"
},
{
"id": "1758190139303_t5o7vv3ak",
"className": "BTTestNode",
"name": "嵌套数据测试节点",
"position": {
"x": 440,
"y": -80
},
"parameters": {
"position": {
"x": 10,
"y": 20
},
"configs": [
{
"name": "hahaa",
"value": 1
}
]
},
"children": []
},
{
"id": "1759479318405_bptb8ltcp",
"className": "LimitTime",
"name": "时间限制器",
"position": {
"x": -40,
"x": -120,
"y": 40
},
"parameters": {
@@ -39,7 +62,7 @@
"className": "LimitTime",
"name": "时间限制器",
"position": {
"x": -360,
"x": -400,
"y": 40
},
"parameters": {
@@ -54,7 +77,7 @@
"className": "BTAnimation",
"name": "播放动画",
"position": {
"x": -360,
"x": -400,
"y": 160
},
"parameters": {
@@ -68,7 +91,7 @@
"className": "BTAnimation",
"name": "播放动画",
"position": {
"x": -40,
"x": -120,
"y": 160
},
"parameters": {
@@ -82,7 +105,7 @@
"className": "BTAnimation",
"name": "播放动画",
"position": {
"x": 260,
"x": 160,
"y": 40
},
"parameters": {
@@ -96,7 +119,7 @@
"className": "BTAnimation",
"name": "播放动画",
"position": {
"x": 420,
"x": 300,
"y": 40
},
"parameters": {
@@ -110,7 +133,7 @@
"className": "BTConditionRandom",
"name": "随机条件节点",
"position": {
"x": -520,
"x": -540,
"y": 40
},
"parameters": {
@@ -123,7 +146,7 @@
"className": "BTConditionRandom",
"name": "随机条件节点",
"position": {
"x": -200,
"x": -260,
"y": 40
},
"parameters": {
@@ -136,7 +159,7 @@
"className": "BTConditionRandom",
"name": "随机条件节点",
"position": {
"x": 120,
"x": 20,
"y": 40
},
"parameters": {
@@ -149,11 +172,11 @@
"className": "LimitTime",
"name": "时间限制器",
"position": {
"x": 420,
"x": 300,
"y": -80
},
"parameters": {
"_max": 3
"_max": 2
},
"children": [
"1759478407706_w30m4btux"
@@ -165,7 +188,7 @@
"className": "Sequence",
"name": "顺序节点",
"position": {
"x": -440,
"x": -480,
"y": -80
},
"parameters": {},
@@ -180,7 +203,7 @@
"className": "Sequence",
"name": "顺序节点",
"position": {
"x": -120,
"x": -200,
"y": -80
},
"parameters": {},
@@ -188,14 +211,14 @@
"1759481282875_5orqavi5y",
"1759479318405_bptb8ltcp"
],
"alias": "奔跑动画分支"
"alias": "奔跑动画"
},
{
"id": "1759488737637_axpz9aqaz",
"className": "Sequence",
"name": "顺序节点",
"position": {
"x": 180,
"x": 80,
"y": -80
},
"parameters": {},
@@ -297,6 +320,18 @@
"targetNodeId": "1758089757615_dp9tw9ka1",
"sourcePointType": "child",
"targetPointType": "parent"
},
{
"id": "conn_1758204108181_90iaioyvg",
"sourceNodeId": "1759488688188_qejfcso50",
"targetNodeId": "1758190139303_t5o7vv3ak",
"sourcePointType": "child",
"targetPointType": "parent"
}
],
"canvasScale": 1.0006385665653545,
"canvasOffset": {
"x": 584.9936143343465,
"y": 498.99074078480237
}
]
}

View File

@@ -3,12 +3,12 @@
"description": "",
"nodes": [
{
"id": "1757930589538_qisfksbwz",
"className": "MemSequence",
"name": "记忆顺序节点",
"id": "1758206972710_bhxebhy7o",
"className": "Sequence",
"name": "顺序节点",
"position": {
"x": -60,
"y": -280
"x": 80,
"y": -320
},
"parameters": {},
"children": [
@@ -20,20 +20,49 @@
"className": "Selector",
"name": "选择节点",
"position": {
"x": 20,
"y": -80
"x": -80,
"y": -220
},
"parameters": {},
"children": [
"1758206988178_55b7kk5va"
],
"alias": "是的发放是的发放"
},
{
"id": "1758206988178_55b7kk5va",
"className": "BTAnimation",
"name": "播放动画",
"position": {
"x": -20,
"y": -40
},
"parameters": {
"_name": "",
"_loop": false
},
"children": []
}
],
"connections": [
{
"id": "conn_1758090635620_zajj5r8g0",
"sourceNodeId": "1757930589538_qisfksbwz",
"id": "conn_1758206976733_208tneycs",
"sourceNodeId": "1758206972710_bhxebhy7o",
"targetNodeId": "1758090634327_mf36nwkdt",
"sourcePointType": "child",
"targetPointType": "parent"
},
{
"id": "conn_1758206989897_46hw88z7h",
"sourceNodeId": "1758090634327_mf36nwkdt",
"targetNodeId": "1758206988178_55b7kk5va",
"sourcePointType": "child",
"targetPointType": "parent"
}
],
"canvasScale": 1.139190980775211,
"canvasOffset": {
"x": 549.4323607689915,
"y": 698.6185343759718
}
]
}

View File

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

View File

@@ -5,7 +5,7 @@
"version": "3.8.6"
},
"dependencies": {
"kunpocc-behaviortree": "^0.1.0",
"kunpocc-behaviortree": "^0.1.1",
"ts-node": "^10.9.2"
}
}

View File

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

335
docs/USED.md Normal file
View File

@@ -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<T>(config: INodeConfig[], entity: T): BehaviorTree<T>
// 内部工作流程:
// 1. 验证配置格式
// 2. 创建节点映射表 (id -> config)
// 3. 递归创建节点树
// - 通过className查找构造函数
// - 根据节点类型选择创建方式
// - 设置节点参数
// - 建立父子关系
// 4. 返回行为树实例
```
## 五、更新声明
## 0.0.1 (2025-09-23)
- 首版本
## 六、联系作者
* 邮箱: gong.xinhai@163.com
* 微信: G0900901
* 扫码加微信:
## 七、版权声明
此插件源代码可商业使用
商业授权范围仅限于在您自行开发的游戏作品中使用
不得进行任何形式的转售、租赁、传播等
## 八、购买须知
本产品为付费虚拟商品,一经购买成功概不退款,请在购买前谨慎确认购买内容。

BIN
image/bt-gui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

BIN
image/image_tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "kunpocc-behaviortree",
"version": "0.1.0",
"version": "0.1.2",
"description": "行为树",
"main": "./dist/kunpocc-behaviortree.cjs",
"module": "./dist/kunpocc-behaviortree.mjs",

View File

@@ -11,7 +11,9 @@ export namespace BT {
int = "number",
float = "float",
string = "string",
bool = "boolean"
bool = "boolean",
object = "object",
array = "array"
}
/**
@@ -46,6 +48,12 @@ export namespace BT {
min?: number,
/** 最大值 */
max?: number,
/** 对象属性定义 - 仅当type为object时使用 */
properties?: { [key: string]: Omit<ParameterInfo, "name"> };
/** 数组元素类型 - 仅当type为array时使用 */
itemType?: ParamType;
/** 数组元素对象定义 - 仅当type为array且itemType为object时使用 */
itemProperties?: { [key: string]: Omit<ParameterInfo, "name"> };
}
/**

View File

@@ -17,11 +17,7 @@ export abstract class LeafNode extends BTNode {
* 次数内返回RUNNING
* 超次返回SUCCESS
*/
@BT.ClassAction("WaitTicks", {
name: "等待次数",
group: "基础行为节点",
desc: "等待指定次数后返回成功",
})
@BT.ClassAction("WaitTicks", { name: "次数等待节点", group: "基础行为节点", desc: "指定次数后返回成功, 否则返回执行中" })
export class WaitTicks extends LeafNode {
@BT.prop({ type: BT.ParamType.int, description: "最大等待次数", defaultValue: 0, step: 1 })
private _max: number;
@@ -50,11 +46,7 @@ export class WaitTicks extends LeafNode {
* 时间等待节点 时间(秒)
* 时间到后返回SUCCESS否则返回RUNNING
*/
@BT.ClassAction("WaitTime", {
name: "等待时间",
group: "基础行为节点",
desc: "等待指定时间(秒)后返回成功",
})
@BT.ClassAction("WaitTime", { name: "时间等待节点", group: "基础行为节点", desc: "等待指定时间(秒)后返回成功, 否则返回执行中" })
export class WaitTime extends LeafNode {
@BT.prop({ type: BT.ParamType.float, description: "等待时间(秒)", defaultValue: 0, step: 0.01 })
private _max: number;

View File

@@ -21,7 +21,7 @@ export abstract class Composite extends BTNode {
*
* 遇到 RUNNING 返回 RUNNING 下次从该节点开始
*/
@BT.ClassComposite("Selector", { name: "选择节点", group: "基础组合节点", desc: "选择节点" })
@BT.ClassComposite("Selector", { name: "选择节点", group: "基础组合节点", desc: "子节点从左到右执行, 子节点状态: 成功则选择成立, 失败继续下一个, 执行中则返回执行中, 下次从该节点开始" })
export class Selector extends Composite {
public override _initialize(global: IBlackboard, branch: IBlackboard): void {
super._initialize(global, branch);
@@ -57,7 +57,7 @@ export class Selector extends Composite {
*
* 遇到 RUNNING 返回 RUNNING 下次从该节点开始
*/
@BT.ClassComposite("Sequence", { name: "顺序节点", group: "基础组合节点", desc: "顺序节点" })
@BT.ClassComposite("Sequence", { name: "顺序节点", group: "基础组合节点", desc: "子节点从左到右执行, 子节点状态: 成功则继续下一个, 失败则停止迭代返回失败, 执行中返回执行中, 下次从该节点开始" })
export class Sequence extends Composite {
public override _initialize(global: IBlackboard, branch: IBlackboard): void {
super._initialize(global, branch);
@@ -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;
@@ -112,11 +113,7 @@ export class Parallel extends Composite {
* 随机选择一个子节点执行
* 返回子节点状态
*/
@BT.ClassComposite("RandomSelector", {
name: "随机选择节点",
group: "基础组合节点",
desc: "随机选择一个子节点执行",
})
@BT.ClassComposite("RandomSelector", { name: "随机选择节点", group: "基础组合节点", desc: "随机选择一个子节点执行, 返回子节点状态" })
export class RandomSelector extends Composite {
private _totalWeight: number = 0;
private _weights: number[] = [];
@@ -165,14 +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;

View File

@@ -40,7 +40,7 @@ export abstract class ConditionDecorator extends Decorator {
* 第一个Child Node节点, 返回 FAILURE, 本Node向自己的Parent Node也返回 SUCCESS
* 第一个Child Node节点, 返回 SUCCESS, 本Node向自己的Parent Node也返回 FAILURE
*/
@BT.ClassDecorator("Inverter", { name: "反转器", group: "基础装饰节点", desc: "反转子节点的执行结果成功变失败失败变成功" })
@BT.ClassDecorator("Inverter", { name: "结果反转节点", group: "基础装饰节点", desc: "反转子节点的执行结果, 成功变失败, 失败变成功, 执行中保持不变" })
export class Inverter extends Decorator {
public tick(dt: number): Status {
const status = this.children[0]!._execute(dt);
@@ -61,7 +61,7 @@ export class Inverter extends Decorator {
* 规定时间内, 根据Child Node的结果, 本节点向自己的父节点也返回相同的结果
* 超时后, 直接返回 FAILURE
*/
@BT.ClassDecorator("LimitTime", { name: "时间限制", group: "基础装饰节点", desc: "限制子节点执行时间,超时返回失败" })
@BT.ClassDecorator("LimitTime", { name: "时间限制节点", group: "基础装饰节点", desc: "限制时间内返回子节点状态, 超时返回失败" })
export class LimitTime extends Decorator {
@BT.prop({ type: BT.ParamType.float, description: "最大时间(秒)", defaultValue: 1 })
protected _max: number = 1;
@@ -96,7 +96,7 @@ export class LimitTime extends Decorator {
* 必须且只能包含一个子节点
* 次数超过后, 直接返回失败; 次数未超过, 返回子节点状态
*/
@BT.ClassDecorator("LimitTicks", { name: "次数限制", group: "基础装饰节点", desc: "限制子节点执行次数,超过次数返回失败" })
@BT.ClassDecorator("LimitTicks", { name: "次数限制节点", group: "基础装饰节点", desc: "子节点成功, 次数+1, 限制次数内返回子节点状态, 超过限制次数返回失败" })
export class LimitTicks extends Decorator {
@BT.prop({ type: BT.ParamType.int, description: "最大次数", defaultValue: 1 })
protected _max: number = 1;
@@ -130,7 +130,7 @@ export class LimitTicks extends Decorator {
* 子节点是成功或失败,累加计数
* 次数超过之后返回子节点状态,否则返回 RUNNING
*/
@BT.ClassDecorator("Repeat", { name: "重复节点", group: "基础装饰节点", desc: "重复执行子节点指定次数" })
@BT.ClassDecorator("Repeat", { name: "重复节点", group: "基础装饰节点", desc: "子节点成功或失败次数+1, 重复执行指定次数" })
export class Repeat extends Decorator {
@BT.prop({ type: BT.ParamType.int, description: "重复次数", defaultValue: 1, min: 1 })
protected _max: number = 1;
@@ -167,7 +167,7 @@ export class Repeat extends Decorator {
*
* 子节点成功 计数+1
*/
@BT.ClassDecorator("RepeatUntilFailure", { name: "重复直到失败", group: "基础装饰节点", desc: "重复执行子节点直到失败或达到最大次数" })
@BT.ClassDecorator("RepeatUntilFailure", { name: "重复直到失败", group: "基础装饰节点", desc: "子节点成功则次数+1, 限制次数内返回执行中, 重复执行子节点直到子节点返回失败, 超过限制次数返回失败" })
export class RepeatUntilFailure extends Decorator {
@BT.prop({ type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, min: 1 })
protected _max: number = 1;
@@ -205,7 +205,7 @@ export class RepeatUntilFailure extends Decorator {
*
* 子节点失败, 计数+1
*/
@BT.ClassDecorator("RepeatUntilSuccess", { name: "重复直到成功", group: "基础装饰节点", desc: "重复执行子节点直到成功或达到最大次数" })
@BT.ClassDecorator("RepeatUntilSuccess", { name: "重复直到成功", group: "基础装饰节点", desc: "子节点失败则次数+1, 限制次数内返回执行中, 重复执行子节点直到子节点返回成功, 超过限制次数返回失败" })
export class RepeatUntilSuccess extends Decorator {
@BT.prop({ type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, step: 1 })
protected _max: number = 1;
@@ -240,7 +240,7 @@ export class RepeatUntilSuccess extends Decorator {
/**
* 权重装饰节点
*/
@BT.ClassDecorator("WeightDecorator", { name: "权重装饰", group: "基础装饰节点", desc: "权重装饰节点" })
@BT.ClassDecorator("WeightDecorator", { name: "权重装饰节点", group: "基础装饰节点", desc: "根据权重随机选择子节点执行, 用于随机选择节点的子节点" })
export class WeightDecorator extends Decorator {
@BT.prop({ type: BT.ParamType.int, description: "权重", defaultValue: 1, step: 1 })
private _weight: number;

View File

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