簡體   English   中英

加快迪克斯特拉

[英]Speeding up Dijkstra

嗨,我為基於圖塊的游戲寫了一個小的Dijkstra實現,用於尋路。 問題是,如果有10個使用此算法的敵人找到目標的最短路徑(目前主要用於巡邏),游戲就會變得很落后。 特別是因為該游戲應在Android智能手機上運行。 我嘗試用sofar來加快整個過程的速度:

1.將通過邊連接的節點數限制為固定數,這意味着只需執行N步,直到使用initializeEgdes方法即可。 這導致了一些丑陋的行為,不是所有巡邏都在執行,而是在某些地方漫長。

2.以每個敵人在自己的線程中計算其最短路徑的方式來對Dijkstra的執行進行仿制。 這是一個問題,我不太了解線程,也沒有使這個想法進入運行狀態(我的敵人對象沒有移動)

我認為關於限制已處理的nodeConnections數量的第一個想法可能會產生相當大的影響,但是我無法確定何時進行處理。

public class Dijkstra {

PathNode[][] allNodes;
TiledMap tiledMap;

public Dijkstra(TiledMap sourceMap) {
    tiledMap = sourceMap;
    generateAllNodes();
}


/**
 * Node that virtualises an actual unit on gameboard, currently a tile.
 *
 * @author Lucas
 */
public class PathNode {
    boolean walkable = true;
    float x = 0;
    float y = 0;
    public final static float width = 32;
    public final static float height = 32;
    DijkstraNode myDijstraNode;

    public PathNode(int xpos, int ypos) {
        x = width * xpos;
        y = height * ypos;
        myDijstraNode = new DijkstraNode(this);
    }
}

/**
 * Node used for the Dijkstra methodes.
 *
 * @author Lucas
 */
public class DijkstraNode implements Comparable<DijkstraNode> {
    PathNode correspondingNode;
    double minDistance = Double.POSITIVE_INFINITY;
    DijkstraNode previous;
    Edge[] adjacencies;

    public DijkstraNode(PathNode myNode) {
        correspondingNode = myNode;
    }

    @Override
    public String toString() {
        return "TILE[" + correspondingNode.x / PathNode.width + "][" + correspondingNode.y / PathNode.height + "]";
    }


    @Override
    public int compareTo(DijkstraNode arg0) {
        // TODO Auto-generated method stub
        return Double.compare(minDistance, arg0.minDistance);
    }

    public void resetNode()
    {
        minDistance= Double.POSITIVE_INFINITY;
        adjacencies=null;
        previous=null;
    }
}

/**
 * An Edge between two dijkstraNodes
 *
 * @author Lucas
 */
class Edge {
    public final DijkstraNode target;
    public final double weight;

    public Edge(DijkstraNode argTarget, double argWeight) {
        target = argTarget;
        weight = argWeight;
    }
}


private List<DijkstraNode> getNeighbours(DijkstraNode u) {

    List<DijkstraNode> neighbours = new ArrayList<DijkstraNode>();

    float originX, originY;
    originX = u.correspondingNode.x / PathNode.width;
    originY = u.correspondingNode.y / PathNode.height;
    TiledMapTileLayer tl = (TiledMapTileLayer) tiledMap.getLayers().get(
            "main_background");
    //Left
    //Checks if the calculated field is still in allNodes
    if (Math.signum(originX - 1) == 1 && allNodes[(int) originY][(int) (originX - 1)].walkable) {
        neighbours.add(allNodes[(int) originY][(int) (originX - 1)].myDijstraNode);
    }
    //Right
    if ((originX + 1) < tl.getWidth() && allNodes[(int) originY][(int) (originX + 1)].walkable) {
        neighbours.add(allNodes[(int) originY][(int) (originX + 1)].myDijstraNode);
    }
    //Up
    if (originY + 1 < tl.getHeight() && allNodes[(int) originY + 1][(int) (originX)].walkable) {
        neighbours.add(allNodes[(int) originY + 1][(int) (originX)].myDijstraNode);
    }
    //Down
    if (Math.signum(originY - 1) == 1 && allNodes[(int) originY - 1][(int) (originX)].walkable) {
        neighbours.add(allNodes[(int) originY - 1][(int) (originX)].myDijstraNode);
    }
    return neighbours;

}

public DijkstraNode getDijkstraNode(com.hhn.liberation.logic.units.Enemy objectToMove) {
    DijkstraNode startNode = null;
    startNode=getDijkstraNode(new Vector2(objectToMove.getX(),objectToMove.getY()));
    return startNode;
}


//Dijkstra Methoden gefunden auf http://www.algolist.com/code/java/Dijkstra%27s_algorithm
public static List<DijkstraNode> getShortestPathTo(DijkstraNode target) {
    List<DijkstraNode> path = new ArrayList<DijkstraNode>();
    for (DijkstraNode vertex = target; vertex != null; vertex = vertex.previous)
        path.add(vertex);
    Collections.reverse(path);
    return path;
}

public static void computePaths(DijkstraNode source) {
    source.minDistance = 0.;
    PriorityQueue<DijkstraNode> vertexQueue = new PriorityQueue<DijkstraNode>();
    vertexQueue.add(source);

    while (!vertexQueue.isEmpty()) {
        DijkstraNode u = vertexQueue.poll();

        // Visit each edge exiting u
        for (Edge e : u.adjacencies) {
            DijkstraNode v = e.target;
            double weight = e.weight;
            double distanceThroughU = u.minDistance + weight;
            if (distanceThroughU < v.minDistance) {
                vertexQueue.remove(v);
                v.minDistance = distanceThroughU;
                v.previous = u;
                vertexQueue.add(v);
            }
        }
    }
}
//Ende Dijkstra Methoden


public DijkstraNode getDijkstraNode(Vector2 target) {
    // TODO Auto-generated method stub
    for (int i = 0; i < allNodes.length; i++) {
        for (int k = 0; k < allNodes[i].length; k++) {
            PathNode currentNeigbour = allNodes[i][k];
            if (currentNeigbour.x <= target.x && currentNeigbour.x + PathNode.width >= target.x &&
                    currentNeigbour.y <= target.y && currentNeigbour.y + PathNode.height >= target.y) {
                return currentNeigbour.myDijstraNode;
            }
        }
    }
    return null;
}

private void generateAllNodes() {
    TiledMapTileLayer tl = (TiledMapTileLayer) tiledMap.getLayers().get("main_background");

    if(allNodes==null)
    {
        allNodes = new PathNode[tl.getHeight()][tl.getWidth()];
        for (int i = 0; i < tl.getHeight(); i++) {
            for (int k = 0; k < tl.getWidth(); k++) {
                allNodes[i][k] = new PathNode(k, i);
                //TODO use provided method in level?
//                checkForObjectCollision(enemy)
                allNodes[i][k].walkable = !Collider.doesCollideWithWall(new Collider(
                        allNodes[i][k]), tiledMap);
            }
        }
    }
    else
    {
        for (int i = 0; i < tl.getHeight(); i++) {
            for (int k = 0; k < tl.getWidth(); k++) {
                allNodes[i][k].myDijstraNode.resetNode();
            }
        }
    }

}


public void initialiseEdges(DijkstraNode startNode) {
    // TODO Auto-generated method stub
    DijkstraNode currentNode = startNode;

    Queue<DijkstraNode> neigbourQueue=new LinkedList<DijkstraNode>();
    neigbourQueue.offer(currentNode);



    while(!neigbourQueue.isEmpty())
    {
        List<DijkstraNode> newNeigbours=innerFunction(neigbourQueue.poll(),0);
        if(newNeigbours!=null)
        neigbourQueue.addAll(newNeigbours);
    }

}


private List<DijkstraNode> innerFunction(DijkstraNode currentNode, int depth) {
    if (currentNode.adjacencies != null) {
        return null;
    }
//        if(depth>15)
//        {
//            currentNode.adjacencies=new Edge[0];
//            return;
//        }

    List<DijkstraNode> neigbours = getNeighbours(currentNode);

    currentNode.adjacencies = new Edge[neigbours.size()];
    for (int i = 0; i < neigbours.size(); i++) {
        DijkstraNode currentNeigbour = neigbours.get(i);
        currentNode.adjacencies[i] = new Edge(currentNeigbour, 1);
    }
//        for (PathNode pt : neigbours) {
//            innerFunction(pt.myDijstraNode,depth+1);
//        }
    return neigbours;
}


}

