Project Icon

fluid-behavior-tree

Unity3D行为树框架 提升AI开发效率与灵活性

fluid-behavior-tree是专为Unity3D项目设计的行为树框架。采用代码驱动和构建器模式,提高大型项目可维护性。框架可扩展,含预构建任务库,支持运行时树可视化,经严格单元测试。能跟踪行为树最后位置并在下一帧恢复,无缝集成Unity,无额外开销。适用于提升AI开发效率的开发者。

流体行为树

构建状态

适用于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.SuccessTaskStatus.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

拉取请求 / 贡献

请查看贡献指南文档以获取更多信息。

贡献者荣誉

感谢这些优秀的人(表情符号说明):

本项目遵循 all-contributors 规范。欢迎任何形式的贡献!

贡献者 ✨

感谢这些优秀的人们(表情符号说明):

本项目遵循 all-contributors 规范。欢迎任何形式的贡献!

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号