diff --git a/INTRODUCE.md b/INTRODUCE.md new file mode 100644 index 0000000..91affb3 --- /dev/null +++ b/INTRODUCE.md @@ -0,0 +1,1466 @@ +# 行为树深度解析 + + + +## 下载地址 + +#### github仓库: [https://github.com/gongxh0901/kunpocc-behaviortree](https://github.com/gongxh0901/kunpocc-behaviortree) + +#### 可视化工具地址: 敬请期待 + + + +## 第一部分:基础概念篇 + +### 1.1 什么是行为树? + +#### 1.1.1 从有限状态机的局限性说起 + +在游戏AI开发中,有限状态机(FSM)曾经是主流的解决方案。想象一个简单的NPC守卫: + +``` +巡逻 → 发现敌人 → 追击 → 失去目标 → 返回巡逻 +``` + +这看起来很直观,但当行为变得复杂时,问题就出现了: + +**状态爆炸问题**:当我们需要添加"受伤时逃跑"、"血量低时求援"、"装备不同武器时使用不同攻击方式"等行为时,状态数量会呈指数级增长。一个稍微复杂的Boss可能需要几十个状态和上百个转换条件。 + +**维护困难**:状态之间的转换关系错综复杂,修改一个状态可能影响多个其他状态,代码变得难以维护和调试。 + +**复用性差**:每个NPC的状态机都是独立的,很难复用已有的行为逻辑。 + +#### 1.1.2 行为树的核心思想:模块化决策 + +行为树(Behavior Tree)的出现就是为了解决这些问题。它的核心思想是**将复杂的决策过程分解为简单的、可复用的模块**。 + +行为树不是用"状态"来描述AI,而是用"行为"。每个行为都是一个独立的模块,可以组合成更复杂的行为。就像搭积木一样,我们可以用基础的行为块构建出复杂的AI逻辑。 + +**关键优势**: + +- **模块化**:每个行为节点职责单一,易于理解和测试 +- **可复用**:基础行为可以在不同的AI中重复使用 +- **易扩展**:添加新行为不会影响现有逻辑 +- **直观性**:树形结构更符合人类的思维方式 + +#### 1.1.3 与传统AI方法的对比 + +| 方法 | 优势 | 劣势 | 适用场景 | +|------|------|------|----------| +| **有限状态机** | 简单直观,性能好 | 状态爆炸,难维护 | 简单的线性行为 | +| **脚本系统** | 灵活性高,易于修改 | 性能差,难以调试 | 剧情驱动的行为 | +| **规则系统** | 逻辑清晰,易于理解 | 规则冲突,优先级复杂 | 基于条件的决策 | +| **行为树** | 模块化,可复用,易扩展 | 学习成本,内存开销 | 复杂的游戏AI | + +### 1.2 行为树的基本结构 + +#### 1.2.1 树形结构的天然优势 + +![image](./image/introduce/tree-example1.png) + +行为树采用树形结构有着深刻的原因: + +**层次化决策**:树形结构天然支持从抽象到具体的层次化决策。根节点代表最高层的决策,叶子节点代表具体的行为执行。 + +**自然的优先级**:树的遍历顺序天然地表达了行为的优先级关系。左边的子树通常比右边的子树优先级更高。 + +**易于理解**:人类的思维过程本身就是树形的。"如果敌人在附近,那么攻击;否则,如果血量低,那么逃跑;否则,继续巡逻。" + +#### 1.2.2 节点类型概览:叶子节点 vs 组合节点 + +行为树的节点主要分为两大类 + +**无子节点**:`行为节点`(也可以叫`动作节点` 或者 `任务节点`)、 条件节点 + +* 树的终端节点,负责具体的行为执行或条件判断 +* 是行为树的"执行单元" + +**有子节点**:`组合节点`、`装饰节点` + +* 拥有一个或多个子节点的内部节点 +* 负责控制子节点的执行顺序和逻辑 +* 是行为树的"控制单元" + +#### 1.2.3 执行流程:从根到叶的遍历机制 + +行为树的执行遵循简单而强大的规则: + +1. **从根节点开始**:每次执行(称为一次"Tick")都从根节点开始 +2. **深度优先遍历**:按照深度优先的顺序遍历树节点 +3. **状态返回**:每个节点执行后返回三种状态之一: + - **Success(成功)**:节点成功完成 + - **Failure(失败)**:节点执行失败 + - **Running(运行中)**:节点正在执行,需要在下次Tick时继续 + +4. **条件控制**:组合节点根据子节点的返回状态决定下一步行为 + +**简单示例**: + +![image](./image/introduce/tree-example2.png) + +执行逻辑: +1. 检查是否发现敌人 +2. 如果发现敌人,执行攻击行为 +3. 如果没有发现敌人,执行巡逻行为 + +这种简单而强大的机制让我们能够构建出复杂而灵活的AI行为。 + +## 第二部分:核心节点类型 + +### 2.1 叶子节点(Leaf Nodes) + +叶子节点是行为树的执行终端,负责具体的行为实现。它们是整个行为树系统的"工作马",承担着实际的游戏逻辑执行。 + +#### 2.1.1 Action节点:具体行为的执行单元 + +Action节点是最基础也是最重要的节点类型,它们执行具体的游戏行为。 + +**核心特征**: + +- **有副作用**:Action节点会改变游戏世界的状态 +- **可能耗时**:某些Action可能需要多帧才能完成 +- **状态丰富**:可以返回Success、Failure、Running三种状态 + +**常见的Action节点类型**: + +``` +移动类Action: +├── MoveTo(移动到指定位置) +├── Patrol(巡逻) +├── Follow(跟随目标) +└── Flee(逃跑) + +战斗类Action: +├── Attack(攻击) +├── CastSpell(释放技能) +├── Block(格挡) +└── Dodge(闪避) + +交互类Action: +├── PickupItem(拾取物品) +├── OpenDoor(开门) +├── Talk(对话) +└── UseItem(使用物品) +``` + +**实现示例**: +```typescript +class AttackAction extends ActionNode { + private attack_duration: number = 1.0; // 攻击持续时间 + private current_time: number = 0; + + public tick(): NodeStatus { + if (this.current_time === 0) { + // 开始攻击动画 + this.startAttackAnimation(); + } + + this.current_time += deltaTime; + + if (this.current_time >= this.attack_duration) { + // 攻击完成 + this.dealDamage(); + this.current_time = 0; + return NodeStatus.SUCCESS; + } + + return NodeStatus.RUNNING; + } +} +``` + +#### 2.1.2 返回状态详解 + +Action节点的返回状态是行为树控制流的基础: + +**Success(成功)**: +- 行为成功完成,通常表示可以继续执行下一个行为 +- 例如:成功移动到目标位置、成功攻击敌人 + +**Failure(失败)**: +- 行为执行失败,通常需要尝试其他行为或重新规划 +- 例如:路径被阻挡无法移动、攻击目标已死亡 + +**Running(运行中)**: + +- 行为正在执行中,需要继续,下一帧会继续执行这个节点 +- 例如:正在移动中、攻击动画播放中 + +### 2.2 条件节点(叶子节点的特殊实现) + +条件节点是叶子节点的特殊实现,专门用于判断游戏状态,为行为树提供决策依据。 + +#### 2.2.1 Condition节点:条件判断的基础 + +**核心特征**: +- **无副作用**:只读取游戏状态,不修改任何数据 +- **瞬时执行**:通常在一帧内完成 +- **纯判断**:只返回Success或Failure,表示条件是否满足 + +#### 2.2.2 与Action节点的区别 + +| 特性 | Action节点 | Condition节点 | +|------|------------|---------------| +| **副作用** | 有,会改变游戏状态 | 无,只读取状态 | +| **执行时间** | 可能多帧 | 通常单帧 | +| **返回状态** | Success/Failure/Running | Success/Failure | +| **职责** | 执行具体行为 | 判断条件 | + +#### 2.2.3 常见条件类型 + +**距离检测类**: +```typescript +class IsEnemyInRange extends ConditionNode { + private attack_range: number = 5.0; + + public tick(): NodeStatus { + const enemy = this.blackboard.get("target_enemy"); + const distance = this.calculateDistance(this.owner, enemy); + + return distance <= this.attack_range ? + NodeStatus.SUCCESS : NodeStatus.FAILURE; + } +} +``` + +**血量判断类**: +```typescript +class IsHealthLow extends ConditionNode { + private low_health_threshold: number = 0.3; // 30% + + public tick(): NodeStatus { + const current_health = this.owner.getHealthPercentage(); + return current_health < this.low_health_threshold ? NodeStatus.SUCCESS : NodeStatus.FAILURE; + } +} +``` + +**状态查询类**: +```typescript +class HasAmmo extends ConditionNode { + public tick(): NodeStatus { + const ammo_count = this.owner.getAmmoCount(); + return ammo_count > 0 ? NodeStatus.SUCCESS : NodeStatus.FAILURE; + } +} +``` + +### 2.3 组合节点(Composite Nodes) + +组合节点是行为树的控制核心,它们决定了子节点的执行顺序和逻辑关系。 + +#### 2.3.1 Sequence节点:顺序执行,全部成功才成功 + +Sequence节点按顺序执行所有子节点,只有当所有子节点都返回Success时,Sequence才返回Success。 + +**执行逻辑**: +1. 从左到右依次执行子节点 +2. 如果子节点返回Success,继续执行下一个 +3. 如果子节点返回Failure,整个Sequence返回Failure +4. 如果子节点返回Running,Sequence也返回Running,下次从这个节点继续 + +**典型应用场景**: +``` +攻击序列(Sequence) +├── 检查是否有目标(Condition) +├── 移动到攻击范围(Action) +├── 面向目标(Action) +└── 执行攻击(Action) +``` + +**实现示例**: +```typescript +class SequenceNode extends CompositeNode { + private current_child_index: number = 0; + + public tick(): NodeStatus { + while (this.current_child_index < this.children.length) { + const child = this.children[this.current_child_index]; + const status = child.tick(); + + switch (status) { + case NodeStatus.SUCCESS: + this.current_child_index++; + continue; // 继续下一个子节点 + + case NodeStatus.FAILURE: + this.current_child_index = 0; // 重置 + return NodeStatus.FAILURE; + + case NodeStatus.RUNNING: + return NodeStatus.RUNNING; // 等待当前子节点完成 + } + } + + // 所有子节点都成功 + this.current_child_index = 0; + return NodeStatus.SUCCESS; + } +} +``` + +#### 2.3.2 Selector节点:选择执行,一个成功即成功 + +Selector节点(也称为Fallback节点)按顺序尝试执行子节点,只要有一个子节点返回Success,Selector就返回Success。 + +**执行逻辑**: +1. 从左到右依次尝试子节点 +2. 如果子节点返回Success,整个Selector返回Success +3. 如果子节点返回Failure,尝试下一个子节点 +4. 如果子节点返回Running,Selector也返回Running + +**典型应用场景**: +``` +战斗策略(Selector) +├── 远程攻击(Sequence) +│ ├── 有弹药?(Condition) +│ └── 射击(Action) +├── 近战攻击(Sequence) +│ ├── 敌人在近战范围?(Condition) +│ └── 挥砍(Action) +└── 逃跑(Action) +``` + +#### 2.3.3 Parallel节点:并行执行,灵活的成功条件 + +Parallel节点同时执行多个子节点,根据预设的成功条件决定返回状态。 + +**成功策略**: +- **RequireOne**:至少一个子节点成功 +- **RequireAll**:所有子节点都成功 +- **RequireCount(N)**:至少N个子节点成功 + +**典型应用场景**: +``` +巡逻并警戒(Parallel - RequireOne) +├── 沿路径巡逻(Action) +├── 扫描敌人(Action) +└── 播放巡逻音效(Action) +``` + +### 2.4 装饰节点(Decorator Nodes) + +装饰节点用于修饰子节点的行为,提供额外的控制逻辑。 + +#### 2.4.1 Inverter:反转子节点结果 + +将子节点的Success和Failure结果互换,Running保持不变。 + +**应用场景**: +``` +如果没有敌人(Inverter) +└── 发现敌人?(Condition) +``` + +#### 2.4.2 Repeater:重复执行逻辑 + +重复执行子节点指定次数或无限次。 + +**变种类型**: +- **RepeatN**:重复N次 +- **RepeatUntilFail**:重复直到失败 +- **RepeatUntilSuccess**:重复直到成功 + +**应用场景**: +``` +持续巡逻(RepeatForever) +└── 巡逻一圈(Action) +``` + +#### 2.4.3 Timer:时间控制机制 + +为子节点添加时间限制或延迟。 + +**变种类型**: +- **Timeout**:超时后返回Failure +- **Delay**:延迟指定时间后执行 +- **Cooldown**:冷却时间控制 + +**应用场景**: +``` +限时攻击(Timeout: 3秒) +└── 连续攻击(Action) +``` + +通过这些核心节点类型的组合,我们可以构建出复杂而灵活的AI行为系统。每种节点都有其特定的用途和最佳实践,理解它们的特性是设计优秀行为树的基础。 + +## 第三部分:执行机制深入 + +### 3.1 Tick机制 + +Tick机制是行为树的心脏,理解它是掌握行为树执行原理的关键。 + +#### 3.1.1 什么是Tick?为什么需要Tick? + +**Tick的定义**: +Tick是行为树的一次完整执行周期。在每个游戏帧中,行为树会被"Tick"一次,从根节点开始遍历整个树结构。 + +**为什么需要Tick机制?** + +**时间分片执行**: +游戏需要保持稳定的帧率(如60FPS),这意味着每帧只有约16.67毫秒的处理时间。复杂的AI行为可能需要多帧才能完成,Tick机制允许行为在多帧之间分片执行。 + +**状态持续性**: +某些行为(如移动、攻击动画)需要持续一段时间。Tick机制让这些行为能够在多次Tick之间保持状态。 + +**响应性**: +每帧都执行Tick确保AI能够及时响应游戏世界的变化,如玩家位置改变、血量变化等。 + +#### 3.1.2 Tick的传播路径 + +Tick在行为树中的传播遵循严格的规则: + +**深度优先遍历**: +``` +根节点(Selector) +├── 战斗行为(Sequence) ← 优先执行 +│ ├── 发现敌人?(Condition) +│ └── 攻击(Action) +└── 巡逻行为(Action) ← 未发现敌人时执行 +``` + +**传播示例**: +```typescript +class BehaviorTree { + private root_node: Node; + + public tick(): void { + // 每帧调用一次 + if (this.root_node) { + this.root_node.tick(); + } + } +} + +abstract class Node { + public tick(): NodeStatus { + // 每个节点的基础Tick逻辑 + this.onEnter(); // 节点首次执行时调用 + const status = this.execute(); // 执行节点逻辑 + + if (status !== NodeStatus.RUNNING) { + this.onExit(); // 节点完成时调用 + } + + return status; + } + + protected abstract execute(): NodeStatus; + protected onEnter(): void {} + protected onExit(): void {} +} +``` + +#### 3.1.3 状态的维护与更新 + +**节点状态生命周期**: + +1. **首次Tick**:节点从未执行状态变为执行状态 +2. **持续Tick**:节点保持Running状态,继续执行 +3. **完成Tick**:节点返回Success或Failure,状态重置 + +**状态维护示例**: +```typescript +class MoveToAction extends ActionNode { + private target_position: Vector3; + private is_moving: boolean = false; + private move_speed: number = 5.0; + + protected execute(): NodeStatus { + if (!this.is_moving) { + // 首次执行:开始移动 + this.startMovement(); + this.is_moving = true; + } + + // 持续执行:更新位置 + const distance = this.updateMovement(); + + if (distance < 0.1) { + // 到达目标:完成移动 + this.is_moving = false; + return NodeStatus.SUCCESS; + } + + return NodeStatus.RUNNING; + } + + protected onExit(): void { + // 清理状态 + this.is_moving = false; + } +} +``` + +### 3.2 黑板系统(Blackboard) + +黑板系统是行为树中的全局数据共享机制,解决了节点之间的数据通信问题。 + +#### 3.2.1 全局数据共享的必要性 + +**问题场景**: +想象一个复杂的Boss AI,它需要: +- 记住玩家的最后位置 +- 跟踪自己的血量状态 +- 管理技能冷却时间 +- 协调多个行为之间的数据 + +如果每个节点都独立管理数据,会导致: +- **数据重复**:多个节点存储相同信息 +- **同步困难**:数据更新时需要通知所有相关节点 +- **耦合严重**:节点之间需要直接引用才能共享数据 + +**黑板系统的解决方案**: +黑板系统提供了一个中央数据存储,所有节点都可以读写共享数据。 + +#### 3.2.2 黑板的读写机制 + +**基础实现**: +```typescript +class Blackboard { + private data_map: Map = new Map(); + + public set(key: string, value: T): void { + this.data_map.set(key, value); + } + + public get(key: string): T | undefined { + return this.data_map.get(key) as T; + } + + public has(key: string): boolean { + return this.data_map.has(key); + } + + public remove(key: string): boolean { + return this.data_map.delete(key); + } +} +``` + +#### 3.2.3 避免数据竞争的设计原则 + +**读写分离原则**: +- **Condition节点**:只读取黑板数据,不修改 +- **Action节点**:可以读取和修改黑板数据 +- **明确职责**:每个数据项应该有明确的写入者 + +**数据所有权原则**: +```typescript +// 好的设计:明确的数据所有权 +class PatrolAction extends ActionNode { + protected execute(): NodeStatus { + // 只修改自己负责的数据 + this.blackboard.set(CURRENT_PATROL_POINT, this.next_point); + this.blackboard.set(PATROL_DIRECTION, this.direction); + return NodeStatus.SUCCESS; + } +} + +class EnemyDetectionAction extends ActionNode { + protected execute(): NodeStatus { + // 只修改自己负责的数据 + const enemy = this.scanForEnemies(); + this.blackboard.set(DETECTED_ENEMY, enemy); + this.blackboard.set(LAST_ENEMY_POSITION, enemy?.position); + return enemy ? NodeStatus.SUCCESS : NodeStatus.FAILURE; + } +} +``` + +### 3.3 状态管理 + +状态管理是行为树稳定运行的基础,涉及节点状态的创建、维护和清理。 + +#### 3.3.1 节点状态的生命周期 + +**节点状态转换图**: +![image](./image/introduce/node-status.png) + +**生命周期管理**: +```typescript +abstract class Node { + private current_status: NodeStatus = NodeStatus.INACTIVE; + private is_first_tick: boolean = true; + + public tick(): NodeStatus { + if (this.is_first_tick) { + this.onEnter(); + this.is_first_tick = false; + } + + this.current_status = this.execute(); + + if (this.current_status !== NodeStatus.RUNNING) { + this.onExit(); + this.is_first_tick = true; // 为下次执行做准备 + } + + return this.current_status; + } + + protected onEnter(): void { + // 节点开始执行时的初始化逻辑 + } + + protected onExit(): void { + // 节点完成执行时的清理逻辑 + } + + protected abstract execute(): NodeStatus; +} +``` + +#### 3.3.2 Running状态的特殊处理 + +Running状态是行为树中最复杂的状态,需要特殊处理: + +**Running状态的特点**: +- 节点正在执行中,需要在下次Tick时继续 +- 节点的内部状态需要保持 +- 父节点需要等待子节点完成 + +**Running状态处理示例**: +```typescript +class SequenceNode extends CompositeNode { + private current_child_index: number = 0; + private running_child: Node | null = null; + + protected execute(): NodeStatus { + // 如果有正在运行的子节点,继续执行它 + if (this.running_child) { + const status = this.running_child.tick(); + + if (status === NodeStatus.RUNNING) { + return NodeStatus.RUNNING; // 继续等待 + } + + // 子节点完成了 + this.running_child = null; + + if (status === NodeStatus.FAILURE) { + this.reset(); + return NodeStatus.FAILURE; + } + + // 成功,继续下一个子节点 + this.current_child_index++; + } + + // 执行剩余的子节点 + while (this.current_child_index < this.children.length) { + const child = this.children[this.current_child_index]; + const status = child.tick(); + + if (status === NodeStatus.RUNNING) { + this.running_child = child; + return NodeStatus.RUNNING; + } + + if (status === NodeStatus.FAILURE) { + this.reset(); + return NodeStatus.FAILURE; + } + + this.current_child_index++; + } + + // 所有子节点都成功 + this.reset(); + return NodeStatus.SUCCESS; + } + + private reset(): void { + this.current_child_index = 0; + this.running_child = null; + } +} +``` + +#### 3.3.3 状态重置与清理机制 + +**自动重置机制**: +```typescript +class BehaviorTree { + private root_node: Node; + private last_tick_result: NodeStatus = NodeStatus.INACTIVE; + + public tick(): void { + const current_result = this.root_node.tick(); + + // 如果树的执行状态发生变化,可能需要重置某些状态 + if (this.last_tick_result !== current_result) { + this.onStatusChanged(this.last_tick_result, current_result); + } + + this.last_tick_result = current_result; + } + + private onStatusChanged(old_status: NodeStatus, new_status: NodeStatus): void { + if (old_status === NodeStatus.RUNNING && new_status !== NodeStatus.RUNNING) { + // 从运行状态转为完成状态,清理临时数据 + this.cleanupTemporaryData(); + } + } + + private cleanupTemporaryData(): void { + // 清理临时的黑板数据 + this.blackboard.remove("temporary_target"); + this.blackboard.remove("movement_path"); + } +} +``` + +**手动重置接口**: +```typescript +interface Resettable { + reset(): void; +} + +class ActionNode extends Node implements Resettable { + public reset(): void { + this.onExit(); + this.current_status = NodeStatus.INACTIVE; + this.is_first_tick = true; + } +} + +// 使用场景:AI状态切换时重置行为树 +class AIController { + private behavior_tree: BehaviorTree; + + public switchBehavior(new_tree: BehaviorTree): void { + // 重置当前行为树 + this.behavior_tree.reset(); + + // 切换到新的行为树 + this.behavior_tree = new_tree; + } +} +``` + +通过深入理解这些执行机制,我们可以构建出稳定、高效的行为树系统。Tick机制保证了行为的时间分片执行,黑板系统解决了数据共享问题,状态管理确保了系统的稳定性。这三个机制相互配合,构成了行为树的执行基础。 + +## 第四部分:游戏AI实战应用 + +### 4.1 NPC行为设计 + +NPC(非玩家角色)是游戏世界的重要组成部分,良好的NPC行为设计能够极大提升游戏的沉浸感和趣味性。 + +#### 4.1.1 基础巡逻行为构建 + +巡逻是最常见的NPC行为,看似简单但包含了行为树设计的核心要素。 + +**简单巡逻行为树**: + +![image](./image/introduce/example-move.png) + +**增强版巡逻行为树**: + +![image](./image/introduce/example-move2.png) + +**节点代码示例**: +```typescript +// 巡逻点数据结构 +interface PatrolPoint { + position: Vector3; + wait_time: number; + look_direction?: Vector3; +} + +class PatrolAction extends ActionNode { + private patrol_points: PatrolPoint[] = []; + private current_point_index: number = 0; + private wait_timer: number = 0; + private patrol_state: 'moving' | 'waiting' = 'moving'; + + constructor(patrol_points: PatrolPoint[]) { + super(); + this.patrol_points = patrol_points; + } + + protected execute(): NodeStatus { + const current_point = this.patrol_points[this.current_point_index]; + + switch (this.patrol_state) { + case 'moving': + return this.handleMovement(current_point); + case 'waiting': + return this.handleWaiting(current_point); + } + } + + private handleMovement(point: PatrolPoint): NodeStatus { + const distance = this.owner.moveTo(point.position); + + if (distance < 0.5) { + // 到达巡逻点 + this.patrol_state = 'waiting'; + this.wait_timer = point.wait_time; + + // 设置朝向 + if (point.look_direction) { + this.owner.lookAt(point.look_direction); + } + } + + return NodeStatus.RUNNING; + } + + private handleWaiting(point: PatrolPoint): NodeStatus { + this.wait_timer -= this.getDeltaTime(); + + if (this.wait_timer <= 0) { + // 等待完成,移动到下一个点 + this.current_point_index = (this.current_point_index + 1) % this.patrol_points.length; + this.patrol_state = 'moving'; + return NodeStatus.SUCCESS; + } + + return NodeStatus.RUNNING; + } +} +``` + +#### 4.1.2 警戒与追击逻辑 + +警戒系统让NPC能够感知并响应玩家的行为,是游戏AI的核心功能。 + +**警戒行为树**: + +![image](./image/introduce/example-scanning1.png) + +**警戒状态设计**: +```typescript +enum AlertLevel { + CALM = 0, // 平静状态 + SUSPICIOUS = 1, // 怀疑状态 + ALERT = 2, // 警戒状态 + COMBAT = 3 // 战斗状态 +} + +class AlertSystem { + private current_level: AlertLevel = AlertLevel.CALM; + private alert_timer: number = 0; + private suspicion_points: number = 0; + + public updateAlert(detected_threat: boolean, delta_time: number): AlertLevel { + if (detected_threat) { + this.increaseSuspicion(); + } else { + this.decreaseSuspicion(delta_time); + } + + return this.updateAlertLevel(); + } + + private increaseSuspicion(): void { + this.suspicion_points += 10; + this.alert_timer = 5.0; // 重置警戒时间 + } + + private decreaseSuspicion(delta_time: number): void { + this.alert_timer -= delta_time; + + if (this.alert_timer <= 0) { + this.suspicion_points = Math.max(0, this.suspicion_points - 5); + } + } + + private updateAlertLevel(): AlertLevel { + if (this.suspicion_points >= 100) return AlertLevel.COMBAT; + if (this.suspicion_points >= 60) return AlertLevel.ALERT; + if (this.suspicion_points >= 20) return AlertLevel.SUSPICIOUS; + return AlertLevel.CALM; + } +} +``` + +#### 4.1.3 多状态NPC的复杂行为树 + +复杂的NPC需要处理多种状态和情况,这时需要更精细的行为树设计。 + +**城镇NPC行为树示例**: + +![image](./image/introduce/example-npc1.png) + +### 4.2 Boss AI决策系统 + +Boss战是游戏的高潮部分,需要复杂而有趣的AI行为来挑战玩家。 + +#### 4.2.1 阶段性战斗逻辑设计 + +大多数Boss都有多个战斗阶段,每个阶段有不同的行为模式。 + +**阶段管理系统**: +```typescript +interface BossPhase { + name: string; + health_threshold: number; // 进入该阶段的血量阈值 + behavior_tree: Node; + entry_actions?: Action[]; // 进入阶段时执行的动作 + exit_actions?: Action[]; // 离开阶段时执行的动作 +} + +class BossPhaseManager { + private phases: BossPhase[] = []; + private current_phase_index: number = 0; + private boss_health: number = 1.0; + + constructor(phases: BossPhase[]) { + this.phases = phases.sort((a, b) => b.health_threshold - a.health_threshold); + } + + public updatePhase(current_health_percentage: number): BossPhase { + const new_phase_index = this.findPhaseIndex(current_health_percentage); + + if (new_phase_index !== this.current_phase_index) { + this.transitionToPhase(new_phase_index); + } + + return this.phases[this.current_phase_index]; + } + + private findPhaseIndex(health_percentage: number): number { + for (let i = 0; i < this.phases.length; i++) { + if (health_percentage >= this.phases[i].health_threshold) { + return i; + } + } + return this.phases.length - 1; + } + + private transitionToPhase(new_phase_index: number): void { + // 执行当前阶段的退出动作 + const current_phase = this.phases[this.current_phase_index]; + if (current_phase.exit_actions) { + current_phase.exit_actions.forEach(action => action.execute()); + } + + // 切换到新阶段 + this.current_phase_index = new_phase_index; + + // 执行新阶段的进入动作 + const new_phase = this.phases[new_phase_index]; + if (new_phase.entry_actions) { + new_phase.entry_actions.forEach(action => action.execute()); + } + } +} +``` + +**三阶段Boss行为树**: + +![image](./image/introduce/example-boss.png) + +#### 4.2.2 技能释放的优先级判断 + +Boss通常拥有多种技能,需要智能的优先级系统来决定使用哪个技能。 + +**技能优先级系统**: +```typescript +interface Skill { + name: string; + cooldown: number; + last_used_time: number; + priority_calculator: (context: BattleContext) => number; + can_use: (context: BattleContext) => boolean; + execute: (target: GameObject) => void; +} + +class SkillManager { + private skills: Skill[] = []; + + public selectBestSkill(context: BattleContext): Skill | null { + const available_skills = this.skills.filter(skill => + this.isSkillAvailable(skill) && skill.can_use(context) + ); + + if (available_skills.length === 0) { + return null; + } + + // 根据优先级排序 + available_skills.sort((a, b) => + b.priority_calculator(context) - a.priority_calculator(context) + ); + + return available_skills[0]; + } + + private isSkillAvailable(skill: Skill): boolean { + const current_time = Date.now(); + return (current_time - skill.last_used_time) >= skill.cooldown * 1000; + } +} + +// 技能定义示例 +const FIREBALL_SKILL: Skill = { + name: "火球术", + cooldown: 3.0, + last_used_time: 0, + + priority_calculator: (context: BattleContext) => { + const distance = context.getDistanceToPlayer(); + const player_health = context.getPlayerHealthPercentage(); + + // 距离越远,优先级越高 + // 玩家血量越低,优先级越高 + return distance * 10 + (1 - player_health) * 20; + }, + + can_use: (context: BattleContext) => { + const distance = context.getDistanceToPlayer(); + return distance >= 5.0 && distance <= 15.0; + }, + + execute: (target: GameObject) => { + // 执行火球攻击 + } +}; +``` + +#### 4.2.3 血量触发的行为切换 + +血量是Boss行为变化的重要触发条件,需要平滑的过渡机制。 + +**血量触发器系统**: +```typescript +interface HealthTrigger { + threshold: number; + trigger_once: boolean; + has_triggered: boolean; + on_trigger: () => void; +} + +class HealthTriggerManager { + private triggers: HealthTrigger[] = []; + private last_health: number = 1.0; + + public addTrigger(threshold: number, callback: () => void, trigger_once: boolean = true): void { + this.triggers.push({ + threshold, + trigger_once, + has_triggered: false, + on_trigger: callback + }); + + // 按阈值排序,从高到低 + this.triggers.sort((a, b) => b.threshold - a.threshold); + } + + public updateHealth(current_health: number): void { + for (const trigger of this.triggers) { + if (this.shouldTrigger(trigger, current_health)) { + trigger.on_trigger(); + trigger.has_triggered = true; + } + } + + this.last_health = current_health; + } + + private shouldTrigger(trigger: HealthTrigger, current_health: number): boolean { + if (trigger.trigger_once && trigger.has_triggered) { + return false; + } + + // 血量从高于阈值降到低于阈值时触发 + return this.last_health >= trigger.threshold && current_health < trigger.threshold; + } +} + +// 使用示例 +const boss_triggers = new HealthTriggerManager(); + +boss_triggers.addTrigger(0.75, () => { + console.log("Boss进入第二阶段!"); + boss.playAnimation("roar"); + boss.summonMinions(2); +}); + +boss_triggers.addTrigger(0.5, () => { + console.log("Boss进入狂暴状态!"); + boss.increaseAttackSpeed(1.5); + boss.enableNewSkills(["地震", "陨石"]); +}); + +boss_triggers.addTrigger(0.25, () => { + console.log("Boss濒死反击!"); + boss.activateShield(); + boss.healOverTime(0.1, 10); // 10秒内回复10%血量 +}); +``` + +### 4.3 群体AI协调 + +群体AI让多个NPC能够协同工作,创造更丰富的游戏体验。 + +#### 4.3.1 小队协作行为 + +小队成员需要分工合作,各司其职。 + +**小队角色定义**: +```typescript +enum SquadRole { + LEADER = "leader", // 队长:指挥和决策 + TANK = "tank", // 坦克:吸引火力 + DPS = "dps", // 输出:主要伤害 + SUPPORT = "support" // 支援:治疗和辅助 +} + +interface SquadMember { + id: string; + role: SquadRole; + position: Vector3; + health_percentage: number; + is_in_combat: boolean; +} + +class Squad { + private members: Map = new Map(); + private leader_id: string | null = null; + private formation: Formation; + + public addMember(member: SquadMember): void { + this.members.set(member.id, member); + + if (member.role === SquadRole.LEADER) { + this.leader_id = member.id; + } + } + + public getFormationPosition(member_id: string): Vector3 { + const member = this.members.get(member_id); + const leader = this.getLeader(); + + if (!member || !leader) { + return new Vector3(0, 0, 0); + } + + return this.formation.getPosition(member.role, leader.position); + } + + public getLeader(): SquadMember | null { + return this.leader_id ? this.members.get(this.leader_id) || null : null; + } + + public getNearestAlly(member_id: string): SquadMember | null { + const member = this.members.get(member_id); + if (!member) return null; + + let nearest: SquadMember | null = null; + let min_distance = Infinity; + + for (const [id, ally] of this.members) { + if (id === member_id) continue; + + const distance = Vector3.distance(member.position, ally.position); + if (distance < min_distance) { + min_distance = distance; + nearest = ally; + } + } + + return nearest; + } +} +``` + +**小队协作行为树**: +``` +小队成员AI(Selector) +├── 队长专属行为(Sequence) +│ ├── 是队长?(Condition) +│ └── 队长行为(Selector) +│ ├── 指挥撤退(Sequence) +│ │ ├── 队伍伤亡过重?(Condition) +│ │ └── 发出撤退信号(Action) +│ ├── 调整阵型(Action) +│ └── 标记目标(Action) +├── 支援队友(Sequence) +│ ├── 有队友需要帮助?(Condition) +│ └── 支援行为(Selector) +│ ├── 治疗受伤队友(Sequence) +│ │ ├── 是支援角色?(Condition) +│ │ ├── 队友血量低?(Condition) +│ │ └── 治疗队友(Action) +│ ├── 掩护撤退队友(Action) +│ └── 分担火力(Action) +├── 保持阵型(Sequence) +│ ├── 不在战斗中?(Condition) +│ └── 移动到阵型位置(Action) +└── 个人战斗行为(根据角色类型) +``` + +#### 4.3.2 群体围攻策略 + +多个敌人围攻玩家时需要协调攻击时机,避免互相干扰。 + +**围攻协调器**: +```typescript +class CombatCoordinator { + private attackers: Map = new Map(); + private max_simultaneous_attacks: number = 2; + private attack_cooldown: number = 1.0; + private last_attack_time: number = 0; + + public requestAttack(attacker_id: string): boolean { + const current_time = Date.now() / 1000; + const active_attackers = this.getActiveAttackers(); + + // 检查是否可以攻击 + if (active_attackers.length >= this.max_simultaneous_attacks) { + return false; + } + + if (current_time - this.last_attack_time < this.attack_cooldown) { + return false; + } + + // 批准攻击 + this.attackers.set(attacker_id, { + id: attacker_id, + attack_start_time: current_time, + is_attacking: true + }); + + this.last_attack_time = current_time; + return true; + } + + public finishAttack(attacker_id: string): void { + const attacker = this.attackers.get(attacker_id); + if (attacker) { + attacker.is_attacking = false; + } + } + + private getActiveAttackers(): AttackerInfo[] { + return Array.from(this.attackers.values()).filter(a => a.is_attacking); + } + + public getSuggestedPosition(attacker_id: string, target_position: Vector3): Vector3 { + const active_attackers = this.getActiveAttackers(); + const angle_step = (Math.PI * 2) / this.attackers.size; + const attacker_index = Array.from(this.attackers.keys()).indexOf(attacker_id); + + const angle = angle_step * attacker_index; + const distance = 3.0; // 围攻距离 + + return new Vector3( + target_position.x + Math.cos(angle) * distance, + target_position.y, + target_position.z + Math.sin(angle) * distance + ); + } +} +``` + +#### 4.3.3 分工合作的实现方式 + +不同类型的NPC需要根据自己的特长分工合作。 + +**任务分配系统**: +```typescript +enum TaskType { + ATTACK_PLAYER = "attack_player", + GUARD_AREA = "guard_area", + PATROL_ROUTE = "patrol_route", + SUPPORT_ALLY = "support_ally", + INVESTIGATE_SOUND = "investigate_sound" +} + +interface Task { + id: string; + type: TaskType; + priority: number; + assigned_to: string | null; + target_position?: Vector3; + target_entity?: string; + deadline?: number; +} + +class TaskManager { + private tasks: Map = new Map(); + private available_agents: Set = new Set(); + + public assignTasks(): void { + const unassigned_tasks = Array.from(this.tasks.values()) + .filter(task => task.assigned_to === null) + .sort((a, b) => b.priority - a.priority); + + for (const task of unassigned_tasks) { + const best_agent = this.findBestAgent(task); + if (best_agent) { + this.assignTask(task.id, best_agent); + } + } + } + + private findBestAgent(task: Task): string | null { + let best_agent: string | null = null; + let best_score = -1; + + for (const agent_id of this.available_agents) { + const score = this.calculateAgentScore(agent_id, task); + if (score > best_score) { + best_score = score; + best_agent = agent_id; + } + } + + return best_agent; + } + + private calculateAgentScore(agent_id: string, task: Task): number { + // 根据智能体类型、距离、当前状态等计算适合度分数 + const agent = this.getAgent(agent_id); + let score = 0; + + // 类型匹配度 + if (this.isAgentSuitableForTask(agent, task)) { + score += 50; + } + + // 距离因素 + if (task.target_position && agent.position) { + const distance = Vector3.distance(agent.position, task.target_position); + score += Math.max(0, 20 - distance); // 距离越近分数越高 + } + + // 当前负载 + score -= agent.current_tasks.length * 10; + + return score; + } +} +``` + +### 4.4 最佳实践 + +#### 4.4.1 游戏AI行为树的设计原则 + +**单一职责原则**: +每个节点应该只负责一个明确的功能,避免复杂的复合逻辑。 + +```typescript +// 好的设计:职责单一 +class CheckHealthCondition extends ConditionNode { + constructor(private threshold: number) { super(); } + + protected execute(): NodeStatus { + return this.owner.getHealthPercentage() < this.threshold ? + NodeStatus.SUCCESS : NodeStatus.FAILURE; + } +} + +// 不好的设计:职责混乱 +class ComplexCondition extends ConditionNode { + protected execute(): NodeStatus { + const health_low = this.owner.getHealthPercentage() < 0.3; + const enemy_near = this.detectNearbyEnemies().length > 0; + const has_ammo = this.owner.getAmmoCount() > 0; + + // 复杂的组合逻辑,难以理解和维护 + return (health_low && !enemy_near) || (!health_low && has_ammo) ? + NodeStatus.SUCCESS : NodeStatus.FAILURE; + } +} +``` + +**可预测性原则**: +AI行为应该对玩家来说是可理解和可预测的,避免随机性过强。 + +**性能优先原则**: +行为树每帧都会执行,必须考虑性能影响。 + + + +## 第五部分:行为树的优势与局限 + +### 5.1 适用场景分析 + +行为树在不同场景下有着不同的适用性: + +#### 5.1.1 最适合的场景 +- **复杂NPC行为**:需要多种行为模式切换的角色 +- **Boss AI设计**:多阶段、多技能的复杂战斗逻辑 +- **策略游戏AI**:需要层次化决策的战略AI +- **模拟游戏**:居民、动物等需要丰富行为的角色 + +#### 5.1.2 适合的场景 +- **RPG游戏AI**:各种职业和角色的行为设计 +- **动作游戏敌人**:需要智能反应的敌人AI +- **开放世界NPC**:需要适应不同情况的世界角色 + +#### 5.1.3 不太适合的场景 +- **简单线性行为**:如简单的移动平台,用状态机更合适 +- **纯反应式行为**:如弹球游戏的碰撞反应 +- **高频率决策**:如实时战略游戏的单位寻路 + +#### 5.1.4 不适合的场景 +- **确定性动画序列**:固定的动画播放序列 +- **物理模拟**:基于物理规律的行为 +- **简单触发器**:简单的条件-动作对应关系 + +### 5.2 与其他方法的权衡 + +#### 5.2.1 行为树 vs 有限状态机 + +```typescript +// 状态机适合的场景:简单、线性的状态转换 +class SimpleEnemyFSM { + private current_state: EnemyState = EnemyState.PATROL; + + public update(): void { + switch (this.current_state) { + case EnemyState.PATROL: + if (this.detectPlayer()) { + this.current_state = EnemyState.CHASE; + } + break; + case EnemyState.CHASE: + if (!this.canSeePlayer()) { + this.current_state = EnemyState.SEARCH; + } else if (this.inAttackRange()) { + this.current_state = EnemyState.ATTACK; + } + break; + // ... 其他状态 + } + } +} + +// 行为树适合的场景:复杂、层次化的行为 +const complex_enemy_bt = { + type: "selector", + children: [ + { + type: "sequence", // 战斗行为 + children: [ + { type: "condition", class: "PlayerInSight" }, + { + type: "selector", + children: [ + { type: "sequence", children: [ + { type: "condition", class: "InAttackRange" }, + { type: "action", class: "Attack" } + ]}, + { type: "action", class: "ChasePlayer" } + ] + } + ] + }, + { type: "action", class: "Patrol" } // 默认行为 + ] +}; +``` + +## 结语 + +行为树作为现代游戏AI的核心技术,为我们提供了一种优雅而强大的方式来设计复杂的AI行为。从基础的节点类型到高级的工程化实践,从简单的NPC巡逻到复杂的Boss战斗系统,行为树都展现出了其独特的优势。 + +随着技术的不断发展,行为树正在与机器学习、云计算等前沿技术结合,为游戏AI带来更多可能性。无论是独立开发者还是大型游戏工作室,掌握行为树技术都将为创造更智能、更有趣的游戏体验提供强有力的支持。 + +希望这篇文章能够帮助你深入理解行为树的原理和实践,在你的游戏开发之路上发挥重要作用。记住,好的AI不仅仅是技术的体现,更是游戏设计理念的延伸。用行为树构建的AI应该服务于游戏的整体体验,让玩家感受到智能而有趣的挑战。 \ No newline at end of file diff --git a/README.md b/README.md index 33db79e..494bb2c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,20 @@ 行为树本质上是一个**有向无环图**,用树状结构来组织和执行行为逻辑。每个节点代表一个行为或决策,通过父子关系形成层次化的控制流。 +* 如果你从来没接触过行为树,可以看下这篇文章 + + [行为树逻辑详解](./docs/BehaviorTree.md) + + +## 可视化编辑器 + +[下载地址:https://store.cocos.com/app/detail/8201](https://store.cocos.com/app/detail/8201) + + +查看详情: [编辑器文档](./docs/USED.md) + +![image](./image/bt-gui.png) + ## 特性 - 🔧 **类型安全**: 完整 TypeScript 支持 @@ -30,13 +44,6 @@ npm install kunpocc-behaviortree 项目根目录下的 `bt-demo`文件夹 - -## GUI编辑器 - -查看详情: [GUI编辑器](./docs/USED.md) - -![image](./image/image_tree.png) - ## 核心概念 #### 状态类型 @@ -55,22 +62,6 @@ enum Status { - **条件节点**: 特殊的叶子节点 (Condition) -## 装饰器 - -> **自行实现的节点,通过装饰器把数据暴露给行为树编辑器** - -##### ClassAction - 行为节点装饰器 - -##### ClassCondition - 条件节点装饰器 - -##### ClassComposite - 组合节点装饰器 - -##### ClassDecorator - 装饰节点装饰器 - -##### prop - 属性装饰器 - - - ## 内置节点 ### 组合节点 (Composite) diff --git a/bt-demo/assets/res.meta b/bt-demo/assets/res.meta deleted file mode 100644 index 95705cd..0000000 --- a/bt-demo/assets/res.meta +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ver": "1.2.0", - "importer": "directory", - "imported": true, - "uuid": "de9afb2e-952c-4e0b-96df-cc676989bed9", - "files": [], - "subMetas": {}, - "userData": {} -} diff --git a/bt-demo/assets/res/spine.meta b/bt-demo/assets/res/spine.meta deleted file mode 100644 index 0f718c6..0000000 --- a/bt-demo/assets/res/spine.meta +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ver": "1.2.0", - "importer": "directory", - "imported": true, - "uuid": "d5a536b5-db1b-42ac-8654-5f6a81341c3a", - "files": [], - "subMetas": {}, - "userData": {} -} diff --git a/bt-demo/assets/resources/config/bt_config.json b/bt-demo/assets/resources/config/bt_config.json index 977447c..6284e19 100644 --- a/bt-demo/assets/resources/config/bt_config.json +++ b/bt-demo/assets/resources/config/bt_config.json @@ -1 +1 @@ -{"bt-tree1":[{"id":"1759488688188_qejfcso50","className":"Selector","parameters":{},"children":["1759488707759_2bmdm1fqt","1759488725107_v8u160t95","1759488737637_axpz9aqaz","1759482034741_cf3mqaqdj","1758190139303_t5o7vv3ak"]},{"id":"1758190139303_t5o7vv3ak","className":"BTTestNode","parameters":{"position":{"x":10,"y":20},"configs":[{"name":"hahaa","value":1}]},"children":[]},{"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":2},"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":"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":[]}],"bttest":[{"id":"1758261718850_lh2zeww5x","className":"Selector","parameters":{},"children":["1758523039812_tjcddh9ze","1758253809172_7ug7k3z91","1758363111204_lop2a6plc","1758523349295_96r7men3n"]},{"id":"1758253809172_7ug7k3z91","className":"Sequence","parameters":{},"children":["1758253982404_6rhda0crn","1758363223180_wgl2lftj9"]},{"id":"1758253982404_6rhda0crn","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758363111204_lop2a6plc","className":"Sequence","parameters":{},"children":["1758523389760_eimzn4sqi","1758523381506_arxf3pn6e"]},{"id":"1758363223180_wgl2lftj9","className":"Selector","parameters":{},"children":["1758371105178_0cdpe0b8s","1758371282480_wtl4l8yy4"]},{"id":"1758371105178_0cdpe0b8s","className":"Sequence","parameters":{},"children":["1758371168774_oeixpztqv","1758371186379_nl05q6e4w"]},{"id":"1758371168774_oeixpztqv","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758371186379_nl05q6e4w","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758371282480_wtl4l8yy4","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523039812_tjcddh9ze","className":"Sequence","parameters":{},"children":["1758523078993_5vt56w1fv","1758523095101_kc0taam2a","1758523118932_tv2q9zeij"]},{"id":"1758523078993_5vt56w1fv","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758523095101_kc0taam2a","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523118932_tv2q9zeij","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523349295_96r7men3n","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523381506_arxf3pn6e","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523389760_eimzn4sqi","className":"BTConditionTest","parameters":{},"children":[]}]} \ No newline at end of file +{"bt-tree1":[{"id":"1759488688188_qejfcso50","className":"Selector","parameters":{},"children":["1759488725107_v8u160t95","1759488737637_axpz9aqaz","1759488707759_2bmdm1fqt","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":2},"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":"1758972524240_4ockrv5jo","className":"Selector","parameters":{},"children":["1758972608716_o2uai5dp8","1758972550481_0iq7imml9","1758972698829_wxfe7ut33"]},{"id":"1758889921667_sjwxkfjs7","className":"BTAnimation","parameters":{"_name":"jump","_loop":false},"children":[]},{"id":"1758889925476_dcyjc7a4o","className":"BTAnimation","parameters":{"_name":"idle","_loop":true},"children":[]},{"id":"1758972550481_0iq7imml9","className":"Sequence","parameters":{},"children":["1758972573109_fxt7magur","1758889921667_sjwxkfjs7"]},{"id":"1758972573109_fxt7magur","className":"BTConditionRandom","parameters":{"_value":0.5},"children":[]},{"id":"1758972608716_o2uai5dp8","className":"Sequence","parameters":{},"children":["1758972608716_ivq9o10bi","1758972608716_zmw9ep5n3"]},{"id":"1758972608716_ivq9o10bi","className":"BTConditionRandom","parameters":{"_value":0.5},"children":[]},{"id":"1758972608716_zmw9ep5n3","className":"BTAnimation","parameters":{"_name":"jump","_loop":false},"children":[]},{"id":"1758972698829_wxfe7ut33","className":"LimitTime","parameters":{"_max":2},"children":["1758889925476_dcyjc7a4o"]}],"bt-tree3":[{"id":"1758979704536_g6jkamjdm","className":"Selector","parameters":{},"children":["1758979708831_vibpbusev","1758979710657_ksqwgrqym","1758979712702_5miziffc9"]},{"id":"1758979708831_vibpbusev","className":"Sequence","parameters":{},"children":["1758979721847_ikkmffinc","1758979717102_hs4zv2ysl"]},{"id":"1758979710657_ksqwgrqym","className":"Sequence","parameters":{},"children":["1758979723586_oa4umrekl","1758979718532_16c9kb7cx"]},{"id":"1758979712702_5miziffc9","className":"Sequence","parameters":{},"children":["1758979720285_9ojvbt7sw"]},{"id":"1758979717102_hs4zv2ysl","className":"BTAnimation","parameters":{"_name":"run","_loop":false},"children":[]},{"id":"1758979718532_16c9kb7cx","className":"BTAnimation","parameters":{"_name":"","_loop":false},"children":[]},{"id":"1758979720285_9ojvbt7sw","className":"BTAnimation","parameters":{"_name":"","_loop":false},"children":[]},{"id":"1758979721847_ikkmffinc","className":"BTConditionRandom","parameters":{"_value":0.5},"children":[]},{"id":"1758979723586_oa4umrekl","className":"BTConditionRandom","parameters":{"_value":0.5},"children":[]}],"bttest":[{"id":"1758261718850_lh2zeww5x","className":"Selector","parameters":{},"children":["1758523039812_tjcddh9ze","1758253809172_7ug7k3z91","1758363111204_lop2a6plc","1758523349295_96r7men3n"]},{"id":"1758253809172_7ug7k3z91","className":"Sequence","parameters":{},"children":["1758253982404_6rhda0crn","1758363223180_wgl2lftj9"]},{"id":"1758253982404_6rhda0crn","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758363111204_lop2a6plc","className":"Sequence","parameters":{},"children":["1758523389760_eimzn4sqi","1758523381506_arxf3pn6e"]},{"id":"1758363223180_wgl2lftj9","className":"Selector","parameters":{},"children":["1758371105178_0cdpe0b8s","1758371282480_wtl4l8yy4"]},{"id":"1758371105178_0cdpe0b8s","className":"Sequence","parameters":{},"children":["1758371168774_oeixpztqv","1758371186379_nl05q6e4w"]},{"id":"1758371168774_oeixpztqv","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758371186379_nl05q6e4w","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758371282480_wtl4l8yy4","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523039812_tjcddh9ze","className":"Sequence","parameters":{},"children":["1758523078993_5vt56w1fv","1758523095101_kc0taam2a","1758523118932_tv2q9zeij"]},{"id":"1758523078993_5vt56w1fv","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758523095101_kc0taam2a","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523118932_tv2q9zeij","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523349295_96r7men3n","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523381506_arxf3pn6e","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758523389760_eimzn4sqi","className":"BTConditionTest","parameters":{},"children":[]}],"example-boss":[{"id":"1758636606871_d00eo32m0","className":"Selector","parameters":{},"children":["1758636606871_nlci5zgin","1758636827735_ghi1jyp6e","1758636606871_73vz04ef6"]},{"id":"1758636606871_nlci5zgin","className":"Sequence","parameters":{},"children":["1758636606871_bfer3pf0k","1758636606871_fz7ji79yr"]},{"id":"1758636606871_bfer3pf0k","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758636606871_fz7ji79yr","className":"Selector","parameters":{},"children":["1758636606871_9xic9f2n1","1758636606871_v7xq9t9ca","1758636606871_3hexy07r4"]},{"id":"1758636606871_9xic9f2n1","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636606871_v7xq9t9ca","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636606871_3hexy07r4","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636606871_ramtsopmx","className":"Sequence","parameters":{},"children":["1758636606871_wkmdmgfdw","1758636926699_fkhgmqdd1","1758636950500_y5gbq9gt9"]},{"id":"1758636606871_wkmdmgfdw","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636606871_73vz04ef6","className":"Selector","parameters":{},"children":["1758637141288_y6xr4qiqo","1758637139642_lhe3fdfhi","1758636606871_4cwadcn7f"]},{"id":"1758636606871_o1bko71f4","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636606871_kman1jm6o","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636606871_4cwadcn7f","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636783944_9xxk4gqyo","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758636827735_ghi1jyp6e","className":"Sequence","parameters":{},"children":["1758636783944_9xxk4gqyo","1758636868515_9gnnfpbvg"]},{"id":"1758636868515_9gnnfpbvg","className":"Selector","parameters":{},"children":["1758636606871_ramtsopmx","1758636975617_40xzee108","1758636981864_rtfejtz1m"]},{"id":"1758636926699_fkhgmqdd1","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636950500_y5gbq9gt9","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636975617_40xzee108","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636981864_rtfejtz1m","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758637139642_lhe3fdfhi","className":"Sequence","parameters":{},"children":["1758637233781_l0o4zg8uh","1758637233781_vrbhvrzj7"]},{"id":"1758637141288_y6xr4qiqo","className":"Sequence","parameters":{},"children":["1758636606871_o1bko71f4","1758636606871_kman1jm6o"]},{"id":"1758637233781_l0o4zg8uh","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758637233781_vrbhvrzj7","className":"BTTestNode2","parameters":{},"children":[]}],"example-npc1":[{"id":"1758635344069_hairxmvmh","className":"Selector","parameters":{},"children":["1758635421003_4s8uj787l","1758635605374_990xn0z9c","1758635344069_4yss1wz7d","1758636072669_whqacjf0i","1758636171277_d7th6ojvm"]},{"id":"1758635344069_4yss1wz7d","className":"Sequence","parameters":{},"children":["1758635344069_gg3q5rxes","1758635344069_7ecq7pfzw"]},{"id":"1758635344069_gg3q5rxes","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758635344069_7ecq7pfzw","className":"Selector","parameters":{},"children":["1758635344069_8ck2fgr24","1758635344069_1wzefq3da","1758635344069_3ezjerufd"]},{"id":"1758635344069_8ck2fgr24","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635344069_1wzefq3da","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635344069_3ezjerufd","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635421003_4s8uj787l","className":"Selector","parameters":{},"children":["1758635460230_zn5vibc1s","1758635463818_pn3pcjsxo","1758635545865_k2vgufpnb"]},{"id":"1758635460230_zn5vibc1s","className":"Sequence","parameters":{},"children":["1758635460230_j09ztl8mq","1758635460230_qvwu6fx64"]},{"id":"1758635460230_j09ztl8mq","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758635460230_qvwu6fx64","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635463818_pn3pcjsxo","className":"Sequence","parameters":{},"children":["1758635463818_pihq95w8k","1758635463818_5lxcl9204"]},{"id":"1758635463818_pihq95w8k","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758635463818_5lxcl9204","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635545865_k2vgufpnb","className":"Sequence","parameters":{},"children":["1758635545865_zlzorqr1s","1758635545865_z6hmdd955"]},{"id":"1758635545865_zlzorqr1s","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758635545865_z6hmdd955","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635605374_990xn0z9c","className":"Selector","parameters":{},"children":["1758635740579_fw4dk6ikf","1758635744921_j7amyl952"]},{"id":"1758635624148_qew2aoutm","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635652784_531a4s3wt","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758635705235_zn4f5x42i","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758635740579_fw4dk6ikf","className":"Sequence","parameters":{},"children":["1758635652784_531a4s3wt","1758635624148_qew2aoutm"]},{"id":"1758635744921_j7amyl952","className":"Sequence","parameters":{},"children":["1758635767133_koukdag8k","1758635705235_zn4f5x42i"]},{"id":"1758635767133_koukdag8k","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758636072669_whqacjf0i","className":"Sequence","parameters":{},"children":["1758636072669_23ygfl1xz","1758636072669_efwoobpa6"]},{"id":"1758636072669_23ygfl1xz","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758636072669_efwoobpa6","className":"Selector","parameters":{},"children":["1758636072669_1a8wocwxo","1758636072669_2f7kryz2k","1758636072669_qq7v8cita"]},{"id":"1758636072669_1a8wocwxo","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636072669_2f7kryz2k","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636072669_qq7v8cita","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758636171277_d7th6ojvm","className":"Sequence","parameters":{},"children":["1758636171277_ga2mbrzxt","1758636171277_m9w7cla2o"]},{"id":"1758636171277_ga2mbrzxt","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758636171277_m9w7cla2o","className":"BTTestNode2","parameters":{},"children":[]}],"example-scanning1":[{"id":"1758633912545_7xy1se8pk","className":"Selector","parameters":{},"children":["1758633912545_z0wbw5zkn","1758633912545_ismgc4xad","1758633912545_cdy2pg1pn","1758634397890_nh8nat3ph"]},{"id":"1758633912545_26tx6w4f1","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633912545_df302i0u7","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633912545_qdoxrynps","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633912545_z0wbw5zkn","className":"Sequence","parameters":{},"children":["1758633987202_p7z2iewl8","1758634022458_f769kvf1x"]},{"id":"1758633912545_ismgc4xad","className":"Sequence","parameters":{},"children":["1758633912545_q02k78ubn","1758634249975_c1i6wxc2w"]},{"id":"1758633912545_nawabdhem","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633912545_q02k78ubn","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758633912545_cdy2pg1pn","className":"Sequence","parameters":{},"children":["1758634317404_8aaeb4ve2","1758634337943_93kaze24m"]},{"id":"1758633912545_lgpy79s0o","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633912545_i1kac3qvv","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633912545_5cqcrrfkg","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633987202_p7z2iewl8","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758634022458_f769kvf1x","className":"Selector","parameters":{},"children":["1758634091921_6xr6c6cul","1758634094741_dk5mmim4z","1758633912545_df302i0u7"]},{"id":"1758634091921_6xr6c6cul","className":"Sequence","parameters":{},"children":["1758634117284_29jp1jxyq","1758633912545_26tx6w4f1"]},{"id":"1758634094741_dk5mmim4z","className":"Sequence","parameters":{},"children":["1758634119520_rz3hx4hno","1758633912545_qdoxrynps"]},{"id":"1758634117284_29jp1jxyq","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758634119520_rz3hx4hno","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758634249975_c1i6wxc2w","className":"Selector","parameters":{},"children":["1758633912545_nawabdhem","1758634290870_im6rplw92","1758634284662_l7hvr7fuo"]},{"id":"1758634284662_l7hvr7fuo","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758634290870_im6rplw92","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758634317404_8aaeb4ve2","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758634337943_93kaze24m","className":"Selector","parameters":{},"children":["1758633912545_lgpy79s0o","1758633912545_i1kac3qvv","1758633912545_5cqcrrfkg"]},{"id":"1758634397890_nh8nat3ph","className":"BTTestNode2","parameters":{},"children":[]}],"test-bttree":[],"tree-example-move1":[{"id":"1758633158053_g12gp05tz","className":"Sequence","parameters":{},"children":["1758633158053_n9lvsqtou","1758633158053_m7mptbzme","1758633230846_qqosra95l"]},{"id":"1758633158053_n9lvsqtou","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633158053_m7mptbzme","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633230846_qqosra95l","className":"BTTestNode2","parameters":{},"children":[]}],"tree-example-move2":[{"id":"1758633408841_o85luvhya","className":"Selector","parameters":{},"children":["1758633372295_1vww23k1k","1758633460046_alqdykjsd","1758633637964_a0khi5e5k"]},{"id":"1758633372295_1vww23k1k","className":"Sequence","parameters":{},"children":["1758633506673_f6rvm02zs","1758633372295_1vokt067a","1758633372295_7vyepkar1","1758633372295_86o7jk1k4"]},{"id":"1758633372295_1vokt067a","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633372295_7vyepkar1","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633372295_86o7jk1k4","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633460046_alqdykjsd","className":"Sequence","parameters":{},"children":["1758633584586_llol3kpvi","1758633460046_l5944c3nc"]},{"id":"1758633460046_l5944c3nc","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633506673_f6rvm02zs","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758633584586_llol3kpvi","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758633637964_a0khi5e5k","className":"Sequence","parameters":{},"children":["1758633637964_dgyhnjuhl","1758633637964_d7uht9tgg","1758633637964_qc31zjqo5"]},{"id":"1758633637964_dgyhnjuhl","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633637964_d7uht9tgg","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758633637964_qc31zjqo5","className":"BTTestNode2","parameters":{},"children":[]}],"tree-example1":[{"id":"1758630775717_d1gipfamh","className":"Selector","parameters":{},"children":["1758630814199_qnitmm2sd","1758630832275_prflitgyu","1758630967937_2c0t3xi6t"]},{"id":"1758630814199_qnitmm2sd","className":"Sequence","parameters":{},"children":["1758630875390_e3dlxo1jg","1758630940801_u6j12wj96"]},{"id":"1758630832275_prflitgyu","className":"Sequence","parameters":{},"children":["1758630915741_ux73zz8ws","1758630955525_n0hw99t1q"]},{"id":"1758630875390_e3dlxo1jg","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758630915741_ux73zz8ws","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758630940801_u6j12wj96","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758630955525_n0hw99t1q","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758630967937_2c0t3xi6t","className":"BTTestNode2","parameters":{},"children":[]}],"tree-example2":[{"id":"1758631669066_247k1fo68","className":"Selector","parameters":{},"children":["1758631669066_yqo3wjnns","1758631669066_g6lvqwonn"]},{"id":"1758631669066_yqo3wjnns","className":"Sequence","parameters":{},"children":["1758631669066_mr87yjkdq","1758631669066_e5qqjm0s8"]},{"id":"1758631669066_mr87yjkdq","className":"BTConditionTest","parameters":{},"children":[]},{"id":"1758631669066_e5qqjm0s8","className":"BTTestNode2","parameters":{},"children":[]},{"id":"1758631669066_g6lvqwonn","className":"BTTestNode2","parameters":{},"children":[]}]} \ No newline at end of file diff --git a/bt-demo/assets/resources/prefab.meta b/bt-demo/assets/resources/prefab.meta deleted file mode 100644 index 2e3d952..0000000 --- a/bt-demo/assets/resources/prefab.meta +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ver": "1.2.0", - "importer": "directory", - "imported": true, - "uuid": "6b9b2da1-08c2-4c40-ab35-e7cb5bb30872", - "files": [], - "subMetas": {}, - "userData": {} -} diff --git a/bt-demo/assets/resources/prefab/spineboy.prefab b/bt-demo/assets/resources/prefab/spineboy.prefab deleted file mode 100644 index dfc0d18..0000000 --- a/bt-demo/assets/resources/prefab/spineboy.prefab +++ /dev/null @@ -1,147 +0,0 @@ -[ - { - "__type__": "cc.Prefab", - "_name": "spineboy", - "_objFlags": 0, - "__editorExtras__": {}, - "_native": "", - "data": { - "__id__": 1 - }, - "optimizationPolicy": 0, - "persistent": false - }, - { - "__type__": "cc.Node", - "_name": "spineboy", - "_objFlags": 0, - "__editorExtras__": {}, - "_parent": null, - "_children": [], - "_active": true, - "_components": [ - { - "__id__": 2 - }, - { - "__id__": 4 - } - ], - "_prefab": { - "__id__": 6 - }, - "_lpos": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": -1000 - }, - "_lrot": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_lscale": { - "__type__": "cc.Vec3", - "x": 0.3, - "y": 0.3, - "z": 1 - }, - "_mobility": 0, - "_layer": 1073741824, - "_euler": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_id": "" - }, - { - "__type__": "cc.UITransform", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 3 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 419.8399963378906, - "height": 686.0800170898438 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.45412539378136013, - "y": 0.011660447470739235 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "dfVeZdqm9E15k7OBD615QP" - }, - { - "__type__": "sp.Skeleton", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 5 - }, - "_customMaterial": null, - "_srcBlendFactor": 2, - "_dstBlendFactor": 4, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_skeletonData": { - "__uuid__": "39a7d8cd-533a-479a-b909-9575bf720338", - "__expectedType__": "sp.SkeletonData" - }, - "defaultSkin": "default", - "defaultAnimation": "jump", - "_premultipliedAlpha": true, - "_timeScale": 1, - "_preCacheMode": 0, - "_cacheMode": 0, - "_sockets": [], - "_useTint": false, - "_debugMesh": false, - "_debugBones": false, - "_debugSlots": false, - "_enableBatch": false, - "loop": true, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "deHPJ9jpdJZq/2PP1E2haI" - }, - { - "__type__": "cc.PrefabInfo", - "root": { - "__id__": 1 - }, - "asset": { - "__id__": 0 - }, - "fileId": "fcg4LyhU9MpITaQy7lW8Ru", - "instance": null, - "targetOverrides": null - } -] \ No newline at end of file diff --git a/bt-demo/assets/resources/prefab/spineboy.prefab.meta b/bt-demo/assets/resources/prefab/spineboy.prefab.meta deleted file mode 100644 index 31c9b55..0000000 --- a/bt-demo/assets/resources/prefab/spineboy.prefab.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.50", - "importer": "prefab", - "imported": true, - "uuid": "610db270-416d-42a9-a228-67b0fe1beee4", - "files": [ - ".json" - ], - "subMetas": {}, - "userData": { - "syncNodeName": "spineboy" - } -} diff --git a/bt-demo/assets/res/spine/spineboy-pro.atlas b/bt-demo/assets/resources/spineboy-pro.atlas similarity index 100% rename from bt-demo/assets/res/spine/spineboy-pro.atlas rename to bt-demo/assets/resources/spineboy-pro.atlas diff --git a/bt-demo/assets/res/spine/spineboy-pro.atlas.meta b/bt-demo/assets/resources/spineboy-pro.atlas.meta similarity index 100% rename from bt-demo/assets/res/spine/spineboy-pro.atlas.meta rename to bt-demo/assets/resources/spineboy-pro.atlas.meta diff --git a/bt-demo/assets/res/spine/spineboy-pro.json b/bt-demo/assets/resources/spineboy-pro.json similarity index 100% rename from bt-demo/assets/res/spine/spineboy-pro.json rename to bt-demo/assets/resources/spineboy-pro.json diff --git a/bt-demo/assets/res/spine/spineboy-pro.json.meta b/bt-demo/assets/resources/spineboy-pro.json.meta similarity index 100% rename from bt-demo/assets/res/spine/spineboy-pro.json.meta rename to bt-demo/assets/resources/spineboy-pro.json.meta diff --git a/bt-demo/assets/res/spine/spineboy-pro.png b/bt-demo/assets/resources/spineboy-pro.png similarity index 100% rename from bt-demo/assets/res/spine/spineboy-pro.png rename to bt-demo/assets/resources/spineboy-pro.png diff --git a/bt-demo/assets/res/spine/spineboy-pro.png.meta b/bt-demo/assets/resources/spineboy-pro.png.meta similarity index 100% rename from bt-demo/assets/res/spine/spineboy-pro.png.meta rename to bt-demo/assets/resources/spineboy-pro.png.meta diff --git a/bt-demo/assets/scene/GameEntry.scene b/bt-demo/assets/scene/GameEntry.scene index 644d46b..34a40c8 100644 --- a/bt-demo/assets/scene/GameEntry.scene +++ b/bt-demo/assets/scene/GameEntry.scene @@ -23,7 +23,7 @@ "_active": true, "_components": [], "_prefab": { - "__id__": 22 + "__id__": 14 }, "_lpos": { "__type__": "cc.Vec3", @@ -54,7 +54,7 @@ }, "autoReleaseAssets": false, "_globals": { - "__id__": 25 + "__id__": 17 }, "_id": "bef93422-3e63-4c0f-a5cf-d926e7360673" }, @@ -71,22 +71,22 @@ "__id__": 3 }, { - "__id__": 6 + "__id__": 9 }, { - "__id__": 8 + "__id__": 7 } ], "_active": true, "_components": [ { - "__id__": 19 + "__id__": 11 }, { - "__id__": 20 + "__id__": 12 }, { - "__id__": 21 + "__id__": 13 } ], "_prefab": null, @@ -199,13 +199,121 @@ }, "_enabled": true, "__prefab": null, - "skeleton": null, - "btConfig": { - "__uuid__": "c8aeef5d-6d0e-4093-848e-7d8f1ca30261", - "__expectedType__": "cc.JsonAsset" + "skeleton": { + "__id__": 6 }, + "btConfig": null, "_id": "69LhmWaZRIUpmYvdiN82Ha" }, + { + "__type__": "sp.Skeleton", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 7 + }, + "_enabled": true, + "__prefab": null, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_skeletonData": { + "__uuid__": "39a7d8cd-533a-479a-b909-9575bf720338", + "__expectedType__": "sp.SkeletonData" + }, + "defaultSkin": "default", + "defaultAnimation": "idle", + "_premultipliedAlpha": true, + "_timeScale": 1, + "_preCacheMode": 0, + "_cacheMode": 0, + "_sockets": [], + "_useTint": false, + "_debugMesh": false, + "_debugBones": false, + "_debugSlots": false, + "_enableBatch": false, + "loop": false, + "_id": "e0SlYqh/pPzaLrAWsV78xj" + }, + { + "__type__": "cc.Node", + "_name": "spineboy", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 8 + }, + { + "__id__": 6 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": -151.948, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 0.5, + "y": 0.5, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "a62KjBSsBMSpCYMjsx0oxG" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 7 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 419.8399963378906, + "height": 686.0800170898438 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.45412539378136013, + "y": 0.011660447470739235 + }, + "_id": "c9XaAZS6pNILxWx4jmSYKE" + }, { "__type__": "cc.Node", "_name": "Camera", @@ -218,7 +326,7 @@ "_active": true, "_components": [ { - "__id__": 7 + "__id__": 10 } ], "_prefab": null, @@ -257,7 +365,7 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 6 + "__id__": 9 }, "_enabled": true, "__prefab": null, @@ -297,149 +405,6 @@ "_trackingType": 0, "_id": "63WIch3o5BEYRlXzTT0oWc" }, - { - "__type__": "cc.Node", - "_objFlags": 0, - "_parent": { - "__id__": 2 - }, - "_prefab": { - "__id__": 9 - }, - "__editorExtras__": {} - }, - { - "__type__": "cc.PrefabInfo", - "root": { - "__id__": 8 - }, - "asset": { - "__uuid__": "610db270-416d-42a9-a228-67b0fe1beee4", - "__expectedType__": "cc.Prefab" - }, - "fileId": "fcg4LyhU9MpITaQy7lW8Ru", - "instance": { - "__id__": 10 - }, - "targetOverrides": null, - "nestedPrefabInstanceRoots": null - }, - { - "__type__": "cc.PrefabInstance", - "fileId": "2eYzhZYv5Mi5OETcYel3W3", - "prefabRootNode": null, - "mountedChildren": [], - "mountedComponents": [], - "propertyOverrides": [ - { - "__id__": 11 - }, - { - "__id__": 13 - }, - { - "__id__": 14 - }, - { - "__id__": 15 - }, - { - "__id__": 16 - }, - { - "__id__": 18 - } - ], - "removedComponents": [] - }, - { - "__type__": "CCPropertyOverrideInfo", - "targetInfo": { - "__id__": 12 - }, - "propertyPath": [ - "_lpos" - ], - "value": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "__type__": "cc.TargetInfo", - "localID": [ - "fcg4LyhU9MpITaQy7lW8Ru" - ] - }, - { - "__type__": "CCPropertyOverrideInfo", - "targetInfo": { - "__id__": 12 - }, - "propertyPath": [ - "_name" - ], - "value": "spineboy" - }, - { - "__type__": "CCPropertyOverrideInfo", - "targetInfo": { - "__id__": 12 - }, - "propertyPath": [ - "_lrot" - ], - "value": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - } - }, - { - "__type__": "CCPropertyOverrideInfo", - "targetInfo": { - "__id__": 12 - }, - "propertyPath": [ - "_euler" - ], - "value": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "__type__": "CCPropertyOverrideInfo", - "targetInfo": { - "__id__": 17 - }, - "propertyPath": [ - "defaultAnimation" - ], - "value": "idle" - }, - { - "__type__": "cc.TargetInfo", - "localID": [ - "deHPJ9jpdJZq/2PP1E2haI" - ] - }, - { - "__type__": "CCPropertyOverrideInfo", - "targetInfo": { - "__id__": 17 - }, - "propertyPath": [ - "_premultipliedAlpha" - ], - "value": true - }, { "__type__": "cc.UITransform", "_name": "", @@ -473,7 +438,7 @@ "_enabled": true, "__prefab": null, "_cameraComponent": { - "__id__": 7 + "__id__": 10 }, "_alignCanvasWithScreen": true, "_id": "12O/ljcVlEqLmVm3U2gEOQ" @@ -516,12 +481,7 @@ "instance": null, "targetOverrides": [ { - "__id__": 23 - } - ], - "nestedPrefabInstanceRoots": [ - { - "__id__": 8 + "__id__": 15 } ] }, @@ -534,11 +494,9 @@ "propertyPath": [ "skeleton" ], - "target": { - "__id__": 8 - }, + "target": null, "targetInfo": { - "__id__": 24 + "__id__": 16 } }, { @@ -550,28 +508,28 @@ { "__type__": "cc.SceneGlobals", "ambient": { - "__id__": 26 + "__id__": 18 }, "shadows": { - "__id__": 27 + "__id__": 19 }, "_skybox": { - "__id__": 28 + "__id__": 20 }, "fog": { - "__id__": 29 + "__id__": 21 }, "octree": { - "__id__": 30 + "__id__": 22 }, "skin": { - "__id__": 31 + "__id__": 23 }, "lightProbeInfo": { - "__id__": 32 + "__id__": 24 }, "postSettings": { - "__id__": 33 + "__id__": 25 }, "bakedWithStationaryMainLight": false, "bakedWithHighpLightmap": false diff --git a/bt-demo/assets/script/BTNode.ts b/bt-demo/assets/script/BTNode.ts index 3572ada..0d4302a 100644 --- a/bt-demo/assets/script/BTNode.ts +++ b/bt-demo/assets/script/BTNode.ts @@ -47,7 +47,7 @@ export class BTAnimation extends BT.LeafNode { } /** 条件节点 */ -@BT.ClassCondition("BTConditionRandom", { name: "随机条件节点", group: "基础条件节点", desc: "随机0-1的值,大于设置值返回成功,否则返回失败" }) +@BT.ClassCondition("BTConditionRandom", { name: "随机条件节点", group: "基础条件节点", desc: "随机0-1的值,小于设置值返回成功,否则返回失败" }) export class BTConditionRandom extends BT.Condition { @BT.prop({ type: BT.ParamType.float, description: "值", defaultValue: 0.5 }) diff --git a/bt-demo/assets/script/Math.ts b/bt-demo/assets/script/Math.ts deleted file mode 100644 index 475da93..0000000 --- a/bt-demo/assets/script/Math.ts +++ /dev/null @@ -1,73 +0,0 @@ -interface Math { - /** - * 限制值 - * @param value 当前值 - * @param min 最小值 - * @param max 最大值 - */ - clampf(value: number, min: number, max: number): number; - - /** - * 随机从 min 到 max 的整数(包含min和max) - * @param min - * @param max - */ - rand(min: number, max: number): number; - - /** - * 随机从 min 到 max的数 - * @param min - * @param max - */ - randRange(min: number, max: number): number; - - /** - * 角度转弧度 - * @param angle 角度 - */ - rad(angle: number): number; - - /** - * 弧度转角度 - * @param radian 弧度 - */ - deg(radian: number): number; - - /** - * 数值平滑渐变 - * @param num1 - * @param num2 - * @param elapsedTime - * @param responseTime - */ - smooth(num1: number, num2: number, elapsedTime: number, responseTime: number): number; -} - -Math.clampf = function (value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max); -}; - -Math.rand = function (min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1) + min); -}; - -Math.randRange = function (min: number, max: number): number { - return Math.random() * (max - min) + min; -}; - -Math.rad = function (angle: number): number { - return (angle * Math.PI) / 180; -}; - -Math.deg = function (radian: number): number { - return (radian * 180) / Math.PI; -}; - -Math.smooth = function (num1: number, num2: number, elapsedTime: number, responseTime: number): number { - let out: number = num1; - if (elapsedTime > 0) { - out = out + (num2 - num1) * (elapsedTime / (elapsedTime + responseTime)); - } - return out; -}; - diff --git a/bt-demo/assets/script/Math.ts.meta b/bt-demo/assets/script/Math.ts.meta deleted file mode 100644 index 454a584..0000000 --- a/bt-demo/assets/script/Math.ts.meta +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ver": "4.0.24", - "importer": "typescript", - "imported": true, - "uuid": "a336ce23-5d73-4280-b2e9-084389a3877e", - "files": [], - "subMetas": {}, - "userData": {} -} diff --git a/bt-demo/extensions-config/bt-editor/bt-tree1.json b/bt-demo/extensions-config/bt-editor/bt-tree1.json index 8f6da46..2736fc9 100644 --- a/bt-demo/extensions-config/bt-editor/bt-tree1.json +++ b/bt-demo/extensions-config/bt-editor/bt-tree1.json @@ -1,6 +1,6 @@ { "name": "bt-tree1", - "description": "这是一个描述", + "description": "行为树描述\n", "nodes": [ { "id": "1759488688188_qejfcso50", @@ -8,47 +8,24 @@ "name": "选择节点", "position": { "x": -60, - "y": -200 + "y": -220 }, "parameters": {}, "children": [ - "1759488707759_2bmdm1fqt", "1759488725107_v8u160t95", "1759488737637_axpz9aqaz", - "1759482034741_cf3mqaqdj", - "1758190139303_t5o7vv3ak" + "1759488707759_2bmdm1fqt", + "1759482034741_cf3mqaqdj" ], - "alias": "根选择节点" - }, - { - "id": "1758190139303_t5o7vv3ak", - "className": "BTTestNode", - "name": "嵌套数据测试节点", - "position": { - "x": 440, - "y": -80 - }, - "parameters": { - "position": { - "x": 10, - "y": 20 - }, - "configs": [ - { - "name": "hahaa", - "value": 1 - } - ] - }, - "children": [] + "alias": "根节点" }, { "id": "1759479318405_bptb8ltcp", "className": "LimitTime", "name": "时间限制器", "position": { - "x": -120, - "y": 40 + "x": -60, + "y": -40 }, "parameters": { "_max": 2 @@ -62,8 +39,8 @@ "className": "LimitTime", "name": "时间限制器", "position": { - "x": -400, - "y": 40 + "x": -340, + "y": -40 }, "parameters": { "_max": 2 @@ -77,8 +54,8 @@ "className": "BTAnimation", "name": "播放动画", "position": { - "x": -400, - "y": 160 + "x": -340, + "y": 40 }, "parameters": { "_name": "walk", @@ -91,22 +68,23 @@ "className": "BTAnimation", "name": "播放动画", "position": { - "x": -120, - "y": 160 + "x": -60, + "y": 40 }, "parameters": { "_name": "run", "_loop": true }, - "children": [] + "children": [], + "alias": "奔跑动画" }, { "id": "1758089757615_dp9tw9ka1", "className": "BTAnimation", "name": "播放动画", "position": { - "x": 160, - "y": 40 + "x": 220, + "y": -60 }, "parameters": { "_name": "jump", @@ -119,8 +97,8 @@ "className": "BTAnimation", "name": "播放动画", "position": { - "x": 300, - "y": 40 + "x": 360, + "y": -60 }, "parameters": { "_name": "idle", @@ -133,8 +111,8 @@ "className": "BTConditionRandom", "name": "随机条件节点", "position": { - "x": -540, - "y": 40 + "x": -480, + "y": -60 }, "parameters": { "_value": 0.3 @@ -146,8 +124,8 @@ "className": "BTConditionRandom", "name": "随机条件节点", "position": { - "x": -260, - "y": 40 + "x": -200, + "y": -60 }, "parameters": { "_value": 0.4 @@ -159,8 +137,8 @@ "className": "BTConditionRandom", "name": "随机条件节点", "position": { - "x": 20, - "y": 40 + "x": 80, + "y": -60 }, "parameters": { "_value": 0.3 @@ -172,8 +150,8 @@ "className": "LimitTime", "name": "时间限制器", "position": { - "x": 300, - "y": -80 + "x": 360, + "y": -140 }, "parameters": { "_max": 2 @@ -188,45 +166,45 @@ "className": "Sequence", "name": "顺序节点", "position": { - "x": -480, - "y": -80 + "x": -400, + "y": -120 }, "parameters": {}, "children": [ "1759481172259_xou25wj2n", "1759479295671_jflit2ek8" ], - "alias": "行走动画分支" + "alias": "行走顺序节点" }, { "id": "1759488725107_v8u160t95", "className": "Sequence", "name": "顺序节点", "position": { - "x": -200, - "y": -80 + "x": -120, + "y": -120 }, "parameters": {}, "children": [ "1759481282875_5orqavi5y", "1759479318405_bptb8ltcp" ], - "alias": "奔跑动画" + "alias": "奔跑顺序节点" }, { "id": "1759488737637_axpz9aqaz", "className": "Sequence", "name": "顺序节点", "position": { - "x": 80, - "y": -80 + "x": 160, + "y": -120 }, "parameters": {}, "children": [ "1759481307863_ja6q4q9bz", "1758089757615_dp9tw9ka1" ], - "alias": "跳跃动画分支" + "alias": "跳跃顺序节点" } ], "connections": [ @@ -320,18 +298,11 @@ "targetNodeId": "1758089757615_dp9tw9ka1", "sourcePointType": "child", "targetPointType": "parent" - }, - { - "id": "conn_1758204108181_90iaioyvg", - "sourceNodeId": "1759488688188_qejfcso50", - "targetNodeId": "1758190139303_t5o7vv3ak", - "sourcePointType": "child", - "targetPointType": "parent" } ], - "canvasScale": 1.0006385665653545, + "canvasScale": 1.25, "canvasOffset": { - "x": 584.9936143343465, - "y": 498.99074078480237 + "x": 723, + "y": 600.875 } } \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/bt-tree2.json b/bt-demo/extensions-config/bt-editor/bt-tree2.json index 0ca65f9..837bed8 100644 --- a/bt-demo/extensions-config/bt-editor/bt-tree2.json +++ b/bt-demo/extensions-config/bt-editor/bt-tree2.json @@ -3,66 +3,193 @@ "description": "", "nodes": [ { - "id": "1758206972710_bhxebhy7o", - "className": "Sequence", - "name": "顺序节点", - "position": { - "x": 80, - "y": -320 - }, - "parameters": {}, - "children": [ - "1758090634327_mf36nwkdt" - ] - }, - { - "id": "1758090634327_mf36nwkdt", + "id": "1758972524240_4ockrv5jo", "className": "Selector", "name": "选择节点", "position": { - "x": -80, + "x": -60, "y": -220 }, "parameters": {}, "children": [ - "1758206988178_55b7kk5va" - ], - "alias": "是的发放是的发放" + "1758972608716_o2uai5dp8", + "1758972550481_0iq7imml9", + "1758972698829_wxfe7ut33" + ] }, { - "id": "1758206988178_55b7kk5va", + "id": "1758889921667_sjwxkfjs7", "className": "BTAnimation", "name": "播放动画", "position": { - "x": -20, - "y": -40 + "x": 80, + "y": -60 }, "parameters": { - "_name": "", + "_name": "jump", "_loop": false }, "children": [] + }, + { + "id": "1758889925476_dcyjc7a4o", + "className": "BTAnimation", + "name": "播放动画", + "position": { + "x": 220, + "y": -60 + }, + "parameters": { + "_name": "idle", + "_loop": true + }, + "children": [] + }, + { + "id": "1758972550481_0iq7imml9", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 20, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758972573109_fxt7magur", + "1758889921667_sjwxkfjs7" + ] + }, + { + "id": "1758972573109_fxt7magur", + "className": "BTConditionRandom", + "name": "随机条件节点", + "position": { + "x": -60, + "y": -60 + }, + "parameters": { + "_value": 0.5 + }, + "children": [] + }, + { + "id": "1758972608716_o2uai5dp8", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -260, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758972608716_ivq9o10bi", + "1758972608716_zmw9ep5n3" + ] + }, + { + "id": "1758972608716_ivq9o10bi", + "className": "BTConditionRandom", + "name": "随机条件节点", + "position": { + "x": -340, + "y": -60 + }, + "parameters": { + "_value": 0.5 + }, + "children": [] + }, + { + "id": "1758972608716_zmw9ep5n3", + "className": "BTAnimation", + "name": "播放动画", + "position": { + "x": -200, + "y": -60 + }, + "parameters": { + "_name": "jump", + "_loop": false + }, + "children": [] + }, + { + "id": "1758972698829_wxfe7ut33", + "className": "LimitTime", + "name": "时间限制节点", + "position": { + "x": 220, + "y": -140 + }, + "parameters": { + "_max": 2 + }, + "children": [ + "1758889925476_dcyjc7a4o" + ] } ], "connections": [ { - "id": "conn_1758206976733_208tneycs", - "sourceNodeId": "1758206972710_bhxebhy7o", - "targetNodeId": "1758090634327_mf36nwkdt", + "id": "conn_1758972580886_zykcbl2vk", + "sourceNodeId": "1758972550481_0iq7imml9", + "targetNodeId": "1758972573109_fxt7magur", "sourcePointType": "child", "targetPointType": "parent" }, { - "id": "conn_1758206989897_46hw88z7h", - "sourceNodeId": "1758090634327_mf36nwkdt", - "targetNodeId": "1758206988178_55b7kk5va", + "id": "conn_1758972582770_9e4pexjcz", + "sourceNodeId": "1758972550481_0iq7imml9", + "targetNodeId": "1758889921667_sjwxkfjs7", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758972608716_6gnj5711e", + "sourceNodeId": "1758972608716_o2uai5dp8", + "targetNodeId": "1758972608716_ivq9o10bi", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758972608716_9y9ytjxa8", + "sourceNodeId": "1758972608716_o2uai5dp8", + "targetNodeId": "1758972608716_zmw9ep5n3", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758972613504_me3rp31l8", + "sourceNodeId": "1758972524240_4ockrv5jo", + "targetNodeId": "1758972608716_o2uai5dp8", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758972674275_rkn2let28", + "sourceNodeId": "1758972524240_4ockrv5jo", + "targetNodeId": "1758972550481_0iq7imml9", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758972717495_fo5zdeslg", + "sourceNodeId": "1758972524240_4ockrv5jo", + "targetNodeId": "1758972698829_wxfe7ut33", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758972724719_hjsvpuvvy", + "sourceNodeId": "1758972698829_wxfe7ut33", + "targetNodeId": "1758889925476_dcyjc7a4o", "sourcePointType": "child", "targetPointType": "parent" } ], - "canvasScale": 1.139190980775211, + "canvasScale": 1.953125, "canvasOffset": { - "x": 549.4323607689915, - "y": 698.6185343759718 + "x": 700, + "y": 665.25 } } \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/bt-tree3.json b/bt-demo/extensions-config/bt-editor/bt-tree3.json new file mode 100644 index 0000000..b58e8bd --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/bt-tree3.json @@ -0,0 +1,193 @@ +{ + "name": "bt-tree3", + "description": "", + "nodes": [ + { + "id": "1758979704536_g6jkamjdm", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -60, + "y": -220 + }, + "parameters": {}, + "children": [ + "1758979708831_vibpbusev", + "1758979710657_ksqwgrqym", + "1758979712702_5miziffc9" + ] + }, + { + "id": "1758979708831_vibpbusev", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -260, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758979721847_ikkmffinc", + "1758979717102_hs4zv2ysl" + ] + }, + { + "id": "1758979710657_ksqwgrqym", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 20, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758979723586_oa4umrekl", + "1758979718532_16c9kb7cx" + ] + }, + { + "id": "1758979712702_5miziffc9", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 220, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758979720285_9ojvbt7sw" + ] + }, + { + "id": "1758979717102_hs4zv2ysl", + "className": "BTAnimation", + "name": "播放动画", + "position": { + "x": -200, + "y": -60 + }, + "parameters": { + "_name": "run", + "_loop": false + }, + "children": [] + }, + { + "id": "1758979718532_16c9kb7cx", + "className": "BTAnimation", + "name": "播放动画", + "position": { + "x": 80, + "y": -60 + }, + "parameters": { + "_name": "", + "_loop": false + }, + "children": [] + }, + { + "id": "1758979720285_9ojvbt7sw", + "className": "BTAnimation", + "name": "播放动画", + "position": { + "x": 220, + "y": -60 + }, + "parameters": { + "_name": "", + "_loop": false + }, + "children": [] + }, + { + "id": "1758979721847_ikkmffinc", + "className": "BTConditionRandom", + "name": "随机条件节点", + "position": { + "x": -340, + "y": -60 + }, + "parameters": { + "_value": 0.5 + }, + "children": [] + }, + { + "id": "1758979723586_oa4umrekl", + "className": "BTConditionRandom", + "name": "随机条件节点", + "position": { + "x": -60, + "y": -60 + }, + "parameters": { + "_value": 0.5 + }, + "children": [] + } + ], + "connections": [ + { + "id": "conn_1758979728478_r812fr61c", + "sourceNodeId": "1758979704536_g6jkamjdm", + "targetNodeId": "1758979708831_vibpbusev", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758979730147_24owqabyj", + "sourceNodeId": "1758979704536_g6jkamjdm", + "targetNodeId": "1758979710657_ksqwgrqym", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758979732272_8ywszwdxr", + "sourceNodeId": "1758979704536_g6jkamjdm", + "targetNodeId": "1758979712702_5miziffc9", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758979734464_rm0ysdtpo", + "sourceNodeId": "1758979708831_vibpbusev", + "targetNodeId": "1758979721847_ikkmffinc", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758979739229_8pnb80e23", + "sourceNodeId": "1758979708831_vibpbusev", + "targetNodeId": "1758979717102_hs4zv2ysl", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758979741003_zg52rxs79", + "sourceNodeId": "1758979710657_ksqwgrqym", + "targetNodeId": "1758979723586_oa4umrekl", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758979744846_1oagoplj0", + "sourceNodeId": "1758979710657_ksqwgrqym", + "targetNodeId": "1758979718532_16c9kb7cx", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758979750437_3oafpzhyz", + "sourceNodeId": "1758979712702_5miziffc9", + "targetNodeId": "1758979720285_9ojvbt7sw", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 1, + "canvasOffset": { + "x": 595, + "y": 618.5 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/example-boss.json b/bt-demo/extensions-config/bt-editor/example-boss.json new file mode 100644 index 0000000..f52ba73 --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/example-boss.json @@ -0,0 +1,494 @@ +{ + "name": "example-boss", + "description": "", + "nodes": [ + { + "id": "1758636606871_d00eo32m0", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -360, + "y": -240 + }, + "parameters": {}, + "children": [ + "1758636606871_nlci5zgin", + "1758636827735_ghi1jyp6e", + "1758636606871_73vz04ef6" + ], + "alias": "Boss选择节点" + }, + { + "id": "1758636606871_nlci5zgin", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -740, + "y": -180 + }, + "parameters": {}, + "children": [ + "1758636606871_bfer3pf0k", + "1758636606871_fz7ji79yr" + ], + "alias": "第三阶段" + }, + { + "id": "1758636606871_bfer3pf0k", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -920, + "y": -80 + }, + "parameters": {}, + "children": [], + "alias": "血量<25%" + }, + { + "id": "1758636606871_fz7ji79yr", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -780, + "y": -80 + }, + "parameters": {}, + "children": [ + "1758636606871_9xic9f2n1", + "1758636606871_v7xq9t9ca", + "1758636606871_3hexy07r4" + ], + "alias": "狂暴行为选择" + }, + { + "id": "1758636606871_9xic9f2n1", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -960, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "火焰吐息" + }, + { + "id": "1758636606871_v7xq9t9ca", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -820, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "地面重击" + }, + { + "id": "1758636606871_3hexy07r4", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -680, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "愤怒冲撞" + }, + { + "id": "1758636606871_ramtsopmx", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -540, + "y": 60 + }, + "parameters": {}, + "children": [ + "1758636606871_wkmdmgfdw", + "1758636926699_fkhgmqdd1", + "1758636950500_y5gbq9gt9" + ], + "alias": "飞行轰炸" + }, + { + "id": "1758636606871_wkmdmgfdw", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -680, + "y": 140 + }, + "parameters": {}, + "children": [], + "alias": "起飞" + }, + { + "id": "1758636606871_73vz04ef6", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -180, + "y": -100 + }, + "parameters": {}, + "children": [ + "1758637141288_y6xr4qiqo", + "1758637139642_lhe3fdfhi", + "1758636606871_4cwadcn7f" + ], + "alias": "第一阶段" + }, + { + "id": "1758636606871_o1bko71f4", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -220, + "y": 240 + }, + "parameters": {}, + "children": [], + "alias": "在攻击范围内?" + }, + { + "id": "1758636606871_kman1jm6o", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -80, + "y": 240 + }, + "parameters": {}, + "children": [], + "alias": "爪击攻击" + }, + { + "id": "1758636606871_4cwadcn7f", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 120, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "位置调整" + }, + { + "id": "1758636783944_9xxk4gqyo", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -560, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "血量<60%" + }, + { + "id": "1758636827735_ghi1jyp6e", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -480, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758636783944_9xxk4gqyo", + "1758636868515_9gnnfpbvg" + ], + "alias": "第二阶段" + }, + { + "id": "1758636868515_9gnnfpbvg", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -400, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758636606871_ramtsopmx", + "1758636975617_40xzee108", + "1758636981864_rtfejtz1m" + ], + "alias": "空中行为" + }, + { + "id": "1758636926699_fkhgmqdd1", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -540, + "y": 140 + }, + "parameters": {}, + "children": [], + "alias": "空中盘旋" + }, + { + "id": "1758636950500_y5gbq9gt9", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -400, + "y": 140 + }, + "parameters": {}, + "children": [], + "alias": "火球轰炸" + }, + { + "id": "1758636975617_40xzee108", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -400, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "俯冲攻击" + }, + { + "id": "1758636981864_rtfejtz1m", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -260, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "着陆休息" + }, + { + "id": "1758637139642_lhe3fdfhi", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -40, + "y": 40 + }, + "parameters": {}, + "children": [ + "1758637233781_l0o4zg8uh", + "1758637233781_vrbhvrzj7" + ], + "alias": "远程攻击" + }, + { + "id": "1758637141288_y6xr4qiqo", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -160, + "y": 120 + }, + "parameters": {}, + "children": [ + "1758636606871_o1bko71f4", + "1758636606871_kman1jm6o" + ], + "alias": "近战攻击" + }, + { + "id": "1758637233781_l0o4zg8uh", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 0, + "y": 160 + }, + "parameters": {}, + "children": [], + "alias": "远程攻击范围内?" + }, + { + "id": "1758637233781_vrbhvrzj7", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 140, + "y": 180 + }, + "parameters": {}, + "children": [], + "alias": "火焰吐息" + } + ], + "connections": [ + { + "id": "conn_1758636606871_hohhzwyui", + "sourceNodeId": "1758636606871_d00eo32m0", + "targetNodeId": "1758636606871_nlci5zgin", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636606871_g4io9w6xa", + "sourceNodeId": "1758636606871_nlci5zgin", + "targetNodeId": "1758636606871_bfer3pf0k", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636606871_uxssfr3ed", + "sourceNodeId": "1758636606871_nlci5zgin", + "targetNodeId": "1758636606871_fz7ji79yr", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636606871_f5z1f2yqo", + "sourceNodeId": "1758636606871_fz7ji79yr", + "targetNodeId": "1758636606871_9xic9f2n1", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636606871_8y8rjemlv", + "sourceNodeId": "1758636606871_fz7ji79yr", + "targetNodeId": "1758636606871_v7xq9t9ca", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636606871_rt5kqakrv", + "sourceNodeId": "1758636606871_fz7ji79yr", + "targetNodeId": "1758636606871_3hexy07r4", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636606871_x2gm9dhxe", + "sourceNodeId": "1758636606871_ramtsopmx", + "targetNodeId": "1758636606871_wkmdmgfdw", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636831033_ojdk1yez2", + "sourceNodeId": "1758636606871_d00eo32m0", + "targetNodeId": "1758636827735_ghi1jyp6e", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636838409_vvu7h2oxv", + "sourceNodeId": "1758636827735_ghi1jyp6e", + "targetNodeId": "1758636783944_9xxk4gqyo", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636881443_9l4j91h2l", + "sourceNodeId": "1758636827735_ghi1jyp6e", + "targetNodeId": "1758636868515_9gnnfpbvg", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636900749_xnmxnysyq", + "sourceNodeId": "1758636868515_9gnnfpbvg", + "targetNodeId": "1758636606871_ramtsopmx", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636941918_meg9myb9f", + "sourceNodeId": "1758636606871_ramtsopmx", + "targetNodeId": "1758636926699_fkhgmqdd1", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636956983_adx271a9m", + "sourceNodeId": "1758636606871_ramtsopmx", + "targetNodeId": "1758636950500_y5gbq9gt9", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636979547_gcm5tlfpz", + "sourceNodeId": "1758636868515_9gnnfpbvg", + "targetNodeId": "1758636975617_40xzee108", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636983730_fkcf1oa0r", + "sourceNodeId": "1758636868515_9gnnfpbvg", + "targetNodeId": "1758636981864_rtfejtz1m", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637120534_9zvi0veg2", + "sourceNodeId": "1758636606871_d00eo32m0", + "targetNodeId": "1758636606871_73vz04ef6", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637146881_ff7nkp6qb", + "sourceNodeId": "1758636606871_73vz04ef6", + "targetNodeId": "1758637141288_y6xr4qiqo", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637156935_u4w7h7pm7", + "sourceNodeId": "1758636606871_73vz04ef6", + "targetNodeId": "1758637139642_lhe3fdfhi", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637192215_aee0g0293", + "sourceNodeId": "1758636606871_73vz04ef6", + "targetNodeId": "1758636606871_4cwadcn7f", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637199301_402o3lx5d", + "sourceNodeId": "1758637141288_y6xr4qiqo", + "targetNodeId": "1758636606871_o1bko71f4", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637201011_83nxti0fo", + "sourceNodeId": "1758637141288_y6xr4qiqo", + "targetNodeId": "1758636606871_kman1jm6o", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637238023_fcezi3h1o", + "sourceNodeId": "1758637139642_lhe3fdfhi", + "targetNodeId": "1758637233781_l0o4zg8uh", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758637240305_v6mf8e059", + "sourceNodeId": "1758637139642_lhe3fdfhi", + "targetNodeId": "1758637233781_vrbhvrzj7", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 1.0893125857312862, + "canvasOffset": { + "x": 1076.4697723606591, + "y": 543.3385223005863 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/example-npc1.json b/bt-demo/extensions-config/bt-editor/example-npc1.json new file mode 100644 index 0000000..4488c78 --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/example-npc1.json @@ -0,0 +1,678 @@ +{ + "name": "example-npc1", + "description": "", + "nodes": [ + { + "id": "1758635344069_hairxmvmh", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -60, + "y": -220 + }, + "parameters": {}, + "children": [ + "1758635421003_4s8uj787l", + "1758635605374_990xn0z9c", + "1758635344069_4yss1wz7d", + "1758636072669_whqacjf0i", + "1758636171277_d7th6ojvm" + ], + "alias": "居民AI 选择节" + }, + { + "id": "1758635344069_4yss1wz7d", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 220, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758635344069_gg3q5rxes", + "1758635344069_7ecq7pfzw" + ], + "alias": "工作" + }, + { + "id": "1758635344069_gg3q5rxes", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": 20, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "在工作时间?" + }, + { + "id": "1758635344069_7ecq7pfzw", + "className": "Selector", + "name": "选择节点", + "position": { + "x": 300, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758635344069_8ck2fgr24", + "1758635344069_1wzefq3da", + "1758635344069_3ezjerufd" + ], + "alias": "工作行为选择" + }, + { + "id": "1758635344069_8ck2fgr24", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 160, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "商店经营" + }, + { + "id": "1758635344069_1wzefq3da", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 300, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "田间劳作" + }, + { + "id": "1758635344069_3ezjerufd", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 440, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "巡逻守卫" + }, + { + "id": "1758635421003_4s8uj787l", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -1040, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758635460230_zn5vibc1s", + "1758635463818_pn3pcjsxo", + "1758635545865_k2vgufpnb" + ], + "alias": "紧急情况处理" + }, + { + "id": "1758635460230_zn5vibc1s", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -1320, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758635460230_j09ztl8mq", + "1758635460230_qvwu6fx64" + ], + "alias": "火灾逃生" + }, + { + "id": "1758635460230_j09ztl8mq", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -1380, + "y": 40 + }, + "parameters": {}, + "children": [], + "alias": "发现火灾" + }, + { + "id": "1758635460230_qvwu6fx64", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -1240, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "逃离火场" + }, + { + "id": "1758635463818_pn3pcjsxo", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -1040, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758635463818_pihq95w8k", + "1758635463818_5lxcl9204" + ], + "alias": "怪物入侵" + }, + { + "id": "1758635463818_pihq95w8k", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -1100, + "y": 40 + }, + "parameters": {}, + "children": [], + "alias": "发现怪物" + }, + { + "id": "1758635463818_5lxcl9204", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -960, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "躲避怪物" + }, + { + "id": "1758635545865_k2vgufpnb", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -760, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758635545865_zlzorqr1s", + "1758635545865_z6hmdd955" + ], + "alias": "天气避难" + }, + { + "id": "1758635545865_zlzorqr1s", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -820, + "y": 40 + }, + "parameters": {}, + "children": [], + "alias": "恶劣天气" + }, + { + "id": "1758635545865_z6hmdd955", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -680, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "寻找庇护所" + }, + { + "id": "1758635605374_990xn0z9c", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -340, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758635740579_fw4dk6ikf", + "1758635744921_j7amyl952" + ], + "alias": "社交互动" + }, + { + "id": "1758635624148_qew2aoutm", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -400, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "开始对话" + }, + { + "id": "1758635652784_531a4s3wt", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -540, + "y": 40 + }, + "parameters": {}, + "children": [], + "alias": "玩家靠近?" + }, + { + "id": "1758635705235_zn4f5x42i", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -120, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "社交行为" + }, + { + "id": "1758635740579_fw4dk6ikf", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -480, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758635652784_531a4s3wt", + "1758635624148_qew2aoutm" + ], + "alias": "与玩家对话" + }, + { + "id": "1758635744921_j7amyl952", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -200, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758635767133_koukdag8k", + "1758635705235_zn4f5x42i" + ], + "alias": "与NPC交流" + }, + { + "id": "1758635767133_koukdag8k", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -260, + "y": 40 + }, + "parameters": {}, + "children": [], + "alias": "附近有其他NPC" + }, + { + "id": "1758636072669_whqacjf0i", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 780, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758636072669_23ygfl1xz", + "1758636072669_efwoobpa6" + ], + "alias": "休息" + }, + { + "id": "1758636072669_23ygfl1xz", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": 580, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "在休息时间?" + }, + { + "id": "1758636072669_efwoobpa6", + "className": "Selector", + "name": "选择节点", + "position": { + "x": 860, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758636072669_1a8wocwxo", + "1758636072669_2f7kryz2k", + "1758636072669_qq7v8cita" + ], + "alias": "休闲行为选择" + }, + { + "id": "1758636072669_1a8wocwxo", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 720, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "商店经营" + }, + { + "id": "1758636072669_2f7kryz2k", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 860, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "酒馆聚会" + }, + { + "id": "1758636072669_qq7v8cita", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 1000, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "街道闲逛" + }, + { + "id": "1758636171277_d7th6ojvm", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 1200, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758636171277_ga2mbrzxt", + "1758636171277_m9w7cla2o" + ], + "alias": "睡眠" + }, + { + "id": "1758636171277_ga2mbrzxt", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": 1140, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "睡觉时间" + }, + { + "id": "1758636171277_m9w7cla2o", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 1280, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "睡觉" + } + ], + "connections": [ + { + "id": "conn_1758635344069_g5evt0a55", + "sourceNodeId": "1758635344069_hairxmvmh", + "targetNodeId": "1758635344069_4yss1wz7d", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635344069_635mfq9i3", + "sourceNodeId": "1758635344069_4yss1wz7d", + "targetNodeId": "1758635344069_gg3q5rxes", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635344069_9s3w5e0un", + "sourceNodeId": "1758635344069_4yss1wz7d", + "targetNodeId": "1758635344069_7ecq7pfzw", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635344069_8x0splgwg", + "sourceNodeId": "1758635344069_7ecq7pfzw", + "targetNodeId": "1758635344069_8ck2fgr24", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635344069_isl3zgjdk", + "sourceNodeId": "1758635344069_7ecq7pfzw", + "targetNodeId": "1758635344069_1wzefq3da", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635344069_qt7oa6p4i", + "sourceNodeId": "1758635344069_7ecq7pfzw", + "targetNodeId": "1758635344069_3ezjerufd", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635424147_iccjn2uwj", + "sourceNodeId": "1758635344069_hairxmvmh", + "targetNodeId": "1758635421003_4s8uj787l", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635460230_zvxht1t8t", + "sourceNodeId": "1758635460230_zn5vibc1s", + "targetNodeId": "1758635460230_j09ztl8mq", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635460230_buaa4nw9s", + "sourceNodeId": "1758635460230_zn5vibc1s", + "targetNodeId": "1758635460230_qvwu6fx64", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635463818_rukkjwv57", + "sourceNodeId": "1758635463818_pn3pcjsxo", + "targetNodeId": "1758635463818_pihq95w8k", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635463818_qqu2vn4ri", + "sourceNodeId": "1758635463818_pn3pcjsxo", + "targetNodeId": "1758635463818_5lxcl9204", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635482801_8qzraey6h", + "sourceNodeId": "1758635421003_4s8uj787l", + "targetNodeId": "1758635460230_zn5vibc1s", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635484959_b57b7mjv2", + "sourceNodeId": "1758635421003_4s8uj787l", + "targetNodeId": "1758635463818_pn3pcjsxo", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635545865_0gg6i98tc", + "sourceNodeId": "1758635545865_k2vgufpnb", + "targetNodeId": "1758635545865_zlzorqr1s", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635545865_kq0md4693", + "sourceNodeId": "1758635545865_k2vgufpnb", + "targetNodeId": "1758635545865_z6hmdd955", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635548778_wu79ybxfq", + "sourceNodeId": "1758635421003_4s8uj787l", + "targetNodeId": "1758635545865_k2vgufpnb", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635607282_il4fwt2yi", + "sourceNodeId": "1758635344069_hairxmvmh", + "targetNodeId": "1758635605374_990xn0z9c", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635743478_nh8my40cm", + "sourceNodeId": "1758635605374_990xn0z9c", + "targetNodeId": "1758635740579_fw4dk6ikf", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635756635_47q580ro0", + "sourceNodeId": "1758635740579_fw4dk6ikf", + "targetNodeId": "1758635652784_531a4s3wt", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635760244_jpdlqjzx6", + "sourceNodeId": "1758635740579_fw4dk6ikf", + "targetNodeId": "1758635624148_qew2aoutm", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635769637_f4ss1fpgi", + "sourceNodeId": "1758635744921_j7amyl952", + "targetNodeId": "1758635767133_koukdag8k", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635771670_kaec4j4lz", + "sourceNodeId": "1758635605374_990xn0z9c", + "targetNodeId": "1758635744921_j7amyl952", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758635814216_xduqega2f", + "sourceNodeId": "1758635744921_j7amyl952", + "targetNodeId": "1758635705235_zn4f5x42i", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636072669_zzey7i401", + "sourceNodeId": "1758636072669_whqacjf0i", + "targetNodeId": "1758636072669_23ygfl1xz", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636072669_ntj9ny811", + "sourceNodeId": "1758636072669_whqacjf0i", + "targetNodeId": "1758636072669_efwoobpa6", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636072669_65be02vyw", + "sourceNodeId": "1758636072669_efwoobpa6", + "targetNodeId": "1758636072669_1a8wocwxo", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636072669_tilypn8pf", + "sourceNodeId": "1758636072669_efwoobpa6", + "targetNodeId": "1758636072669_2f7kryz2k", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636072669_3eblz933g", + "sourceNodeId": "1758636072669_efwoobpa6", + "targetNodeId": "1758636072669_qq7v8cita", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636106650_2q3md7ywn", + "sourceNodeId": "1758635344069_hairxmvmh", + "targetNodeId": "1758636072669_whqacjf0i", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636171277_n2pvad7qp", + "sourceNodeId": "1758636171277_d7th6ojvm", + "targetNodeId": "1758636171277_ga2mbrzxt", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636186409_7udx1m2k5", + "sourceNodeId": "1758635344069_hairxmvmh", + "targetNodeId": "1758636171277_d7th6ojvm", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758636230675_q9z4toddg", + "sourceNodeId": "1758636171277_d7th6ojvm", + "targetNodeId": "1758636171277_m9w7cla2o", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 1, + "canvasOffset": { + "x": -253, + "y": 424.5 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/example-scanning1.json b/bt-demo/extensions-config/bt-editor/example-scanning1.json new file mode 100644 index 0000000..98d26ec --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/example-scanning1.json @@ -0,0 +1,494 @@ +{ + "name": "example-scanning1", + "description": "", + "nodes": [ + { + "id": "1758633912545_7xy1se8pk", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -180, + "y": -240 + }, + "parameters": {}, + "children": [ + "1758633912545_z0wbw5zkn", + "1758633912545_ismgc4xad", + "1758633912545_cdy2pg1pn", + "1758634397890_nh8nat3ph" + ], + "alias": "守卫AI" + }, + { + "id": "1758633912545_26tx6w4f1", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -760, + "y": 140 + }, + "parameters": {}, + "children": [], + "alias": "攻击" + }, + { + "id": "1758633912545_df302i0u7", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -340, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "搜索敌人" + }, + { + "id": "1758633912545_qdoxrynps", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -480, + "y": 140 + }, + "parameters": {}, + "children": [], + "alias": "追击敌人" + }, + { + "id": "1758633912545_z0wbw5zkn", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -680, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758633987202_p7z2iewl8", + "1758634022458_f769kvf1x" + ], + "alias": "战斗模式" + }, + { + "id": "1758633912545_ismgc4xad", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -80, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758633912545_q02k78ubn", + "1758634249975_c1i6wxc2w" + ], + "alias": "警戒模式" + }, + { + "id": "1758633912545_nawabdhem", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -160, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "调查可疑位置" + }, + { + "id": "1758633912545_q02k78ubn", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -180, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "处于警戒状态?" + }, + { + "id": "1758633912545_cdy2pg1pn", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 360, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758634317404_8aaeb4ve2", + "1758634337943_93kaze24m" + ], + "alias": "怀疑模式" + }, + { + "id": "1758633912545_lgpy79s0o", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 280, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "查看声音方向" + }, + { + "id": "1758633912545_i1kac3qvv", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 420, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "接近可以位置" + }, + { + "id": "1758633912545_5cqcrrfkg", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 560, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "提高警觉" + }, + { + "id": "1758633987202_p7z2iewl8", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -800, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "处于战斗状态?" + }, + { + "id": "1758634022458_f769kvf1x", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -620, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758634091921_6xr6c6cul", + "1758634094741_dk5mmim4z", + "1758633912545_df302i0u7" + ], + "alias": "战斗行为选择" + }, + { + "id": "1758634091921_6xr6c6cul", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -820, + "y": 60 + }, + "parameters": {}, + "children": [ + "1758634117284_29jp1jxyq", + "1758633912545_26tx6w4f1" + ], + "alias": "攻击" + }, + { + "id": "1758634094741_dk5mmim4z", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -540, + "y": 60 + }, + "parameters": {}, + "children": [ + "1758634119520_rz3hx4hno", + "1758633912545_qdoxrynps" + ], + "alias": "追击" + }, + { + "id": "1758634117284_29jp1jxyq", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -900, + "y": 120 + }, + "parameters": {}, + "children": [], + "alias": "敌人在范围内?" + }, + { + "id": "1758634119520_rz3hx4hno", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -620, + "y": 120 + }, + "parameters": {}, + "children": [], + "alias": "敌人可见?" + }, + { + "id": "1758634249975_c1i6wxc2w", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -20, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758633912545_nawabdhem", + "1758634290870_im6rplw92", + "1758634284662_l7hvr7fuo" + ], + "alias": "警戒行为选择" + }, + { + "id": "1758634284662_l7hvr7fuo", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 120, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "扩大搜索范围" + }, + { + "id": "1758634290870_im6rplw92", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -20, + "y": 60 + }, + "parameters": {}, + "children": [], + "alias": "呼叫支援" + }, + { + "id": "1758634317404_8aaeb4ve2", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": 260, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "处于怀疑状态?" + }, + { + "id": "1758634337943_93kaze24m", + "className": "Selector", + "name": "选择节点", + "position": { + "x": 420, + "y": -40 + }, + "parameters": {}, + "children": [ + "1758633912545_lgpy79s0o", + "1758633912545_i1kac3qvv", + "1758633912545_5cqcrrfkg" + ], + "alias": "怀疑行为选择" + }, + { + "id": "1758634397890_nh8nat3ph", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 540, + "y": -120 + }, + "parameters": {}, + "children": [], + "alias": "正常巡逻" + } + ], + "connections": [ + { + "id": "conn_1758633912545_72krgicoe", + "sourceNodeId": "1758633912545_7xy1se8pk", + "targetNodeId": "1758633912545_z0wbw5zkn", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633912545_bhua2nzbe", + "sourceNodeId": "1758633912545_7xy1se8pk", + "targetNodeId": "1758633912545_ismgc4xad", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633912545_qztd5a8yt", + "sourceNodeId": "1758633912545_ismgc4xad", + "targetNodeId": "1758633912545_q02k78ubn", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633912545_fv0m9fjvz", + "sourceNodeId": "1758633912545_7xy1se8pk", + "targetNodeId": "1758633912545_cdy2pg1pn", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633989141_2t28ad61w", + "sourceNodeId": "1758633912545_z0wbw5zkn", + "targetNodeId": "1758633987202_p7z2iewl8", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634024382_250r9sidn", + "sourceNodeId": "1758633912545_z0wbw5zkn", + "targetNodeId": "1758634022458_f769kvf1x", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634096585_wmf98bvny", + "sourceNodeId": "1758634022458_f769kvf1x", + "targetNodeId": "1758634091921_6xr6c6cul", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634098806_r2eard8uu", + "sourceNodeId": "1758634022458_f769kvf1x", + "targetNodeId": "1758634094741_dk5mmim4z", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634121545_kb9vs2npo", + "sourceNodeId": "1758634091921_6xr6c6cul", + "targetNodeId": "1758634117284_29jp1jxyq", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634123128_3oq7o7eyv", + "sourceNodeId": "1758634094741_dk5mmim4z", + "targetNodeId": "1758634119520_rz3hx4hno", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634125701_vu0xgjyca", + "sourceNodeId": "1758634094741_dk5mmim4z", + "targetNodeId": "1758633912545_qdoxrynps", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634168660_65ptgegud", + "sourceNodeId": "1758634022458_f769kvf1x", + "targetNodeId": "1758633912545_df302i0u7", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634191735_9dp24mq79", + "sourceNodeId": "1758634091921_6xr6c6cul", + "targetNodeId": "1758633912545_26tx6w4f1", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634251707_a24kslpjc", + "sourceNodeId": "1758633912545_ismgc4xad", + "targetNodeId": "1758634249975_c1i6wxc2w", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634271128_gpkx0yz3a", + "sourceNodeId": "1758634249975_c1i6wxc2w", + "targetNodeId": "1758633912545_nawabdhem", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634293633_vn1hyilmw", + "sourceNodeId": "1758634249975_c1i6wxc2w", + "targetNodeId": "1758634290870_im6rplw92", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634295590_opwzkozm3", + "sourceNodeId": "1758634249975_c1i6wxc2w", + "targetNodeId": "1758634284662_l7hvr7fuo", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634319737_nkogdm0cs", + "sourceNodeId": "1758633912545_cdy2pg1pn", + "targetNodeId": "1758634317404_8aaeb4ve2", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634344054_vo7vw8fmt", + "sourceNodeId": "1758633912545_cdy2pg1pn", + "targetNodeId": "1758634337943_93kaze24m", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634385597_4o9rgtnom", + "sourceNodeId": "1758634337943_93kaze24m", + "targetNodeId": "1758633912545_lgpy79s0o", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634387383_fdxkfmsjs", + "sourceNodeId": "1758634337943_93kaze24m", + "targetNodeId": "1758633912545_i1kac3qvv", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634389050_8ad7tojaj", + "sourceNodeId": "1758634337943_93kaze24m", + "targetNodeId": "1758633912545_5cqcrrfkg", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758634403465_it5m1di95", + "sourceNodeId": "1758633912545_7xy1se8pk", + "targetNodeId": "1758634397890_nh8nat3ph", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 0.7882997760000008, + "canvasOffset": { + "x": 758.2, + "y": 526.7 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/test-bttree.json b/bt-demo/extensions-config/bt-editor/test-bttree.json new file mode 100644 index 0000000..93ae083 --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/test-bttree.json @@ -0,0 +1,11 @@ +{ + "name": "test-bttree", + "description": "", + "nodes": [], + "connections": [], + "canvasScale": 1, + "canvasOffset": { + "x": 723, + "y": 531.5 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/tree-example-move1.json b/bt-demo/extensions-config/bt-editor/tree-example-move1.json new file mode 100644 index 0000000..922c5cf --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/tree-example-move1.json @@ -0,0 +1,86 @@ +{ + "name": "tree-example-move1", + "description": "", + "nodes": [ + { + "id": "1758633158053_g12gp05tz", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -60, + "y": -220 + }, + "parameters": {}, + "children": [ + "1758633158053_n9lvsqtou", + "1758633158053_m7mptbzme", + "1758633230846_qqosra95l" + ], + "alias": "巡逻顺序节点" + }, + { + "id": "1758633158053_n9lvsqtou", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -200, + "y": -120 + }, + "parameters": {}, + "children": [], + "alias": "移动到下一巡逻点" + }, + { + "id": "1758633158053_m7mptbzme", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -60, + "y": -120 + }, + "parameters": {}, + "children": [], + "alias": "等待片刻" + }, + { + "id": "1758633230846_qqosra95l", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 80, + "y": -120 + }, + "parameters": {}, + "children": [], + "alias": "更新巡逻点" + } + ], + "connections": [ + { + "id": "conn_1758633158053_o1n2n1h4x", + "sourceNodeId": "1758633158053_g12gp05tz", + "targetNodeId": "1758633158053_n9lvsqtou", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633175180_5ukpygitx", + "sourceNodeId": "1758633158053_g12gp05tz", + "targetNodeId": "1758633158053_m7mptbzme", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633234380_cyje40zf5", + "sourceNodeId": "1758633158053_g12gp05tz", + "targetNodeId": "1758633230846_qqosra95l", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 1.2507982082066933, + "canvasOffset": { + "x": 569, + "y": 704.2467267515208 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/tree-example-move2.json b/bt-demo/extensions-config/bt-editor/tree-example-move2.json new file mode 100644 index 0000000..69ca749 --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/tree-example-move2.json @@ -0,0 +1,269 @@ +{ + "name": "tree-example-move2", + "description": "", + "nodes": [ + { + "id": "1758633408841_o85luvhya", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -60, + "y": -220 + }, + "parameters": {}, + "children": [ + "1758633372295_1vww23k1k", + "1758633460046_alqdykjsd", + "1758633637964_a0khi5e5k" + ], + "alias": "智能巡逻选择节点" + }, + { + "id": "1758633372295_1vww23k1k", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -400, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758633506673_f6rvm02zs", + "1758633372295_1vokt067a", + "1758633372295_7vyepkar1", + "1758633372295_86o7jk1k4" + ], + "alias": "调查异常" + }, + { + "id": "1758633372295_1vokt067a", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -480, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "移动到异常位置" + }, + { + "id": "1758633372295_7vyepkar1", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -340, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "调查" + }, + { + "id": "1758633372295_86o7jk1k4", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -200, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "返回巡逻路线" + }, + { + "id": "1758633460046_alqdykjsd", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 20, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758633584586_llol3kpvi", + "1758633460046_l5944c3nc" + ], + "alias": "响应呼叫" + }, + { + "id": "1758633460046_l5944c3nc", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 80, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "前往支援" + }, + { + "id": "1758633506673_f6rvm02zs", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -620, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "发现异常?" + }, + { + "id": "1758633584586_llol3kpvi", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -60, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "收到求援信号?" + }, + { + "id": "1758633637964_a0khi5e5k", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 360, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758633637964_dgyhnjuhl", + "1758633637964_d7uht9tgg", + "1758633637964_qc31zjqo5" + ], + "alias": "巡逻" + }, + { + "id": "1758633637964_dgyhnjuhl", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 220, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "移动到巡逻点" + }, + { + "id": "1758633637964_d7uht9tgg", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 360, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "环顾四周" + }, + { + "id": "1758633637964_qc31zjqo5", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 500, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "等待" + } + ], + "connections": [ + { + "id": "conn_1758633372295_qdmeu2m29", + "sourceNodeId": "1758633372295_1vww23k1k", + "targetNodeId": "1758633372295_1vokt067a", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633372295_myjl0d27a", + "sourceNodeId": "1758633372295_1vww23k1k", + "targetNodeId": "1758633372295_7vyepkar1", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633372295_1ron3sff3", + "sourceNodeId": "1758633372295_1vww23k1k", + "targetNodeId": "1758633372295_86o7jk1k4", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633410470_8lnhy9at6", + "sourceNodeId": "1758633408841_o85luvhya", + "targetNodeId": "1758633372295_1vww23k1k", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633460046_1zy4a0vls", + "sourceNodeId": "1758633460046_alqdykjsd", + "targetNodeId": "1758633460046_l5944c3nc", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633463466_ztkanoztc", + "sourceNodeId": "1758633408841_o85luvhya", + "targetNodeId": "1758633460046_alqdykjsd", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633508904_9pyqismh3", + "sourceNodeId": "1758633372295_1vww23k1k", + "targetNodeId": "1758633506673_f6rvm02zs", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633587533_wh3ccayol", + "sourceNodeId": "1758633460046_alqdykjsd", + "targetNodeId": "1758633584586_llol3kpvi", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633637964_8n9zrweqx", + "sourceNodeId": "1758633637964_a0khi5e5k", + "targetNodeId": "1758633637964_dgyhnjuhl", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633637964_y4rn7rwgc", + "sourceNodeId": "1758633637964_a0khi5e5k", + "targetNodeId": "1758633637964_d7uht9tgg", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633637964_x5ey2s8z7", + "sourceNodeId": "1758633637964_a0khi5e5k", + "targetNodeId": "1758633637964_qc31zjqo5", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758633639995_rdo0y6s9l", + "sourceNodeId": "1758633408841_o85luvhya", + "targetNodeId": "1758633637964_a0khi5e5k", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 1.0680104605497773, + "canvasOffset": { + "x": 569, + "y": 513.5 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/tree-example1.json b/bt-demo/extensions-config/bt-editor/tree-example1.json new file mode 100644 index 0000000..9458c6c --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/tree-example1.json @@ -0,0 +1,168 @@ +{ + "name": "tree-example1", + "description": "", + "nodes": [ + { + "id": "1758630775717_d1gipfamh", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -60, + "y": -220 + }, + "parameters": {}, + "children": [ + "1758630814199_qnitmm2sd", + "1758630832275_prflitgyu", + "1758630967937_2c0t3xi6t" + ], + "alias": "根选择节点" + }, + { + "id": "1758630814199_qnitmm2sd", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -260, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758630875390_e3dlxo1jg", + "1758630940801_u6j12wj96" + ], + "alias": "攻击顺序节点" + }, + { + "id": "1758630832275_prflitgyu", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": 20, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758630915741_ux73zz8ws", + "1758630955525_n0hw99t1q" + ], + "alias": "逃跑顺序节点" + }, + { + "id": "1758630875390_e3dlxo1jg", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -340, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "敌人在附近" + }, + { + "id": "1758630915741_ux73zz8ws", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -60, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "血量低" + }, + { + "id": "1758630940801_u6j12wj96", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -200, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "攻击" + }, + { + "id": "1758630955525_n0hw99t1q", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 80, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "逃跑" + }, + { + "id": "1758630967937_2c0t3xi6t", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 220, + "y": -120 + }, + "parameters": {}, + "children": [], + "alias": "巡逻" + } + ], + "connections": [ + { + "id": "conn_1758630929220_k30loxdah", + "sourceNodeId": "1758630814199_qnitmm2sd", + "targetNodeId": "1758630875390_e3dlxo1jg", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758630930951_5wgaug4ju", + "sourceNodeId": "1758630775717_d1gipfamh", + "targetNodeId": "1758630814199_qnitmm2sd", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758630932626_fz1jivc35", + "sourceNodeId": "1758630775717_d1gipfamh", + "targetNodeId": "1758630832275_prflitgyu", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758630934663_zoq1ugzkq", + "sourceNodeId": "1758630832275_prflitgyu", + "targetNodeId": "1758630915741_ux73zz8ws", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758630951335_f8qdr57vl", + "sourceNodeId": "1758630814199_qnitmm2sd", + "targetNodeId": "1758630940801_u6j12wj96", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758630957361_s28qi8xnd", + "sourceNodeId": "1758630832275_prflitgyu", + "targetNodeId": "1758630955525_n0hw99t1q", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758630973456_60p9k2k07", + "sourceNodeId": "1758630775717_d1gipfamh", + "targetNodeId": "1758630967937_2c0t3xi6t", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 1.2507982082066933, + "canvasOffset": { + "x": 465.5, + "y": 644.8338118617028 + } +} \ No newline at end of file diff --git a/bt-demo/extensions-config/bt-editor/tree-example2.json b/bt-demo/extensions-config/bt-editor/tree-example2.json new file mode 100644 index 0000000..71d1a9d --- /dev/null +++ b/bt-demo/extensions-config/bt-editor/tree-example2.json @@ -0,0 +1,107 @@ +{ + "name": "tree-example2", + "description": "", + "nodes": [ + { + "id": "1758631669066_247k1fo68", + "className": "Selector", + "name": "选择节点", + "position": { + "x": -60, + "y": -220 + }, + "parameters": {}, + "children": [ + "1758631669066_yqo3wjnns", + "1758631669066_g6lvqwonn" + ], + "alias": "根选择节点" + }, + { + "id": "1758631669066_yqo3wjnns", + "className": "Sequence", + "name": "顺序节点", + "position": { + "x": -120, + "y": -120 + }, + "parameters": {}, + "children": [ + "1758631669066_mr87yjkdq", + "1758631669066_e5qqjm0s8" + ], + "alias": "攻击顺序节点" + }, + { + "id": "1758631669066_mr87yjkdq", + "className": "BTConditionTest", + "name": "测试条件节点", + "position": { + "x": -200, + "y": -60 + }, + "parameters": {}, + "children": [], + "alias": "敌人在附近" + }, + { + "id": "1758631669066_e5qqjm0s8", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": -60, + "y": -40 + }, + "parameters": {}, + "children": [], + "alias": "攻击" + }, + { + "id": "1758631669066_g6lvqwonn", + "className": "BTTestNode2", + "name": "空行为节点", + "position": { + "x": 80, + "y": -120 + }, + "parameters": {}, + "children": [], + "alias": "巡逻" + } + ], + "connections": [ + { + "id": "conn_1758631669066_ioakn40wn", + "sourceNodeId": "1758631669066_yqo3wjnns", + "targetNodeId": "1758631669066_mr87yjkdq", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758631669066_x29ua1dz1", + "sourceNodeId": "1758631669066_247k1fo68", + "targetNodeId": "1758631669066_yqo3wjnns", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758631669066_m60t8xsq3", + "sourceNodeId": "1758631669066_yqo3wjnns", + "targetNodeId": "1758631669066_e5qqjm0s8", + "sourcePointType": "child", + "targetPointType": "parent" + }, + { + "id": "conn_1758631669066_3zbf492g0", + "sourceNodeId": "1758631669066_247k1fo68", + "targetNodeId": "1758631669066_g6lvqwonn", + "sourcePointType": "child", + "targetPointType": "parent" + } + ], + "canvasScale": 1.2507982082066933, + "canvasOffset": { + "x": 465.5, + "y": 644.8338118617028 + } +} \ No newline at end of file diff --git a/docs/BehaviorTree.md b/docs/BehaviorTree.md new file mode 100644 index 0000000..fb1f30b --- /dev/null +++ b/docs/BehaviorTree.md @@ -0,0 +1,252 @@ +# 硬核游戏开发 - 使用BehaviorTree实现游戏AI决策的开发详解 + +## 🎮 一个让策划抓狂的Bug + +> "这个BOSS太蠢了!它明明看到玩家了,为什么还在那里发呆?" + +这是我在某公司实习时,听到最多的一句话。那是一个魔幻RPG项目,我负责实现一个"智能"的龙王BOSS。按照策划的设计文档,这个BOSS应该能够: + +- 🔍 **侦察阶段**:在玩家进入领域前巡逻,警戒四周 +- ⚔️ **战斗阶段**:发现玩家后立即进入战斗状态,根据距离选择近战或远程攻击 +- 🩸 **血怒阶段**:血量低于30%时进入狂暴模式,攻击力翻倍 +- 🛡️ **防御阶段**:受到大量伤害时短暂防御,恢复少量血量 +- 💨 **逃跑阶段**:血量极低时尝试逃跑,寻找掩体 + +听起来很简单对吧?我天真地用了一堆 `if-else` 语句: + +```javascript +function updateBoss() { + if (boss.hp < 0.1 * boss.maxHp) { + if (canEscape()) { + escape(); + } else if (canDefend()) { + defend(); + } else { + attack(); + } + } else if (boss.hp < 0.3 * boss.maxHp) { + if (playerInRange()) { + berserkerAttack(); + } else { + moveToPlayer(); + } + } else if (playerDetected()) { + if (playerDistance() < 5) { + meleeAttack(); + } else { + rangedAttack(); + } + } else { + patrol(); + } +} +``` + +**结果?** 一场灾难! + +- BOSS经常在攻击和防御之间疯狂切换 +- 有时候明明发现了玩家,却突然开始巡逻 +- 血怒状态下居然还会尝试逃跑 +- 最要命的是:每次策划要求调整优先级,我都要重写整个逻辑! + +三个通宵后,我的代码变成了一个800行的意大利面条,连我自己都看不懂了。更糟糕的是,每次修复一个bug,就会产生三个新bug。 + +**直到我遇到了行为树...** + +一周后,同样的BOSS逻辑,我用行为树重新实现了: + +``` +龙王BOSS行为树: +根节点(优先选择器) +├── 逃跑分支(血量 < 10%) +│ ├── 寻找掩体 +│ └── 快速移动 +├── 防御分支(受到重击) +│ ├── 播放防御动画 +│ └── 恢复少量血量 +├── 血怒分支(血量 < 30%) +│ ├── 进入狂暴状态 +│ └── 疯狂攻击 +├── 战斗分支(发现玩家) +│ ├── 距离判断 +│ ├── 近战攻击 OR 远程攻击 +│ └── 追击玩家 +└── 巡逻分支(默认行为) + ├── 沿路径移动 + └── 警戒四周 +``` + +**奇迹发生了!** + +- ✅ BOSS行为逻辑清晰,优先级明确 +- ✅ 策划可以直接看懂并提出修改意见 +- ✅ 新增行为只需要添加新分支,不影响现有逻辑 +- ✅ 调试时可以清楚看到每一步的决策过程 +- ✅ 代码从800行缩减到200行,可读性提升10倍! + +更重要的是,当策划说"能不能让BOSS在血量50%时召唤小怪"时,我只需要在行为树中插入一个新分支,5分钟搞定! + +--- + +**这就是行为树的魅力。** + +作为一名游戏开发者,你是否也遇到过类似的问题?当游戏中的怪物、NPC需要表现出复杂行为时——巡逻、追击、逃跑、施法、观察环境并做出判断——背后往往不应该是一条又一条 if-else 的堆叠,而是一套清晰、可扩展、可调试的决策框架:**行为树(Behavior Tree)**。 + +本指南将带你从零开始认识行为树,让你也能构建出让策划赞不绝口的游戏 AI 决策系统! + +## 一、开篇 + +行为树是一种用于描述 AI 决策逻辑的树形结构:由根节点驱动,组合节点(如「选择器」「序列」)负责控制流程,装饰节点用于修饰行为,叶子节点则执行具体动作或进行条件判断。相较有限状态机(FSM),行为树更易组合与复用,能自然表达“尝试 A 否则 B”“按顺序执行直到成功”等复杂模式,并且非常适合可视化编辑与热更新。 + +## 二、行为树执行流程 - 一个哥布林的日常决策 + +想象一下,你是一只聪明的哥布林守卫,正在洞穴门口值班。每一秒钟,你的大脑都在运转着一套复杂的决策系统——这就是行为树! + +### 1. 三种人生状态(节点状态) + +就像人生一样,行为树中的每个节点都只有三种可能的状态: + +* **成功** ✅ - "太好了!任务完成!" +* **失败** ❌ - "唉,这条路走不通..." +* **运行中** ⏳ - "别催,我还在努力呢!" + +这就像你在思考"今晚吃什么"时的状态:要么想到了(成功),要么放弃了(失败),要么还在纠结中(运行中)。 + +### 2. 决策节点大家族 - 每个都有自己的性格 + +#### 2.1 选择节点 - "备胎专家" + +**性格特点**:永不放弃的乐观主义者,总是有Plan B、Plan C... + +想象你是个饿肚子的哥布林,面前有三个选择: +1. 去厨房找剩菜 +2. 去花园抓虫子 +3. 啃树皮充饥 + +选择节点就像一个不死心的你: +- 先试试厨房有没有剩菜(第一个子节点) +- 如果厨房空空如也(失败),那就去花园抓虫子(第二个子节点) +- 虫子也没有?那就啃树皮吧(第三个子节点) +- 只要有一个成功了,选择节点就满意地说:"搞定!" + +**执行规则**: +``` +从左到右挨个试: +✅ 子节点成功 → "太好了!" → 选择节点成功 +❌ 子节点失败 → "下一个!" → 继续尝试 +⏳ 子节点运行中 → "等等..." → 选择节点也运行中 +``` + +#### 2.2 顺序节点 - "完美主义者" + +**性格特点**:严格按计划执行,一步都不能错 + +还是那只饿肚子的哥布林,但这次你决定做一顿大餐: +1. 先洗手 +2. 然后准备食材 +3. 接着开火做饭 +4. 最后享用美食 + +顺序节点就像一个强迫症患者: +- 必须先洗手,洗不干净就不继续 +- 洗手成功了,才能准备食材 +- 食材准备好了,才能开火 +- 任何一步失败,整个计划泡汤! + +**执行规则**: +``` +严格按顺序来: +✅ 子节点成功 → "很好,下一步!" → 继续执行 +❌ 子节点失败 → "完蛋了!" → 顺序节点失败 +⏳ 子节点运行中 → "慢慢来..." → 顺序节点等待 +``` + +#### 2.3 行为节点 - "实干家" + +**性格特点**:话不多说,撸起袖子就是干 + +这是真正干活的节点,比如: +- "巡逻10秒钟" +- "攻击敌人" +- "播放死亡动画" + +行为节点就像一个靠谱的员工,给它一个任务,它会: +- 立即开始执行(返回"运行中") +- 完成后汇报结果("成功"或"失败") +- 有些任务需要时间,会持续返回"运行中" + +#### 2.4 条件节点 - "侦察兵" + +**性格特点**:眼观六路,耳听八方,专门负责收集情报 + +条件节点就像哨兵,负责观察环境: +- "敌人在视野内吗?" +- "血量低于30%吗?" +- "身上有钥匙吗?" + +它们动作很快,瞬间给出答案:要么"是"(成功),要么"不是"(失败),没有"也许"。 + +### 3. 一个完整的哥布林决策故事 + +让我们看看一只哥布林守卫的完整思考过程: + +``` +哥布林的行为树: +根节点(选择) +├── 战斗分支(顺序) +│ ├── 条件:发现敌人? +│ ├── 行为:冲向敌人 +│ └── 行为:攻击 +├── 巡逻分支(顺序) +│ ├── 条件:在巡逻路径上? +│ └── 行为:继续巡逻 +└── 待机分支 + └── 行为:原地等待 +``` + +**执行过程**: + +1. **第一轮思考**: + - 选择节点:"让我看看该做什么..." + - 尝试战斗分支 → 检查"发现敌人?" → 没有敌人(失败) + - 尝试巡逻分支 → 检查"在巡逻路径上?" → 是的(成功)→ 开始巡逻(运行中) + +2. **第二轮思考**(巡逻进行中): + - 选择节点:继续之前的决策 + - 巡逻分支:巡逻还在进行中... + +3. **第三轮思考**(突然发现敌人): + - 选择节点:重新评估 + - 尝试战斗分支 → "发现敌人?" → 有敌人!(成功)→ 冲向敌人(运行中) + +这就是行为树的魅力:每一帧都在重新评估,动态调整策略,就像一个真正在思考的智能体! + +### 4. 可视化流程图 - 让抽象变具体 + +看完了哥布林的故事,让我们用更直观的方式来理解行为树的执行过程。 + +#### 4.1 一颗简单的行为树结构 + +![image](../image/maunal/flow.png) + +这就是我们刚才讲的哥布林决策树的可视化版本。每个方块代表一个节点,箭头表示执行流向。 + +#### 4.2 执行流程的"心跳" + +行为树就像一颗跳动的心脏,每一帧(通常是1/60秒)都会从根节点开始"心跳"一次: + +![image](../image/maunal/flow2.png) + +**执行流程就像这样**: +1. **心跳开始** - 从根节点开始 +2. **向下探索** - 根据节点类型决定如何执行子节点 +3. **状态回传** - 叶子节点的结果层层向上传递 +4. **等待下次心跳** - 一帧结束,等待下一帧继续 + +这种"心跳式"的执行方式让AI能够: +- 🔄 **实时响应**:每帧都重新评估环境 +- 🎯 **优先级明确**:重要的行为总是先被考虑 +- 🔧 **易于调试**:可以清楚看到每一步的决策过程 +- 🚀 **性能友好**:只执行必要的节点,避免浪费 + +> **小贴士**:想象行为树就像一个永不疲倦的大脑,每一瞬间都在问自己:"现在最应该做什么?"然后立即行动! \ No newline at end of file diff --git a/docs/GUI-USED.md b/docs/GUI-USED.md new file mode 100644 index 0000000..8011417 --- /dev/null +++ b/docs/GUI-USED.md @@ -0,0 +1,237 @@ +# 行为树使用指南 + +本指南将详细介绍如何使用 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 顶部菜单栏中选择 `extension or 扩展` -> `kunpo` -> `行为树编辑器` + + +## 三、编辑器介绍 + +#### 快捷键 + +- **打开编辑器**: `Ctrl+Shift+K` (Windows) / `Cmd+Shift+K` (Mac) +- **导出配置**: `Ctrl+Shift+L` (Windows) / `Cmd+Shift+L` (Mac) + +#### 菜单访问 + +在 Cocos Creator 顶部菜单栏中选择 `extension or 扩展` -> `kunpo` -> `行为树编辑器` + + +### 编辑器功能介绍 + +行为树编辑器提供了一个直观的可视化界面,让你可以轻松创建和管理复杂的行为树结构。 + +#### 可视化节点编辑 +- **拖拽创建**:从左侧节点库拖拽节点到画布中 +- **分组管理**:节点按功能分组显示,便于查找 +- **实时预览**:节点显示类型图标和描述信息 + +#### 属性参数配置 +- **类型校验**:支持数字、字符串、布尔值、对象、数组等类型 +- **默认值**:自动填充装饰器中定义的默认值 +- **约束验证**:支持最小值、最大值、步长等约束条件 + +#### 连接线管理 +- **可视连接**:通过拖拽连接点创建父子关系 +- **自动布局**:连接线自动避让,保持界面整洁 +- **连接验证**:防止创建非法的节点连接关系 + +#### 画布操作 +- **缩放平移**:鼠标滚轮缩放,拖拽平移画布 +- **多选操作**:支持框选多个节点进行批量操作 + +#### 节点管理 +- **别名设置**:为节点设置有意义的别名,便于理解 +- **复制粘贴**:快速复制节点及其子树结构 +- **删除操作**:删除节点时自动清理相关连接 + +### 导出文件使用 + +#### 在项目中使用导出配置 + +##### 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. 创建行为树实例 + +```typescript +// entity参数 可以是任意想要关联的类型 +let btTree1: BT.INodeConfig[] = this.bt_config.json["bt-tree1"] +this._tree = BT.createBehaviorTree(btTree1, entity); +``` + +## 四、扩展编辑器节点池 + +### 节点装饰器 + +装饰器系统是连接自定义节点和编辑器的桥梁 +只有通过装饰器装饰的节点,才能在编辑器中的节点池中显示 + +* 行为节点装饰器 ***ClassAction*** +* 条件节点装饰器 ***ClassCondition*** +* 组合节点装饰器 ***ClassComposite*** +* 装饰节点装饰器 ***ClassDecorator*** +* 属性装饰器 ***prop*** + +下面我们通过一段示例代码来展示一下装饰器的使用 + +```typescript +// BT.ClassAction 是行为节点装饰器 +// MyNode参数需要和类名相同 +@BT.ClassAction("MyNode", { name: "显示名称", group: "节点分组", desc: "节点描述" }) +export class MyNode extends BT.LeafNode { + + // 基础类型参数装饰器 + // type: 参数类型 + // description: 参数描述 + // defaultValue: 参数默认值 + // min: 参数最小值 + // max: 参数最大值 + // step: + + @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` | + + +## 五、更新声明 + +## 0.0.1 (2025-09-23) +- 首版本 + +## 六、联系作者 + +* 邮箱: gong.xinhai@163.com +* 微信: G0900901 +* 扫码加微信: + + +## 七、版权声明 +此插件源代码可商业使用 + +商业授权范围仅限于在您自行开发的游戏作品中使用 + +不得进行任何形式的转售、租赁、传播等 + + +## 八、购买须知 +本产品为付费虚拟商品,一经购买成功概不退款,请在购买前谨慎确认购买内容。 \ No newline at end of file diff --git a/docs/USED.md b/docs/USED.md deleted file mode 100644 index a89ca1d..0000000 --- a/docs/USED.md +++ /dev/null @@ -1,335 +0,0 @@ -# 行为树使用指南 - -![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/image_tree.png b/image/image_tree.png index c75ad13..50fadea 100644 Binary files a/image/image_tree.png and b/image/image_tree.png differ diff --git a/image/introduce/example-boss.png b/image/introduce/example-boss.png new file mode 100644 index 0000000..7b1a0c7 Binary files /dev/null and b/image/introduce/example-boss.png differ diff --git a/image/introduce/example-move.png b/image/introduce/example-move.png new file mode 100644 index 0000000..e4af2bb Binary files /dev/null and b/image/introduce/example-move.png differ diff --git a/image/introduce/example-move2.png b/image/introduce/example-move2.png new file mode 100644 index 0000000..0e74cd3 Binary files /dev/null and b/image/introduce/example-move2.png differ diff --git a/image/introduce/example-npc1.png b/image/introduce/example-npc1.png new file mode 100644 index 0000000..380d7b9 Binary files /dev/null and b/image/introduce/example-npc1.png differ diff --git a/image/introduce/example-scanning1.png b/image/introduce/example-scanning1.png new file mode 100644 index 0000000..0e74cd3 Binary files /dev/null and b/image/introduce/example-scanning1.png differ diff --git a/image/introduce/node-status.png b/image/introduce/node-status.png new file mode 100644 index 0000000..f4050d4 Binary files /dev/null and b/image/introduce/node-status.png differ diff --git a/image/introduce/tree-example1.png b/image/introduce/tree-example1.png new file mode 100644 index 0000000..81b1639 Binary files /dev/null and b/image/introduce/tree-example1.png differ diff --git a/image/introduce/tree-example2.png b/image/introduce/tree-example2.png new file mode 100644 index 0000000..ae61b65 Binary files /dev/null and b/image/introduce/tree-example2.png differ diff --git a/image/maunal/extension.png b/image/maunal/extension.png new file mode 100644 index 0000000..aac6794 Binary files /dev/null and b/image/maunal/extension.png differ diff --git a/image/maunal/flow.png b/image/maunal/flow.png new file mode 100644 index 0000000..571c81f Binary files /dev/null and b/image/maunal/flow.png differ diff --git a/image/maunal/flow2.png b/image/maunal/flow2.png new file mode 100644 index 0000000..bb04b32 Binary files /dev/null and b/image/maunal/flow2.png differ diff --git a/image/maunal/scene.png b/image/maunal/scene.png new file mode 100644 index 0000000..584b509 Binary files /dev/null and b/image/maunal/scene.png differ