加速Dijkstra

时间:2015-05-08 14:59:10

标签: java android performance algorithm dijkstra

您好我在基于平铺的游戏中为路径寻找编写了一个小型Dijkstra实现。问题是,如果有10个敌人使用这种算法来找到目标的最短路径(主要用于目前的巡逻),那么游戏就会变得非常迟钝。特别是因为游戏应该在Android智能手机上运行。 我试着用什么来加速整个过程:

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;
}


}

5 个答案:

答案 0 :(得分:0)

由于你说它是一个基于图块的游戏,我认为有效路径可以在两个方向上穿过,成本与方向无关(或者足够接近独立)。在这种情况下,如果你从每个敌人的位置开始使用Dijkstra,你所做的工作比你需要做的还要多。

相反,从目标开始,找到同一Dijkstra运行中所有敌人的最短路径(即在找到每个敌人的路径后终止)。这种方法的最坏情况成本与敌人的数量无关。

答案 1 :(得分:0)

有一个CodeReview论坛。作为初学者,您可以忘记浮点并使用整数运算,例如使用距离的平方。

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

如果最多8个邻居,则保留容量8:

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

你认为算法更具决定性是正确的,但这太好了不能发表评论。

答案 2 :(得分:0)

假设你有一个正常的游戏循环,那么路径寻找通常需要一次更新迭代。我认为你在一个单独的线程中做到这一点的解决方案是一个很好的解决方案。许多游戏都面临这个问题,你不希望你的角色在计算寻路时保持不动。您可以通过计算原始路径(可以在一次更新迭代中计算)来解决这个问题,只需要一点点运气,您的角色将开始沿着一个良好的方向行走,然后在完全计算后获取正确的路径。有时候他们会朝错误的方向走几帧,然后转身去拿完全计算的路径。

如果无法使线程解决方案正常工作,您可以在帧更新中执行Dijkstra计算的一部分,然后暂停,绘制并计算下一个更新帧中的下一部分。

答案 3 :(得分:0)

在游戏设计方面,你可以做很多小事,而不是加快算法速度。一个这样的例子是控制敌人找到路径的频率。例如,如果你正在做一个简单的塔防游戏,你可以让敌人从它们的生成点到终点确定一条路径,只要它们是静态的。如果地图继续改变,你可以展开寻路,这样只有几个敌人在每一帧都找到一条路径。不是每帧都为每个敌人调用computePaths,而是每次以3或4块为单位进行,取决于你需要多快运行它。

如果您只想坚持优化算法,我建议您查看A *(A Star)算法。它与Dijkstra相似,但根据我的经验,它似乎对大多数游戏来说效果更好。我已经在Android和Flash中使用它进行了几场游戏,并且能够使用上述方法动态地找到新路径,与100多个敌人保持一致的40fps。

如果您有兴趣,我可以深入了解并发布一些代码示例

答案 4 :(得分:0)

一般来说,在UI线程上进行复杂计算是个坏主意。您可以随时优化代码,但在速度较慢的设备上,性能会有所下降。 解决方案应该是将计算卸载到 AsyncTask。我认为最佳解决方案是在AsyncTask上每隔X毫秒进行一次计算,并在主线程中使用此数据,直到更新到达。