MMORPG戰斗系統隨筆(三)、AI系統簡介
轉載請標明出處http://www.rzrgm.cn/zblade/
在設計一款游戲的時候,如果我們是玩家,是希望自己能夠操作角色暢玩游戲的。在一款MMORPG游戲中,大部分的實際游戲角色,是需要玩家來操作的,通過在游戲大世界相互完成游戲中的任務等等來體驗游戲。在大世界交互場景中,不可避免的會有怪物的存在,也會有NPC,某些策劃布置的場景角色怪物等等。同時,在常見的MMORPG游戲中,自動戰斗是不可避免的一個功能。這些表面的角色或者功能的背后,其實就是游戲的AI機制。
說到游戲的AI,和現在比較流行的人工智能有一定的區別。現在的人工智能是全面的AI,包含數據收集,數據分析,行為支配等等操作。而游戲中的AI,屬于比較特定的一類,主要是為戰斗系統提供的一種機制。現在主流的機制主要分為兩類:狀態機和行為樹。這兩類機制,各有其優勢,在實際的應用中需要結合實際的需求來采用。我分別在兩款游戲中采用了這兩種機制,下面也算一個個人的總結吧。
一、狀態機
狀態機這種機制,主要是針對戰斗狀態不太復雜的應用場景。如果我們的角色,在整個游戲過程中,只有少數幾個可以列舉的狀態,比如:站立,尋敵,攻擊,行走,受擊這樣可以枚舉出來的幾個狀態,那么,我們可以考慮用狀態機的方式實現這幾種狀態的相互切換。具體的關于狀態機的實現,我這兒就不再贅述了,網上有很多詳細的講解,也有比較多開源的代碼,大家可以參考github上的代碼類似的實現自己的狀態機。我就寫幾點個人的一些總結吧。
1、從基礎搭建開始。
網上比較多的狀態機,都是結合其實際的應用場景,包含一些特定的實現設計在里面,我們在搭建自己的狀態機的時候,可以從一個細小的狀態開始搭建,先搭建一個節點,然后依據同樣的設計,搭建類似的多個節點;
2、保持節點的簡潔性和去耦性。
通常,在最初的節點搭建完后,是不可能一勞永逸的,后續反復的修改,都會對這些節點進行特定的修改和某些特定規則的設計。
舉個列子,射擊這樣一個狀態節點,A英雄的設計是單發,B英雄是連續10發,那么如何保證對這兩個英雄的同一個射擊狀態的兼容?一種設計方式,是將當前的英雄作為一個參數進行傳遞,那么在執行的時候,就讀取當前傳入參數的英雄的具體配置,進行相關的射擊動作。把射擊相關的邏輯封裝在該英雄對應的攻擊執行器中,那么其具體執行的射擊單發還是射擊10發,就可以作為一個循環執行,單發循環一次,10發循環10次。
以前我的設計實現思路,是針對單一的英雄,特定重載實現其對應的射擊節點,這樣也算一種解決方法,只是這樣的設計有一個弊端,就是隨著英雄類型的增多,其對應的特定實現會不斷的擴大,相應的重載實現的版本會增多。如果設計思路和文本清晰,那還可以維護,如果設計思路不清晰,那么就會帶來維護的消耗。
3、做好節點的剝離,避免節點耦合太多。
通常狀態機是用在比較簡易的游戲類型中,角色本身的狀態類型不會太多,那么對應的狀態節點應該避免相互之間的耦合和功能交叉,狀態的切換可以走相同的屬性設置來實現交互。如果隨著設計的變化,狀態實現越來越多,可以考慮用行為樹來實現,避免相互之間的耦合太多。
二、行為樹
在RPG游戲中,行為樹是用的比較多的一種AI機制。就其本質而言,行為樹是狀態機的一種更高封裝的實現。我個人的理解,行為樹就是將特定的行為節點進行封裝,做成葉子節點,這樣可以實現任意節點的拼接。想象一下,如果我們把每個特定的狀態機進一步的封裝,做成一片片葉子,這樣我們在搭建樹的時候,就可以收集特定的葉子,來搭建特定的樹,最后得到特定功能的行為樹,這就是我對行為樹的一個簡易理解:D 如果用更為規范的說法,那么這些葉子節點,就是行為樹中的行為節點,行為樹的行為節點的執行結果,可以用枚舉的方式列出:
RunningStatus =
{
INVALID = 0,
SUCCESS = 1, --執行成功
FAILURE = 2, --執行失敗
RUNNING = 3, --執行中
}
一棵樹的搭建,不能只有葉子,還需要有枝干,這就需要行為樹中的一些特定的節點來搭建這棵樹,這就是行為樹中的控制節點(Control Node)的作用。注意一點,行為節點是和游戲實際關聯的,在行為節點中,我們會去具體的定義如何攻擊(shoot/attack),尋路(patrol),閑置(idle)等,但是在控制節點中,其具體運行的邏輯是和實際游戲數據沒有關聯的,其只需要負責基本的控制邏輯即可 。通過控制節點,我們可以清晰的知道整個行為樹的執行邏輯,這就是行為樹的一大優勢:執行邏輯可見。 行為樹主要有以下幾種控制節點,舉例說一下:
4種Composite節點
1、Sequence節點: 順序執行控制節點,其執行的基本邏輯是:對其下面的所有葉子節點,均順序執行,直到遇到返回為FAILURE的節點。(我對這種節點的理解,就是串聯電路的思維,電流順序走過每個電阻(葉子),如果遇到第一個無法流過的電阻,則返回,否則會一直流過去,直到所有電阻都流過)
2、Selector節點:選擇執行控制節點,其執行的基本邏輯是:對其下面的所有葉子節點,順序執行,直到遇到第一個返回為SUCCESS/RUNNING執行結果的節點。觀察上面的Sequence節點,和Selector節點的區別僅僅在于選中的節點的返回結果的不同處理。在其他地方好像對選擇節點進行了分類,可以分為:帶優先級的選擇節點,不帶優先級的選擇節點,帶權值的選擇節點。帶優先級,是指在選擇那個節點更新的時候,會根據優先級進行比較來選擇。不帶優先級的時候,一般會設置為每次更新的時候會沿用上一次的更新節點,因為一般更新的時候,會有一段時間持續處于某個節點的狀態(目前我用的就是這樣的選擇節點),而對于帶權重的選擇節點,一般用來做多樣的隨機性,比如游戲中的寵物,需要表現一個交互動作,那么可以做多個交互動作,在每次做選擇節點的時候,根據權重隨機一個節點用來表示交互動作。
3、Parallel節點:并行執行控制節點,其執行的基本邏輯是:對其下面的所有葉子節點,各自執行一次,如果返回結果不為RUNNING,則分別統計SUCCESS和FAILURE的結果,一般結果會采用“與”和“或”的操作。比如當前Parallel節點的返回條件可以分為 全SUCCESS或者任意一個SUCCESS,全FAILURE或者任意一個FAILURE。在執行完所有葉子節點后,可以對比其執行的SUCCESS和FAILURE節點的個數,和其設置的返回結果對比,如果滿足則返回SUCCESS(SUCCESS條件)或者FAIULURE(FAILURE條件),或者返回RUNNING。
4、Detector節點:檢測執行控制節點,其執行的基本邏輯是:對其下面的所有葉子節點,逐個執行,如果返回的不為RUNNING,則檢測,如果為SUCCESS,則接著執行,為FAILURE,則執行結束。即遇到第一個返回為FAILURE的節點,就結束執行。
6種Decorator節點
1、Invert節點:反轉裝飾節點,其執行的基本邏輯是:只有一個葉子節點,如果返回為SUCCESS,則返回FAILURE;如果返回為FAILURE,則返回SUCCESS;否則返回RUNNING
2、Sucees節點:Success裝飾節點,其執行的基本邏輯是:只有一個葉子節點,執行后,直接返回SUCCESS
3、SuccessToRunning節點:根據名字就可以知道,其葉子節點在執行完后,如果返回為SUCCESS,則修改其結果為RUNNING,其他的狀態不改變,返回最終執行狀態給上一層。
4、FailureTORunning節點:類似于上一個節點,其葉子節點在執行完后,如果返回為FAILURE,則修改其結果為RUNNING,其他的狀態不改變,返回最終執行狀態給上一層。
5、SuppressSuccess節點:suppress的意思是抑制,所以這個節點的功能,就是不準返回SUCCESS的執行結果,如果葉子節點返回為RUNNING則返回RUNNING,其他都返回為FAILURE給上一層
6、SuppressFailure節點:類似于上一個節點,該節點只會返回RUNNING或者SUCCESS這兩種執行結果
除了常見的Compositor節點和Decorator節點,還有Condition節點,依據前面兩種類型節點,不難推出后面的Condition節點的功能,就是在某些條件下才觸發某些特定返回結果的一些節點。
簡而言之,行為樹就是在三個大類的控制節點:Compositor節點、Decorator節點、Condition節點的搭建下,結合各個行為葉子節點,拼接出一個基本的AI執行機制,舉個例子:
bt_atk
<Root> <Selector> <Attack> <Homing> </Selector> </Root>
這是一個簡單的站在原地攻擊的行為樹設計,首先執行Selector節點,然后順序執行其下面的所有葉子節點,首先會執行攻擊的葉子節點,如果返回為Success,則繼續執行Homing節點。所以其基本的設計思想就是:觸發一次攻擊檢測,如果有攻擊對象,則執行攻擊,返回SUCCESS,同時結束本次tick;否則返回FAILUER,那么就會執行下一個節點Homing。
通過構建一顆基本的行為樹,我們可以組件一顆更大的樹,比如我們再構建一顆巡邏的行為樹:
bt_patrol
<Root> <Selector> <Patrol> <Homing> </Selector> </Root>
通過這兩顆基本的行為樹,我們可以組建一個更大的行為樹:
bt_monster
<Root> <Selector> <Sequence> <Patrol> <Homing> </Sequence> <Sequence> <Attack> <Homing> </Sequence> </Selector> </Root>
當然我只是一個引申,更加具體的設計可以根據具體的設計來實現,有時候不只是葉子節點可以復用,某些樹也可以整體作為一個葉子復用,這樣可以實現設計的復用。
在行為樹中,一般的設計思路,是會構建一個行為樹的模版,比如上面的bt_atk,在每個使用該模版的角色進行初始化行為樹的時候,是從該模版緩存中取出一份,然后初始化相關的信息得到一份實例,在進行行為樹更新的時候,是更新其對應的實例。
而且為了通用各個樹干和葉子節點,都是采用數據封包傳遞,在數據包中封閉當前角色相關的信息或者黑板信息,這樣在每個節點更新的時候,都是從數據封包中獲取當前角色相關的信息,從而進行對應的邏輯更新。這樣,就避免了一些設計上的將數據封存在action行為節點上的問題,通過封包的傳遞,可以降低耦合,提高復用性。
對于行為樹的優化,我提一個優化點吧。大部分的行為樹在具體的項目中應用都是結合具體的設計來實現的,所以我采用的優化未必適合于其他游戲的優化,但是可以采用一個更新頻率的設置來降低行為樹的更新頻率,這個是可以通用的。我在測試服務器的代碼性能的時候發現,當場景中的角色數量比較少的時候,大部分的游戲性能都被場景中的怪占用了,而怪物的更新中對于AI的更新又是一個很大的占用。我采用一種距離配置的方法進行優化,當怪物周邊沒有玩家的時候,降低怪物的更新頻率或者就不執行怪物的AI更新,當怪物進入玩家的視野的時候,采用較高的更新頻率,當怪物進入玩家的攻擊范圍的時候,采用正常的更新頻率。通過不同距離檢測設置不同的更新頻率,可以較好的優化在玩家個數較少時的怪物AI性能。
總結:游戲中兩種常見的狀態機和行為樹都做了一個簡單的講解,當然現在網上比較多相關的資料,如果想深入的學習,可以搜集相關的資料研究,有較多的開源代碼也可以參考研究一下。當然我說的都是較為淺顯的設計,具體的狀態機和行為樹節點的設計,其中具體邏輯的編寫,是需要結合實際的游戲設計來實現的,這就需要程序和策劃具體的商量和實現了。好了,今天AI的簡介就說到這兒,下一篇再說說一些優化的總結吧:D

浙公網安備 33010602011771號