基于Unity行为树设计与实现的尝试

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


总结起来,就是:

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
  • 5
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值