pawns and basic pathfinding

pull/1/head
Valerie 2022-05-19 06:27:48 -04:00
parent 5dd6d8579b
commit 8e1f91d947
10 changed files with 345 additions and 32 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -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();
}
}

View File

@ -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(

View File

@ -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> T get(Class<T> clazz) {
return this.scene.get(clazz);
}
@Override

View File

@ -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;
}
}

View File

@ -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<Node> 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;
}
}
}

View File

@ -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<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) {
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<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;
}
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();
}
}

View File

@ -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;
}
}

View File

@ -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<IRenderable> renderables = new ArrayList<IRenderable>();
// specific
private GameObject terrain;
public <T> T get(Class<T> 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

View File

@ -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<Character, Sprite> charset = new HashMap<Character, Sprite>();
charset.put('A', new Sprite(atlas, new Vector4i( 0, 0, 8, 16)));