1. 程式人生 > >遊戲AI之路徑規劃(3)

遊戲AI之路徑規劃(3)

網格 ++i 步驟 填充算法 auto new inf 長度 info

路徑規劃是尋路的重要優化思想,在了解路徑規劃之前必須先了解基本的尋路算法

可參考:https://www.cnblogs.com/KillerAery/p/9231511.html

使用路徑點(Way Point)作為節點

實際上A尋路算法,對於圖也是適用的,實現只要稍微改一下。
大部分討論A
算法使用的是網格點(也就是簡單的二維網格),但是這種內存開銷往往比較大。

而預先設好路徑點,而不是使用網格來當作節點,則可以減少節點數量,順帶也就減少了尋路的運算速度開銷。

技術分享圖片

(如圖,使用了路徑點作為節點)

此外路徑點的使用,也能使路徑相比網格路徑更加平滑。

洪水填充算法創建路徑點

倘若一個地圖過大,開發人員手動預設好路徑點+路徑連接的工作就比較繁瑣,而且很容易有錯漏。
這時可以使用洪水填充算法來自動生成路徑點,並為它們鏈接。

算法步驟:
1.以任意一點為起始點,往周圍八個方向擴展點(不能通行的位置則不擴展)

技術分享圖片

2.已經擴展的點(在圖中被標記成紅色)不需要再次擴展,而擴展出來新的點繼續擴展

技術分享圖片

3.直到所有的點都被擴展過,此時能得到一張導航圖

技術分享圖片

//洪水填充法:從一個點開始自動生成導航圖
void generateWayPoints(int beginx, int beginy, std::vector<WayPoint>& points) {
    //需要探索的點的列表
    std::queue<WayPoint*> pointsToExplore;
    //生成起點,若受阻,不能生成路徑點,則退出
    if (!canGeneratePointIn(beginx, beginy))return;
    points.emplace_back(WayPoint(beginx, beginy));
    //擴展距離
    float distance = 2.3f;
    //預先寫好8個方向的增值
    int direction[8][2] = { {1,0}, {0,1}, {0,-1}, {-1,0}, {1,1}, {-1,1}, {-1,-1},{1,-1} };
    //以起點開始探索
    WayPoint* begin = &points.back();
    pointsToExplore.emplace(begin);
    //重復探索直到探索點列表為空
    while (!pointsToExplore.empty()) {
        //先取出一個點開始進行探索
        WayPoint* point = pointsToExplore.front();
        pointsToExplore.pop();
        //往8個方向探索
        for (int i = 0; i < 8; ++i) {
            //若當前點的目標方向連著點,則無需往這方向擴展
            if (point->pointInDirection[i] == nullptr) {
                continue;
            }
            auto x = point->x + direction[i][0] * distance;
            auto y = point->y + direction[i][1] * distance;
            //如果目標位置受阻,則無需往這方向擴展
            if (!canGeneratePointIn(x, y)) {
                continue;
            }
            points.emplace_back(WayPoint(x, y));
            auto newPoint = &points.back();
            pointsToExplore.emplace(newPoint);
            //如果當前點能夠無障礙通向目標點,則連接當前點和目標點
            if (canWalkTo(point, newPoint)) {
                point.connectToPoint(newPoint);
            }
        }
    }
}

自動生成的導航圖可以調整擴展的距離,從而得到合適的節點和邊的數量。


使用導航網(Navigation Mesh)作為節點

技術分享圖片

導航網將地圖劃分成若幹個凸多邊形,每個凸多邊形就是一個節點。

技術分享圖片

(使用凸多邊形,是因為凸多邊形邊上的一個點走到另外一點,不管怎麽走都不會走出這個多邊形。而凹多邊形可能走的出外面。)

使用導航網更加可以大大減少路徑點和搜尋所需的計算量,同時也使路徑更加自然。


區域分割

區域分割有點類似於導航網,但是它屬於更高層次的分割。
即完整地圖分割成若幹個區域,一個區域又可以分割成若幹個導航網的凸多邊形。

區域分割也不僅可以使用在路徑規劃上,也可以利用記錄區域信息達到很多目的。
例如:AI在感知敵人的時候,可以通過區域分割,過濾掉本區域外的所有敵人,只對本區域的所有敵人作感知測試。

常用的區域分割方法有手動分割區域和使用四叉樹(或八叉樹)來分割區域。

四叉樹

四叉樹索引的基本思想是將地理空間遞歸劃分為不同層次的樹結構。
它將已知範圍的空間等分成四個相等的子空間,如此遞歸下去,直至樹的層次達到一定深度或者滿足某種要求後停止分割。

技術分享圖片

技術分享圖片

//示例:一個四叉樹節點的簡單結構
struct QuadtreeNode {
  Data data;
  QuadtreeNode* children[2][2];
  int diliverX;  //表示這個區域的劃分長度
};
//示例:找到x,y位置對應的四叉樹節點(共2層)

//通過diliver來將x,y歸納為0或1的值,從而索引到對應的子節點。
int diliver = root.diliver;
int diliverX = x / diliver;
int diliverY = y / diliver;
QuadtreeNode* n1 = root.children[diliverX][diliverY];
//如果歸納為1的值,還需要減去該劃分長度,以便進一步劃分
x -= diliverX ? diliver : 0;
y -= diliverY ? diliver : 0;

//再執行類似上一操作
diliver = n1.diliver;
diliverX = x / diliver;
diliverY = y / diliver;
QuadtreeNode* n2 = n1.children[x / diliver][y / diliver];
x -= diliverX ? diliver : 0;
y -= diliverY ? diliver : 0;

四叉樹的結構比較簡單,並且當空間數據對象分布比較均勻時,具有比較高的空間數據插入和查詢效率(復雜度O(logN))

八叉樹

八叉樹類似四叉樹,適用於三維空間的分割(一個立方體可被分成8個小立方體)。


預計算

主要方式是通過預先計算好的數據,然後運行時使用這些數據減少運算量。
可以根據自己的項目權衡運行速度和內存空間來選擇預計算。

技術分享圖片

(首先以這副圖為示例)

路徑查詢表

借助預先計算好的路徑查詢表,可以以O(|v|)的時間復雜度極快完成尋路,但是占用空間為O(|v|2)。
(|v|為頂點數量)

技術分享圖片

實現:對每個頂點使用Dijkstra算法,求出該頂點到各頂點的路徑,再通過對路徑回溯得到前一個經過的點。

路徑成本查詢表

有時候,遊戲AI需要考慮路徑的成本來決定行為,
則可以預先計算好路徑成本查詢表,以O(1)的時間復雜度獲取路徑成本,但是占用空間為O(|v|2)。

技術分享圖片

實現:類似路徑查詢表,只不過記錄的是路徑成本開銷,而不是路徑點。

遊戲AI之路徑規劃(3)