AI行为树

用行为树来控制AI行为

项目上线的时候有很多竞品压力,关于AI方面并没有做太多的规则,为了快速上线服务器只是实现了AI的移动和攻击两个行为,其移动规则只是随机寻点移动,攻击行为也只是攻击最近的敌人。随着项目的发展,策划对AI的智能程度提出了要求,所以我们对AI的功能进行了加强。 我在之前的RPG项目中也做过AI,是基于状态机的,简单的RPG野外怪物使用状态机完全可以满足需求,但是目前的项目对AI行为的要求比较细致,所以使用了行为树。记录一下行为树的基本原理和使用情况

行为树的基本组成

最简单的AI使用if else语句就可以实现,但是随着AI行为的复杂度越来越高,要提供给策划配置等等需求的出现,必须使用一种更为方便清晰的方法来实现AI,行为树就是一个不错的选择。要实现一个行为树,首先要把怪物的各种行为模块化,比如巡逻行为,追击行为,普通攻击行为,技能攻击行为,各种条件判断行为(自身血量是否不足30%,队友人数是否少于1个…)等等,之后利用行为树的原理将这些模块进行组合,以达到行为的多样性,这种多样性的组合是基于行为数量指数级增加的,所以提供给策划配置的灵活性非常好。一棵行为树表示一组AI逻辑,要执行这组AI逻辑,需要从根节点开始遍历执行整棵树,遍历执行的过程中,父节点根据其自身类别选择需要执行的子节点并执行,子节点执行完后将执行结果返回给父节点, 一棵普通的行为树主要由四种节点组成,我把他们分为两类,枝干节点和叶子节点。枝干节点肯定不是行为树的末端,它们主要的功能是对自己枝干下的节点进行“管理”,如何执行自己枝干下的节点是枝干节点需要做的事情。叶子节点一定是行为树的末端,它们通常是一个动作,如攻击,移动等等。

–顺序节点(Sequence):枝干节点,顺序执行子节点,只要碰到一个子节点返回false,则返回false;否则返回true。

–选择节点(Selector):枝干节点,顺序执行子节点,只要碰到一个子节点返回true,则返回true;否则返回false。

–条件节点(Condition):叶子节点,执行条件判断,返回判断结果。

–动作节点(Action):叶子节点,执行设定的动作,一般返回true。

下面是一棵简单的普通行为树,这棵行为树的根节点是一个选择节点(Selector),根节点下有两个顺序节点的枝干,每个顺序节点下面是叶子节点的条件和动作。把这课行为树用语言描述一下就是,怪物开始行动,执行根节点,首先选择移动还是攻击,顺序执行移动(11)和攻击(12),当执行移动(11)的条件中战场内无敌人(111)为true,那么继续执行下面的条件自身范围内是否有友方(112),如果返回true那么就执行定点移动行为(113),执行113结束后整棵行为树执行结束,从根节点开始再次执行。如果111或者112返回false,那么执行攻击(12),如果视野范围内有敌人,那么就攻击距离最近的敌人(122),如果没有敌人,那么整棵行为树执行结束,从根节点再次开始执行。


普通行为树


项目中的代码实现:

//////////////////////////////////////////////////////////////////////////
///
///    @file     Behaviour.h
///
///    @date    2016-11-22  20:02:52
///
///    @brief    行为树
///
//////////////////////////////////////////////////////////////////////////
#ifndef __behaviour__Behaviour__
#define __behaviour__Behaviour__
#include <string>
#include <vector>
#include <_BaseGameObject.h>
namespace BehaviorTree
{
    //每个行为的状态
    enum Status
    {
        BH_INVALID, //无效
        BH_SUCCESS,    //成功
        BH_FAILURE,    //失败
        BH_RUNNING,    //执行中
    };
    //行为基类
    class Behavior
    {
    public:
        virtual Status update(uint32& delay){return BH_SUCCESS;};
        virtual void onInitialize(){}
        virtual void onTerminate(Status) {}
        virtual void addBehavior(Behavior* node){}
        virtual    void init(){};

