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是一致的,如下圖:

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM