Behavior Tree实践

  又是闲得蛋疼的几周呀~~~~~这段时间我理论和实践了所谓的Behavior Tree(行为树).

 

  行为树的介绍可以看这里:

  http://www.aisharing.com/archives/tag/%E8%A1%8C%E4%B8%BA%E6%A0%91

 

  经过这几周的努力,我目前已经在自己的MiniCraft中增加了行为树模块,包括可视化工具.先简要阐述下我的实现方法,然后我们再来实际构建一个行为树,让它跑起来.

 

  我实现了三种行为树节点:序列(Sequence)节点,条件(Condition)节点和行为(Action)节点.

  序列节点是从左到右依序执行,直到某个子节点执行成功.

  条件节点只能有一个子节点,若条件满足则执行子节点.相当有趣的是,就我的领悟,行为树的特点是很大程度上是数据驱动的(黑板,条件节点条件定义),这里的条件是用户自己定义的表达式字符串,比如 UnitNum == 3,这就涉及到了语句解析.尝试实现过一遍之后,我感觉就像在搞简化的编译器一样!其实我这里最关键这个lexer是用CE3的行为树的条件解析器代码,它没有支持算术判定和变量,我用很丑和低效的实现加上了.只能说是能用起来而已.

  行为节点必须是叶子节点,它代表了一个最终被执行的行为,比如向敌人基地移动.行为树的叶子节点必须是行为节点,这就保证了每次遍历树后必会产生一个待执行的行为.每个行为可是代码定义的一个类或一个脚本函数(目前我只支持了代码类).

 

  很重要的一个概念是"黑板(Blackboard)".它是整个行为树的数据仓库,能辅助条件节点的判定.我们可直接把它看成Ogre::NameValueList,即一个键值表,怎么定义数据项完全由用户决定,比如定义一个整数类型参数UnitNum与上面的UnitNum == 3相对应.关键就是如何更新黑板的数据,直接在代码里肯定不行,行为树系统就是要完全脱离代码修改,使得策划人员能快速进行工作流.那当然是用脚本了,于是我设计每个行为树可定义一个黑板更新lua函数,用户在其中调用客户端脚本函数获取所需信息,然后用来更新黑板.当然,这样就涉及到了客户端需要提供一个相当完善的导出函数功能集,不过我想这一点应该是顺风顺水的.这样,通过黑板,条件判定,脚本和可视化工具的配合,就能让策划自己去摆弄了.

  

  核心的东西就是这些,现在我以我刚完成的行为树系统为依托,来实际演练一个比较简单的Behavior制作流程------星际2的Scv采矿.之前我已经实现了FSM(有限状态机)的版本,现在换行为树玩玩了 :D

  先比比划划在纸上分析出整个逻辑结构,再用编辑器产出(其实我想说的是直接在纸上分析出来,然后写xml就行了,但是通常这是给策划等非技术人员用的啊,必须得提供编辑器囧).

预想的树结构应该是这个样子:

              

  简单分析下:

  Root是空条件节点必执行,往下先判定Scv当前是否已载有资源(IsCarryingRes),满足则继续判定它是否回到了基地(IsNearBase),是则返还资源(ReturnRes),否则移动回基地(MoveToBase).

  这是左边第一条支路,若未成功执行则来到中间这条,判断Scv是否处在辛勤的采集状态(IsGathering),满足则继续判定已采集的时间是否达到了3秒(fGatheringTime>=3),是则让其获取到了资源(GetRes),否则继续采集行为吧(Gathering)

  若Scv又没载有资源,又没在采集,则判定第三条支路,其是否正处于采集点(IsNearRes),是则开始采集行为(Gathering)

  最后,以上支路都没满足,那么Scv必定处于向资源点移动行为中(MoveToRes).

 

  假设这个花1小时分析清后,那么用工具产出只要10分钟........如下图:

  产出的xml如下:

<?xml version='1.0' encoding='utf-8' ?>
<Root>
    <BehaviorTemplate name="Scv" race="Terran">
        <BehaviorTree>
            <SequenceNode>
                <ConditionNode expression="IsCarryingRes==true">
                    <SequenceNode>
                        <ConditionNode expression="IsNearBase==true">
                            <ActionNode behavior="ReturnRes"/>
                        </ConditionNode>
                        <ActionNode behavior="MoveToBase"/>
                    </SequenceNode>
                </ConditionNode>
                <ConditionNode expression="IsGathering==true">
                    <SequenceNode>
                        <ConditionNode expression="fGatheringTime greaterequal 3.0">
                            <ActionNode behavior="RetriveRes"/>
                        </ConditionNode>
                        <ActionNode behavior="GatherRes"/>
                    </SequenceNode>
                </ConditionNode>
                <ConditionNode expression="IsNearRes==true">
                    <ActionNode behavior="GatherRes"/>
                </ConditionNode>
                <ActionNode behavior="MoveToRes"/>
            </SequenceNode>
        </BehaviorTree>
        <BlackBoard>
            <Variable name="IsCarryingRes" value="false" type="bool"/>
            <Variable name="IsGathering" value="false" type="bool"/>
            <Variable name="IsNearRes" value="false" type="bool"/>
            <Variable name="IsNearBase" value="false" type="bool"/>
            <Variable name="fGatheringTime" value="0" type="float"/>
        </BlackBoard>
        <Script filename="ScvBlackboard.lua" entry="BBUpdate_Scv"/>
    </BehaviorTemplate>
</Root>

  最后是我们的Scv的黑板更新脚本函数:

 1 --与代码相对应
 2 eHarvestStage_ToRes                =    0
 3 eHarvestStage_NearRes            =    1
 4 eHarvestStage_Gather            =    2
 5 eHarvestStage_Return            =    3
 6 eHarvestStage_NearBase            =    4
 7 eHarvestStage_None                =    5
 8 
 9 --------------------------------
10 ---Scv blackboard
11 ---------------------------------
12 function BBUpdate_Scv(unitID)
13     obj = UnitTable[unitID]
14     curStage = obj:GetHarvestStage()
15 
16     isGathering = false
17     isCarryRes = false
18     isNearBase = false
19     isNearRes = false
20 
21     if curStage == eHarvestStage_NearRes then
22         isNearRes = true
23     elseif curStage == eHarvestStage_Gather then
24         isGathering = true
25     elseif curStage == eHarvestStage_Return then
26         isCarryRes = true
27     elseif curStage == eHarvestStage_NearBase then
28         isCarryRes = true
29         isNearBase = true
30     end
31 
32     obj:SetBlackboardParamBool("IsCarryingRes", isCarryRes)
33     obj:SetBlackboardParamBool("IsGathering", isGathering)
34     obj:SetBlackboardParamBool("IsNearRes", isNearRes)
35     obj:SetBlackboardParamBool("IsNearBase", isNearBase)
36 
37     fTime = obj:GetGatheringTime()
38     obj:SetBlackboardParamFloat("fGatheringTime", fTime)
39 end

   Ok了,只是所有的行为我目前都是在代码中完成的,还是需要提供脚本支持才好,行为类像下面这个样子:

1 ///向可采集资源移动
2 class aiBehaviorMoveToRes : public Kratos::aiBehavior
3 {
4 public:
5     virtual    void    Execute(Ogre::Any& owner);
6     virtual    void    Update(Ogre::Any& owner, float dt);
7     virtual void    Exit(Ogre::Any& owner);
8 };

   执行游戏后发现结果跟FSM是一致的,如下图:

    

 

转载于:https://www.cnblogs.com/mavaL/archive/2013/04/07/3001860.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值