既然您說這是一個基於圖塊的游戲,那么我想有效路徑可以在兩個方向上遍歷,而成本與方向無關(或者足夠接近獨立方向)。 在這種情況下,如果您從每個敵人的位置開始使用Dijkstra,您所做的工作將比您需要做的更多。

相反,應從目標處開始,並找到在同一段Dijkstra中通往所有敵人的最短路徑(即,僅在找到通往每個敵人的路徑后才終止)。 這種方法的最壞情況代價是與敵人數量無關。

有一個CodeReview論壇。 首先,您可能會忘記浮點並使用整數算術,例如使用距離的平方。

Math.signum(originX - 1) == 1 ⇔
⇔ originX - 1 > 0 ⇔
⇔ originX > 1

如果最多8個鄰居,則保留8個容量:

List<DijkstraNode> neighbours = new ArrayList<DijkstraNode>(8);

您認為該算法更具決定性是對的,但這太好了以至於無法評論。

假設您有一個正常的游戲循環,那么尋路通常需要很長時間進行一次更新迭代。 我認為在單獨的線程中執行此操作的解決方案是一個不錯的選擇。 許多游戲都遇到此問題,並且您不希望在計算尋路時角色保持靜止。 您可以通過計算粗略的路徑(可以在一次更新迭代中計算出)來解決此問題,如果運氣好的話,您的角色將開始朝着大致正確的方向行走,然后在完全計算出正確的路徑后就選擇了正確的路徑。 有時,他們會在錯誤的方向上走幾幀,然后轉身以獲取完整計算出的路徑。

如果無法使用線程解決方案,則可以在框架更新內執行Dijkstra計算的一部分,然后暫停,繪制並計算下一個更新框架中的下一部分。

您可以做很多小事情,這些事情更多的是游戲設計而不是加速算法。 一個這樣的例子就是控制敵人多久找到一條道路。 例如,如果您正在做一個簡單的塔防游戲,只要敵人是靜態的,就可以讓他們確定一次從它們的生成點到終點的路徑。 如果地圖繼續更改,則可以展開尋路,以便每幀只有幾個敵人在尋找路徑。 而不是每幀為每個敵人都調用computePaths,而是一次以3或4的塊為單位執行此操作,具體取決於您運行它的速度。

如果您只想堅持優化算法,建議您研究A *(A Star)算法。 它與Dijkstra的相似,但以我的經驗來看,它在大多數游戲中似乎要好得多。 我已經將其用於Android和Flash上​​的幾款游戲,並且能夠使用100%的敵人使用上述方法動態地找到新路徑,從而保持穩定的40fps。

如果您有興趣,我可以更深入地介紹一些代碼示例

一般來說,在UI線程上進行復雜的計算是一個壞主意。 您總是可以對代碼進行更多優化,但是在速度較慢的設備上會降低性能。 解決方案應該是將計算卸載到AsyncTask 我認為,您的最佳解決方案是每隔X毫秒在AsyncTask上進行一次計算,並在主線程中使用此數據,直到更新到來為止。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM