refactor pathfinding

pull/1/head
Valerie 2022-05-20 01:46:26 -04:00
parent 557d0eb569
commit a35182e368
8 changed files with 205 additions and 145 deletions

View File

@ -1,10 +1,8 @@
package xyz.valnet.hadean;
import xyz.valnet.engine.graphics.Drawing;
import xyz.valnet.engine.graphics.Sprite;
import xyz.valnet.engine.math.Vector4f;
import xyz.valnet.hadean.gameobjects.Camera;
import xyz.valnet.hadean.gameobjects.Terrain;
import xyz.valnet.hadean.util.Assets;
// TODO make these tiles REAL gameobjects...
@ -24,7 +22,6 @@ public class Tile {
public void render(Camera camera) {
Assets.flat.pushColor(isWalkable() ? color : new Vector4f(0.1f, 0.1f, 0.1f, 1f));
camera.draw(sprite, x, y);
// Drawing.drawSprite(sprite, Terrain.left + x * Terrain.TILE_SIZE, Terrain.top + y * Terrain.TILE_SIZE);
Assets.flat.popColor();
}

View File

@ -8,7 +8,7 @@ import xyz.valnet.hadean.scenes.GameScene;
public class Camera extends GameObject {
private int tileWidth = 24;
private int tileWidth = 16;
// TODO link these in some way to the real resolution.
private int screenWidth = 1024, screenHeight = 576;

View File

@ -6,13 +6,14 @@ import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glVertex2f;
import static xyz.valnet.engine.util.Math.lerp;
import java.util.Stack;
import xyz.valnet.engine.graphics.Drawing;
import xyz.valnet.engine.math.Vector2f;
import xyz.valnet.engine.scenegraph.GameObject;
import xyz.valnet.hadean.Tile;
import xyz.valnet.hadean.pathfinding.AStarPathfinder;
import xyz.valnet.hadean.pathfinding.IPathfinder;
import xyz.valnet.hadean.pathfinding.Node;
import xyz.valnet.hadean.pathfinding.Path;
import xyz.valnet.hadean.scenes.GameScene;
import xyz.valnet.hadean.util.Assets;
@ -23,12 +24,13 @@ public class Pawn extends GameObject {
private float counter = 0;
private Stack<Node> path;
private Path path;
private final float speed = 50f;
private Camera camera;
private Terrain terrain;
private IPathfinder pathfinder;
public Pawn(GameScene scene) {
super(scene);
@ -38,6 +40,7 @@ public class Pawn extends GameObject {
public void start() {
camera = get(Camera.class);
terrain = get(Terrain.class);
pathfinder = new AStarPathfinder(terrain);
}
@Override
@ -45,7 +48,7 @@ public class Pawn extends GameObject {
Drawing.setLayer(0.5f);
if(path != null && path.size() > 0) {
if(path != null && !path.isComplete()) {
Node next = path.peek();
float t = counter / speed;
camera.draw(Assets.pawn, lerp(x - 0.5f, next.x, t), lerp(y - 0.5f, next.y, t));
@ -80,7 +83,7 @@ public class Pawn extends GameObject {
@Override
public void tick(float dTime) {
if(path != null && !path.empty()) move();
if(path != null && !path.isComplete()) move();
else newPath();
}
@ -102,7 +105,7 @@ public class Pawn extends GameObject {
int idy = (int)Math.floor(dy);
// try to make a new path.
path = get(Terrain.class).getPath(ix, iy, idx, idy);
path = pathfinder.getPath(ix, iy, idx, idy);
}
private void move() {

View File

@ -1,18 +1,13 @@
package xyz.valnet.hadean.gameobjects;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Stack;
import xyz.valnet.engine.graphics.Drawing;
import xyz.valnet.engine.scenegraph.GameObject;
import xyz.valnet.hadean.Tile;
import xyz.valnet.hadean.pathfinding.Node;
import xyz.valnet.hadean.pathfinding.IPathable;
import xyz.valnet.hadean.scenes.GameScene;
// TODO SPLIT PATHABLES. | implements IPathable, the thing that has callbacks for interfacing with a pathfinder.
public class Terrain extends GameObject {
public class Terrain extends GameObject implements IPathable {
public static final int WORLD_SIZE = 24;
public static final int TILE_SIZE = 8;
@ -42,134 +37,6 @@ public class Terrain extends GameObject {
return tiles[x][y];
}
private Node findNodeInList(List<Node> nodes, int x, int y) {
for(Node node : nodes) {
if(node.x == x && node.y == y) return node;
}
return null;
}
private Node getPathfindingNode(int x, int y, List<Node> open, List<Node> closed, Node parent, int dstX, int dstY) {
// TODO this isnt necessarily the BOUNDS so... think about that.
if(x < 0 || y < 0 || x >= WORLD_SIZE || y >= WORLD_SIZE) {
return null;
}
Node node = findNodeInList(open, x, y);
if(node == null) {
node = findNodeInList(closed, x, y);
}
if(node == null) {
node = new Node();
node.x = x;
node.y = y;
node.from = parent;
node.g = 0;
if(parent != null) {
node.g += parent.g;
}
if(node.from == null) {
node.g = 0;
} else {
node.g += (int)(Math.round(10 * Math.sqrt(Math.pow(node.from.x - x, 2) + Math.pow(node.from.y - y, 2))));
}
node.h = (int)(Math.round(10 * Math.sqrt(Math.pow(dstX - x, 2) + Math.pow(dstY - y, 2))));
}
return node;
}
private Node[] getNeighbors(Node base, List<Node> open, List<Node> closed, int dstX, int dstY) {
Node[] neighbors = new Node[8];
neighbors[0] = getPathfindingNode(base.x - 1, base.y - 1, open, closed, base, dstX, dstY);
neighbors[1] = getPathfindingNode(base.x, base.y - 1, open, closed, base, dstX, dstY);
neighbors[2] = getPathfindingNode(base.x + 1, base.y - 1, open, closed, base, dstX, dstY);
neighbors[3] = getPathfindingNode(base.x - 1, base.y, open, closed, base, dstX, dstY);
neighbors[4] = getPathfindingNode(base.x + 1, base.y, open, closed, base, dstX, dstY);
neighbors[5] = getPathfindingNode(base.x - 1, base.y + 1, open, closed, base, dstX, dstY);
neighbors[6] = getPathfindingNode(base.x, base.y + 1, open, closed, base, dstX, dstY);
neighbors[7] = getPathfindingNode(base.x + 1, base.y + 1, open, closed, base, dstX, dstY);
return neighbors;
}
public Stack<Node> getPath(int x1, int y1, int x2, int y2) {
List<Node> open = new ArrayList<Node>();
List<Node> closed = new ArrayList<Node>();
if(getTile(x2, y2).isWalkable() == false) return null;
open.add(getPathfindingNode(x1, y1, open, closed, null, x2, y2));
while (open.size() != 0) {
open.sort(new Comparator<Node>() {
@Override
public int compare(Node a, Node b) {
int aCost = a.getCost();
int bCost = b.getCost();
if(aCost > bCost) {
return 1;
} else if (aCost < bCost) {
return -1;
} else if (a.h > b.h) {
return 1;
} else if (a.h < b.h) {
return -1;
} else {
return 0;
}
}
});
Node current = open.get(0);
open.remove(current);
closed.add(current);
if(current.x == x2 && current.y == y2) {
Stack<Node> path = new Stack<Node>();
Node n = current;
while(n != null) {
path.push(n);
n = n.from;
}
path.pop();
return path;
}
Node[] neighbors = getNeighbors(current, open, closed, x2, y2);
for(Node node : neighbors) {
if(node == null) continue;
if(closed.contains(node)) continue;
if(!getTile(node.x, node.y).isWalkable()) continue;
if(open.contains(node)) {
int newGCost = current.g + (int)(Math.round(10 * Math.sqrt(Math.pow(node.x - current.x, 2) + Math.pow(node.y - current.y, 2))));
if(node.g > newGCost) {
if(closed.contains(node)) {
closed.remove(node);
}
if(!open.contains(node)) {
open.add(node);
}
node.g = newGCost;
node.from = current;
}
} else {
open.add(node);
}
}
}
// i guess theres no path?!
return null;
}
@Override
public void render() {
// left = 400 - (WORLD_SIZE * TILE_SIZE / 2);
@ -183,4 +50,14 @@ public class Terrain extends GameObject {
}
}
@Override
public boolean isWalkable(int x, int y, int fromX, int fromY) {
return getTile(x, y).isWalkable();
}
@Override
public boolean isInBounds(int x, int y) {
return x < 0 || y < 0 || x >= WORLD_SIZE || y >= WORLD_SIZE;
}
}

View File

@ -0,0 +1,143 @@
package xyz.valnet.hadean.pathfinding;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Stack;
public class AStarPathfinder implements IPathfinder {
private IPathable pathable;
public AStarPathfinder(IPathable pathable) {
this.pathable = pathable;
}
private Node findNodeInList(List<Node> nodes, int x, int y) {
for(Node node : nodes) {
if(node.x == x && node.y == y) return node;
}
return null;
}
private Node getPathfindingNode(int x, int y, List<Node> open, List<Node> closed, Node parent, int dstX, int dstY) {
// TODO this isnt necessarily the BOUNDS so... think about that.
if(pathable.isInBounds(x, y)) {
return null;
}
Node node = findNodeInList(open, x, y);
if(node == null) {
node = findNodeInList(closed, x, y);
}
if(node == null) {
node = new Node();
node.x = x;
node.y = y;
node.from = parent;
node.g = 0;
if(parent != null) {
node.g += parent.g;
}
if(node.from == null) {
node.g = 0;
} else {
node.g += (int)(Math.round(10 * Math.sqrt(Math.pow(node.from.x - x, 2) + Math.pow(node.from.y - y, 2))));
}
node.h = (int)(Math.round(10 * Math.sqrt(Math.pow(dstX - x, 2) + Math.pow(dstY - y, 2))));
}
return node;
}
private Node[] getNeighbors(Node base, List<Node> open, List<Node> closed, int dstX, int dstY) {
Node[] neighbors = new Node[8];
neighbors[0] = getPathfindingNode(base.x - 1, base.y - 1, open, closed, base, dstX, dstY);
neighbors[1] = getPathfindingNode(base.x, base.y - 1, open, closed, base, dstX, dstY);
neighbors[2] = getPathfindingNode(base.x + 1, base.y - 1, open, closed, base, dstX, dstY);
neighbors[3] = getPathfindingNode(base.x - 1, base.y, open, closed, base, dstX, dstY);
neighbors[4] = getPathfindingNode(base.x + 1, base.y, open, closed, base, dstX, dstY);
neighbors[5] = getPathfindingNode(base.x - 1, base.y + 1, open, closed, base, dstX, dstY);
neighbors[6] = getPathfindingNode(base.x, base.y + 1, open, closed, base, dstX, dstY);
neighbors[7] = getPathfindingNode(base.x + 1, base.y + 1, open, closed, base, dstX, dstY);
return neighbors;
}
public Path getPath(int x1, int y1, int x2, int y2) {
List<Node> open = new ArrayList<Node>();
List<Node> closed = new ArrayList<Node>();
if(!pathable.isWalkable(x2, y2, 0, 0)) return null;
open.add(getPathfindingNode(x1, y1, open, closed, null, x2, y2));
while (open.size() != 0) {
open.sort(new Comparator<Node>() {
@Override
public int compare(Node a, Node b) {
int aCost = a.getCost();
int bCost = b.getCost();
if(aCost > bCost) {
return 1;
} else if (aCost < bCost) {
return -1;
} else if (a.h > b.h) {
return 1;
} else if (a.h < b.h) {
return -1;
} else {
return 0;
}
}
});
Node current = open.get(0);
open.remove(current);
closed.add(current);
if(current.x == x2 && current.y == y2) {
Stack<Node> path = new Stack<Node>();
Node n = current;
while(n != null) {
path.push(n);
n = n.from;
}
path.pop();
return new Path(path);
}
Node[] neighbors = getNeighbors(current, open, closed, x2, y2);
for(Node node : neighbors) {
if(node == null) continue;
if(closed.contains(node)) continue;
if(!pathable.isWalkable(node.x, node.y, 0, 0)) continue;
if(open.contains(node)) {
int newGCost = current.g + (int)(Math.round(10 * Math.sqrt(Math.pow(node.x - current.x, 2) + Math.pow(node.y - current.y, 2))));
if(node.g > newGCost) {
if(closed.contains(node)) {
closed.remove(node);
}
if(!open.contains(node)) {
open.add(node);
}
node.g = newGCost;
node.from = current;
}
} else {
open.add(node);
}
}
}
// i guess theres no path?!
return null;
}
}

View File

@ -0,0 +1,6 @@
package xyz.valnet.hadean.pathfinding;
public interface IPathable {
public boolean isWalkable(int x, int y, int fromX, int fromY);
public boolean isInBounds(int x, int y);
}

View File

@ -0,0 +1,5 @@
package xyz.valnet.hadean.pathfinding;
public interface IPathfinder {
public Path getPath(int x1, int y1, int x2, int y2);
}

View File

@ -0,0 +1,29 @@
package xyz.valnet.hadean.pathfinding;
import java.util.Iterator;
import java.util.Stack;
public class Path implements Iterable<Node> {
private Stack<Node> nodes;
public Path(Stack<Node> nodes) {
this.nodes = nodes;
}
public Node peek() {
return nodes.peek();
}
public Node pop() {
return nodes.pop();
}
public boolean isComplete() {
return nodes.isEmpty();
}
@Override
public Iterator<Node> iterator() {
return nodes.iterator();
}
}