added path finding algorithm
This commit is contained in:
17
src/de/miaurizius/jgame2d/ai/Node.java
Normal file
17
src/de/miaurizius/jgame2d/ai/Node.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
146
src/de/miaurizius/jgame2d/ai/PathFinder.java
Normal file
146
src/de/miaurizius/jgame2d/ai/PathFinder.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -207,6 +200,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 +288,56 @@ 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()) {
|
||||||
|
onPath = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int nextX = panel.pFinder.pathList.getFirst().col * panel.tileSize;
|
||||||
|
int nextY = panel.pFinder.pathList.getFirst().row * panel.tileSize;
|
||||||
|
|
||||||
|
int enLeftX = worldX + solidArea.x;
|
||||||
|
int enRightX = worldX + solidArea.x + solidArea.width;
|
||||||
|
int enTopY = worldY + solidArea.y;
|
||||||
|
int enBottomY = worldY + solidArea.y + solidArea.height;
|
||||||
|
|
||||||
|
if(enTopY > nextY && enLeftX >= nextX && enRightX < nextX + panel.tileSize) direction = Direction.UP;
|
||||||
|
if(enTopY < nextY && enLeftX >= nextX && enRightX < nextX + panel.tileSize) direction = Direction.DOWN;
|
||||||
|
if(enTopY >= nextY && enBottomY < nextY + panel.tileSize) {
|
||||||
|
if(enLeftX > nextX) {
|
||||||
|
direction = Direction.LEFT;
|
||||||
|
} else if(enLeftX < nextX) {
|
||||||
|
direction = Direction.RIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enTopY > nextY && enLeftX > nextX) {
|
||||||
|
direction = Direction.UP;
|
||||||
|
checkCollision();
|
||||||
|
if(collision) direction = Direction.LEFT;
|
||||||
|
}
|
||||||
|
if(enTopY > nextY && enLeftX < nextX) {
|
||||||
|
direction = Direction.UP;
|
||||||
|
checkCollision();
|
||||||
|
if(collision) direction = Direction.RIGHT;
|
||||||
|
}
|
||||||
|
if(enTopY < nextY && enLeftX > nextX) {
|
||||||
|
direction = Direction.DOWN;
|
||||||
|
checkCollision();
|
||||||
|
if(collision) direction = Direction.LEFT;
|
||||||
|
}
|
||||||
|
if(enTopY < nextY && enLeftX < nextX) {
|
||||||
|
direction = Direction.DOWN;
|
||||||
|
checkCollision();
|
||||||
|
if(collision) direction = Direction.RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextCol = panel.pFinder.pathList.getFirst().col;
|
||||||
|
int nextRow = panel.pFinder.pathList.getFirst().row;
|
||||||
|
if(nextCol == goalCol && nextRow == goalRow) onPath = false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user