diff --git a/res/textures.png b/res/textures.png index 052e2df..8259941 100644 Binary files a/res/textures.png and b/res/textures.png differ diff --git a/src/main/java/xyz/valnet/engine/graphics/Drawing.java b/src/main/java/xyz/valnet/engine/graphics/Drawing.java index 77ff94b..752e87b 100644 --- a/src/main/java/xyz/valnet/engine/graphics/Drawing.java +++ b/src/main/java/xyz/valnet/engine/graphics/Drawing.java @@ -12,6 +12,12 @@ public class Drawing { drawSprite(sprite, x, y, sprite.width, sprite.height); } + private static float layer = 0f; + + public static void setLayer(float layer) { + Drawing.layer = layer; + } + public static void drawSprite(Sprite sprite, int x, int y, int width, int height) { // lazy texture binding if(bound != sprite.atlas) { @@ -21,16 +27,16 @@ public class Drawing { glBegin(GL_QUADS); glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x, sprite.sourceBoxUV.y); - glVertex2f(x, y); + glVertex3f(x, y, layer); glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x + sprite.sourceBoxUV.z, sprite.sourceBoxUV.y); - glVertex2f(x + width, y); + glVertex3f(x + width, y, layer); glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x + sprite.sourceBoxUV.z, sprite.sourceBoxUV.y + sprite.sourceBoxUV.w); - glVertex2f(x + width, y + height); + glVertex3f(x + width, y + height, layer); glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x, sprite.sourceBoxUV.y + sprite.sourceBoxUV.w); - glVertex2f(x, y + height); + glVertex3f(x, y + height, layer); glEnd(); } } \ No newline at end of file diff --git a/src/main/java/xyz/valnet/engine/graphics/Sprite.java b/src/main/java/xyz/valnet/engine/graphics/Sprite.java index 5d8cf36..eea101c 100644 --- a/src/main/java/xyz/valnet/engine/graphics/Sprite.java +++ b/src/main/java/xyz/valnet/engine/graphics/Sprite.java @@ -10,6 +10,10 @@ public class Sprite { public final int width; public final int height; + public Sprite(Texture tex, int x, int y, int w, int h) { + this(tex, new Vector4i(x, y, w, h)); + } + public Sprite(Texture tex, Vector4i box) { sourceBoxPixels = box; sourceBoxUV = new Vector4f( diff --git a/src/main/java/xyz/valnet/engine/scenegraph/GameObject.java b/src/main/java/xyz/valnet/engine/scenegraph/GameObject.java index ea51b4c..0c548f0 100644 --- a/src/main/java/xyz/valnet/engine/scenegraph/GameObject.java +++ b/src/main/java/xyz/valnet/engine/scenegraph/GameObject.java @@ -1,10 +1,16 @@ package xyz.valnet.engine.scenegraph; -public class GameObject implements IRenderable, ITickable { - // private IScene scene; +import xyz.valnet.hadean.scenes.GameScene; - public GameObject(IScene scene) { - // this.scene = scene; +public class GameObject implements IRenderable, ITickable { + private final GameScene scene; + + public GameObject(GameScene scene) { + this.scene = scene; + } + + protected T get(Class clazz) { + return this.scene.get(clazz); } @Override diff --git a/src/main/java/xyz/valnet/hadean/Tile.java b/src/main/java/xyz/valnet/hadean/Tile.java new file mode 100644 index 0000000..cd56361 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/Tile.java @@ -0,0 +1,31 @@ +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.Terrain; +import xyz.valnet.hadean.util.Assets; + +public class Tile { + private final int x, y; + private final Vector4f color = new Vector4f((float) Math.random() * 0.1f, 0.4f + (float) Math.random() * 0.15f, (float) Math.random() * 0.05f, 1f); + private final Sprite sprite = Assets.defaultTerrain[(int)Math.floor(Math.random() * Assets.defaultTerrain.length)]; + private final boolean obstacle; + + public Tile(int x, int y) { + float distanceFromOrigin = 0.9f - (float)Math.sqrt(x * x + y * y) / 42; + this.x = x; + this.y = y; + this.obstacle = Math.random() > 0.8f; + } + + public void render() { + Assets.flat.pushColor(isWalkable() ? color : new Vector4f(0.1f, 0.1f, 0.1f, 1f)); + Drawing.drawSprite(sprite, Terrain.left + x * Terrain.TILE_SIZE, Terrain.top + y * Terrain.TILE_SIZE); + Assets.flat.popColor(); + } + + public boolean isWalkable() { + return !obstacle; + } +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Pawn.java b/src/main/java/xyz/valnet/hadean/gameobjects/Pawn.java new file mode 100644 index 0000000..131c223 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Pawn.java @@ -0,0 +1,111 @@ +package xyz.valnet.hadean.gameobjects; + +import static xyz.valnet.engine.util.Math.lerp; + +import java.util.Stack; + +import xyz.valnet.engine.graphics.Drawing; +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.engine.scenegraph.GameObject; +import xyz.valnet.hadean.pathfinding.Node; +import xyz.valnet.hadean.scenes.GameScene; +import xyz.valnet.hadean.util.Assets; + +import static org.lwjgl.opengl.GL20.*; +// import org.lwjgl.opengl.GL20; + +public class Pawn extends GameObject { + + private float x, y; + private float dx, dy; + + private float counter = 0; + + private Stack path; + + private final float speed = 20f; + + public Pawn(GameScene scene) { + super(scene); + } + + @Override + public void render() { + + Drawing.setLayer(0.15f); + + if(path != null && path.size() > 0) { + float cx = (int)(Terrain.left + (x - 0.5f) * Terrain.TILE_SIZE); + float cy = (int)(Terrain.top + (y - 0.5f) * Terrain.TILE_SIZE); + + Node n = path.peek(); + + float nx = Terrain.left + n.x * Terrain.TILE_SIZE; + float ny = Terrain.top + n.y * Terrain.TILE_SIZE; + + System.out.println("" + n.x + " " + n.y); + + Drawing.drawSprite(Assets.pawn, (int)lerp(cx, nx, counter / speed), (int)lerp(cy, ny, counter / speed)); + + if(path != null) { + for(Node node : path) { + glBegin(GL_LINES); + if(node.from == null) { + glVertex2f(Terrain.left + x * Terrain.TILE_SIZE, Terrain.top + y * Terrain.TILE_SIZE); + } else { + glVertex2f(Terrain.left + (node.from.x + 0.5f) * Terrain.TILE_SIZE, Terrain.top + (node.from.y + 0.5f) * Terrain.TILE_SIZE); + } + glVertex2f(Terrain.left + (node.x + 0.5f) * Terrain.TILE_SIZE, Terrain.top + (node.y + 0.5f) * Terrain.TILE_SIZE); + glEnd(); + } + } + } else { + int nx = (int)(Terrain.left + (x - 0.5f) * Terrain.TILE_SIZE); + int ny = (int)(Terrain.top + (y - 0.5f) * Terrain.TILE_SIZE); + + Drawing.drawSprite(Assets.pawn, nx, ny); + } + + // Drawing.setLayer(0.1f); + // Assets.flat.pushColor(Vector4f.black); + // Drawing.drawSprite(Assets.pawn, (int)(Terrain.left + dx * Terrain.TILE_SIZE) - Assets.pawn.width / 2, (int)(Terrain.top + dy * Terrain.TILE_SIZE) - Assets.pawn.height / 2); + + // Assets.flat.swapColor(new Vector4f(1, 0, 0, 1)); + // Drawing.setLayer(0.05f); + + // Assets.flat.popColor(); + } + + @Override + public void tick(float dTime) { + counter ++; + if(counter >= speed) action(); + } + + + + private void action() { + if(path == null || path.empty()) { + dx = 0.5f + (float)Math.floor(Math.random() * Terrain.WORLD_SIZE); + dy = 0.5f + (float)Math.floor(Math.random() * Terrain.WORLD_SIZE); + + int ix = (int)Math.floor(x); + int iy = (int)Math.floor(y); + + int idx = (int)Math.floor(dx); + int idy = (int)Math.floor(dy); + + path = get(Terrain.class).getPath(ix, iy, idx, idy); + if(path != null) { + counter = 0; + } + } else { + Node n = path.pop(); + x = n.x + 0.5f; + y = n.y + 0.5f; + counter = 0; + } + + } + +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java b/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java index fc81051..71198c8 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java @@ -1,47 +1,178 @@ package xyz.valnet.hadean.gameobjects; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; + import xyz.valnet.engine.graphics.Drawing; -import xyz.valnet.engine.graphics.Sprite; -import xyz.valnet.engine.math.Vector4f; import xyz.valnet.engine.scenegraph.GameObject; import xyz.valnet.engine.scenegraph.IScene; -import xyz.valnet.hadean.util.Assets; +import xyz.valnet.hadean.Tile; +import xyz.valnet.hadean.pathfinding.Node; +import xyz.valnet.hadean.scenes.GameScene; +// implements IPathable, the thing that has callbacks for interfacing with a pathfinder. public class Terrain extends GameObject { - private final int WORLD_SIZE = 30; - private final int TILE_SIZE = 8; + public static final int WORLD_SIZE = 40; + public static final int TILE_SIZE = 8; - private int[][] variation = new int[WORLD_SIZE][WORLD_SIZE]; - private Vector4f[][] colorVariations = new Vector4f[WORLD_SIZE][WORLD_SIZE]; + public static int left, top; - public Terrain(IScene scene) { + private Tile[][] tiles = new Tile[WORLD_SIZE][WORLD_SIZE]; + + public Terrain(GameScene scene) { super(scene); for (int i = 0; i < WORLD_SIZE; i++) { for (int j = 0; j < WORLD_SIZE; j++) { - variation[i][j] = (int) Math.floor(Math.random() * 4); - colorVariations[i][j] = new Vector4f((float) Math.random() * 0.2f, 0.4f + (float) Math.random() * 0.2f, (float) Math.random() * 0.05f, 1f); + tiles[i][j] = new Tile(i, j); } } } + public Tile getTile(int x, int y) { + 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) { + if(x < 0 || y < 0 || x >= WORLD_SIZE || y >= WORLD_SIZE) { + // * out of bounds + 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; + } + + 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() { - int left = 400 - (WORLD_SIZE * TILE_SIZE / 2); - int top = 225 - (WORLD_SIZE * TILE_SIZE / 2); + left = 400 - (WORLD_SIZE * TILE_SIZE / 2); + top = 225 - (WORLD_SIZE * TILE_SIZE / 2); - - Sprite s; - Assets.flat.pushColor(Vector4f.one); + Drawing.setLayer(0f); for (int i = 0; i < WORLD_SIZE; i++) { for (int j = 0; j < WORLD_SIZE; j++) { - s = Assets.defaultTerrain[variation[i][j]]; - Assets.flat.swapColor(colorVariations[i][j]); - Drawing.drawSprite(s, left + i * s.width, top + j * s.height); + tiles[i][j].render(); } } - Assets.flat.popColor(); - } } diff --git a/src/main/java/xyz/valnet/hadean/pathfinding/Node.java b/src/main/java/xyz/valnet/hadean/pathfinding/Node.java new file mode 100644 index 0000000..b3463c5 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/pathfinding/Node.java @@ -0,0 +1,10 @@ +package xyz.valnet.hadean.pathfinding; + +public class Node { + public int x, y, g, h; + public Node from; + + public int getCost() { + return g + h; + } +} diff --git a/src/main/java/xyz/valnet/hadean/scenes/GameScene.java b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java index 74dcf8c..1331ed9 100644 --- a/src/main/java/xyz/valnet/hadean/scenes/GameScene.java +++ b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java @@ -5,6 +5,7 @@ import java.util.List; import xyz.valnet.engine.scenegraph.GameObject; import xyz.valnet.engine.scenegraph.IScene; +import xyz.valnet.hadean.gameobjects.Pawn; import xyz.valnet.hadean.gameobjects.Terrain; public class GameScene implements IScene { @@ -14,12 +15,20 @@ public class GameScene implements IScene { // private List renderables = new ArrayList(); // specific - private GameObject terrain; + + public T get(Class clazz) { + for(GameObject obj : objects) { + if(clazz.isInstance(obj)) { + return clazz.cast(obj); + } + } + return null; + } @Override public void render() { for(GameObject obj : objects) { - ((Terrain)obj).render(); + obj.render(); } } @@ -32,8 +41,10 @@ public class GameScene implements IScene { @Override public void enable() { - terrain = new Terrain(this); - objects.add(terrain); + objects.add(new Terrain(this)); + for(int i = 0; i < 3; i ++) { + objects.add(new Pawn(this)); + } } @Override diff --git a/src/main/java/xyz/valnet/hadean/util/Assets.java b/src/main/java/xyz/valnet/hadean/util/Assets.java index cd567d3..869389e 100644 --- a/src/main/java/xyz/valnet/hadean/util/Assets.java +++ b/src/main/java/xyz/valnet/hadean/util/Assets.java @@ -19,6 +19,7 @@ public class Assets { public static final Tile9 fireFrame; public static final Sprite[] defaultTerrain; + public static final Sprite pawn; public static final SimpleShader flat; @@ -34,6 +35,8 @@ public class Assets { new Sprite(atlas, new Vector4i(32, 64, 8, 8)) }; + pawn = new Sprite(atlas, 48, 88, 8, 8); + Map charset = new HashMap(); charset.put('A', new Sprite(atlas, new Vector4i( 0, 0, 8, 16)));