refactor pathfinding
parent
557d0eb569
commit
a35182e368
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package xyz.valnet.hadean.pathfinding;
|
||||
|
||||
public interface IPathfinder {
|
||||
public Path getPath(int x1, int y1, int x2, int y2);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue