行为树概念

1.介绍:行为树Behavior Tree简称BT,是一颗包含了层级节点的树结构,一般用来控制AI的决策行为,但其逻辑清晰的可视化效果现在也可以用来做各种配置;

2.原理:行为树通过自顶向下的遍历方式,通过一些条件来搜索这颗树,最终根据当前条件确定需要做的行为(叶子节点),来执行它,达到AI决策的效果;

行为树组成

行为树主要由组合节点、装饰节点、条件节点、行为节点四种抽象节点抽象而成;

1.组合节点(Composites):主要包含Sequence顺序组合节点、Selector选择组合节点、Parallel并行组合节点,这些节点之间也可以相互之间进行搭配组合,组成新的组合节点;

2.修饰节点(Decorator):连接树叶的树枝,各种类型的修饰节点,这些节点决定了AI如何从树的顶端根据不同的情况来沿着不同的路径来到最终的叶子这一过程;让子节点循环操作或者让子节点一直运行知道返回某个运行状态,或者对结果取反;

3.条件节点(Conditinals):用来判断条件是否成立;行为树的设计也遵守职责单一的原则,将判断也专门做一个节点独立处理,比如判断目标是否在视野内,这种功能在Action节点中也可以写,但是会影响Action的单一原则;一般条件节点在组合节点的中,后面紧跟的是条件成立得Action节点;

4.行为节点(Action):行为节点是树得最末端,属于叶子节点,一般是AI实际做事情的命令;

工作流程

1.工作流(Flow):行为树由多种不同类型的节点构成,它们都拥有一个共同的核心功能,即它们会返回三种状态中其中一个作为结果,这三种状态是:

  • 成功:Success;
  • 失败:Failure;
  • 运行中:Running;

根节点实例(BtNode)

根节点的实现很简单,但是其中要定义好相关的状态,这里我们用枚举来表示,之后所有的节点都继承自该节点;

public abstract class BtNode
{
    public virtual BtResultEnum DoAction()
    {
        return BtResultEnum.None;
    }
}

public enum BtResultEnum
{
    None = 0,
    Successful = 1,
    Fail = 2,
    Running = 3,
}

组合节点实例(BtComposite)

组合节点只是一个中间节点,它的出现只是为了过滤执行下面的子节点,我们实现一个父类节点,里面用一个List集合来储存子节点,之后所有的组合节点都继承自该节点, 后面的组合节点有多种实例,我们一个个分开讨论;:

public class BtComposite : BtNode
{
    protected List<BtNode> childrenLst;
    public BtComposite()
    {
        childrenLst = new List<BtNode>();
    }

    public void AddChild(BtNode node)
    {
        this.childrenLst.Add(node);
    }
}

顺序组合节点(BtSequence)

顺序节点是按顺序从左到右执行每一个子节点,当有子节点返回Fail,就返回Fail,不再实行后面的子节点,全部返回成功才返回成功:

public class BtSequence : BtComposite
{
    private int index;

    public BtSequence()
    {

    }

    private void Reset()
    {
        index = 0;
    }

    public override BtResultEnum DoAction()
    {
        //不存在子节点时直接返回Fail
        if (this.childrenLst == null || this.childrenLst.Count == 0)
        {
            return BtResultEnum.Fail;
        }

        if (this.index >= this.childrenLst.Count)
        {
            Reset();
        }
        BtResultEnum _result = BtResultEnum.None;

        for(int length = this.childrenLst.Count; index < length; ++index)
        {
            _result = this.childrenLst[index].DoAction();
            //有一个节点返回Fail,则直接返回Fail,不执行后面的子节点
            if (_result == BtResultEnum.Fail)
            {
                Reset();
                return _result;
            }
            else if (_result == BtResultEnum.Running)
            {
                return _result;
            }
            else
            {
                continue;
            }
        }
        Reset();
        return BtResultEnum.Successful;
    }
}

选择组合节点(BtSelect)

选择节点和顺序节点刚好相反,从左到右每帧执行一个子节点,当有子节点返回成功时,直接返回成功,不再执行后买你的子节点你,如果全部返回失败,才返回失败:

public class BtSelect : BtComposite
{
    private int index;

    public BtSelect()
    {
        Reset();
    }

    private void Reset()
    {
        index = 0;
    }

    public override BtResultEnum DoAction()
    {
        if (this.childrenLst == null || this.childrenLst.Count == 0)
        {
            return BtResultEnum.Fail;
        }

        if (index >= this.childrenLst.Count)
        {
            Reset();
        }
        BtResultEnum _result = BtResultEnum.None;
        for(int length = this.childrenLst.Count; index < length;index++)
        {
            _result = this.childrenLst[index].DoAction();
            //有一个节点返回Successful,则直接返回Successful,不执行后面的子节点
            if (_result == BtResultEnum.Successful)
            {
                Reset();
                return _result;
            }else if (_result==BtResultEnum.Running)
            {
                return _result;
            }
            else
            {
                continue;
            }
        }

        Reset();
        return BtResultEnum.Fail;
    }
}

