/// ...
behaviorTree = new Root(
new Service(0.5f, () => { behaviorTree.Blackboard["foo"] = !behaviorTree.Blackboard.Get<bool>("foo"); },
new Selector(
new BlackboardCondition("foo", Operator.IS_EQUAL, true, Stops.IMMEDIATE_RESTART,
new Sequence(
new Action(() => Debug.Log("foo")),
new WaitUntilStopped()
)
),
new Sequence(
new Action(() => Debug.Log("bar")),
new WaitUntilStopped()
)
)
)
);
behaviorTree.Start();
//...
前言
NPBahave是GitHub上开源的一个行为树,其代码简洁有力,与Unity耦合较低,[toc]适合拿来做双端行为树。
注意,由于时间关系,原文中的链接这里将不再提供引用。开源链接
https://github.com/meniku/NPBehave
正文
本人将为其贡献一个)NPBehave基于功能强大且灵活的基于代码的方法,从behavior库定义行为树,并混合了虚幻引擎的一些很棒的行为树概念。与传统的行为树不同,事件驱动的行为树不需要每帧从根节点遍历。它们保持当前状态,只有在实际需要时才继续遍历。这使得它们的性能更高,使用起来也更简单。
在NPBehave中,您将发现大多数节点类型来自传统的行为树,但也有一些类似于虚幻引擎中的节点类型。不过,添加您自己的自定义节点类型也相当容易。
安装
只需将NPBehave文件夹放入Unity项目中。还有一个Examples子文件夹,其中有一些您可能想要参考的示例场景。
例子:“Hello World” 行为树
让我们开始一个例子
当您运行此命令时,您将注意到“Hello World”将被一次又一次地打印出来。这是因为当遍历过树中的最后一个节点时,根节点将重新启动整个树。如果不需要这样,可以添加一个waituntilstop节点,如下所示:
到目前为止,这个行为树中还没有任何事件驱动。在我们深入研究之前,您需要了解黑板(Blackboards)是什么。
Blackboards(黑板)
在NPBehave中,就像在虚幻引擎中一样,我们有黑板。你可以把它们看作是你的AI的“记忆”。在NPBehave中,黑板是基于可以观察更改的字典。我们主要使用
Service来存储和更新黑板中的值。我们使用BlackboardCondition或BlackboardQuery来观察黑板的变化,然后遍历bahaviour树。您也可以在其他任何地方访问或修改黑板的值(您也可以经常从Action节点访问它们)。当您实例化一个
根(Root)时,黑板将自动创建,但是您也可以使用它的构造函数提供另一个实例(这对于共享黑板(Shared Blackboards)特别有用)例子:一个事件驱动的行为树
这有一个使用黑板的事件驱动的行为树例子
这个示例将在每500毫秒交替打印“foo”和“bar”。我们使用一个
服务装饰器节点在黑板上切换foo boolean值。我们使用BlackboardCondition装饰器节点根据这个boolean值来决定是否执行分支。BlackboardCondition还会根据这个值监视黑板的变化(依据黑板的当前值和我们提供的值做为判断基准),Stops.IMMEDIATE_RESTART作用是如果条件不再为真,则当前执行的分支将停止,如果条件再次为真,则立即重新启动。请注意,您应该将服务放在真正的方法中,而不是使用lambdas,这将使您的树更具可读性。更复杂的行为也是如此。
终止原则
一些装饰器(如BlackboardCondition、Condition或BlackboardQuery)有一个stopsOnChange参数,允许定义stop规则。该参数允许装饰器停止其父
组合(Composite)中正在运行的子树。他是您用来掌控NPBehave中的事件驱动的主要工具。较
低优先级的节点是在其父组合中的当前节点之后定义的节点。最有用和最常用的stop规则是SELF、IMMEDIATE_RESTARTt或LOWER_PRIORITY_IMMEDIATE_RESTART。
不过,如果你对虚幻引擎的行为树形成了惯性思维,就要小心了。在NPBehave中,LOWER_PRIORITY和BOTH具有稍微不同的含义。IMMEDIATE_RESTART实际上匹配Unreal的Both,而LOWER_PRIORITY_IMMEDIATE_RESTART匹配Unreal的Lower Priority。
作者提供了如下终止原则
黑板的替代品
在NPBehave中,您在一个MonoBehaviour中定义您的行为树,因为没有必要将所有内容都存储在黑板中。如果没有BlackboardDecorator或BlackboardQuery,则使用其他终止规则而不是Stops.NONE。你可能根本不需要它们出现在黑板上。您还可以使用普通的成员变量——它通常更干净、编写速度更快、性能更好。这意味着在这种情况下,您不会使用NPBehave的事件驱动特性,但这通常是不必要的。
如果你想在不使用黑板的情况下使用stopsOnChange终止规则,NPBehave中存在两种替代方法:
终止规则参数。当提供除Stops.NONE之外的任何其他值,且给定查询函数的结果发生更改时,条件将频繁地检查条件并根据stop规则中断节点。请注意,此方法不是事件驱动的,它查询每一帧(或在提供的时间间隔内),因此如果大量使用它们,可能会导致大量不必要的查询。然而,对于简单的情况,它通常是足够的,并且比Blackboard-Key、Service和BlackboardCondition的组合简单得多。节点执行结果
在NPBehave中,节点可以成功也可以失败。与传统的行为树不同,节点执行时没有返回结果。相反,一旦节点执行完成(成功或失败),节点本身将告诉父节点。在创建自己的节点类型时,务必记住这一点。
节点类型
在NPBehave中,我们有四种不同的节点类型:
只有一个子节点,用于修改子节点的结果或在执行子节点时执行其他操作(例如,更新黑板的Service)终止树
如果你的怪物被杀死了,或者你销毁了游戏对象,你应该停止树。你可以在你的脚本中加入如下内容:
运行时Debugger
可以使用调试器组件在运行时在检查器中调试行为树。
共享黑板
您可以选择在AI的多个实例之间共享黑板。如果您想实现某种集群行为,这将非常有用。此外,您可以创建黑板层次结构,这允许您将共享黑板与非共享黑板组合起来。 您可以使用UnityContext.GetSharedBlackboard(name)在任何地方访问共享的blackboard实例。
拓展库
请参考现有的节点实现了解如何创建自定义节点类型,但是在创建之前至少要阅读以下黄金规则。
黄金法则
最终会回溯到上层节点的Stopped()函数!请查看现有的实现以供参考。实现任务
对于任务,可以从任务类扩展并覆盖DoStart()和DoStop()方法。在DoStart()中,您启动您的逻辑,一旦您完成了,您将使用适当的结果调用Stopped(bool result)。您的节点可能被另一个节点取消,因此请确保实现DoStop(),进行适当的清理并在它之后立即调用Stopped(bool result)。 对于一个相对简单的示例,请查看Wait Task.cs。 正如黄金规则部分已经提到的,在NPBehave中,您必须在节点停止之后始终调用Stopped(bool result)。因此,目前不支持在多个帧上挂起取消操作,这将导致不可预测的行为。
实现观察装饰器
编写装饰器要比编写任务复杂得多。然而,为了方便起见,存在一个特殊的基类。ObservingDecorator。这个类可用于简单地实现“条件”装饰器,这些装饰器可选地使用stopsOnChange 终止规则。 您所要做的就是从它ObservingDecorator扩展并覆盖bool IsConditionMet()方法。如果希望支持stop - rules,还必须实现startobservice()和stopobserve()。对于一个简单的示例,请查看Condition Decorator.cs
实现常规装饰器
对于常规装饰器,可以从Decorator.cs扩展并覆盖DoStart()、DoStop()和DoChildStopped(Node child, bool result)方法。 您可以通过访问Decoratee属性启动或停止已装饰节点,并在其上调用start()或stop()。 如果您的decorator接收到DoStop()调用,它将负责相应地停止Decoratee,并且在这种情况下不会立即调用Stopped(bool result)。相反,它将在DoChildStopped(Node child, bool result)方法中执行该操作。请注意,DoChildStopped(Node child, bool result)并不一定意味着您的decorator停止了decoratee, decoratee本身也可能停止,在这种情况下,您不需要立即停止decoratee(如果您想实现诸如冷却等功能,这可能很有用)。要查明装饰器是否被停止,可以查询它的isstoprequired属性。 对于非常基本的实现,请查看Failer Node.cs;对于稍微复杂一点的实现,请查看Repeater Node.cs。 此外,您还可以实现DoParentCompositeStopped()方法,即使您的装饰器处于非活动状态,也可以调用该方法。如果您想为在装饰器stopped后仍保持活动的侦听器执行额外的清理工作,这是非常有用的。以ObservingDecorator为例。
实现组合
组合节点需要对库有更深入的理解,通常不需要实现新的节点。如果您真的需要一个新的组合,请在GitHub项目上创建一个票据,或者与我联系,我将尽力帮助您正确地完成它。
结点状态
很有可能你不需要访问它们,但了解它们仍然是件好事:
可以使用CurrentState属性检索当前状态
时钟
您可以使用节点中的时钟注册计时器,或者在每一帧上得到通知。使用RootNode.Clock访问时钟。查看
Wait Task.cs以获得关于如何在时钟上注册计时器的示例。 默认情况下,行为树将使用UnityContext指定的全局时钟。这个时钟每一帧都更新一次。在某些情况下,你可能想要拥有更多的控制权。例如,您可能想要限制或暂停对一组AI的更新。由于这个原因,您可以向根节点和Blackboard提供自己的受控时钟实例,这允许您精确地控制何时更新行为树。查看 Clock Throttling .cs。结点类型汇总
Root
组合结点
Selector
Selector(params Node[] children):按顺序运行子元素,直到其中一个子元素成功(如果其中一个子元素成功,则成功)。
Sequence
Sequence(params Node[] children):按顺序运行子节点,直到其中一个失败(如果所有子节点都没有失败,则成功)。
Parallel
Parallel(Policy successPolicy, Policy failurePolicy, params Node[] children): 并行运行子节点。
当failurePolocity为Polociy.ONE。当其中一个孩子失败时,并行就会停止,返回失败。
当successPolicy为Policy.ONE。当其中一个孩子失败时,并行将停止,返回成功。
如果并行没有因为Policy.ONE而停止。它会一直执行,直到所有的子节点都完成,然后如果所有的子节点都成功或者失败,它就会返回成功。
RandomSelector
RandomSelector(params Node[] children):按随机顺序运行子进程,直到其中一个子进程成功(如果其中一个子进程成功,则成功)。注意,对于打断规则,最初的顺序定义了优先级。
RandomSequence
RandomSequence(params Node[] children):以随机顺序运行子节点,直到其中一个失败(如果没有子节点失败,则成功)。注意,对于打断规则,最初的顺序定义了优先级。
任务结点
Action
Action(System.Action action):(总是立即成功完成)
Action(System.Func singleFrameFunc): 可以成功或失败的操作(返回false to fail)
Action(Func<bool, Result> multiframeFunc):可以在多个帧上执行的操作( Result.BLOCKED——你的行动还没有准备好 Result.PROGRESS——当你忙着这个行为的时候, Result.SUCCESS或Result.FAILED——成功或失败)。
Action(Func<Request, Result> multiframeFunc2): 与上面类似,但是Request会给你一个状态信息: Request.START表示它是您的操作或返回结果的第一个标记或者是Result.BLOCKED最后一个标记。 Request.UPDATE表示您最后一次返回Request.PROGRESS; Request.CANCEL意味着您需要取消操作并返回结果。成功或者Result.FAILED。
Wait
WaitUntilStopped
装饰器结点
BlackboardCondition
BlackboardQuery
Condition
Cooldown
Failer
Inverter
Observer
Random
Repeater
Service
Succeeder
TimeMax
TimeMin
WaitForCondition
后记
本文档仅供参考,一切以代码为准!