好的,針對敵人 AI 角色在平台地形之間的移動處理上,總算是告一個段落了。
發了有整整三篇日誌,快五個月都在寫這塊功能,大概是目前專案裡費時最長的機制,我可以保證這是最後一篇了。




上一篇 #32 結語提及調整性質的功能一個也沒跳票,如標題所述,這篇主要就是要來做個收尾的。
◆【場景地形複雜度】
用以設定一個場景中連續地形分佈複雜程度的欄位,它會影響到實際遊戲運行期間,AI 角色在尋路上使用的演算法還有效能上的利用狀況。
先回頭看原本的演算法,它們讓 AI 角色在尋路時,會盡可能地尋找最短路徑,而忽略地形之間是否存在著其他障礙物的情形。
下圖是模擬此類場合的例子,移動目標是最左上角的地形:

※ 這部分的漏洞已經被修掉了,所以這個範例是手動操作來示例的。
可以看到,AI 在尋路只是一昧地透過演算法所得到的最短路徑來行進,沒有考量到這類阻礙所帶來的變數,也就導致該次的尋路沒辦法讓 AI 到達目標位置。
而這次的問題也很明顯,就是移動路徑上,出現了角色碰撞體無法通過的區塊所致。
判定所謂移動路徑上能不能讓角色碰撞體通過還好說,但問題就出在這個「移動路徑」的範圍是怎麼樣的。
AI 在不同地形之間移動時,起跳點還有落點處,都是會因為路徑的不同而產生變化的:

也因如此,在尋路的過程中,也只能盡可能地去列出所有可能的路徑,然後根據路徑之間來源地形的不同,去針對相異的移動路徑來計算移動成本。
比方說前面提及會被障礙物所阻擋的路徑,就將其移動成本直接標記為最大值,這樣能在最後比對路徑間的成本時,使其被選擇的權重降到最低。
至於所謂「列出所有可能的路徑」的這個做法,這邊採用的是深度優先搜尋(DFS),其耗費的時間是會因為場景內的地形數量,而導致它以指數級別的程度來增加的。

