如何從零開始構建遊戲AI系統?
跟怪物和角色AI打交道也有段日子了,受之前底層架構限制。沒辦法自由的做一些修改和擴充套件,現在結合這些經驗總結一下,在從零構建的時候需要考慮的點和問題。
AI應該做成什麼樣?
我認為,我們不斷地追求更聰明的AI,主要為了在滿足玩家期望的同時,儘可能的節省我們的工作量。一個能夠滿足玩家期望的AI應該具備以下2個點:提供符合常識的反饋,和不符合常識的反饋。符合常識的反饋,使其增強代入感,而異於常識的反饋,則是玩家想要體驗的東西。
01. 讓玩家看到他覺得正確的
正確的,即是符合常識的,是符合玩家認知內應當發生的一些因果。人被砍會受傷,人被殺就會死,疲勞了動作會遲緩,看見敵人就要警惕,敵人靠近就要攻擊,打的過我要追,打不過我就跑。這些都是符合我們現實生活的常識性行為。
那麼讓玩家在干涉環境時,看到他覺得對的反饋,就是AI的其中一個重要功能。越普適的地方,玩家接觸越多的反饋,就越要如此。對於怪物AI的部分,怪物的觸發與睡眠,巡邏和追擊,在進入戰鬥時不同條件下的行為模式,以及這些行為模式之間的切換,都儘可能要符合我們的常識,除非……
02. 超出玩家的期待
我看見了一個敵人,悄咪咪地走了過去。瞄準,開了一槍,臥槽沒打中,趕緊藏箱子裡!臥槽他好像看見我了?……哦還好還好還好沒看……他幹嘛呢,無線電?……停一下停一下怎麼就請求支援了,還來了一個排的人,都衝我來的嗎!?救命啊!
這種超出期待的,所謂異於常識的反饋,是出於一個大的常識框架下的。敵人發現了我,所以對我進行攻擊。但通常的遊戲,敵人發現我後的行為,不是立刻攻擊,就是立刻停止,所以呼叫援兵是異於玩家常識的。在大框架下,其選擇的行為方式越超出玩家想象,就越能給玩家留下深刻的印象。這種新鮮的感覺使得他願意去嘗試,體驗更多的玩法,以換取更多刺激的體驗。
無論符合玩家常識,或是超出玩家期待,本質上都是設計師對玩家行為和心理的預測與應對。在這一點上,瞭解人的情感是很重要的。而AI系統的重要性在於,它使得設計師可以根據玩家行為變化,動態地給出不同的應對措施,同時這個設計是可重複利用的。
03. 讓策劃省事
對的,最終目的還是讓策劃省事,你們知道我曾經做boss挑戰的時候幹過什麼嗎?一個動作遊戲做了個小範圍內的時間軸boss,硬生生靠猜玩家行為做出來了。然後這套只是完全隨機打幾個組合連續技的boss花了我接近一週的時間去構思和除錯,一旦玩家摸清套路,之前看上去各種預讀的技能就變成了毫無卵用的雜耍,而趕完版本重新用AI來做,這套東西一個下午就配出來了,還順帶做了優化。AI相比之下,是應用面最廣、量產成本最低的一個實現方式。
所以說AI相關係統的需求包含了:儘可能多的能夠對不同條件的情況作出判斷和迴應,並且能夠儘量節省策劃的配置量和思考量。
怎麼設計AI系統?
對於不同專案來說需要考慮的因素有很多,最終導向的AI相關的功能也千差萬別,我就是想要一個非常擬真的生態系統,和我只想要一個一旦發現就幾乎會追你一輩子的怪物AI實現方式是完全不同的。都是精細的AI判斷,開放世界的AI和動作遊戲的AI生效的部分也完全不一樣,系統的重點在於2個部分:怎麼將我們想要自動化的行為抽象和切分,還有用什麼形式來編輯和儲存這些AI。
01. 對大的行為模式切割
對於怪物來說,比較常見的一些AI行為模式如下:
- 閒置:不執行任何有意義反饋的狀態,像小範圍的遛彎、隨便蹦蹦跳跳之類的行為其實是可以包含在內的。
- 戰鬥:準備從技能池中選取合適的技能釋放的狀態。
- 移動:未觸發戰鬥時,從一個位置移動到另一位置的模式,比如巡邏和脫戰返回。
- 追擊:當觸發了戰鬥後,從一個位置移動到另一位置的模式。
- 互動:與玩家進行非戰鬥互動的部分,比較個性化,就先合併到一起來說。
這些行為模式其實覆蓋了一個怪物可能與玩家互動的絕大部分行為,在這個基礎上再進行細分,去做各個行為模組的邏輯,我認為是比較好的。
一個AI行為應當包含的是:在某個檢測物件滿足某種觸發條件下,某個行為物件執行了某個特殊行為組合,也就可以分解成如下的幾個部分。
02. 觸發條件和檢測物件
觸發條件:攻擊類、受擊類、擊殺類、死亡、tick/週期檢測。
觸發條件需要支援計數,因為有瞬間性行為,所以對條件要有一個計數有效時間。作為if(*)內的條件語句,自然也要有且或非。
檢測物件:檢測物件與我的關係、檢測物件的相關條件(血量、距離、特殊標記、屬性等)。
我將常態化的條件部分切分進了檢測物件中,這樣將檢測物件的屬性封裝成了一個整體,方便呼叫。
03. 行為與行為物件
行為:尋路、釋放技能、固有屬性修改、特殊標記與取消等。
行為集:這算是我上個專案的遺恨了,多個行為的序列與併發,或者乾脆將他們按照狀態機的方式封裝成一個行為模式,這樣可以定義一套很完整的行為。
行為物件:行為物件有時未必只針對自己,比如說一個怪物頭頭髮出指令,讓其他怪物跟他一起巡邏,這相當於一個技能修改了幾個怪物的行為模式,所以行為裡需要對行為物件進行指定。但本著AI掛誰身上誰辦事兒的原則,執行行為的個體一定是自己,不需要額外進行區分。
04. 一些特殊部分
這部分我暫時不清楚要怎麼劃分,因為實際上AI在遊戲中的應用有兩部分控制需求相對集中:互動狀態的改變,和具體技能釋放的選擇。其中後者主要應用在一些特定戰鬥下,較多的技能分配上。有些團隊會選擇直接通過關卡邏輯來調整,有些是完全隨機選擇,有些是固定時間軸,更多是以上幾種的混合控制。
這類AI有時候甚至會單分出一個功能來實現,比如單獨控制怪物的lua指令碼。而我今天說的更偏向於利用一個完整的功能將這些囊括進去。根據團隊開發習慣和專案側重不同,實現時很多方式的評估結果是不一樣的。
工具相關的問題
關於AI方面,我之前經歷過兩個不同的專案。一個是走狀態機,使用表格做特殊行為的檢測和執行。一個是UE4,選擇了原生的AIController來控制。我曾經一度非常吹捧ue4的行為樹與其編輯器,但是後來也慢慢有一些想法在改變。
01. 學習成本
我本身對於程式是有一定了解的,也說不上很好吧,自己在unity寫過狀態機,平時能看看專案程式碼,瞅瞅策劃失傳的無主欄位幹什麼用。學行為樹的時候看了看大致思路:“哦,一個大型的if-else巢狀集合”,基本上就知道怎麼做了,細節的部分程式規範一下,跟藍圖一樣設計。這方面表格看起來反而有些吃力,開啟一張表密密麻麻的都是數,一個一個看批註煩都煩死了。
長時間下來,我發現有時候需要程式指點的零碎地方,比預想的要多。隨著經歷更多的事情,我意識到自己終歸和程式存在著本質差別,那麼並沒有自學過程式,本身沒有程式功底的人呢?即便程式也是會隨著經驗的不斷加深而重構程式碼,一個龐大而複雜的AI自然也是。更多時候,策劃並不需要那麼多資料隨時可以訪問,我們應該主動封閉一部分不必要的資料,來減少策劃的設計負擔。那麼資料具體暴露到什麼度,是需要程式和策劃之間,根據雙方的能力和默契程度不斷協調的,這部分更多是需要靠經驗決定的。
從我的認知角度講,直接按照一個完整事件來封裝一條AI,是最直觀和容易認知的。想到一條寫一條,之後再決定檢測次序和優先順序,組合成一個大的AI鏈。實際上串聯排好後會發現,這種結構和行為樹還蠻像的。另外,整體功能引數越複雜,思考難度就越大,功能引數越簡潔,要實現同一個複雜功能可能需要的步驟就越多,這兩種情況最終都是會提高學習成本的,需要根據需求平衡和規避。
02. 量產與個性化
開頭我要以狀態機的形式定義一些行為模式,一方面是因為這種切分形式符合人的樸素思維,另一方面是因為對其中一部分行為模式繼續切分,對於設計師來說本身就是無意義的。為什麼程式會存在方法這個概念?為什麼會有封裝、繼承和多型?——因為省事=清晰=方便複用,我為什麼要每次都重新在行為樹裡執行一遍判斷是否有敵人,有敵人是否在攻擊範圍內,在的話選擇技能對其釋放,而不讓他直接走“攻擊模式”這個行為呢?
這部分其實是和上面聯動的,就算使用行為樹,這些基礎的部分也會被封裝成一個行為集合,這也是我為什麼在前面對行為集怨念的原因,不過這部分有很多東西會需要評估如何進行封裝,我目前還是持中性態度,以程式的意見為最先考量。
03. 出現bug時
上一個專案另外一個怨念的部分就是不能打斷點,可憋死我了!(我不是我沒有
客戶端高階程式設計師熊寶寶給我講:“就算把客戶端程式碼給你,邏輯不是你寫的,我不告訴你哪個類,你要斷去哪斷?我們自己看以前程式碼都需要理解和查bug的時間,你能保證斷得出來,其他策劃能嗎?”
是這樣的,而且我也不能,斷點程式這玩意兒確實如他所說,並不是策劃應當觸碰的領域。就算程式互相之間也很難查bug,更何況策劃,但我並不是想要斷點的功能,而是想知道策劃配置能夠正確行進至哪一部分,方便定位問題。
最起碼對於自己朝夕相處擺弄的功能,絕大部分人知曉一個最簡單的工作原理,在這個基礎上迅速定位問題,很多配置錯誤其實不需要再經過程式也能快速查找出來,能夠有效提高雙方的開發效率。這方面編輯器,尤其是內嵌在引擎可以同時保證調整和監控的編輯器,是有得天獨厚優勢的。
04. 後續擴充套件與持續維護
擴充套件包含兩個維度:程式方面的擴充套件和策劃表/編輯器結構的擴充套件。這兩部分分別會引發不同的維護問題,最終都會反饋到策劃這邊:前者會使得功能擴充套件本身難以推進,後者則會讓配置量越來越大,容錯性和bug排除難度等,都會越來越惡化。
另一方面,由於我在這裡吃過大虧,所以這件事一定要記住:一旦產品上線,所有已有功能的修改,哪怕是純配置都要經過程式,保證在其知曉的情況下進行修改。
因為一旦上線,玩家在某個功能獲得了資料(很多情況下是付費了的)並進入了資料庫儲存,你的配置改動也許會使得連結的資料來源頭改變,那麼很有可能會產生髒資料。這個髒資料可以理解為無主資料,但無論如何玩家是不可能接受【清庫】這種處理方式的,直接對伺服器中的髒資料進行遍歷處理又會極大的增加伺服器負荷,最後很可能需要伺服器特別為這些髒資料新增處理程式碼。這樣有可能最終會形成策劃資料和程式程式碼的一些廢棄資料,影響整潔度,進一步阻礙下一次的功能擴充套件。
關於如何保證一個系統的可擴充套件性,我的傾向和經驗是:在設計之初想的可以非常大,然後和盤托出講給程式,之後共同閹割無關功能,到目前需求的部分。這種工作方式需要雙方互相的一定程度的信任,但程式知道你未來打的什麼算盤,架構上一開始就會留好那個方向的餘地。當然如果自己沒想好或者計劃趕不上變化的時候,總會有做出難以擴充套件的系統的情況,那個時候我的建議也只有儘早跟上級溝通確認不能實現的效果是否是剛需,一旦有這種情況,也只能請程式老哥重新幫忙修改一下了不是?畢竟危樓之上壘得再高,還是危樓。
05. 合併分支
這塊我自己不同專案的經驗也不多,只能先丟擲問題:編輯器在上述幾個問題中大部分是優於表格的,但唯獨這塊有著很大劣勢。在合併分支版本時,單獨的某些部分的值很難被合併,ue4的map、blueprint,unity的scene和prefab都是如此,他們很難達到像xml一樣快速合併的靈活性,只能作為一個整體。尤其是blueprint這類本身就是視覺化程式設計的模組,合併某個版本甚至會需要你單獨為其擺放一部分藍圖和創造新的變數。這會增加很多無謂的分支bug。編輯器輸出xml化是一個解決方案,但多數策劃看不懂這件事還是很容易出現,難道只能以大功能模組為切分去做更新了嗎?
本文來自 騰訊遊戲學院 ,觀點不代表本站立場,轉載請聯絡原作者 https://mp.weixin.qq.com/s/WO6kBKixn_HiuBSzNgUaiQ