![](/img/trans.png)
[英]Will multiple solutions for speeding up Dijkstra lower performance?
[英]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.