并行组合节点(BtParallel)

并行节点一帧将全部子节执行一遍,无论该节点你返回成功还是失败,都不影响其余节点的执行,但是该节点也需要根据每个子节点的返回状态决定自己的返回状态;

public class BtParallel : BtComposite
{
    public BtParallel()
    {

    }
}

并行顺序组合节点(BtParallelSequence)

一帧将全部子节点执行一遍,全部返回False才返回False,否则返回True;

public class BtParallelSequence : BtParallel
{
    private List<BtNode> m_pWaitNodes;
    private bool m_pIsSuccess;
    public BtParallelSequence()
    {
        m_pWaitNodes = new List<BtNode>();
        m_pIsSuccess = false;
    }

    public override BtResultEnum DoAction()
    {
        if (this.childrenLst == null || this.childrenLst.Count == 0)
        {
            return BtResultEnum.Successful;
        }

        BtResultEnum _result = BtResultEnum.None;
        List<BtNode> _waitNodes = new List<BtNode>();
        List<BtNode> _mainNodes = new List<BtNode>();
        _mainNodes = this.m_pWaitNodes.Count > 0 ? this.m_pWaitNodes : this.childrenLst;

        for (int i = 0, length = _mainNodes.Count; i < length; i++)
        {
            _result = _mainNodes[i].DoAction();
            switch (_result)
            {
                case BtResultEnum.Successful:
                    m_pIsSuccess = true;
                    break;
                case BtResultEnum.Running:
                    _waitNodes.Add(_mainNodes[i]);
                    break;
                default:
                    break;
            }
        }

        if (_waitNodes.Count > 0)
        {
            this.m_pWaitNodes = _waitNodes;
            return BtResultEnum.Running;
        }

        _result = checkResult();
        Reset();
        return _result;
    }

    private BtResultEnum checkResult()
    {
        return m_pIsSuccess ? BtResultEnum.Successful : BtResultEnum.Fail;
    }

    private void Reset()
    {
        m_pWaitNodes.Clear();
        m_pIsSuccess = false;
    }
}

并行选择组合节点(BtParallelSelector)

一帧将全部子节点执行一遍,全部返回True才返回True,否则返回False;

public class BtParallelSelector : BtParallel
{
    private List<BtNode> m_pWaitNodes;
    private bool m_pIsFail;
    public BtParallelSelector()
    {
        m_pWaitNodes = new List<BtNode>();
        m_pIsFail = false;
    }

    public override BtResultEnum DoAction()
    {
        if (this.childrenLst == null || this.childrenLst.Count == 0)
        {
            return BtResultEnum.Fail;
        }

        BtResultEnum _result = BtResultEnum.None;
        List<BtNode> _waitNodes = new List<BtNode>();
        List<BtNode> _mainNodes = new List<BtNode>();
        _mainNodes = this.m_pWaitNodes.Count > 0 ? this.m_pWaitNodes : this.childrenLst;

        for(int i = 0, length = _mainNodes.Count; i < length; i++)
        {
            _result = _mainNodes[i].DoAction();
            switch (_result)
            {
                case BtResultEnum.Successful:
                    break;
                case BtResultEnum.Running:
                    _waitNodes.Add(_mainNodes[i]);
                    break;
                default:
                    m_pIsFail = true;
                    break;
            }
        }

        if (_waitNodes.Count > 0)
        {
            this.m_pWaitNodes = _waitNodes;
            return BtResultEnum.Running;
        }

        _result = checkResult();
        Reset();
        return _result;
    }

    private BtResultEnum checkResult()
    {
        return m_pIsFail ? BtResultEnum.Fail : BtResultEnum.Successful;
    }

    private void Reset()
    {
        m_pWaitNodes.Clear();
        m_pIsFail = false;
    }
}

装饰节点(BtDecorator)

该节点只包含一个子节点,并未该子节点添加一些特殊的功能,如让子节点循环操作或者让子task一直运行直到其返回某个运行状态值,或者将task的返回值取反等等,之后所有定义的装饰节点继承自该节点;

public class BtDecorator : BtNode
{
    private BtNode child;
    public BtDecorator()
    {
        child = null;
    }

    protected void SetChild(BtNode node)
    {
        child = node;
    }
}

条件节点(BtCondition)

用于判断某条件是否成立。目前看来,是Behavior Designer为了贯彻职责单一的原则,将判断专门作为一个节点独立处理,比如判断某目标是否在视野内,其实在攻击的Action里面也可以写,但是这样Action就不单一了,不利于视野判断处理的复用,之后所有节点继承自该节点;

public class BtCondition : BtNode
{
    public override BtResultEnum DoAction()
    {
        return BtResultEnum.Fail;
    }
}

行为节点(BtAction)

行为节点是真正做事的节点,其为叶节点。一般来说行为节点是有我们自己编写的节点,之后所有该节点继承自该节点;

public class BtAction : BtNode
{
    //处理具体的行为逻辑
}

简单的案例

Behabior Designer插件使用