        Behavior():
            m_eStatus (BH_INVALID)
            ,action_id_(0)
        {};
        virtual ~Behavior()
        {};
        Status tick(uint32& delay);
        Status getState()
        {
            return m_eStatus;
        }
        bool setState(uint32 _eStatus)
        {
            m_eStatus = (BehaviorTree::Status)_eStatus;
            return true;
        }
    private:
        Status m_eStatus;
    public:
        uint32 action_id_;
        uint32 std_id_;
        typedef std::vector <Behavior*> Behaviors;
        Behaviors m_Children;
    };
    //枝类
    class Composite : public Behavior
    {
    public:
        virtual void onTerminate(Status) {}
        virtual void addBehavior(Behavior* node)
        {
            m_Children.push_back(node);
        }
    };
    //顺序节点
    class Sequence : public Composite
    {
    public:

        virtual void onInitialize();
        virtual Status update(uint32& delay);

        Behaviors::iterator m_CurrentChild;
    };
    //选择节点
    class Selector : public Composite
    {
    public:
        virtual void onInitialize();
        virtual Status update(uint32& delay);
        virtual void onTerminate();
        Behaviors::iterator m_CurrentChild;
    };
    //并行节点
    class Parallel : public Composite
    {
    public:
        virtual void onInitialize();
        virtual Status update(uint32& delay);
        Behaviors::iterator m_CurrentChild;
    };

    //并行节点(全部节点执行完成结束)
    class ParallelEx : public Composite
    {
    public:
        virtual void onInitialize();
        virtual Status update(uint32& delay);
        Behaviors::iterator m_CurrentChild;
    };
}
#endif //__behaviour__Behaviour__




//////////////////////////////////////////////////////////////////////////
///
///    @file     Behaviour.cpp
///
///
///    @brief    行为树
///
//////////////////////////////////////////////////////////////////////////

#include "Behaviour.h"
namespace BehaviorTree
{
    Status Behavior::tick(uint32& delay)
    {
        if (m_eStatus == BH_INVALID)
        {
            onInitialize();
        }
        m_eStatus = update(delay);
        if (m_eStatus != BH_RUNNING)
        {
            onTerminate(m_eStatus);
        }
        return m_eStatus;
    }

    void Sequence::onInitialize()
    {
        m_CurrentChild = m_Children.begin();
    }
    Status Sequence::update(uint32& delay)
    {
        while (true)
        {
            Status s = (*m_CurrentChild)->tick(delay);
            if (s != BH_SUCCESS) {
                return s;
            }

            // 最后一个节点,执行完了
            if(++m_CurrentChild == m_Children.end())
            {
                m_CurrentChild = m_Children.begin();
                return BH_SUCCESS;
            }
        }
        return BH_INVALID;
    }

    void Selector::onInitialize()
    {
        m_CurrentChild = m_Children.begin();
    }
    Status Selector::update(uint32& delay)
    {
        while (true)
        {
            Status s = (*m_CurrentChild)->tick(delay);

            if (s != BH_FAILURE) {

                return s;
            }

            if(++m_CurrentChild == m_Children.end())
            {
                m_CurrentChild = m_Children.begin();
                return BH_FAILURE;
            }
        }
        return BH_INVALID;
    }

    void Selector::onTerminate()
    {
        setState(BH_INVALID);
    }


    void Parallel::onInitialize()
    {
        m_CurrentChild = m_Children.begin();
    }

    Status Parallel::update( uint32& delay )
    {
        return BH_INVALID;
    }


    void ParallelEx::onInitialize()
    {
        m_CurrentChild = m_Children.begin();
    }

    Status ParallelEx::update( uint32& delay )
    {
        while (true)
        {
            bool b_break = true;
            uint32 delay_prev=delay;
            for (auto ite = m_Children.begin();ite!=m_Children.end();++ite)
            {
                Status s = (*ite)->tick(delay);
                if (delay<delay_prev)
                {
                    delay_prev = delay;
                }
                if (BH_RUNNING==s)
                {
                    b_break = false;
                }
            }
            delay = delay_prev;
            //if ( delay<100 )
            //{
            //    delay = 100;
            //}
            if (true == b_break)
            {
                return BH_SUCCESS;
            }
            else
            {
                return BH_RUNNING;
            }
        }
        return BH_INVALID;
    }
}

上面的代码是行为树的基类,四种节点中的叶子节点(条件节点,动作节点)可以直接继承Behavior类,顺序节点是Sequence类,选择节点是Selector,我的代码里还加入了一种扩展的节点,并行节点Parallel类,这种枝干节点会同时执行枝干下的节点,不论这些节点返回什么,加入这种节点主要是为了实现同时移动同时攻击这种动作。我们项目中继承自Behavior类的部分代码(一个动作节点,一个条件节点):

