You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java实现A*路径规划算法陷入无限循环的问题排查与解决

斜视角游戏A*寻路长距离冻结问题排查与解决

背景

正在2D斜视角(isometric)游戏中实现A*路径规划算法,此前在标准网格游戏中已成功实现,但本次仅部分成功。已完成笛卡尔坐标与网格坐标的转换,当前基于网格系统开发。

问题现象

短距离移动(约6-8个格子)或连续小步移动均正常,但长距离移动时程序完全冻结,经确认是寻路算法导致。推测算法错误判断路径代价,偏离正确路径,冻结时长超过30秒(未等待至是否自行结束)。当前仅采用4方向邻接节点,理论计算代价应很小,不应引发冻结。

注意事项

因斜视角游戏特性,坐标采用float类型,但通过取整处理,角色移动后的位置始终对应整数网格坐标;节点并非预定义,而是从当前节点动态生成子节点,仅需返回角色移动的坐标列表作为路径。

相关代码

PathFinder类

public class PathFinder {

private Node nodeStart;
private Node nodeGoal;
private float cost = 10f;


Set<Node> open = new HashSet<>();
Set<Node> closed = new HashSet<>();

public PathFinder(Node start, Node goal){
    this.nodeStart = start;
    this.nodeGoal = goal;
    open.add(nodeStart);
}

public List<Node> findPath(){

    while (!open.isEmpty()){
        Node currentNode = open.stream().min(Comparator.comparing(Node::getF)).get();
        open.remove(currentNode);
        closed.add(currentNode);
        if(currentNode.equals(nodeGoal)){
            return reversePath(currentNode);
        }
        else{
            for(Node child : getAdjacents(currentNode)){
                if(!child.isBlock() && !closed.contains(child)) {
                    child.setNodeData(currentNode, cost);
                    open.add(child);
                }
                else{
                    boolean changed = child.checkBetterPath(currentNode, cost);
                    if (changed) {
                        // Remove and Add the changed node, so that the PriorityQueue can sort again its
                        // contents with the modified "finalCost" value of the modified node
                        open.remove(child);
                        open.add(child);
                    }
                }
            };
        }

    }
    return new ArrayList<>();
}

private List<Node> getAdjacents(Node currentNode){
    ArrayList<Node> children =  new ArrayList<>();

    children.add(new Node(currentNode.getX()+1, currentNode.getY(), currentNode, nodeGoal));
    children.add(new Node(currentNode.getX()-1, currentNode.getY(), currentNode, nodeGoal));
    children.add(new Node(currentNode.getX(), currentNode.getY() - 1, currentNode, nodeGoal));
    children.add(new Node(currentNode.getX(), currentNode.getY() + 1, currentNode, nodeGoal));

    return children;
}

public List<Node> reversePath(Node goal){
    List<Node> finalPath = new ArrayList<>();
    Node tmp;
    Node current;
    current = goal;
    while(current.getParent() != null){
        finalPath.add(0, current);
        current = current.getParent();
    }
    return finalPath;
}}

Node类

public class Node {

private boolean isBlock;
private Node parent;
private float x;
private float y;

private float g;
private float f;
private float h;

Node finalNode;

public Node(float x, float y){
    this.x = x;
    this.y = y;
}
public Node(float x, float y, Node parent, Node finalNode){
    this.x = x;
    this.y = y;
    this.parent = parent;
    this.finalNode = finalNode;
    calculateHeuristic(finalNode);
}

public void calculateHeuristic(Node finalNode) {
    this.h = Math.abs(finalNode.getX() - getX()) + Math.abs(finalNode.getY() - getY());
}
public void setNodeData(Node currentNode, float cost) {
    float gCost = currentNode.getG() + cost;
    setParent(currentNode);
    setG(gCost);
    calculateFinalCost();
}

public boolean checkBetterPath(Node currentNode, float cost) {
    float gCost = currentNode.getG() + cost;
    if (gCost < getG()) {
        setNodeData(currentNode, cost);
        return true;
    }
    return false;
}

private void calculateFinalCost() {
    float finalCost = getG() + getH();
    setF(finalCost);
}

public float getX() {
    return x;
}

public float getY() {
    return y;
}

public Node getParent() {
    return parent;
}

public boolean isBlock() {
    return isBlock;
}

public void setBlock(boolean block) {
    isBlock = block;
}

public void setParent(Node parent) {
    this.parent = parent;
}

public void setX(float x) {
    this.x = x;
}

public void setY(float y) {
    this.y = y;
}

public float getG() {
    return g;
}

public void setG(float g) {
    this.g = g;
}

public float getF() {
    return f;
}

public void setF(float f) {
    this.f = f;
}

public float getH() {
    return h;
}

public void setH(float h) {
    this.h = h;
}
@Override
public boolean equals(Object arg0) {
    Node other = (Node) arg0;
    return this.getX() == other.getX() && this.getY() == other.getY();
}

@Override
public String toString() {
    return "Node [row=" + x + ", col=" + y + "]";
}}

Main类

public class Main {
public static void main(String[] args) {
    PathFinder pathFinder = new PathFinder(new Node(1,1), new Node(14,1));

    List<Node> path = pathFinder.findPath();
    System.out.println(path);
}}

解决方案

最初通过自定义移除函数解决了部分问题,但超远距离(如100+格子)仍会冻结。经调试发现问题根源在于private float cost = 10f;:实际每次仅移动1格,该设置过度放大了路径代价权重,削弱了启发式值的引导作用。将其修改为private float cost = 1f;后,长距离寻路的冻结问题彻底解决。


内容的提问来源于stack exchange,提问作者user2592518

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 00:09:53