背景 - 我目前正在尝试在我的游戏中实现 A* 算法。我之前已经在标准网格风格游戏中做到了这一点,并设法让它工作,但由于某种原因,我在这里只能获得部分成功。我正在尝试将其实现到等距 2D 游戏中。我已经将所有内容从笛卡尔坐标转换为网格坐标,因此它可以与网格系统一起使用。
行为 - 最初,它有效。您可以一次毫无问题地移开约 6-8 个方块。小动作接连奏效。然而,如果超过这个数量,一切都会冻结。我已经确定这绝对是导致崩溃的路径查找。我认为该算法出于某种原因会出错,并偏离轨道,确定正确的路径成本太高。这很奇怪,因为我只使用 4 个相邻点,并且计算成本应该非常小,并且不会导致冻结。这也不是一个小冻结,冻结>30秒(我还没有等足够长的时间来看看它是否真的完成,因为我认为它走错了方向,而且永远不会)
要考虑的事情 - 由于该游戏的本质是等距的,并且从笛卡尔到网格的转换(反之亦然),所有内容都是浮点数而不是整数。然而,例如,当检查角色移动后的位置时,它始终处于适当的整数网格参考中(因此不存在“im at 1.9,check at 2.9”行为。由于我进行舍入,它始终为 int。另外,节点不是预先定义的。当从当前节点创建子节点时,节点是“动态”定义的。这是因为这个系统与游戏无关,我只关心角色必须获得的坐标列表基本上以所需的路径返回。
这是 A* 算法:
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;
}}
这是我的节点类:
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 + "]";
}}
以下是导致崩溃的一些测试数据:
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);
}}
您的解决方案有两个缺陷。
这与您的问题没有直接关系,但可能会导致严重的副作用。 如here所述,由于精度问题,不应将浮点数与
==
进行比较。
Java中的HashSet使用对象的HashCode方法来与值进行比较。由于您没有为 Node 覆盖它,因此即使您的 equals() 方法认为它们相等,它也会给出两个不同的值:
public static void main(String[] args) {
final var a = new Node(1.0f, 1.0f);
final var b = new Node(1.0f, 1.0f);
System.out.println(a.equals(b));
// true
System.out.println(a.hashCode());
// 918221580
System.out.println(b.hashCode());
// 2055281021
}
因此,只需为 Node 正确实现 hashCode() 即可解决问题:
@Override
public int hashCode() {
return Objects.hash(this.getX(), this.getY());
}