查阅了一些行为树资料,目前最主要是参考了这篇文章,看完后感觉行为树实乃强大,绝对是替代状态机的不二之选。但从理论看起来很简单的行为树,真正着手起来却发现很多细节无从下手。
总结起来,就是:
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#不能多继承,抽象类可能会导致某些时候会很棘手,所以还是用接口。虽然目前还未发现接口的好处。
在进行抽象设计的时候,接口的纯粹性虽然看起来更加清晰,不过有时候遇到需要重复使用某些类函数的时候就挺麻烦,让人感觉有点不利于复用。public enum RunStatus
{
Completed,
Failure,
Running,
}
public interface IBehaviourTreeNode
{
RunStatus status { get; set; }
string nodeName { get; set; }
bool Enter(object input);
bool Leave(object input);
bool Tick(object input, object output);
RenderableNode renderNode { get; set; }
IBehaviourTreeNode parent { get; set; }
IBehaviourTreeNode Clone();
}
/************************************************************************/
/* 组合结点 */
/************************************************************************/
public interface ICompositeNode : IBehaviourTreeNode
{
void AddNode(IBehaviourTreeNode node);
void RemoveNode(IBehaviourTreeNode node);
bool HasNode(IBehaviourTreeNode node);
void AddCondition(IConditionNode node);
void RemoveCondition(IConditionNode node);
bool HasCondition(IConditionNode node);
ArrayList nodeList { get; }
ArrayList conditionList { get; }
}
/************************************************************************/
/* 选择节点 */
/************************************************************************/
public interface ISelectorNode : ICompositeNode
{
}
/************************************************************************/
/*顺序节点 */
/************************************************************************/
public interface ISequenceNode : ICompositeNode
{
}
/************************************************************************/
/* 平行(并列)节点 */
/************************************************************************/
public interface IParallelNode : ICompositeNode
{
}
//
/************************************************************************/
/* 装饰结点 */
/************************************************************************/
public interface IDecoratorNode : IBehaviourTreeNode
{
}
/************************************************************************/
/* 条件节点 */
/************************************************************************/
public interface IConditionNode
{
string nodeName { get; set; }
bool ExternalCondition();
}
/************************************************************************/
/* 行为节点 */
/************************************************************************/
public interface IActionNode : IBehaviourTreeNode
{
}
public interface IBehaviourTree
{
}
很多节点的接口都是空的,目前唯一的作用就是用于类型判断,很可能在最后也没有什么实际的作用,搞不好就是所谓的过度设计。如果最终确定没有用再删掉吧。
接口里出现了一个渲染节点,目的是为了能够更方便的把这个节点和负责渲染的节点联系到一起,方便节点的可视化。
如果只有接口,每次实现接口都要重复做很多工作,为了利用面向对象的复用特性,就来实现一些父类
public class BaseNode
{
public BaseNode() { nodeName_ = this.GetTyp