第 31 天我们学过有限状态机(FSM),它的痛点是:状态一多,_physics_process 里全是 if state == ... 的跳转蜘蛛网,加一个行为就要动到处。今天换一条更抗「长大」的路线——行为树(Behavior Tree, BT):把 AI 决策拆成一棵可组合的树,每个节点只负责一件小事,整棵树每帧从根「走」一遍决定敌人此刻该干嘛。这是《光环》《最后生还者》这一类商业游戏里的主流做法,也是独立游戏让敌人「看起来有心眼」最省力的方式。
今日速览
- 行为树 = 一棵会「逐帧重跑」的决策树:每帧从根节点 Tick 一次,每个节点只回三种结果之一——
SUCCESS(成功)、FAILURE(失败)、RUNNING(正在做,还没完)。 - 两种组合节点撑起 90% 逻辑:
Sequence(顺序)=「先 A 再 B,全成功才成功」,Selector(选择)=「先试 A,不行再试 B,成功一个就停」。前者像「AND」,后者像「OR」。 - 落叶节点分两类:
Action(动作,会真正改变游戏世界,可以返回 RUNNING 跨多帧)和Condition(条件,只检查不改变,只回成功/失败)。 - Godot 里用 Beehave 插件最省事:它是 node-based 的,直接在场景树里把行为树搭出来挂到敌人节点上,还带编辑器内运行时调试视图和性能监monitor,能实时看到「现在走到了哪个叶子」。Godot 4.5+ 请用 Beehave 2.10+。
- 行为树不是越多越好:官方作者自己说,简单 AI 用行为树是「杀鸡用牛刀」,复杂 NPC/多敌人复用逻辑时才划算。
核心学习内容
1. 先记住「三个状态 + 两种组合」
行为树唯一的运行机制就是 Tick:每帧(或固定间隔)从根节点开始,父节点按规则「踢」子节点,子节点返回状态,父节点根据状态决定下一步。三个状态的含义必须刻进脑子:
SUCCESS:这件事做完了/成立。FAILURE:这件事做不了/不成立。RUNNING:这件事正在进行,下一帧接着做(追击敌人时帧帧都在 RUNNING)。
两种组合节点的语义:
| 节点 | 别名 | 规则 | 像 | 典型用途 |
|---|---|---|---|---|
| Sequence | 顺序 | 从左到右跑,遇到 FAILURE 就失败,全部 SUCCESS 才成功 | AND | 看见玩家 → 转过去 → 开火 |
| Selector | 选择/回退 | 从左到右跑,遇到 SUCCESS 就成功,全部 FAILURE 才失败 | OR | 能攻击就攻击,否则能追就追,否则巡逻 |
只要记住:Sequence 是「一路做到底」,Selector 是「挑第一个能成的」,一棵树的读法就通了大半。
2. 装饰器与「黑板」:让树会记忆、会节流
光有组合节点还不够,工程里真正好用的是两类东西:
- 装饰器(Decorator):套在某棵子树上,改写它的返回或执行次数。常用的有
Inverter(取反)、Cooldown(冷却,避免敌人每帧狂发招)、Repeat/RepeatUntilFail(重复巡逻)、TimeLimit(限时)、以及「条件守卫」——只有当条件满足时才允许进这棵子树(例如「血量 > 30% 才允许正面硬刚」)。 - 黑板(Blackboard):一块共享数据黑板,存
has_target、last_known_position、health_ratio这类信息。条件节点负责「写」,动作节点负责「读」,节点之间不直接互相调用,于是子树可以整块搬到别的敌人身上复用。
举个实战结构,一个「会心眼」的敌人,用 Selector 从高优先级到低优先级排:
Selector(根)
├─ Sequence【受击逃跑】: 条件(血量<30%) → 动作(跑向盟友/掩体)
├─ Sequence【攻击】 : 条件(在攻击范围 & 冷却好) → 动作(播放攻击动画)
├─ Sequence【追击】 : 条件(看得见玩家) → 动作(沿导航追上去)
├─ Sequence【警戒】 : 条件(有 last_known_position) → 动作(走过去搜查3秒) → 清空记忆
└─ 动作【巡逻】 : 在巡逻点之间来回走
这是行为树最舒服的地方:「逃跑」永远比「追击」优先,你只要把它放在 Selector 最左边就行,不用写一个巨大的优先级 if-else。要加新行为(比如「呼叫增援」),插一个子树进去,完全不碰其他分支。
3. 在 Godot 里落地(Beehave)
Beehave 的安装很短:从 Release 下载后,把 addons/beehave 放进项目的 /addons,在 项目设置 > 插件 里启用,再把 script_templates 放进项目即可。之后你会得到这几类核心节点:
BeehaveTree:根节点,负责 Tick 频率(可设每 N 帧一次以省性能)。SequenceComposite/SelectorComposite:就是上面的顺序/选择。ActionLeaf/ConditionLeaf:你写自定义脚本继承它们,实现tick(actor, blackboard)返回状态。- 各种装饰器:直接从节点库拖。
它的调试视图是关键生产力:运行游戏时能在编辑器里看到整棵树实时高亮「现在停在哪个节点」,敌人发呆时一眼就知道卡在哪。做 Steam 游戏时,AI 卡住是最难查的 bug 之一,这个视图能省掉大量 print。
自定义一个叶子的最小骨架(Action,返回 RUNNING 表示在执行):
extends ActionLeaf
func tick(actor: Node, blackboard: Blackboard) -> int:
var target = blackboard.get_value("target")
if target == null:
return FAILURE
actor.move_toward_node(target) # 你自己的移动逻辑
return SUCCESS if actor.reached(target) else RUNNING
最后一句叮嘱:行为树负责「决策」,移动/寻路交给 NavAgent(第 43 天学过 NavigationRegion2D + NavigationAgent2D),两者配合,敌人就既会想又会走。
🎯 DeepSeek 提示词
① 让 DeepSeek 把「敌人需求」翻译成一棵行为树
你是一位资深游戏 AI 设计师,精通行为树(Behavior Tree):Sequence=顺序(全成功才成功)、Selector=选择(成功一个即停)、Condition=只检查、Action=可改变世界并可返回 RUNNING。
【任务】把我的敌人需求翻译成一棵行为树,并标明每个节点的类型与返回值含义。
【我的敌人需求】一个近战小兵:血量低于 30% 会逃跑找掩体;玩家在攻击距离内且攻击冷却好了就打;看得见玩家就追;看不见但记得最后位置就过去搜查 3 秒然后放弃;否则在固定巡逻点之间巡逻。
【输出格式】
1. 用缩进文本画出整棵树(每行:节点类型(名称) : 该节点职责)。
2. 给出「优先级从高到低」的一句话解释。
3. 列出需要用到的黑板键(名字 + 类型 + 谁来写/谁来读)。
4. 指出哪几个子树可以抽成「可复用子树」以给其他敌人类复用。
【约束】只用 Sequence / Selector / Condition / Action 和必要的装饰器;不要引入无关概念;输出用中文。
② 让 DeepSeek 写 Beehave 的自定义 ActionLeaf / ConditionLeaf 脚本
你是 Godot 4 + GDScript 专家,熟悉 Beehave 行为树插件(Godot 4.5+ 用 Beehave 2.10+)。Beehave 的叶子脚本继承 ActionLeaf 或 ConditionLeaf,实现 tick(actor, blackboard) -> int,返回 Beehave 的 SUCCESS/FAILURE/RUNNING。
【任务】为下面这棵行为树里缺失的叶子,生成完整、可直接使用的 GDScript 脚本。
【树结构】(在此粘贴你上面得到的行为树)
【我要的叶子】1) 条件:血量低于阈值 2) 动作:跑向最近掩体 3) 动作:沿导航追击玩家 4) 动作:在巡逻点之间巡逻。
【输出格式】每个叶子一个代码块,含:文件名、extends 谁、tick 完整实现、用到的黑板键、以及「在场景里怎么挂到 BeehaveTree 下」的一句话说明。
【约束】Godot 4 稳定版 API;只用 Node2D/CharacterBody2D/NavigationAgent2D 等内置节点;代码带中文注释;不要在 tick 里做耗时寻路初始化;用 blackboard.get_value / set_value 读写共享数据。
③ 让 DeepSeek 当「行为树排错员」,诊断敌人为什么发呆/抽搐
你是行为树调试专家。行为树每帧从根 Tick,节点返回 SUCCESS/FAILURE/RUNNING;常见 bug 是「某个 Condition 永远为 FAILURE 导致整条 Selector 落空」或「某个 Action 忘了返回 RUNNING 导致帧帧重来、动画抽搐」。
【任务】根据我描述的「敌人坏行为」+ 树结构 + 运行时日志,定位根因并给出最小修复方案。
【敌人坏行为】(例:敌人看到玩家却在原地不停切朝向 / 一直站着不动 / 一靠近就立刻后退)
【我的树结构】(在此粘贴)
【运行时日志】(粘贴 Beehave 调试视图里观察到的每帧停留节点 / 或你的 print 日志)
【输出格式】
1. 最可能的 1–3 个根因,按可能性排序,各写明「树上哪个节点出问题、为什么」。
2. 每个根因给一个最小修改(改条件阈值 / 改返回状态 / 调整 Selector 顺序 / 加 Cooldown 装饰器)。
3. 给一段「改造后的树片段」。
【约束】只基于我给的信息推断,不要泛泛讲行为树理论;无法确定时明确列出还需要我提供哪条信息。
学习心得
做一款能上 Steam 的游戏,敌人「会不会想」往往直接决定玩家会不会觉得这游戏「有内容」。FSM 能撑起前期,但一旦敌人种类变多、行为变复杂,行为树的模块化 + 可复用 + 可视化调试会帮你省下大量维护时间——这恰好是个人开发者最缺的资源。
结合「用 DeepSeek 做 Steam 游戏」的目标,AI 与行为树是绝配:行为树本身就是结构化、可被描述的(树 + 状态 + 条件),DeepSeek 极擅长把「自然语言里的敌人性格」翻译成节点树,再逐叶子生成脚本。上面三条提示词构成闭环——①设计树 → ②生成叶子 → ③排错,正好覆盖从 0 到能跑的全过程。
明日关注
- 把今天三条提示词跑一遍,产出一个
Enemy_BT场景挂到现有 Godot 项目的敌人上,接上第 43 天的 NavigationAgent2D。 - 给行为树加一个黑板存
target/last_known_position,让「警戒搜查」子树能记住玩家的最后位置。 - 装好 Beehave 插件后,用它的运行时调试视图抓一次敌人发呆,验证「一眼定位卡点」是否真省时间。
本文由自动化学习流水线每日生成,坚持即学即记。
评论