行为树的原理及实现

2023-11-05

查阅了一些行为树资料,目前最主要是参考了这篇文章,看完后感觉行为树实乃强大,绝对是替代状态机的不二之选。但从理论看起来很简单的行为树,真正着手起来却发现很多细节无从下手。


总结起来,就是:

1、行为树只是单纯的一棵决策树,还是决策+控制树。为了防止不必要的麻烦,我目前设计成单纯的决策树。

2、什么时候执行行为树的问题,也就是行为树的Tick问题,是在条件变化的时候执行一次,还是只要对象激活,就在Update里面一直Tick。前者明显很节省开销,但那样设计的最终结果可能是最后陷入事件发送的泥潭中。那么一直Tick可能是最简单的办法,于是就引下面出新的问题。目前采用了一直Tick的办法。

3、基本上可以明显节点有   Composite Node、 Decorator Node、 Condition Node、 Action Node,但具体细节就很头疼。比如组合节点里的Sequence Node。这个节点是不是在每个Tick周期都从头迭代一次子节点,还是记录正在运行的子节点。每次都迭代子节点,就感觉开销有点大。记录运行节点就会出现条件冗余问题,具体后面再讨论。目前采用保存当前运行节点的办法。

4、条件节点(Condition Node)的位置问题。看到很多设计都是条件节点在最后才进行判断,而实际上,如果把条件放在组合节点处,就可以有效短路判断,不再往下迭代。于是我就采用了这种方法。


设计开始

在Google Code上看到的某个行为树框架,用的是抽象类做节点。考虑到C#不能多继承,抽象类可能会导致某些时候会很棘手,所以还是用接口。虽然目前还未发现接口的好处。

在进行抽象设计的时候,接口的纯粹性虽然看起来更加清晰,不过有时候遇到需要重复使用某些类函数的时候就挺麻烦,让人感觉有点不利于复用。

[csharp]  view plain  copy
  1. public enum RunStatus  
  2. {  
  3.     Completed,  
  4.     Failure,  
  5.     Running,  
  6. }  
  7.   
  8. public interface IBehaviourTreeNode  
  9. {  
  10.     RunStatus status { getset; }  
  11.     string nodeName { getset; }  
  12.     bool Enter(object input);  
  13.     bool Leave(object input);  
  14.     bool Tick(object input, object output);  
  15.     RenderableNode renderNode { getset; }  
  16.     IBehaviourTreeNode parent { getset; }  
  17.     IBehaviourTreeNode Clone();  
  18. }  
  19.   
  20. /************************************************************************/  
  21. /* 组合结点                                                             */  
  22. /************************************************************************/  
  23. public interface ICompositeNode : IBehaviourTreeNode  
  24. {  
  25.     void AddNode(IBehaviourTreeNode node);  
  26.     void RemoveNode(IBehaviourTreeNode node);  
  27.     bool HasNode(IBehaviourTreeNode node);  
  28.   
  29.     void AddCondition(IConditionNode node);  
  30.     void RemoveCondition(IConditionNode node);  
  31.     bool HasCondition(IConditionNode node);  
  32.   
  33.     ArrayList nodeList { get; }  
  34.     ArrayList conditionList { get; }  
  35. }  
  36.   
  37. /************************************************************************/  
  38. /* 选择节点                                                             */  
  39. /************************************************************************/  
  40. public interface ISelectorNode : ICompositeNode  
  41. {  
  42.   
  43. }  
  44.   
  45. /************************************************************************/  
  46. /*顺序节点                                                              */  
  47. /************************************************************************/  
  48. public interface ISequenceNode : ICompositeNode  
  49. {  
  50.   
  51. }  
  52.   
  53. /************************************************************************/  
  54. /* 平行(并列)节点                                                             */  
  55. /************************************************************************/  
  56. public interface IParallelNode : ICompositeNode  
  57. {  
  58.   
  59. }  
  60.   
  61. //  
  62.   
  63. /************************************************************************/  
  64. /* 装饰结点                                                             */  
  65. /************************************************************************/  
  66. public interface IDecoratorNode : IBehaviourTreeNode  
  67. {  
  68.   
  69. }  
  70.   
  71. /************************************************************************/  
  72. /* 条件节点                                                             */  
  73. /************************************************************************/  
  74. public interface IConditionNode  
  75. {  
  76.     string nodeName { getset; }  
  77.     bool ExternalCondition();  
  78. }  
  79.   
  80. /************************************************************************/  
  81. /* 行为节点                                                             */  
  82. /************************************************************************/  
  83. public interface IActionNode : IBehaviourTreeNode  
  84. {  
  85.   
  86. }  
  87.   
  88. public interface IBehaviourTree  
  89. {  
  90.       
  91. }  

很多节点的接口都是空的,目前唯一的作用就是用于类型判断,很可能在最后也没有什么实际的作用,搞不好就是所谓的过度设计。如果最终确定没有用再删掉吧。

接口里出现了一个渲染节点,目的是为了能够更方便的把这个节点和负责渲染的节点联系到一起,方便节点的可视化。


如果只有接口,每次实现接口都要重复做很多工作,为了利用面向对象的复用特性,就来实现一些父类