//定点循环移动行为
class CActionMoveLoop : public BehaviorTree::Behavior
{
public:
    CActionMoveLoop(IHZArenaPlayer* player,uint32 action_id);
    ~CActionMoveLoop();
public:
    virtual BehaviorTree::Status update(uint32& delay);
    virtual void onInitialize();
    virtual void onTerminate(BehaviorTree::Status){setState(BehaviorTree::BH_INVALID);};
private:
    World3DPosition            GetoutOnePoint(bool& result);
    int32                    point_index_;
    int32                    order_type_;//0顺时针,1逆时针
    std::vector<WorldPosition>        path_point_;        ///< 路径
    void                    init();
    IPlayer*                player_;
    CFightAIPlugin*            ai_plugin_;
    World3DPosition            target_point_;
};


//检测条件,属性检测
class CConditionProp : public BehaviorTree::Behavior
{
public:
    CConditionProp(IHZArenaPlayer* player,uint32 action_id);
    ~CConditionProp(){};
public:
    virtual BehaviorTree::Status update(uint32& delay);
    virtual void onInitialize();
    virtual void onTerminate(BehaviorTree::Status){};
    void                    init();
private:
    uint32                    compare_type_;
    uint32                    prop_value_;
    uint32                    prop_percent_;
    IPlayer*                player_;
    CFightAIPlugin*            ai_plugin_;
};

策划通过绘制行为树逻辑图来梳理逻辑,然后通过逻辑图输出逻辑配置到xml中,生成一棵行为树的代码:

BehaviorTree::Behavior* CFightAIPlugin::create_tree_root( const StdTreeRoot_data::IStdRoot* std_root )
{
    switch (std_root->get_RootType())
    {
        //顺序
    case 1:
        {
            BehaviorTree::Sequence* rootseq = new BehaviorTree::Sequence();
            for (uint32 i = 0; i<std_root->StdChild_size(); i++) 
            {
                auto std_child = std_root->get_StdChild(i);
                if (std_child->get_NodeType()==1)
                {
                    auto std_node = get_StdTreeRoot_manager()->find_StdRoot_byRootID(std_child->get_ChildID());
                    if (NULL == std_node)
                    {
                        continue;
                    }
                    rootseq->m_Children.push_back(create_tree_root(std_node));
                }
                else
                {
                    auto std_node = get_StdTreeAction_manager()->find_StdAction_byActionID(std_child->get_ChildID());
                    if (NULL == std_node)
                    {
                        continue;
                    }
                    rootseq->m_Children.push_back(create_tree_leaf(std_node));
                }
            }
            return rootseq;
        }
        //选择
    case 2:
        {
            BehaviorTree::Selector* rootsel = new BehaviorTree::Selector();
            for (uint32 i = 0; i<std_root->StdChild_size(); i++) 
            {
                auto std_child = std_root->get_StdChild(i);
                if (std_child->get_NodeType()==1)
                {
                    auto std_node = get_StdTreeRoot_manager()->find_StdRoot_byRootID(std_child->get_ChildID());
                    if (NULL == std_node)
                    {
                        continue;
                    }
                    rootsel->m_Children.push_back(create_tree_root(std_node));
                }
                else
                {
                    auto std_node = get_StdTreeAction_manager()->find_StdAction_byActionID(std_child->get_ChildID());
                    if (NULL == std_node)
                    {
                        continue;
                    }
                    rootsel->m_Children.push_back(create_tree_leaf(std_node));
                }
            }
            return rootsel;
        }
        //并行
    case 3:
        {
            BehaviorTree::ParallelEx* rootpara = new BehaviorTree::ParallelEx();
            for (uint32 i = 0; i<std_root->StdChild_size(); i++) 
            {
                auto std_child = std_root->get_StdChild(i);
                if (std_child->get_NodeType()==1)
                {
                    auto std_node = get_StdTreeRoot_manager()->find_StdRoot_byRootID(std_child->get_ChildID());
                    if (NULL == std_node)
                    {
                        continue;
                    }
                    rootpara->m_Children.push_back(create_tree_root(std_node));
                }
                else
                {
                    auto std_node = get_StdTreeAction_manager()->find_StdAction_byActionID(std_child->get_ChildID());
                    if (NULL == std_node)
                    {
                        continue;
                    }
                    rootpara->m_Children.push_back(create_tree_leaf(std_node));
                }
            }
            return rootpara;
        }
        //动作或者条件
    case 4:
        {
            RLOG(MINFO)<<"root can not be action!!!";
            break;
        }
    default:
        break;
    }
    RLOG(MINFO)<<"root wrong:"<<std_root->get_RootID();
    return NULL;
}


