适用于Unity3D项目的行为树。采用代码驱动方法编写,使用建造者模式以最大化大型项目的可维护性。灵感来自Fluent Behavior Tree。
特性
- 可扩展,编写自己的自定义可重用节点
- 预建任务库,快速启动你的AI
- 树可视化工具,在运行时调试你的树
- 通过TDD和单元测试进行大量测试
- 跟踪行为树的最后位置并在下一帧恢复
- 为Unity构建(无集成开销)
支持
如果你有问题或需要帮助,请加入Discord社区。
在Trello板上查看即将推出的功能和开发进度。
入门
创建树时,你需要将它们存储在变量中,以正确缓存所有必要的数据。
using UnityEngine;
using CleverCrow.Fluid.BTs.Tasks;
using CleverCrow.Fluid.BTs.Trees;
public class MyCustomAi : MonoBehaviour {
[SerializeField]
private BehaviorTree _tree;
private void Awake () {
_tree = new BehaviorTreeBuilder(gameObject)
.Sequence()
.Condition("自定义条件", () => {
return true;
})
.Do("自定义动作", () => {
return TaskStatus.Success;
})
.End()
.Build();
}
private void Update () {
// 每帧更新我们的树
_tree.Tick();
}
}
返回的TaskStatus的作用
根据你为任务状态返回的不同值,会发生不同的情况。
- Success:节点已完成,下一次
tree.Tick()
将重新启动树(如果没有其他节点要运行) - Failure:与Success相同,只是表示节点失败
- Continue:下次调用
tree.Tick()
时重新运行此节点。树会跟踪指针引用,只有在调用tree.Reset()
时才能清除。
树可视化工具
只要你的树存储变量设置为public
或有SerializeField
属性,你就可以在游戏运行时在编辑器中打印树的可视化效果。请注意,你无法在游戏未运行时查看树,因为树必须先构建才能可视化。
扩展树
你可以通过几行代码安全地向行为树添加新代码,允许你自定义BT的同时支持未来版本的升级。
using UnityEngine;
using CleverCrow.Fluid.BTs.Tasks;
using CleverCrow.Fluid.BTs.Tasks.Actions;
using CleverCrow.Fluid.BTs.Trees;
public class CustomAction : ActionBase {
protected override TaskStatus OnUpdate () {
Debug.Log(Owner.name);
return TaskStatus.Success;
}
}
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomAction (this BehaviorTreeBuilder builder, string name = "我的动作") {
return builder.AddNode(new CustomAction { Name = name });
}
}
public class ExampleUsage : MonoBehaviour {
public void Awake () {
var bt = new BehaviorTreeBuilder(gameObject)
.Sequence()
.CustomAction()
.End();
}
}
安装
Fluid Behavior Tree通过Unity的包管理器使用。要使用它,你需要在Packages/manifest.json
文件中添加以下行。之后,你就可以从Unity的包管理器窗口中可视化控制你正在使用的Fluid Behavior Tree的具体版本。这必须这样做,以便你的Unity编辑器可以连接到NPM的包注册表。
{
"scopedRegistries": [
{
"name": "NPM",
"url": "https://registry.npmjs.org",
"scopes": [
"com.fluid"
]
}
],
"dependencies": {
"com.fluid.behavior-tree": "2.2.0"
}
}
特定版本的存档和发布说明可在发布页面上找到。
示例场景
你可能想看看夺旗示例项目,了解Fluid Behavior Tree在你的项目中的实际使用方式。它演示了实时使用情况,单位试图夺取旗帜,同时抓取能量提升以试图获得优势。
目录
示例场景
你可能想查看夺旗示例项目,以了解流式行为树如何在你的项目中使用的实际示例。它演示了实时使用情况,单位试图夺取旗帜,同时抢夺能量提升以试图获得优势。
库
流式行为树附带了一个强大的预制动作、条件、组合和其他节点库,以帮助加速你的开发过程。
动作
通用动作
你可以即时创建一个通用动作。如果你发现自己经常重复使用相同的动作,你可能需要查看编写自定义动作的部分。
.Sequence()
.Do("自定义动作", () => {
return TaskStatus.Success;
})
.End()
等待
在行为树上跳过指定数量的滴答。
.Sequence()
// 在树上等待1个滴答后继续
.Wait(1)
.Do(MyAction)
.End()
等待时间
等待指定的秒数(以deltaTime
计)过去。
.Sequence()
.WaitTime(2.5f)
.Do(MyAction)
.End()
条件
通用条件
你可以即时创建一个通用条件。如果你发现自己经常重复使用相同的条件,你可能需要查看编写自定义条件的部分。
.Sequence()
.Condition("自定义条件", () => {
return true;
})
.Do(MyAction)
.End()
随机概率
根据给定的概率随机评估节点为真或假。
.Sequence()
// 50%的概率返回成功
.RandomChance(1, 2)
.Do(MyAction)
.End()
组合
序列
按顺序运行每个子节点,并期望获得成功状态以触发下一个节点。如果返回失败,序列将停止执行子节点并向父节点返回失败。
注意每个组合后面都必须跟着一个.End()
语句。这确保在构建树时你的节点被正确嵌套。
.Sequence()
.Do(() => { return TaskStatus.Success; })
.Do(() => { return TaskStatus.Success; })
// 此后的所有任务都不会运行,序列将退出
.Do(() => { return TaskStatus.Failure; })
.Do(() => { return TaskStatus.Success; })
.End()
选择器
运行每个子节点,直到返回成功。
.Selector()
// 运行但失败
.Do(() => { return TaskStatus.Failure; })
// 将在此停止,因为节点返回成功
.Do(() => { return TaskStatus.Success; })
// 不运行
.Do(() => { return TaskStatus.Success; })
.End()
随机选择器
使用洗牌算法随机选择一个子节点。寻找直到返回成功
或每个节点都失败。每次树初始运行时都会重新洗牌。
.SelectorRandom()
.Do(() => { return TaskStatus.Failure; })
.Do(() => { return TaskStatus.Success; })
.Do(() => { return TaskStatus.Failure; })
.End()
并行
同时运行所有子节点,直到它们全部返回成功。如果任何一个返回失败,则退出并停止所有正在运行的节点。
.Parallel()
// 这两个任务每帧都会运行
.Do(() => { return TaskStatus.Continue; })
.Do(() => { return TaskStatus.Continue; })
.End()
装饰器
装饰器是包装任何节点以改变返回值(或执行特殊逻辑)的父元素。它们非常强大,是动作、条件和组合的绝佳补充。
通用装饰器
你可以用自己的自定义装饰器代码包装任何节点。这允许你自定义可重用的功能。
注意:你必须手动调用子节点的 Update()
方法,否则它不会触发。另外,每个装饰器后面都必须跟一个 .End()
语句。否则树将无法正确构建。
.Sequence()
.Decorator("返回成功", child => {
child.Update();
return TaskStatus.Success;
})
.Do(() => { return TaskStatus.Failure; })
.End()
.Do(() => { return TaskStatus.Success; })
.End()
反转器
如果子节点返回 TaskStatus.Success
或 TaskStatus.Failure
,则反转返回的状态。
不改变 TaskStatus.Continue
。
.Sequence()
.Inverter()
.Do(() => { return TaskStatus.Success; })
.End()
.End()
返回成功
如果子节点返回 TaskStatus.Failure
,则返回 TaskStatus.Success
。
不改变 TaskStatus.Continue
。
.Sequence()
.ReturnSuccess()
.Do(() => { return TaskStatus.Failure; })
.End()
.End()
返回失败
如果子节点返回 TaskStatus.Success
,则返回 TaskStatus.Failure
。
不改变 TaskStatus.Continue
。
.Sequence()
.ReturnFailure()
.Do(() => { return TaskStatus.Success; })
.End()
.End()
永远重复
无论子节点返回什么状态,都返回 TaskStatus.Continue
。这个装饰器(及其所有后代任务)可以通过调用 BehaviorTree.Reset()
来中断。
.Sequence()
.RepeatForever()
.Do(() => { return TaskStatus.Success; })
.End()
.End()
重复直到失败
如果子节点返回 TaskStatus.Failure
,则返回 TaskStatus.Failure
,否则返回 TaskStatus.Continue
。
.Sequence()
.RepeatUntilFailure()
.Do(() => { return TaskStatus.Success; })
.End()
.End()
重复直到成功
如果子节点返回 TaskStatus.Success
,则返回 TaskStatus.Success
,否则返回 TaskStatus.Continue
。
.Sequence()
.RepeatUntilSuccess()
.Do(() => { return TaskStatus.Success; })
.End()
.End()
创建可重用的行为树
只需几行代码就可以组合树。这允许你创建可注入的行为树,将不同的节点捆绑在一起,用于复杂的功能,如搜索或攻击。
请注意,拼接树需要为注入构建一个新树,因为节点只在 .Build()
时进行深度复制。
using CleverCrow.Fluid.BTs.Trees;
using CleverCrow.Fluid.BTs.Tasks;
using UnityEngine;
public class MyCustomAi : MonoBehaviour {
private BehaviorTree _tree;
private void Awake () {
var injectTree = new BehaviorTreeBuilder(gameObject)
.Sequence()
.Do("自定义动作", () => {
return TaskStatus.Success;
})
.End();
_tree = new BehaviorTreeBuilder(gameObject)
.Sequence()
.Splice(injectTree.Build())
.Do("自定义动作", () => {
return TaskStatus.Success;
})
.End()
.Build();
}
private void Update () {
// 每帧更新我们的树
_tree.Tick();
}
}
创建自定义可重用节点
Fluid Behavior Tree 的强大之处在于你可以编写自己的节点并将它们添加到构建器中,而无需编辑任何源代码。你甚至可以创建添加新构建器功能的 Unity 包。例如,我们可以用几行代码编写一个新的树构建器方法,如下所示,它设置了 AI 系统的目标。
var tree = new BehaviorTreeBuilder(gameObject)
.Sequence()
.AgentDestination("查找敌人", target)
.Do(() => {
// 激活追击敌人代码
return TaskStatus.Success;
})
.End()
.Build();
你的第一个自定义节点和扩展
创建你的第一个自定义动作并实现它应该只需要大约 3 分钟。首先创建一个新的动作。
using CleverCrow.Fluid.BTs.Tasks;
using CleverCrow.Fluid.BTs.Tasks.Actions;
using UnityEngine;
using UnityEngine.AI;
public class AgentDestination : ActionBase {
private NavMeshAgent _agent;
public Transform target;
protected override void OnInit () {
_agent = Owner.GetComponent<NavMeshAgent>();
}
protected override TaskStatus OnUpdate () {
_agent.SetDestination(target.position);
return TaskStatus.Success;
}
}
接下来,我们需要用新的 AgentDestination 动作扩展 BehaviorTreeBuilder
脚本。有关 C# 类扩展的更多信息,请参阅官方文档。
using CleverCrow.Fluid.BTs.Trees;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder AgentDestination (this BehaviorTreeBuilder builder, string name, Transform target) {
return builder.AddNode(new AgentDestination {
Name = name,
target = target,
});
}
}
现在你完成了!你已经创建了一个自定义动作和可扩展的行为树构建器,它可以适应未来的版本。以下示例将更多地涉及相同的内容。但每个示例都涵盖了不同的节点类型。
自定义动作
你可以使用以下模板创建自己的自定义动作。这对于捆绑你经常使用的代码非常有用。
using UnityEngine;
using CleverCrow.Fluid.BTs.Tasks;
using CleverCrow.Fluid.BTs.Tasks.Actions;
public class CustomAction : ActionBase {
// 仅在首次运行此节点时触发(非常适合缓存数据)
protected override void OnInit () {
}
// 每次该节点开始运行时触发。如果该节点上次返回TaskStatus.Continue则不会触发
protected override void OnStart () {
}
// 每次在树上调用`Tick()`并运行该节点时触发
protected override TaskStatus OnUpdate () {
// 指向拥有行为树的GameObject
Debug.Log(Owner.name);
return TaskStatus.Success;
}
// 每当该节点运行后退出时触发
protected override void OnExit () {
}
}
将你的新节点添加到扩展中。
using CleverCrow.Fluid.BTs.Trees;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomAction (this BehaviorTreeBuilder builder, string name = "My Action") {
return builder.AddNode(new CustomAction {
Name = name,
});
}
}
自定义条件
可以使用以下示例模板添加自定义条件。你会想要使用这些来进行诸如视线、AI是否可以移动到某个位置等需要复杂检查的任务。
using UnityEngine;
using CleverCrow.Fluid.BTs.Tasks;
public class CustomCondition : ConditionBase {
// 仅在该节点首次运行时触发(非常适合缓存数据)
protected override void OnInit () {
}
// 每次该节点开始运行时触发。如果该节点上次返回TaskStatus.Continue则不会触发
protected override void OnStart () {
}
// 每次在树上调用`Tick()`并运行该节点时触发
protected override bool OnUpdate () {
// 指向拥有行为树的GameObject
Debug.Log(Owner.name);
return true;
}
// 每当该节点运行后退出时触发
protected override void OnExit () {
}
}
使用以下代码片段将新条件添加到你的行为树构建器中。
using CleverCrow.Fluid.BTs.Trees;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomCondition (this BehaviorTreeBuilder builder, string name = "My Condition") {
return builder.AddNode(new CustomCondition {
Name = name,
});
}
}
自定义组合器
Fluid Behavior Tree不仅限于自定义操作和条件。你可以使用相当简单的API创建新的组合器类型。以下是一个基本序列的示例。
using CleverCrow.Fluid.BTs.TaskParents.Composites;
using CleverCrow.Fluid.BTs.Tasks;
public class CustomSequence : CompositeBase {
protected override TaskStatus OnUpdate () {
for (var i = ChildIndex; i < Children.Count; i++) {
var child = Children[ChildIndex];
var status = child.Update();
if (status != TaskStatus.Success) {
return status;
}
ChildIndex++;
}
return TaskStatus.Success;
}
}
将自定义组合器添加到你的行为树就像添加操作一样简单。只需一行代码。
using CleverCrow.Fluid.BTs.Trees;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomSequence (this BehaviorTreeBuilder builder, string name = "My Sequence") {
return builder.ParentTask<CustomSequence>(name);
}
}
自定义装饰器
装饰器也可以自定义编写以减少重复代码。
using CleverCrow.Fluid.BTs.Decorators;
using CleverCrow.Fluid.BTs.Tasks;
public class CustomInverter : DecoratorBase {
protected override TaskStatus OnUpdate () {
if (Child == null) {
return TaskStatus.Success;
}
var childStatus = Child.Update();
var status = childStatus;
switch (childStatus) {
case TaskStatus.Success:
status = TaskStatus.Failure;
break;
case TaskStatus.Failure:
status = TaskStatus.Success;
break;
}
return status;
}
}
实现装饰器与组合器类似。如果你需要在组合器上设置参数,你会想要查看BehaviorTreeBuilder.AddNodeWithPointer()
方法。
using CleverCrow.Fluid.BTs.Trees;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomInverter (this BehaviorTreeBuilder builder, string name = "My Inverter") {
// 如果你需要从参数设置自定义组合器数据,请参见BehaviorTreeBuilder.AddNodeWithPointer()
return builder.ParentTask<CustomInverter>(name);
}
}
格式化问题
如果你使用自动格式化工具,它可能会破坏构建器语法的代码格式。要避免这种情况,你可以在JetBrains Rider中关闭格式化,如下所示。如果你需要特定的IDE,搜索特定的格式化禁用注释应该不难。
// @formatter:off
_tree = new BehaviorTreeBuilder(gameObject)
.Sequence()
.Condition("Custom Condition", () => {
return true;
})
.Do("Custom Action", () => {
return TaskStatus.Success;
})
.End()
.Build();
// @formatter:on
每日构建
要访问包管理器友好的develop
每日构建,你需要手动编辑你的Packages/manifest.json
,如下所示。
{
"dependencies": {
"com.fluid.behavior-tree": "https://github.com/ashblue/fluid-behavior-tree.git#nightly"
}
}
请注意,要获取更新的每日构建,你必须删除此行以及清单中任何相关的锁定数据,让Unity重新构建,然后再添加回来。因为Unity会锁定Git url作为包的提交哈希。
开发环境
如果你希望运行开发环境,你需要安装node.js。然后在根目录下运行以下命令一次:
npm install
如果你想创建构建,在根目录运行npm run build
,它会填充dist
文件夹。
提交代码
所有的提交都应该使用Commitizen(运行npm install
时会自动安装)。提交会在发布时自动编译为版本号,所以这一点非常重要。没有使用Commitizen的提交的PR将会被拒绝。
要进行提交,在根目录的终端中输入以下命令:
npm run commit
拉取请求 / 贡献
请查看贡献指南文档以获取更多信息。
贡献者荣誉
感谢这些优秀的人(表情符号说明):
Ash Blue 💻 | Jesse Talavera-Greenberg 💻 | PureSaltProductions 📓 | Martin Duvergey 🐛 | call-stack 🐛 | Piotr Jastrzebski 💻 | Sounghoo 💻 |
TNThomas 🐛 💻 |
本项目遵循 all-contributors 规范。欢迎任何形式的贡献!
贡献者 ✨
感谢这些优秀的人们(表情符号说明):
本项目遵循 all-contributors 规范。欢迎任何形式的贡献!