[csharp]  view plain  copy
  1. public class BaseNode  
  2. {  
  3.     public BaseNode() { nodeName_ = this.GetType().Name + "\n"; }  
  4.   
  5.     protected RunStatus status_ = RunStatus.Completed;  
  6.     protected string nodeName_;  
  7.     protected RenderableNode renderNode_;  
  8.     protected IBehaviourTreeNode parent_;  
  9.   
  10.     public virtual RunStatus status { get { return status_; } set { status_ = value; } }  
  11.     public virtual string nodeName { get { return nodeName_; } set { nodeName_ = value; } }  
  12.     public virtual RenderableNode renderNode { get { return renderNode_; } set { renderNode_ = value; } }  
  13.     public virtual IBehaviourTreeNode parent { get { return parent_; } set { parent_ = value; } }  
  14.     public virtual IBehaviourTreeNode Clone() {  
  15.         var clone = new BaseNode();  
  16.         clone.status_ = status_;  
  17.         clone.nodeName_ = nodeName_;  
  18.         clone.renderNode_ = renderNode_;  
  19.         clone.parent_ = parent_;  
  20.         return clone as IBehaviourTreeNode;  
  21.     }  
  22. }  
  23.   
  24. public class BaseActionNode : IActionNode  
  25. {  
  26.     public BaseActionNode() { nodeName_ = this.GetType().Name + "\n"; }  
  27.     protected RunStatus status_ = RunStatus.Completed;  
  28.     protected string nodeName_;  
  29.     protected RenderableNode renderNode_;  
  30.     protected IBehaviourTreeNode parent_;  
  31.     public virtual RunStatus status { get { return status_; } set { status_ = value; } }  
  32.     public virtual string nodeName { get { return nodeName_; } set { nodeName_ = value; } }  
  33.     public virtual RenderableNode renderNode { get { return renderNode_; } set { renderNode_ = value; } }  
  34.     public virtual IBehaviourTreeNode parent { get { return parent_; } set { parent_ = value; } }  
  35.     public virtual IBehaviourTreeNode Clone()  
  36.     {  
  37.         var clone = new BaseActionNode();  
  38.         clone.status_ = status_;  
  39.         clone.nodeName_ = nodeName_;  
  40.         clone.renderNode_ = renderNode_;  
  41.         clone.parent_ = parent_;  
  42.         return clone as IBehaviourTreeNode;  
  43.     }  
  44.   
  45.     public virtual bool Enter(object input)  
  46.     {  
  47.         status_ = RunStatus.Running;  
  48.         return true;  
  49.     }  
  50.   
  51.     public virtual bool Leave(object input)  
  52.     {  
  53.         status_ = RunStatus.Completed;  
  54.         return true;  
  55.     }  
  56.   
  57.     public virtual bool Tick(object input, object output)  
  58.     {  
  59.         return true;  
  60.     }  
  61. }  
  62. public class BaseCondictionNode {  
  63.     protected string nodeName_;  
  64.     public virtual string nodeName { get { return nodeName_; } set { nodeName_ = value; } }  
  65.     public BaseCondictionNode() { nodeName_ = this.GetType().Name+"\n"; }  
  66.     public delegate bool ExternalFunc();  
  67.     protected ExternalFunc externalFunc;  
  68.     public static ExternalFunc GetExternalFunc(BaseCondictionNode node) {  
  69.         return node.externalFunc;  
  70.     }  
  71. }  
  72.   
  73. public class Precondition : BaseCondictionNode, IConditionNode{  
  74.     public Precondition(ExternalFunc func) { externalFunc = func; }  
  75.     public Precondition(BaseCondictionNode pre) { externalFunc = BaseCondictionNode.GetExternalFunc(pre); }  
  76.     public bool ExternalCondition()  
  77.     {  
  78.         if (externalFunc != nullreturn externalFunc();  
  79.         else return false;  
  80.     }  
  81. }  
  82.   
  83. public class PreconditionNOT : BaseCondictionNode, IConditionNode  
  84. {  
  85.     public PreconditionNOT(ExternalFunc func) { externalFunc = func; }  
  86.     public PreconditionNOT(BaseCondictionNode pre) { externalFunc = BaseCondictionNode.GetExternalFunc(pre); }  
  87.     public bool ExternalCondition()  
  88.     {  
  89.         if (externalFunc != nullreturn !externalFunc();  
  90.         else return false;  
  91.     }  
  92. }  
  93.   
  94. public class BaseCompositeNode : BaseNode{  
  95.     protected ArrayList nodeList_ = new ArrayList();  
  96.     protected ArrayList conditionList_ = new ArrayList();  
  97.     protected int runningNodeIndex = 0;  
  98.     protected bool CheckNodeAndCondition() {  
  99.         if (nodeList_.Count == 0)  
  100.         {  
  101.             status_ = RunStatus.Failure;  
  102.             Debug.Log("SequenceNode has no node!");  
  103.             return false;  
  104.         }  
  105.         return CheckCondition();  
  106.     }  
  107.     protected bool CheckCondition() {  
  108.         foreach (var node in conditionList_)  
  109.         {  
  110.             var condiction = node as IConditionNode;  
  111.             if (!condiction.ExternalCondition())  
  112.                 return false;  
  113.         }  
  114.         return true;  
  115.     }  
  116.     public virtual void AddNode(IBehaviourTreeNode node) { node.parent = (IBehaviourTreeNode)this; nodeList_.Add(node); }  
  117.     public virtual void RemoveNode(IBehaviourTreeNode node) { nodeList_.Remove(node); }  
  118.     public virtual bool HasNode(IBehaviourTreeNode node) { return nodeList_.Contains(node); }  
  119.   
  120.     public virtual void AddCondition(IConditionNode node) { conditionList_.Add(node); }  
  121.     public virtual void RemoveCondition(IConditionNode node) { conditionList_.Remove(node); }  
  122.     public virtual bool HasCondition(IConditionNode node) { return conditionList_.Contains(node); }  
  123.   
  124.     public virtual ArrayList nodeList { get { return nodeList_; } }  
  125.     public virtual ArrayList conditionList { get { return conditionList_; } }  
  126.   
  127.     public override IBehaviourTreeNode Clone()  
  128.     {  
  129.         var clone = base.Clone() as BaseCompositeNode;  
  130.         clone.nodeList_.AddRange(nodeList_);  
  131.         clone.conditionList_.AddRange(conditionList_);  
  132.         clone.runningNodeIndex = runningNodeIndex;  
  133.         return clone as IBehaviourTreeNode;  
  134.     }  
  135. }  


然后实现具体的节点,先是序列节点

[csharp]  view plain  copy
  1. public class SequenceNode : BaseCompositeNode, ISequenceNode  
  2. {  
  3.     public SequenceNode(bool canContinue_ = false) { canContinue = canContinue_; }  
  4.     public bool canContinue = false;  
  5.   
  6.   
  7.     public bool Enter(object input)  
  8.     {  
  9.         var checkOk = CheckNodeAndCondition();  
  10.         if (!checkOk) return false;  
  11.         var runningNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  12.         checkOk = runningNode.Enter(input);  
  13.         if (!checkOk) return false;  
  14.         status_ = RunStatus.Running;  
  15.         return true;  
  16.     }  
  17.   
  18.     public bool Leave(object input)  
  19.     {  
  20.         if (nodeList_.Count == 0)  
  21.         {  
  22.             status_ = RunStatus.Failure;  
  23.             Debug.Log("SequenceNode has no node!");  
  24.             return false;  
  25.         }  
  26.         var runningNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  27.         runningNode.Leave(input);  
  28.         if (canContinue)  
  29.         {  
  30.             runningNodeIndex++;  
  31.             runningNodeIndex %= nodeList_.Count;  
  32.         }  
  33.         status_ = RunStatus.Completed;  
  34.         return true;  
  35.     }  
  36.   
  37.     public bool Tick(object input, object output)  
  38.     {  
  39.         if (status_ == RunStatus.Failure) return false;  
  40.         if (status_ == RunStatus.Completed) return true;  
  41.           
  42.         var runningNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  43.         var checkOk = CheckCondition();  
  44.         if (!checkOk)  
  45.         {  
  46.             return false;  
  47.         }  
  48.   
  49.         switch (runningNode.status)  
  50.         {  
  51.             case RunStatus.Running:  
  52.                 if (!runningNode.Tick(input, output))  
  53.                 {  
  54.                     runningNode.Leave(input);  
  55.                     return false;  
  56.                 }  
  57.   
  58.                 break;  
  59.             default:  
  60.                 runningNode.Leave(input);  
  61.                 runningNodeIndex++;  
  62.                 if(runningNodeIndex >= nodeList_.Count)break;  
  63.                 var nextNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  64.                 var check = nextNode.Enter(input);  
  65.                 if (!check) return false;  
  66.                 break;  
  67.         }  
  68.         return true;  
  69.     }  
  70.   
  71.     public override IBehaviourTreeNode Clone()  
  72.     {  
  73.         var clone = base.Clone() as SequenceNode;  
  74.         clone.canContinue = canContinue;  
  75.         return clone;  
  76.     }  
  77. }  

这就是序列节点的设计,但是明显看起来很不爽,里面还出现了一个别扭的变量canContinue 。为什么会出现这个?因为序列节点的特点就是遇到一个子节点FALSE,就会停止并返回FALSE,但是这里我想用序列节点来做根节点,如果是根节点遇到这种情况,那么就不会执行下一个节点,而我看了很多种对于几大节点的描述,似乎都没提到这个。很多都用序列节点做根节点,有些就直接说是根节点。那么要么根节点另外实现,要么改一下序列节点。因为如果序列节点是非根节点的情况下,如果不是每次都从头开始,似乎又会引来新的问题,虽然目前还没想到会出什么问题。不过最后实现执行起来之后发现,用选择节点其实是一样的。所以目前这样的设计,可能是有根本上的问题。希望哪位大神可以指点一下。


然后是选择节点,根据了所有FALSE才返回FALSE的特点设计了


[csharp]  view plain  copy
  1. public class SelectorNode : BaseCompositeNode, ISelectorNode  
  2. {  
  3.     public bool Enter(object input)  
  4.     {  
  5.         var checkOk = CheckNodeAndCondition();  
  6.         if (!checkOk) return false;  
  7.          
  8.         do  
  9.         {  
  10.             var runningNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  11.             checkOk = runningNode.Enter(input);  
  12.             if (checkOk) break;  
  13.             runningNodeIndex++;  
  14.             if (runningNodeIndex >= nodeList_.Count) return false;  
  15.         } while (!checkOk);  
  16.           
  17.         status_ = RunStatus.Running;  
  18.         return true;  
  19.     }  
  20.   
  21.     public bool Leave(object input)  
  22.     {  
  23.         var runningNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  24.         runningNode.Leave(input);  
  25.         runningNodeIndex = 0;  
  26.         status_ = RunStatus.Completed;  
  27.         return true;  
  28.     }  
  29.   
  30.     public bool Tick(object input, object output)  
  31.     {  
  32.         if (status_ == RunStatus.Failure) return false;  
  33.         if (status_ == RunStatus.Completed) return true;  
  34.         var checkOk1 = CheckCondition();  
  35.         if (!checkOk1) return false;  
  36.         var runningNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  37.         switch (runningNode.status)  
  38.         {  
  39.             case RunStatus.Running:  
  40.                 if (!runningNode.Tick(input, output))  
  41.                 {  
  42.                     runningNode.Leave(input);  
  43.                     return false;  
  44.                 }  
  45.   
  46.                 break;  
  47.             default:  
  48.                 runningNode.Leave(input);  
  49.                 runningNodeIndex++;  
  50.                 if (runningNodeIndex >= nodeList_.Count) return false;  
  51.   
  52.                 bool checkOk = false;  
  53.                 do  
  54.                 {  
  55.                     var nextNode = nodeList_[runningNodeIndex] as IBehaviourTreeNode;  
  56.                     checkOk = nextNode.Enter(input);  
  57.                     if (checkOk) break;  
  58.                     runningNodeIndex++;  
  59.                     if (runningNodeIndex >= nodeList_.Count) return false;  
  60.                 } while (!checkOk);  
  61.                 break;  
  62.         }  
  63.         return true;  
  64.     }  
  65. }  

目前对于我的简单DEMO,组合节点只需要这两个就够了,实际上只需要选择节点、条件节点、动作节点就够了。所以说设计是不完全的,虽然能够实现目标需求,但是实际工作量仍挺大,具体接下来会说明。


行为节点



先放一些渲染节点的代码。实际上我基本上是第一次接触自己去渲染一种数据结构,看完网上的大牛们随随便便就能写出个数据结构的示意图,不得不佩服。我一时半会没想出怎么渲染出树状结构,于是就简单的把树按层分组,一层一层渲染,缺点就是不能很好的表现树的样子,父子关系不能很好的表示。这里放出来希望能抛砖引玉。我以后可能会去完事它,但是现在首先是要搞清楚行为树。实现这个完全是为了看看节点是否正确放置,以方便调试。

[csharp]  view plain  copy
  1. public class RenderableNode  
  2. {  
  3.     public RenderableNode parent;  
  4.     public IBehaviourTreeNode targetNode;  
  5.     public Rect posRect = new Rect();  
  6.     public string name;  
  7.     public int layer;  
  8.     public RunStatus staus;  
  9.     public override string ToString()  
  10.     {  
  11.         return name + "\n" + staus.ToString();  
  12.     }  
  13.     public virtual void Render()  
  14.     {  
  15.         bool running = staus == RunStatus.Running;  
  16.         var rect = posRect;  
  17.         rect.y -= (posRect.height / 2);  
  18.   
  19.         var oldColor = GUI.color;  
  20.          if (running)  
  21.         {  
  22.             GUI.color = Color.green;  
  23.         }  
  24.         GUI.Box(rect, ToString());  
  25.         GUI.color = oldColor;  
  26.   
  27.         if (parent == null && targetNode != null && targetNode.parent!=null)  
  28.         {  
  29.             parent = targetNode.parent.renderNode;  
  30.         }  
  31.         if (parent != null)  
  32.         {  
  33.             Vector2 parentPos = new Vector2();  
  34.             parentPos.x = parent.posRect.x + parent.posRect.width;  
  35.             parentPos.y = parent.posRect.y;  
  36.             GUIHelper.DrawLine(new Vector2(rect.x, rect.y + rect.height / 2), parentPos, running?Color.green:Color.yellow);  
  37.         }  
  38.           
  39.     }  
  40. }  
  41.   
  42. public class RenderableCondictionNode : RenderableNode  
  43. {  
  44.     public IConditionNode targetCondictionNode;  
  45.     public override string ToString() { parent = nullreturn name; }  
  46.     public override void Render()  
  47.     {  
  48.         var rect = posRect;  
  49.         rect.y -= (posRect.height / 2);  
  50.   
  51.         var oldColor = GUI.color;  
  52.         if (targetCondictionNode.ExternalCondition())  
  53.             GUI.color = Color.green;  
  54.         else  
  55.             GUI.color = Color.blue;  
  56.         GUI.Box(rect, ToString());  
  57.         GUI.color = oldColor;  
  58.     }  
  59. }  
  60.   
  61. public class EmptyNode : RenderableNode { public override void Render() { } }  
  62.   
  63. public class NodeBox  
  64. {  
  65.     public Rect posRect = new Rect();  
  66.     public List<RenderableNode> nodeList = new List<RenderableNode>();  
  67.     public void AddNode(RenderableNode node)  
  68.     {  
  69.         nodeList.Add(node);  
  70.     }  
  71.     public void Render()  
  72.     {  
  73.         posRect.y = Screen.height / 2;  
  74.         Rect rect = new Rect();  
  75.   
  76.         foreach (var node in nodeList)  
  77.         {  
  78.             var n = node;  
  79.             rect.height += (n.posRect.height + 1);  
  80.             rect.width = n.posRect.width + 10;  
  81.         }  
  82.         rect.height += 10;  
  83.         rect.x = posRect.x - rect.width / 2;  
  84.         rect.y = posRect.y - rect.height / 2;  
  85.         //GUI.Box(rect, "");  
  86.         posRect.width = rect.width;  
  87.         posRect.height = rect.height;  
  88.         float height = 0;  
  89.         for (var i = 0; i < nodeList.Count; i++)  
  90.         {  
  91.             var n = nodeList[i];  
  92.             n.posRect.y = rect.y + height + n.posRect.height / 2 + 5;  
  93.             n.posRect.x = rect.x + 5;  
  94.             n.Render();  
  95.             height += n.posRect.height + 1;  
  96.         }  
  97.     }  
  98. }  


放一张渲染出来的效果


虽然每一组都只是简单的居中,不过效果看起来还可以接受


然后从图中就可以看到问题了。所有正条件,都会有一个反条件,不这么做就无法在条件改变时,让当前节点返回FALSE,从而让行为树去寻找其他节点。而如果用状态机来做的话,条件肯定只用判断一次,比如

[csharp]  view plain  copy
  1. if(run){  
  2.    Run();  
  3. }  
  4. else{  
  5.    Walk();  
  6. }  
那么可能就回到最初的组合节点的设计了,组合节点就不得不每次都扫描条件。其实本质上我是在担心开销问题,因为变成节点后,就不在是if else那么简单,而是变成了函数调用的开销。简单的AI还好,如果大量复杂的AI,每次对整棵树进行扫描估计够呛。但是目前的设计,条件节点就会非常多,条件不完备就会出现BUG,似乎也不是非常好的情况。



最后放出一些细节

[csharp]  view plain  copy
  1. class PatrolAction : BaseActionNode {  
  2.   
  3.         public PatrolAction() { nodeName_ += "巡逻行为"; }  
  4.   
  5.         public override bool Tick(object input_, object output_)  
  6.         {  
  7.            // var input = input_ as WarriorInputData;  
  8.             var output = output_ as WarriorOutPutData;  
  9.             output.action = WarriorActon.ePatrol;  
  10.             return true;  
  11.         }  
  12.     }  
  13.   
  14.     class RunAwayAction : BaseActionNode {  
  15.         public RunAwayAction() { nodeName_ += "逃跑行为"; }  
  16.   
  17.         public override bool Tick(object input_, object output_)  
  18.         {  
  19.             // var input = input_ as WarriorInputData;  
  20.             var output = output_ as WarriorOutPutData;  
  21.             output.action = WarriorActon.eRunAway;  
  22.             return true;  
  23.         }  
  24.     }  
  25.   
  26.     class AttackAction : BaseActionNode {  
  27.         public AttackAction() { nodeName_ += "攻击行为"; }  
  28.   
  29.         public override bool Tick(object input_, object output_)  
  30.         {  
  31.             // var input = input_ as WarriorInputData;  
  32.             var output = output_ as WarriorOutPutData;  
  33.             output.action = WarriorActon.eAttack;  
  34.             return true;  
  35.         }  
  36.     }  
  37.   
  38.     class CrazyAttackAction : BaseActionNode {  
  39.         public CrazyAttackAction() { nodeName_ += "疯狂攻击行为"; }  
  40.   
  41.         public override bool Tick(object input_, object output_)  
  42.         {  
  43.             // var input = input_ as WarriorInputData;  
  44.             var output = output_ as WarriorOutPutData;  
  45.             output.action = WarriorActon.eCrazyAttack;  
  46.             return true;  
  47.         }  
  48.     }  
  49.   
  50.     class AlertAction : BaseActionNode  
  51.     {  
  52.         public AlertAction() { nodeName_ += "警戒行为"; }  
  53.         public override bool Tick(object input_, object output_)  
  54.         {  
  55.             // var input = input_ as WarriorInputData;  
  56.             var output = output_ as WarriorOutPutData;  
  57.             output.action = WarriorActon.eAlert;  
  58.             return true;  
  59.         }  
  60.     }  


[csharp]  view plain  copy
  1. private ICompositeNode rootNode = new SelectorNode();  
  2.     private WarriorInputData inputData = new WarriorInputData();  
  3.     private WarriorOutPutData outputData = new WarriorOutPutData();  
  4.     // Use this for initialization  
  5.     public void Start()  
  6.     {  
  7.         inputData.attribute = GetComponent<CharacterAttribute>();  
  8.   
  9.         rootNode.nodeName += "根";  
  10.   
  11.         //条件  
  12.         var hasNoTarget = new PreconditionNOT(() => { return inputData.attribute.hasTarget; });  
  13.         hasNoTarget.nodeName = "无目标";  
  14.         var hasTarget = new Precondition(hasNoTarget);  
  15.         hasTarget.nodeName = "发现目标";  
  16.         var isAnger = new Precondition(() => { return inputData.attribute.isAnger; });  
  17.         isAnger.nodeName = "愤怒状态";  
  18.         var isNotAnger = new PreconditionNOT(isAnger);  
  19.         isNotAnger.nodeName = "非愤怒状态";  
  20.         var HPLessThan500 = new Precondition(() => { return inputData.attribute.health < 500; });  
  21.         HPLessThan500.nodeName = "血少于500";  
  22.         var HPMoreThan500 = new PreconditionNOT(HPLessThan500);  
  23.         HPMoreThan500.nodeName = "血大于500";  
  24.         var isAlert = new Precondition(() => { return inputData.attribute.isAlert; });  
  25.         isAlert.nodeName = "警戒";  
  26.         var isNotAlert = new PreconditionNOT(isAlert);  
  27.         isNotAlert.nodeName = "非警戒";  
  28.   
  29.   
  30.         var patrolNode = new SequenceNode();  
  31.         patrolNode.nodeName += "巡逻";  
  32.         patrolNode.AddCondition(hasNoTarget);  
  33.         patrolNode.AddCondition(isNotAlert);  
  34.         patrolNode.AddNode(new PatrolAction());  
  35.   
  36.         var alert = new SequenceNode();  
  37.         alert.nodeName += "警戒";  
  38.         alert.AddCondition(hasNoTarget);  
  39.         alert.AddCondition(isAlert);  
  40.         alert.AddNode(new AlertAction());  
  41.           
  42.         var runaway = new SequenceNode();  
  43.         runaway.nodeName += "逃跑";  
  44.         runaway.AddCondition(hasTarget);  
  45.         runaway.AddCondition(HPLessThan500);  
  46.         runaway.AddNode(new RunAwayAction());  
  47.   
  48.         var attack = new SelectorNode();  
  49.         attack.nodeName += "攻击";  
  50.         attack.AddCondition(hasTarget);  
  51.         attack.AddCondition(HPMoreThan500);  
  52.   
  53.         var attackCrazy = new SequenceNode();  
  54.         attackCrazy.nodeName += "疯狂攻击";  
  55.         attackCrazy.AddCondition(isAnger);  
  56.         attackCrazy.AddNode(new CrazyAttackAction());  
  57.         attack.AddNode(attackCrazy);  
  58.   
  59.         var attackNormal = new SequenceNode();  
  60.         attackNormal.nodeName += "普通攻击";  
  61.         attackNormal.AddCondition(isNotAnger);  
  62.         attackNormal.AddNode(new AttackAction());  
  63.         attack.AddNode(attackNormal);  
  64.   
  65.         rootNode.AddNode(patrolNode);  
  66.         rootNode.AddNode(alert);  
  67.         rootNode.AddNode(runaway);  
  68.         rootNode.AddNode(attack);  
  69.         var ret = rootNode.Enter(inputData);  
  70.         if (!ret)  
  71.         {  
  72.             Debug.Log("无可执行节点!");  
  73.         }  
  74.     }  
  75.       
  76.     // Update is called once per frame  
  77.     void Update () {  
  78.         var ret = rootNode.Tick(inputData, outputData);  
  79.   
  80.         if (!ret)  
  81.             rootNode.Leave(inputData);  
  82.   
  83.         if (rootNode.status == RunStatus.Completed)  
  84.         {  
  85.             ret = rootNode.Enter(inputData);  
  86.             if (!ret)  
  87.                 rootNode.Leave(inputData);  
  88.         }  
  89.         else if (rootNode.status == RunStatus.Failure)  
  90.         {  
  91.             Debug.Log("BT Failed");  
  92.             enabled = false;  
  93.         }  
  94.   
  95.         if (outputData.action != inputData.action)  
  96.         {  
  97.             OnActionChange(outputData.action, inputData.action);  
  98.             inputData.action = outputData.action;  
  99.         }  
  100.     }  
  101.   
  102.     void OnActionChange(WarriorActon action, WarriorActon lastAction) {  
  103.       //  print("OnActionChange "+action+" last:"+lastAction);  
  104.         switch (lastAction)  
  105.         {  
  106.             case WarriorActon.ePatrol:  
  107.                 GetComponent<WarriorPatrol>().enabled = false;  
  108.                 break;  
  109.             case WarriorActon.eAttack:  
  110.             case WarriorActon.eCrazyAttack:  
  111.                 GetComponent<WarriorAttack>().enabled = false;  
  112.                 break;  
  113.             case WarriorActon.eRunAway:  
  114.                 GetComponent<WarriorRunAway>().enabled = false;  
  115.                 break;  
  116.             case WarriorActon.eAlert:  
  117.                 GetComponent<WarriorAlert>().enabled = false;  
  118.                 break;  
  119.         }  
  120.   
  121.         switch (action) {   
  122.             case WarriorActon.ePatrol:  
  123.                 GetComponent<WarriorPatrol>().enabled = true;  
  124.                 break;  
  125.             case WarriorActon.eAttack:  
  126.                 var attack = GetComponent<WarriorAttack>();  
  127.                 attack.revenge = false;  
  128.                 attack.enabled = true;  
  129.                 break;  
  130.             case WarriorActon.eCrazyAttack:  
  131.                 var crazyAttack = GetComponent<WarriorAttack>();  
  132.                 crazyAttack.revenge = true;  
  133.                 crazyAttack.enabled = true;  
  134.                 break;  
  135.             case WarriorActon.eRunAway:  
  136.                 GetComponent<WarriorRunAway>().enabled = true;  
  137.                 break;  
  138.             case WarriorActon.eAlert:  
  139.                 GetComponent<WarriorAlert>().enabled = true;  
  140.                 break;  
  141.             case WarriorActon.eIdle:  
  142.                 GetComponent<WarriorPatrol>().enabled = false;  
  143.                 GetComponent<WarriorAttack>().enabled = false;  
  144.                 GetComponent<WarriorRunAway>().enabled = false;  
  145.                 break;  
  146.         }  
  147.     }  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

行为树的原理及实现 的相关文章

  • UART通信原理

    UART 通信格式 串口全称叫做串行接口 通常也叫做 COM 接口 串行接口指的是数据一个一个的顺序传输 通信线路简单 使用两条线即可实现双向通信 一条用于发送 一条用于接收 串口通信距离远 但是速度相对会低 串口是一种很常用的工业接口 I
  • python国内镜像源

    让python pip使用国内镜像 国内源 清华 https pypi tuna tsinghua edu cn simple 阿里云 http mirrors aliyun com pypi simple 中国科技大学 https pyp
  • 有关HC-05蓝牙模块的学习记录

    文章目录 HC 05学习笔记 一 HC 05的基本硬件介绍 二 工作原理 三 使用方法 一 硬件连接 二 软件控制 1 第一个片段 2 第二个片段 四 具体应用 一 信息的传送 二 手机操纵单片机 五 注意事项 一 AT状态 1 第一种方法
  • mysql存储过程之循环(WHILE,REPEAT和LOOP)

    MySQL提供循环语句 允许我们根据条件重复执行一个SQL代码块其中有三个循环语句 WHILE REPEAT和LOOP 我们接下来分别看下 首先是WHILE语句来看下语法 WHILE expression DO statements END
  • 在 C++ STL 中复制的不同方法

    文章目录 1 C STL 中复制的不同方法及实例解析 传送门 gt gt AutoSAR实战系列300讲 糖果Autosar 总目录 1 C STL 中复制的不同方法及实例解析 C STL 中存在各种不同的 copy 它们允许以不同的方式执
  • idea设置控制台为单独的窗口

    如图 点击控制台右上角的齿轮 设置 按钮 然后点击windowed mode就可以分离出来了
  • 推荐一组用过好几年的非常稳定的dns

    通常宽带或路由里 都是未设置dns 自动获取的是本地运营商推荐的dns 那些dns非常非常的不稳定 经常打不开网页或有些图片不显示 甚至更可恶的还有广告劫持 下面推荐一组用过好几年的dns 比较稳定 分享给大家 223 5 5 5 4 2
  • you need to install ‘unbuffer‘ (from package expect or expect-dev)

    在下载完RK3399 Linux SDK后的第一次编译时 报以下错误 you need to install unbuffer from package expect or expect dev log saved on home user
  • Java循环查询数据库优化

    1 static 和 final 的用法 static 的作用从三个方面来谈 分别是静态变量 静态方法 静态类 静态变量 声明为 static 的静态变量实质上就是全局变量 当声明一个对象时 并不产生static 变量的拷贝 而是该类所有实
  • 使用git fetch和git merge手动解决一次pull request冲突

    问题提出 github上的一次 pull request 出现了 conflicts 需要解决合并冲突 冲突的内容主要是新增功能的代码和修改的注释 问题思考 由于之前没有更新分支 同时又提交了分支中的代码 而git不能在不丢失提交的情况下对
  • 自动驾驶和自然语言如何结合?NuPrompt来了!

    点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 gt gt 点击进入 自动驾驶之心 大模型 技术交流群 自动驾驶语言提示 原标题 Language Prompt for Autonomous Driving 论文链接
  • VMware Workstation17下载安装、环境搭建、网络配置最小化安装操作步骤

    一 VMware Workstation17下载安装 1 官网下载 官网网址 https www vmware com cn products workstation pro html 安装Workstation 17 Pro for Wi
  • openssl+http实现https

    openssl详解及实现https openssl详解及实现https OpenSSL 是一个安全套接字层密码库 囊括主要的密码算法 常用的密钥和证书封装管理功能及SSL协议 并提供丰富的应用程序供测试或其它目的使用 秘钥算法和协议 对称加
  • 数据库基础--多表查询

    在数据库查询过程中无法避免的需要从两张表中同时查询数据 此时我们需要用到各式各样的多表查询方式 接下来 简单介绍一下数据库基础的多表查询方式 以便于让大家更好的了解多表查询的过程 多表查询 项目开发中 在进行数据库表结构设计时 会根据业务需
  • 【五一创作】iSH修改hostname(主机名)【美化】【短篇技术类文章】

    最后一次更新 2023 4 30 请勿利用文章内的相关技术从事非法测试 由于传播 利用此文所提供的信息而造成的任何直接或者间接的后果及损失 均由使用者本人负责 作者不为此承担任何责任 文章目录 1 前言 简单 的介绍 2 踩坑 摸索 3 解
  • Arduin调节舵机的思路

    一 先判断舵机能够调节的最大值和最小值 二 让舵机转动能够平滑 三 用二维数组和for循环实现码垛机器人的效果 四 机械臂在指令模式的基础上增加手柄模式 手柄模式可以 通过键盘上字符按键操控舵机机械臂 五 通过蓝牙模块进行蓝牙无线设备连接
  • Python读取mat文件-转csv文件

    这篇教程主要介绍如何使用Python读取mat文件并且转csv文件 一 读取mat文件和转CSV 首先将MATLAB生成的mat文件存储在一个目录下 import pandas as pd import scipy from scipy i
  • 王者荣耀服务器维护七月,《王者荣耀》7.28不停服维护更新攻略教程 7月28日更新公告...

    在王者荣耀的游戏中 7月28日进行了不停服的更新维护 此次更新除了常规的修复以外 还带来了蔷薇珍宝阁活动 接下来就让小编带大家一起来看看详细的内容吧 王者荣耀2021年7月28日全服不停机更新公告 更新时间 7月28日8 30 9 30 更
  • RL 暂态电路与磁能

    前言 RL 电路是一个电阻 R 和 自感线圈 L 组成的 RL 电路 在连接或者接通电源U 的时候 由于自感电动势的作用 电路中的电流不会瞬间改变 而是一个连续的渐变的过程 通常这个时间很短暂 所以被称为暂态过程 正文 看看书上是怎么写的
  • 【iOS】—— APP启动流程

    文章目录 APP启动流程 冷启动和热启动 APP完整的启动流程 1 main函数执行前 系统会做的事 2 main函数执行后 3 首屏渲染完成后 Mach O APP启动流程 冷启动和热启动 冷启动 启动时 App的进程不在系统里 需要开启

随机推荐

  • CSS设置字间距、行间距、首行缩进

    CSS设置字间距 行间距 首行缩进 ps 本人亲测 阿里云2核4G5M的服务器性价比很高 新用户一块多一天 老用户三块多一天 最高可以买三年 感兴趣的可以戳一下 阿里云折扣服务器 字间距 1 text indent设置抬头距离css缩进 即
  • 大数据时代,区块链在数据安全领域有什么样的表现?

    大数据时代之下 一如我们无法抗拒科技进步带来的便捷及欢愉 我们同样也无法避免在享受这一切的过程中留下自己的 数字足迹 正因如此 数据如今已然被纳入企业的战略资源 开始指导决策 成为其提高行业核心竞争力的关键一环 当今的数字化时代 数据可谓是
  • ubuntu18.04安装GPU PyTorch

    转载自这篇文章 安装GPU版本的PyTorch 这里选择用pip进行安装 首先需要安装pip 执行命令sudo apt intall python pip3 该步骤可以跳过 现在建议配置pip虚拟环境 为此我们需要配置virtualenv
  • 一、用 ChatGPT 充当面试官

    目录 一 如何让 ChatGPT 充当面试官 1 1正确使用 1 2 反例 二 模拟面试 2 1 ChatGPT 让我介绍自己 2 2 ChatGPT 提问技术问题 2 2 1 技术问题 2 2 2 下一个问题
  • wifi名称可以有空格吗_但是名称中不能有空格

    Excel表格的每一个单元格都有一个默认的名称 其命名规则是列标加横标 例如A1表示第一列 第一行的单元格 如果要将某单元格重新命名 可以运用以下两种方法 工具 原料 Microsoft Office WPS Office 方法一 1 打开
  • [305]mysql1062错误:Duplicate entry '...' for key 'PRIMARY

    问题解释 Duplicate entry for key PRIMARY 即插入数据时 要插入数据的主键数据 已经存在 不能再重复添加了 例 Duplicate entry 0 for key PRIMARY是指主键为0的数据已经存在 不能
  • Linux常用命令大全(非常全!!!)

    前言 本文特点 授之以渔 了解命令学习方法 用途 不再死记硬背 拒绝漫无目的 准确无误 所有命令执行通过 环境为centos7 拒绝复制粘贴 实用性高 命令多为实际工作中用到的 实例讲解 拒绝纯理论 条理清晰 分类归纳 快速找到想要的命令
  • java疯狂讲义 笔记_《疯狂Java讲义》阅读笔记1

    2 2 UML统一建模语言 从粗粒度到细粒度 最常用的UML图 部署图 从物理处理器和设备的角度画图 其中一个设备中可能包括零个或若干个组件 用例图 表示的是一系列功能 一个用例表示系统的一个功能模块 如登录模块 组件图 多个类共同组成的j
  • OpenGL学习笔记(2)第一个程序——犹他茶壶(Teapot)

    好了 python opengl的开发环境搭建好后 我们就可以开始学习了 这里 我们先学习一个常见的例子 犹他茶壶 先贴代码 from OpenGL GL import from OpenGL GLU import from OpenGL
  • 【ReactNative/JS】uint8array转string convert uint8array to string

    客户端 服务器使用的protobuffer交互 客户端收到的是uint8array 面临着从unit8array转string 我使用的是下面的Crossplatform method Working with node js or oth
  • H3C官网-inode客户端下载

    打开 新华三官网 点击登录 用户名 yx800 密码 01230123 MacOS 安装 iNode Client 的事故与故事 提示 libCoreUtils dylib 将对您的电脑造成伤害 知乎 怎么用mac通过inode上网 知乎
  • js对象获取属性值的方法([]和.方式的不同)

    javascript获取目标对象的属性值 有两种方法 1 通过object key 即 的方式 2 通过object key 即 方式 下面用一个例子来说明 通过 方式获取属性值 key是静态值 即 h value 时 h是没有 为静态值
  • python中的生成器(generator)

    一 生成器 生成器是 Python 中非常有用的一种数据类型 它可以让你在 Python 中更加高效地处理大量数据 生成器可以让你一次生成一个值 而不是一次生成一个序列 这样可以节省内存并提高性能 二 实现generator的两种方式 py
  • js逆向加密五邑大学教务系统密码AES实现模拟登录(仅供参考)

    最近下班无聊 就看了一下之前写的教务系统模拟登录代码 python 爬虫 整体逻辑大概自己总结了一下 1 请求验证码图片 2 对输入的密码进行加密 3 封装账号 密码 验证码 发送post请求 但是在第2步的时候对输入的密码进行加密的过程中
  • 解决Git中fatal: refusing to merge unrelated histories

    Git的报错 在使用Git的过程中有时会出现一些问题 那么在解决了每个问题的时候 都需要去总结记录下来 下次不再犯 一 fatal refusing to merge unrelated histories 今天在使用Git创建项目的时候
  • Python中关于列表list的各种技能整理【定义、增删查改、函数、列表表达式】附练习题

    大家早上好 本人姓吴 如果觉得文章写得还行的话也可以叫我吴老师 欢迎大家跟我一起走进数据分析的世界 一起学习 感兴趣的朋友可以关注我的数据分析专栏 里面有许多优质的文章跟大家分享哦 今天带大家温习的是Python中的列表操作 全篇博文没有难
  • 【数据库学习】数据库平台:Postgres(PG)与PostgreSQL

    中文文档 PostgreSQL 10 6举例 Postgres原理及底层实现 1 安装配置与常见命令 1 安装与配置 安装 yum install https rpm 1 gt 安装目录 bin目录 二进制可执行文件目录 此目录下有post
  • Lim测试平台快速上手教程

    一 数据准备 这里我们将Lim平台作为测试项目 并通过编写一个简单的用例来介绍一下LIm的功能和流程 用例的步骤如下 用户登录 创建项目 修改项目 项目地址 http 121 43 43 59 二 编写用例 大多的接口测试平台都需要用户先维
  • 17_LinuxLCD驱动

    目录 Framebuffer设备 LCD驱动简析 LCD驱动程序编写 LCD屏幕参数节点信息修改 LCD 屏幕背光节点信息 使能Linux logo显示 设置LCD作为终端控制台 Framebuffer设备 先来回顾一下裸机的时候LCD驱动
  • 行为树的原理及实现

    查阅了一些行为树资料 目前最主要是参考了这篇文章 看完后感觉行为树实乃强大 绝对是替代状态机的不二之选 但从理论看起来很简单的行为树 真正着手起来却发现很多细节无从下手 总结起来 就是 1 行为树只是单纯的一棵决策树 还是决策 控制树 为了