簡化一下來比對,極端假設場景內的所有地形皆可互相通行,n 是該場景內的地形數量。
※ 目前遊戲內所實裝的尋路演算法,一律不會經過重複的地形。
n = 10, 則路徑數量為 2^(10-1) - 1 = 511 條。
n = 20, 則路徑數量為 2^(20-1) - 1 = 524,287 條。
n = 30, 則路徑數量為 2^(30-1) - 1 = 536,870,911 條。
雖然說一般常見的場景內的地形數量,大致上都集中在 10 個以內。
但這也就是說,只要場景內的地形數量一多,AI 角色只是在尋路上就有可能找到天荒地老。
所以在尋路的設計上,基於效能上的考量,會設定一個界限值,當尋路的路徑數到達一定程度之後,就只會從找到的路徑之間來篩選有效路徑,而非理論上所存在的最佳路徑。
回歸正題,有了上面的案例之後,這個場景地形複雜度的欄位在設定上,目前給出了 3 個級別:
∎ 穩定(最常見)
建立於地形分佈上,移動路徑完全不會阻擋到任何場景內 AI 角色的場合,採用原先的最短路徑演算法,在尋路的效能上是最好的,即便是地形數量較為大量的場景也能較為輕鬆地應付。
∎ 複雜(預設)
場景內的地形分佈上,會有讓 AI 角色在尋路期間受到阻擋的障礙物存在,故採用的即是我們前面提及那套的演算法。但這些障礙物仍然是屬於靜態的,不會隨時間產生位移。也因如此,地形之間的尋路成本是可以被暫存的,在第二次以後的尋路上,所耗費的效能成本會降低。
∎ 動態(成本最高)
捨棄了上一個「複雜」程度中的尋路成本暫存處理,也就是說,AI 角色在每次的尋路期間,都會判定移動路徑間是否受阻。
適合場景內的地形在遊戲過程中,會產生頻繁變化或是位置調整的場合,屬於效能耗費上最為高昂的地形複雜度設置。
◆【尋路適應功能】
AI 角色的尋路只是最為基礎的需求,如果它們不懂得變通的話,反而會顯得有些許笨拙。
能夠依據當下的戰況來進行隨機應變,這樣才不愧為一種‘︁AI’︁吧?
其實當初在角色參數的設計上,有給它們加上一個智力的欄位,現在看來就是應用的好機會了。
∎ 無效跳躍記錄
前兩篇日誌有提及到,在獲取兩個地形節點之間的跳躍拋物線上,仍然是會有一定的誤差的。
也因此,即便拋物線預判上能讓角色跳躍到目的地,實際進行跳躍處理的時候,還是有可能會出現少數跳不到目標地形的狀況。
AI 角色在跳躍期間,如果未受外力干擾,卻出現跳躍失敗的場合,一旦此過程的計數超過了設定上的值,往後的尋路就會把該段路徑給進行忽略。
∎ 危險路徑
這邊指的是對 AI 來說,前往特定地形時會有風險的情形。
AI 在地形之間進行跳躍的過程,玩家是可以進行一定程度上的預判的。
舉例來說,你可以在地形邊界處蹲點,等敵人跳上來的那一瞬間,再給他捶回去。
∎ 漫遊隨機尋路敵人受擊之後,就會開始對該段路徑產生警戒,進而規避之。當然,這個警戒行為是受敵人設定上的智力參數所影響的,比較笨一點的敵人就有更高的機會讓玩家去蹲跳躍點來輸出。
AI 角色當前沒有仇恨目標時,就稱為漫遊階段。
原先處於漫遊狀態的 AI,就會根據其移動積極性來隨機在所處的地形上來回踱步。
而跨越不同連續地形的功能已經完成的現在,其他地形就能納入 AI 隨機移動的範圍內了。然而為了避免隨機尋路到過遠或不適宜的地形上,需要滿足下面兩點條件:
① 屬於角色當前所在地形的鄰近地形:避免角色可能會前往太遠的地方
② 處於角色偵測碰撞體的範圍內:其尺寸受角色本身機動能力影響
◆【瞬間傳送】圖中綠色的矩形區塊就是偵測範圍,當同時有多個目標符合條件時,則會隨機擇一前往。
一定程度上無視地形阻礙,不使用跳躍進而直接到達目標地形的做法。
說是瞬間傳送,但預設的情況下,AI 角色所能夠傳送的目標地形,仍然會受到其機動能力所篩選。
一來是為了避免 AI 角色傳送到非預期的位置,此外就是地形的佈局能夠直接沿用尋路系統的,不必再重寫一套瞬間傳送專用的演算法,也能在運行期間即時切換成非瞬間傳送模式,變成一般的跳躍尋路處理。當然,敵人透過瞬間傳送的最大優勢就是無敵幀,以及可以忽略尋路的中間路徑,直接傳送到尋路終點位置。這讓敵人在追擊與退守的過程中,一定程度上能夠產生較大的威脅性,至少玩家不太能夠在特定位置蹲點來對它們輸出了…?※ 上述的設置皆視敵人類型而定上一篇結尾本來是想說要繪製瞬間傳送的特效,但考量到因為不同敵人角色的圖像尺寸也會有所差異,傳送特效可能沒辦法適應不同尺寸的角色,所以後來是決定改成讓角色圖像在傳送的時候,產生扭曲的過渡效果來呈現,如此一來就沒有特效需要去適應尺寸的問題了。◆【Hitbox 判定微調】這是因應瞬間傳送功能完成之後所發現的漏洞,同樣也已經修掉了,這邊只能言傳。事發情況是,在敵人傳送到其他座標的那一幀,Hitbox 仍然會判定為有效,造成視覺上看來,攻擊者在 A 點出刀,傳送走的受擊者在遠處的 B 點產生了受傷的情形。由於目前專案中的 Hitbox 是透過 OnTriggerEnter2D() 及 OnTriggerExit2D(),先把接觸中的角色存入集合內,在下一次的更新階段內,如果它在有效的攻擊生效時間間隔範圍中,則觸發造成傷害的內容。然而問題就出在 Unity 內部的物理更新執行順序,上述兩個與碰撞體接觸或離開的處理方法,它們的執行順序是晚於 FixedUpdate() 的。此部分是調整 Edit > Project Settings > Script Execution Order 也沒有辦法進而解決的。這就導致明明目標角色已經與 Hitbox 相互脫離了,卻因為同時間下 OnTriggerExit2D() 還沒觸發,FixedUpdate() 階段該角色還存在接觸中的集合內,進而出現傳送走了卻還是打得中的情形。沒有辦法,只能在 Hitbox 判定的期間,再進一步檢查兩碰撞體之間是否處於接觸中的狀況了。試了幾個方案,直接講測下來的結果。Collider2D.IsTouching() 背後運作的更新週期同樣晚於 FixedUpdate(),沒辦法即時反映接觸狀況,不可行。這類方法總算是不需要仰賴物理的更新週期,而是即時性地在座標空間中進行判斷,故能夠符合我們的需求。最後是決定透過 Collider2D.Overlap() 來做為 Hitbox 精準判定的條件之一,也總算沒有再發生那種攻擊打歪的狀況了。下一階段的開發進度是地圖系統,這包含從選單中啟用的,還有常駐在畫面角落的,也是屬於使用者介面的範疇,大概也得花上些時間來設計。往好處想,總算是不用在物理演算這塊上繼續折騰了。但在那之前,SiNiSistar2 出了,去玩個先,告辭。