Compare commits

...

4 Commits

Author SHA1 Message Date
c02ae0302c made hitboxed visible in debug mode (closed #2) 2025-12-12 21:47:17 +01:00
4276c24392 made path visible in debug mode 2025-12-12 21:41:04 +01:00
9484797ced fixed a pathfinding bug 2025-12-12 21:37:18 +01:00
4ab53ceab9 added path finding algorithm 2025-12-12 21:33:52 +01:00
6 changed files with 263 additions and 11 deletions

View File

@@ -0,0 +1,17 @@
package de.miaurizius.jgame2d.ai;
public class Node {
Node parent;
public int col, row;
int gCost, hCost, fCost;
boolean solid;
boolean open;
boolean checked;
public Node(int col, int row) {
this.col = col;
this.row = row;
}
}

View File

@@ -0,0 +1,146 @@
package de.miaurizius.jgame2d.ai;
import de.miaurizius.jgame2d.core.GamePanel;
import java.util.ArrayList;
public class PathFinder {
GamePanel panel;
Node[][] node;
ArrayList<Node> openList = new ArrayList<>();
public ArrayList<Node> pathList = new ArrayList<>();
Node startNode, goalNode, currentNode;
boolean goalReached;
int step;
public PathFinder(GamePanel panel) {
this.panel = panel;
instantiateNodes();
}
private void instantiateNodes() {
node = new Node[panel.maxWorldCol][panel.maxWorldRow];
int col = 0;
int row = 0;
while(col < panel.maxWorldCol && row < panel.maxWorldRow) {
node[col][row] = new Node(col, row);
col++;
if(col != panel.maxWorldCol) continue;
col = 0;
row++;
}
}
private void resetNodes() {
// RESET OPEN LIST
int col = 0, row = 0;
while(col < panel.maxWorldCol && row < panel.maxWorldRow) {
node[col][row].open = false;
node[col][row].checked = false;
node[col][row].solid = false;
col++;
if(col != panel.maxWorldCol) continue;
col = 0;
row++;
}
// RESET OTHER SETTINGS
openList.clear();
pathList.clear();
goalReached = false;
step = 0;
}
public void setNodes(int startCol, int startRow, int goalCol, int goalRow) {
resetNodes();
startNode = node[startCol][startRow];
currentNode = startNode;
goalNode = node[goalCol][goalRow];
openList.add(currentNode);
int col = 0, row = 0;
while(col < panel.maxWorldCol && row < panel.maxWorldRow) {
// SOLID NODES
int tileNum = panel.tileM.mapTileNum[panel.currentMap.getIndex()][col][row];
if(panel.tileM.tile[tileNum].collision) {
node[col][row].solid = true;
}
for(int i = 0; i < panel.iTile.length; i++) {
if(panel.iTile[panel.currentMap.getIndex()][i] != null && panel.iTile[panel.currentMap.getIndex()][i].destructible) {
int itCol = panel.iTile[panel.currentMap.getIndex()][i].worldX/panel.tileSize;
int itRow = panel.iTile[panel.currentMap.getIndex()][i].worldY/panel.tileSize;
node[itCol][itRow].solid = true;
}
}
// SET COST
getCost(node[col][row]);
col++;
if(col != panel.maxWorldCol) continue;
col = 0;
row++;
}
}
private void getCost(Node node) {
// G COST
int xD = Math.abs(node.col - startNode.col);
int yD = Math.abs(node.row - startNode.row);
node.gCost = xD + yD;
// H COST
xD = Math.abs(node.col - goalNode.col);
yD = Math.abs(node.row - goalNode.row);
node.hCost = xD + yD;
// F COST
node.fCost = node.gCost + node.hCost;
}
public boolean search() {
while(!goalReached && step < 500) {
int col = currentNode.col;
int row = currentNode.row;
currentNode.checked = true;
openList.remove(currentNode);
if(row - 1 >= 0) openNode(node[col][row-1]);
if(col - 1 >= 0) openNode(node[col-1][row]);
if(row + 1 < panel.maxWorldRow) openNode(node[col][row+1]);
if(col + 1 < panel.maxWorldCol) openNode(node[col+1][row]);
int bestNodeIndex = 0;
int bestNodeFCost = 999;
for(int i = 0; i < openList.size(); i++) {
if(openList.get(i).fCost < bestNodeFCost) {
bestNodeIndex = i;
bestNodeFCost = openList.get(i).fCost;
}
else if(openList.get(i).fCost == bestNodeFCost) if(openList.get(i).gCost < openList.get(bestNodeIndex).gCost) bestNodeIndex = i;
}
if(openList.isEmpty()) break;
currentNode = openList.get(bestNodeIndex);
if(currentNode == goalNode) {
goalReached = true;
trackPath();
}
step++;
}
return goalReached;
}
private void openNode(Node node) {
if(node.open || node.checked || node.solid) return;
node.open = true;
node.parent = currentNode;
openList.add(node);
}
private void trackPath() {
Node current = goalNode;
while(current != startNode) {
pathList.add(0, current);
current = current.parent;
}
}
}

View File

@@ -1,5 +1,6 @@
package de.miaurizius.jgame2d.core; package de.miaurizius.jgame2d.core;
import de.miaurizius.jgame2d.ai.PathFinder;
import de.miaurizius.jgame2d.core.enums.GameState; import de.miaurizius.jgame2d.core.enums.GameState;
import de.miaurizius.jgame2d.core.enums.Map; import de.miaurizius.jgame2d.core.enums.Map;
import de.miaurizius.jgame2d.core.handlers.*; import de.miaurizius.jgame2d.core.handlers.*;
@@ -33,7 +34,7 @@ public class GamePanel extends JPanel implements Runnable {
int fScreenWidth = screenWidth; int fScreenWidth = screenWidth;
int fScreenHeight = screenHeight; int fScreenHeight = screenHeight;
BufferedImage tempScreen; BufferedImage tempScreen;
Graphics2D fg2; public Graphics2D fg2;
// WORLD SETTINGS // WORLD SETTINGS
public final int maxWorldCol = 50; public final int maxWorldCol = 50;
@@ -54,6 +55,7 @@ public class GamePanel extends JPanel implements Runnable {
public Sound sfx = new Sound(); public Sound sfx = new Sound();
public Sound music = new Sound(); public Sound music = new Sound();
public Config config = new Config(this); public Config config = new Config(this);
public PathFinder pFinder = new PathFinder(this);
Thread gameThread; Thread gameThread;
// ENTITY AND OBJECT // ENTITY AND OBJECT

View File

@@ -40,6 +40,7 @@ public class Entity {
public boolean dying; public boolean dying;
public boolean hpBarOn; public boolean hpBarOn;
public boolean consumable; public boolean consumable;
public boolean onPath;
// COUNTER // COUNTER
public int spriteCounter; public int spriteCounter;
@@ -86,15 +87,7 @@ public class Entity {
// DEFAULT // DEFAULT
public void update() { public void update() {
setAction(); setAction();
collisionOn = false; checkCollision();
panel.collisionH.checkTile(this);
panel.collisionH.checkObject(this, false);
panel.collisionH.checkEntity(this, panel.npc[panel.currentMap.getIndex()]);
panel.collisionH.checkEntity(this, panel.monster[panel.currentMap.getIndex()]);
panel.collisionH.checkEntity(this, panel.iTile[panel.currentMap.getIndex()]);
boolean contactPlayer = panel.collisionH.checkPlayer(this);
if(this.type == EntityType.MONSTER && contactPlayer) damagePlayer(attack);
if(!collisionOn) { if(!collisionOn) {
switch (direction) { switch (direction) {
@@ -162,6 +155,11 @@ public class Entity {
changeOpacity(graphics2d, 1f); changeOpacity(graphics2d, 1f);
} }
if(panel.keyH.debug) {
graphics2d.setColor(new Color(255, 0, 0, 70));
graphics2d.fillRect(worldX - panel.player.worldX + panel.player.screenX, worldY - panel.player.worldY + panel.player.screenY, panel.tileSize, panel.tileSize);
}
} }
// INTERACTION // INTERACTION
@@ -207,6 +205,17 @@ public class Entity {
} //If entity is consumable } //If entity is consumable
public void checkDrop() { public void checkDrop() {
}
public void checkCollision() {
collisionOn = false;
panel.collisionH.checkTile(this);
panel.collisionH.checkObject(this, false);
panel.collisionH.checkEntity(this, panel.npc[panel.currentMap.getIndex()]);
panel.collisionH.checkEntity(this, panel.monster[panel.currentMap.getIndex()]);
panel.collisionH.checkEntity(this, panel.iTile[panel.currentMap.getIndex()]);
boolean contactPlayer = panel.collisionH.checkPlayer(this);
if(this.type == EntityType.MONSTER && contactPlayer) damagePlayer(attack);
} }
public void dropItem(Entity droppedItem) { public void dropItem(Entity droppedItem) {
for(int i = 0; i < panel.obj[panel.currentMap.getIndex()].length; i++) { for(int i = 0; i < panel.obj[panel.currentMap.getIndex()].length; i++) {
@@ -284,5 +293,46 @@ public class Entity {
public void changeOpacity(Graphics2D graphics2d, float opacity) { public void changeOpacity(Graphics2D graphics2d, float opacity) {
graphics2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)); graphics2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
} }
public void searchPath(int goalCol, int goalRow) {
int startCol = (worldX + solidArea.x) / panel.tileSize;
int startRow = (worldY + solidArea.y) / panel.tileSize;
panel.pFinder.setNodes(startCol, startRow, goalCol, goalRow);
if (panel.pFinder.search()) {
int nextCol = panel.pFinder.pathList.getFirst().col;
int nextRow = panel.pFinder.pathList.getFirst().row;
int enLeftX = worldX + solidArea.x;
int enTopY = worldY + solidArea.y;
int targetX = nextCol * panel.tileSize;
int targetY = nextRow * panel.tileSize;
// UP
if (nextRow < startRow) {
if (enLeftX < targetX) direction = Direction.RIGHT;
else if (enLeftX > targetX) direction = Direction.LEFT;
else direction = Direction.UP;
}
// DOWN
else if (nextRow > startRow) {
if (enLeftX < targetX) direction = Direction.RIGHT;
else if (enLeftX > targetX) direction = Direction.LEFT;
else direction = Direction.DOWN;
}
else if (nextCol < startCol) { // DOWN LEFT
if (enTopY < targetY) direction = Direction.DOWN;
else if (enTopY > targetY) direction = Direction.UP;
else direction = Direction.LEFT;
}
else if (nextCol > startCol) { // RIGHT
if (enTopY < targetY) direction = Direction.DOWN;
else if (enTopY > targetY) direction = Direction.UP;
else direction = Direction.RIGHT;
}
} else onPath = false;
}
} }

View File

@@ -18,9 +18,22 @@ public class OldManNPC extends Entity {
speed = 1; speed = 1;
getImage(); getImage();
setDialogue(); setDialogue();
solidArea.x = 8;
solidArea.y = 16;
solidAreaDefaultX = solidArea.x;
solidAreaDefaultY = solidArea.y;
solidArea.width = 30;
solidArea.height = 30;
} }
public void setAction() { public void setAction() {
if(onPath) {
searchPath(12, 9);
return;
}
actionLock++; actionLock++;
if(actionLock != 120) return; //lock action for x frames if(actionLock != 120) return; //lock action for x frames
Random rand = new Random(); Random rand = new Random();
@@ -30,6 +43,12 @@ public class OldManNPC extends Entity {
if(i > 50 && i <= 75) direction = Direction.LEFT; if(i > 50 && i <= 75) direction = Direction.LEFT;
if(i > 75) direction = Direction.RIGHT; if(i > 75) direction = Direction.RIGHT;
actionLock = 0; actionLock = 0;
}
@Override
public void speak() {
super.speak();
super.onPath = true;
} }
// SETTING THINGS UP // SETTING THINGS UP

View File

@@ -138,7 +138,14 @@ public class TileManager {
worldX - panel.tileSize < panel.player.worldX + panel.player.screenX && worldX - panel.tileSize < panel.player.worldX + panel.player.screenX &&
worldY + panel.tileSize > panel.player.worldY - panel.player.screenY && worldY + panel.tileSize > panel.player.worldY - panel.player.screenY &&
worldY - panel.tileSize < panel.player.worldY + panel.player.screenY worldY - panel.tileSize < panel.player.worldY + panel.player.screenY
) graphics2D.drawImage(tile[tileNum].image, screenX, screenY, null); ) {
graphics2D.drawImage(tile[tileNum].image, screenX, screenY, null);
// if(panel.keyH.debug && tile[tileNum].collision) {
// graphics2D.setColor(new Color(255, 0, 0, 70));
// graphics2D.fillRect(worldX - panel.player.worldX + panel.player.screenX, worldY - panel.player.worldY + panel.player.screenY, panel.tileSize, panel.tileSize);
// }
}
worldCol++; worldCol++;
@@ -147,6 +154,17 @@ public class TileManager {
worldRow++; worldRow++;
} }
} }
if(panel.keyH.debug) {
graphics2D.setColor(new Color(255, 0, 0, 70));
for(int i = 0; i < panel.pFinder.pathList.size(); i++) {
int worldX = panel.pFinder.pathList.get(i).col * panel.tileSize;
int worldY = panel.pFinder.pathList.get(i).row * panel.tileSize;
int screenX = worldX - panel.player.worldX + panel.player.screenX;
int screenY = worldY - panel.player.worldY + panel.player.screenY;
graphics2D.fillRect(screenX, screenY, panel.tileSize, panel.tileSize);
}
}
} }
} }