BehaviorTree::Behavior* CFightAIPlugin::create_tree_leaf( const StdTreeAction_data::IStdAction* std_action )
{
    switch (std_action->get_ActionType())
    {
    //移动行为
    case 1:
        {
            return create_move_action(std_action);
            break;
        }
    //条件检测
    case 2:
        {
            return create_check_action(std_action);
            break;
        }
    //攻击行为
    case 3:
        {
            return create_attack_action(std_action);
            break;
        }
    //其他行为
    case 4:
        {
            return create_other_action(std_action);
            break;
        }
    default:
        {
            break;
        }

    }
    RLOG(MINFO)<<"No This Big Type:"<<std_action->get_ActionSubType()<<" id:"<<std_action->get_ActionID();
    return NULL;
}


BehaviorTree::Behavior* CFightAIPlugin::create_move_action( const StdTreeAction_data::IStdAction* std_action )
{
    switch (std_action->get_ActionSubType())
    {
    // 多坐标点顺序移动:到过的坐标点会被移除
    case 1:
        {
            CActionMovePath *move_path = new CActionMovePath(m_pPlayerObj,std_action->get_ActionID());
            return move_path;
        }
    // 多坐标点循环移动:反复循环
    case 2:
        {
            CActionMoveLoop *move_loop = new CActionMoveLoop(m_pPlayerObj,std_action->get_ActionID());
            return move_loop;
        }
    // 坐标点半径巡逻
    case 3:
        {
            CActionMovePatrol *move_patrol = new CActionMovePatrol(m_pPlayerObj,std_action->get_ActionID());
            return move_patrol;
        }
    // 指定id移动
    case 4:
        {
            CActionMoveToAI *move_ai = new CActionMoveToAI(m_pPlayerObj,std_action->get_ActionID());
            return move_ai;
        }
    // 最近敌人移动
    case 5:
        {
            CActionMoveToCloseEnemy *move_close_enemy = new CActionMoveToCloseEnemy(m_pPlayerObj,std_action->get_ActionID());
            return move_close_enemy;
        }
    // 占旗点巡逻
    case 6:
        {
            CActionMoveFlag *move_patrol_flag = new CActionMoveFlag(m_pPlayerObj,std_action->get_ActionID());
            return move_patrol_flag;
        }
    default:
        {
            break;
        }

    }
    RLOG(MINFO)<<"No This Move Action Type:"<<std_action->get_ActionSubType()<<" id:"<<std_action->get_ActionID();
    return NULL;
}

这些代码片段包含了行为树的创建,通过配置表来生成一棵复杂的行为树。然后我们还需要在一个循环中执行这棵树的逻辑:

uint32 CFightAIPlugin::AIUpdateBT()
{
    if (behavior_tree_==NULL)
    {
        return 0;
    }

    if (BehaviorTree::BH_RUNNING != behavior_status_)
    {
        behavior_now_ = behavior_tree_;
        behavior_now_->onInitialize();
        RLOG(MINFO)<<"----------loop---------";//执行结束,从树根重新开始执行
    }
    uint32 delay = 1000;
    behavior_status_ = behavior_now_->update(delay);//update当前的节点
    RLOG(MINFO)<<"ai delay:"<<delay;
    return delay;
}

AIUpdateBT()函数会在定时器下每间隔一段时间(100~1000ms,根据自己的需求调整每一次update的时间)update一次,来驱动整棵树的执行。

掌握了行为树的基本原理就可以实现各种各样复杂的AI逻辑并且条理清晰,便于管理,不会因为逻辑的复杂而使代码混乱,下面的图片展示了一个比上面例子更复杂的逻辑,你完全可以根据自己项目的需要来配置一个更大的行为树,它会严格按照你的配置来执行。


行为树