diff --git a/src/de/miaurizius/jgame2d/ai/Node.java b/src/de/miaurizius/jgame2d/ai/Node.java new file mode 100644 index 0000000..2fd6925 --- /dev/null +++ b/src/de/miaurizius/jgame2d/ai/Node.java @@ -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; + } + +} diff --git a/src/de/miaurizius/jgame2d/ai/PathFinder.java b/src/de/miaurizius/jgame2d/ai/PathFinder.java new file mode 100644 index 0000000..fea42b6 --- /dev/null +++ b/src/de/miaurizius/jgame2d/ai/PathFinder.java @@ -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 openList = new ArrayList<>(); + public ArrayList 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; + } + } + +} diff --git a/src/de/miaurizius/jgame2d/core/GamePanel.java b/src/de/miaurizius/jgame2d/core/GamePanel.java index e6d3206..beacbbb 100644 --- a/src/de/miaurizius/jgame2d/core/GamePanel.java +++ b/src/de/miaurizius/jgame2d/core/GamePanel.java @@ -1,5 +1,6 @@ package de.miaurizius.jgame2d.core; +import de.miaurizius.jgame2d.ai.PathFinder; import de.miaurizius.jgame2d.core.enums.GameState; import de.miaurizius.jgame2d.core.enums.Map; import de.miaurizius.jgame2d.core.handlers.*; @@ -33,7 +34,7 @@ public class GamePanel extends JPanel implements Runnable { int fScreenWidth = screenWidth; int fScreenHeight = screenHeight; BufferedImage tempScreen; - Graphics2D fg2; + public Graphics2D fg2; // WORLD SETTINGS public final int maxWorldCol = 50; @@ -54,6 +55,7 @@ public class GamePanel extends JPanel implements Runnable { public Sound sfx = new Sound(); public Sound music = new Sound(); public Config config = new Config(this); + public PathFinder pFinder = new PathFinder(this); Thread gameThread; // ENTITY AND OBJECT diff --git a/src/de/miaurizius/jgame2d/entity/Entity.java b/src/de/miaurizius/jgame2d/entity/Entity.java index e5eb937..fa3d404 100644 --- a/src/de/miaurizius/jgame2d/entity/Entity.java +++ b/src/de/miaurizius/jgame2d/entity/Entity.java @@ -40,6 +40,7 @@ public class Entity { public boolean dying; public boolean hpBarOn; public boolean consumable; + public boolean onPath; // COUNTER public int spriteCounter; @@ -86,15 +87,7 @@ public class Entity { // DEFAULT public void update() { setAction(); - 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); + checkCollision(); if(!collisionOn) { switch (direction) { @@ -207,6 +200,17 @@ public class Entity { } //If entity is consumable 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) { 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) { 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; + } } diff --git a/src/de/miaurizius/jgame2d/entity/npc/OldManNPC.java b/src/de/miaurizius/jgame2d/entity/npc/OldManNPC.java index 0d24b06..68b0513 100644 --- a/src/de/miaurizius/jgame2d/entity/npc/OldManNPC.java +++ b/src/de/miaurizius/jgame2d/entity/npc/OldManNPC.java @@ -18,9 +18,22 @@ public class OldManNPC extends Entity { speed = 1; getImage(); setDialogue(); + + solidArea.x = 8; + solidArea.y = 16; + solidAreaDefaultX = solidArea.x; + solidAreaDefaultY = solidArea.y; + solidArea.width = 30; + solidArea.height = 30; } public void setAction() { + + if(onPath) { + searchPath(12, 9); + return; + } + actionLock++; if(actionLock != 120) return; //lock action for x frames Random rand = new Random(); @@ -30,6 +43,12 @@ public class OldManNPC extends Entity { if(i > 50 && i <= 75) direction = Direction.LEFT; if(i > 75) direction = Direction.RIGHT; actionLock = 0; + + } + @Override + public void speak() { + super.speak(); + super.onPath = true; } // SETTING THINGS UP