18.尋找你周圍的道路:在設有障礙物的網格中尋路

18.Finding Your Way Around: Pathfinding on a grid with obstacles

In the last post we saw about distances with horizontal and vertical movement, and how to move one object towards another using horizontal and vertical moves on an empty grid. But what if there are obstacles in the way? For that we need a pathfinding algorithm. This is not particularly mathematical, and it is a bit intricate, but it will come in handy for our next game.

上一篇帖子裏我們瞭解了關於水平和垂直運動的距離,以及如何使用水平和垂直移動來讓一個物體在空格里朝着另一個物體移動。但是如果移動路徑上有障礙物那又如何?爲此我們需要一個路徑尋找算法。這不是特殊的數學內容,而且有一點麻煩,但是在下一個遊戲中將要使用它。

Signposts

路標

How do you calculate all the shortest paths to a particular destination square on a grid? Your initial inclination might be to have, for each source square, a list of directions. So something like this (blue star is the source, red square is the destination, with each arrow showing which square to move to next):

在一個網格里你如何計算所有的到達某個特定目標方格的最短路徑?你最初的想法可能是給每個源方格設置一個方向列表。於是就像這樣(藍星是源,紅星是目標,每個箭頭顯示了下一步要向哪個方格移動):

But in fact, consider the next square on that route — its path will look like this:

但是事實上,考慮到路徑上的下一個方格——它的路線將看起來像這樣:

So actually, we don’t need a full list of directions for each source square, we just need one direction: to the next square. For a fixed destination, each square acts like a signpost, pointing the way to the next square. By following the signposts on each square, you can arrive at the destination. This means that for any given destination square, we need to form a complete grid of signposts, that will look something like this:

於是實際上,我們不需要給每個源方格設一個完全的方向列表,而僅需要一個方向:指向下一個方格。對於一個固定的目標,每個方格的作用像路標一樣,指示通往下一個方格的方向。通過每個方格的路標引導,你便可以到達目的地。這意味着對於任意給定的目標方格,你需要形成一個完全的路標網格,如下圖所示:

The Idea

主要思想

So how do we build up this grid of signposts? You might think that we begin at the source square, but actually that doesn’t work so well: we’ve no idea which direction will be successful from the source, because we don’t know what walls might be in the way. Instead, we start at the destination, because from the squares exactly adjacent to the destination, we know which way to head:

於是怎樣來創建這個路標網格呢?你可能會想到我們從源方格開始,但是實際上那樣並不能很好地運行:我們並不知道從源方格開始往哪個方向移動會成功,因爲我們不知道路徑上有怎樣的屏障。反之,我們從目標開始,因爲從與目標完全相鄰的方格開始,我們知道哪條道路去前進:

Then from the squares adjacent to those, we also know which way to head:

接着從與它們相鄰的方格開始,我們也知道哪條道路去前進:

And so we spider outwards, searching all adjacent squares. At each square we see if the route we are currently tracing from the destination is shorter than the one which has been recorded. If the current route is shorter, continue, otherwise (if the old route is shorter) then give up. We will almost certainly reach situations where we have multiple ways in which we can head, for example the squares with the multiple smaller red arrows above. In these situations, we use the rule we saw inour last post to decide which way to head: the way that gives the shortest diagonal distance. In the picture above this is equal both ways, so we just make an arbitrary choice.

於是同樣地我們十字交叉向外擴展,搜索所有相鄰的方格。對於每個方格我們看看當前從目標追蹤而來的路徑是否比之前保存的要短。如果當前路徑更短,則繼續搜索,否則(如果之前的路徑更短)便放棄。我們幾乎確定地將會到達一些位置,那兒我們會面對多種不同的道路,比如上圖中擁有幾個紅色小箭頭的方格。在這些位置上,我們使用上一篇帖子裏學習的規則來決定哪條道路去前進:擁有最短斜線距離的路徑。在上圖中兩條路徑是相等的,於是我們僅需要任選一條。

Show me the Code

展示代碼

I’m not going to go into great depth on the code, but here it is if you want to see exactly what’s happening:

我不打算深入介紹代碼,但是如果你想確切地知道發生了什麼,你可以參考如下代碼:

    private void calculatePathFrom(byte[][] paths, int[][] dist, int fromX, int fromY, byte newDir, int newDist)
    {
        if (outOfBounds(fromX, fromY) || occupied[fromX][fromY])
            return; //Outside world or blocked
            
        byte prevDir = paths[fromX][fromY];

        if (prevDir == STAY)
            return; // At destination square again
            
        int prevDist = dist[fromX][fromY];
        
        if (prevDir != 0 && prevDist != 0 && prevDist < newDist)
            return; //Already processed and already have shorter path
        
        if (prevDir != 0 && prevDist > 1 && prevDist == newDist
            && (prevDir == newDir || distanceSq(toX - moveX(fromX, newDir), toY - moveY(fromY, newDir)) > distanceSq(toX - moveX(fromX, prevDir), toY - moveY(fromY, prevDir))))
            return; //Already processed and already have shorter or equal path
        
        paths[fromX][fromY] = newDir;
        dist[fromX][fromY] = newDist;
        
        calculatePathFrom(paths, dist, fromX - 1, fromY, RIGHT, newDist + 1);
        calculatePathFrom(paths, dist, fromX + 1, fromY, LEFT, newDist + 1);
        calculatePathFrom(paths, dist, fromX, fromY - 1, UP, newDist + 1);
        calculatePathFrom(paths, dist, fromX, fromY + 1, DOWN, newDist + 1);
    }

The code has four early exits:

該代碼有四個早期地出口:

  1. The square we want to find a path from is occupied by an obstacle, or is outside the grid. It is not possible to make a path from such a location.
  2. We have arrived back at the destination square (e.g. while looking for a pathto an adjacent square) — this never makes sense, because the shortest path to the destination will never gothrough the destination on the way (just stop there!).
  3. We have already found a shorter path from this square, so our latest path (and all extensions of it) will just be longer than what is already going to/through this square.
  4. We have already found a path of equal length from this square, but the previous one had a better diagonal distance (seelast post), so prefer the old path. Otherwise, if the path is of equal length but the new one has a better diagonal distance, we continue and overwrite the old path.

      1.  我們希望尋找路徑的方格被障礙物所佔據,或者在網格之外。從這樣的位置是不可能形成一條路徑的。

      2.  我們回溯到了目標方格(比如,在搜索相鄰方格的路徑時)——這是沒有意義的,因爲指向目的地的最短路徑永遠不會穿過目的地(只是停在那!)。

      3.  我們已經找到了一條離該方格更短的路徑,而最近找到的路徑正好長於已經存在的路徑。

      4.  我們已經找到了一條離該方格相同長度的路徑,然而之前的路徑擁有更短的對角線距離(參照上一篇帖子),於是使用之前的路徑。否則,如果路徑相等而新路徑有更短的對角線距離,我們繼續搜索同時覆蓋舊的路徑。

Assuming none of the above are true, we replace the previous path with our new path, and spider outwards (last four lines) looking for other paths. You can seethe scenario in action — run the scenario and move your mouse over a square to see all the shortest paths to that square.

假設以上條件都不爲真,我們便用新路徑替換之前的路徑,同時十字交叉向外擴展(最後四行代碼)來尋找其他路徑。你可以看看運行中的遊戲劇本——執行遊戲劇本並移動鼠標滑過一個方格去查看該方格的所有最短路徑。

發佈了0 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章