diff --git a/src/main/java/xyz/valnet/hadean/Tile.java b/src/main/java/xyz/valnet/hadean/Tile.java index 18a126a..325077d 100644 --- a/src/main/java/xyz/valnet/hadean/Tile.java +++ b/src/main/java/xyz/valnet/hadean/Tile.java @@ -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(); } diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Camera.java b/src/main/java/xyz/valnet/hadean/gameobjects/Camera.java index 91980a8..5a21512 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/Camera.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Camera.java @@ -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; diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Pawn.java b/src/main/java/xyz/valnet/hadean/gameobjects/Pawn.java index 9422de9..ea7487d 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/Pawn.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Pawn.java @@ -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 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() { diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java b/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java index 5f46511..16e9c4d 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java @@ -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 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 open, List 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 open, List 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 getPath(int x1, int y1, int x2, int y2) { - List open = new ArrayList(); - List closed = new ArrayList(); - - 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() { - @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 path = new Stack(); - - 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; + } + } diff --git a/src/main/java/xyz/valnet/hadean/pathfinding/AStarPathfinder.java b/src/main/java/xyz/valnet/hadean/pathfinding/AStarPathfinder.java new file mode 100644 index 0000000..0aef9d7 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/pathfinding/AStarPathfinder.java @@ -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 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 open, List 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 open, List 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 open = new ArrayList(); + List closed = new ArrayList(); + + 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() { + @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 path = new Stack(); + + 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; + } +} diff --git a/src/main/java/xyz/valnet/hadean/pathfinding/IPathable.java b/src/main/java/xyz/valnet/hadean/pathfinding/IPathable.java new file mode 100644 index 0000000..a6a6039 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/pathfinding/IPathable.java @@ -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); +} diff --git a/src/main/java/xyz/valnet/hadean/pathfinding/IPathfinder.java b/src/main/java/xyz/valnet/hadean/pathfinding/IPathfinder.java new file mode 100644 index 0000000..d18fd18 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/pathfinding/IPathfinder.java @@ -0,0 +1,5 @@ +package xyz.valnet.hadean.pathfinding; + +public interface IPathfinder { + public Path getPath(int x1, int y1, int x2, int y2); +} diff --git a/src/main/java/xyz/valnet/hadean/pathfinding/Path.java b/src/main/java/xyz/valnet/hadean/pathfinding/Path.java new file mode 100644 index 0000000..ad22018 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/pathfinding/Path.java @@ -0,0 +1,29 @@ +package xyz.valnet.hadean.pathfinding; + +import java.util.Iterator; +import java.util.Stack; + +public class Path implements Iterable { + private Stack nodes; + + public Path(Stack 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 iterator() { + return nodes.iterator(); + } +}