由於我們的項目采用的尋路解決方案是:客戶端使用 unity 原生的尋路系統,服務器采用 RecastNavigation 系統,而服務器的尋路數據來自於從 unity 導出的,所以理論上兩邊的尋路結果應該是一樣的,可事實上並非如此,unity 無論如何尋路,都能表現出比較自然的結果,但是服務器卻有時會出現比較奇怪的結果。
由以上三張截圖可以看到,起始點和終點稍微有點變化,結果就會出現較大的差異,尋路結果往往會“拐彎”,這種結果勢必是不能接受的,unity 里不會出現這種情況,經過多次試驗並仔細查看和分析,發現是因為尋路網格里出現了過於“細長”的三角形導致的,那如何修改呢,我在官方論壇里向作者(Mikko Mononen)提問(原帖地址點這里,自行解決 FanQiang 問題):1.我覺得 unity 的尋路系統是基於原作者的 RecastNavigation 系統編寫的;2.這種結果是否是由“細長”三角形導致的,該如何解決呢?
很開心作者很快就恢復了:1.unity 的尋路系統確實是基於 RecastNavigation 編寫的,但是 Detour 系統已經被深度改寫了,(后來發現作者的 twitter 簡介是 unity 的 ai 程序員);2.這個問題確實由於“細長”三角形引起的,NavMesh 系統當遇到特別寬或者三邊長度比特別大的三角形時會出問題。而作者很久前就在自己的博客里敘述過這個問題,並給出了解決方案,博客地址:
解決方法是:改變A*尋路結果中節點的放置處理,NavMesh 系統里默認使用多邊形邊的中點來作為路徑通過的點,如果想要好看的結果,應該使用距離最近的點,即你有 A, A 兩個點在一條線段 L 的兩邊,那么這個點應該是線段 AB 和線段 L 的交點。
1 if (neighbourNode->flags == 0) 2 { 3 float sa[3], sb[3]; 4 getPortalPoints(bestRef, bestPoly, bestTile, 5 neighbourRef, neighbourPoly, neighbourTile, 6 sa, sb); 7 float t = 0.5f; 8 dtDistancePtSegSqr2D(endPos, sa,sb, t); 9 t = dtClamp(t, 0.1f, 0.9f); 10 dtVlerp(neighbourNode->pos, sa,sb, t); 11 }
這段代碼在:
https://github.com/memononen/recastnavigation/blob/master/Detour/Source/DetourNavMeshQuery.cpp#L1029
https://github.com/memononen/recastnavigation/blob/master/Detour/Source/DetourNavMeshQuery.cpp#L1321
也可以使用 "bestNode->pos" 替換 "endPos",這樣就可以使用“最近”的點來替換當前 A* 里的“終點”,同時作者指出,像我這種客戶端和服務器的解決方案都只能不斷的調節調試去優化視覺上的結果,並不存在一個百分百准確的方案,不過很開心的是,這樣修改完一實驗,果然結果“正常”了,現在客戶端和服務器的尋路90%的情況都能保持一致,極個別的情況還是會有差異,不過結果不會再特別“奇怪”,所以已經很滿足了,太感謝原作者 mikko 同學了。
題外話,上面這小段代碼原來使用的是
getEdgeMidPoint 來求中點,作者用的
dtDistancePtSegSqr2D 這個函數是用來計算點 endPos 到線段 sa 和 sb 的垂直距離直線的交點 t,t 的數值就是線段 sa-t 和 線段 t-sb 的長度比值。當然這個結果不錯了,但是還不是最好的,作者的原博客使用的是 dtIntersectSegSeg2D(ap, aq, bp, bq, s, t),計算的是線段 ap-aq 和線段 bp-bq 交點 m 在兩條線段上的比值 s = ||ap-m|| / ||m-aq||,t = ||bp-m|| / ||m-bq||,這樣一來計算出來的才是真正的最近距離,兩點之間直線最短嘛;這個函數非常有意思,內部使用了 PertDotProduct 這個數學公式,這個才是最有意思的,但不太容易理解,就是向量 A 和向量 B 的 PertDotProduct 結果為:A 的法向量和向量B 的點乘積,幾何意義是兩條線段組成的平行四邊形的面積(||a||.||b||.sina),而兩條線段的交點分別在每個線段上的比值,正好可以通過計算 PertDotProduct 的比值來獲得,因為他們組成了一個共底邊的平行四邊形,所以面積比等於高之比,而高正好分別就是其中一條線段的起點到交點的距離和起點到終點的距離。我自己的解決方案是選用的最后一種 dtIntersectSegSeg2D,效果也是最好的。
最近一直太忙,都沒更新,其實積累了很多心得在我的印象筆記里,慢慢補充到這里吧。