package se.urmo.game.entities; import se.urmo.game.collision.GhostCollisionChecker; import se.urmo.game.main.Game; import se.urmo.game.map.GameMap; import se.urmo.game.util.Direction; import se.urmo.game.util.LoadSave; import se.urmo.game.util.MiscUtil; import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import java.awt.image.BufferedImage; import java.util.List; public class Ghost { private static final int COLLISION_BOX_SIZE = 16; private static final int GHOST_SPEED = 1; private static final int GHOST_MOVEMENT_UPDATE_FREQUENCY = 2; private static int GHOST_SIZE = 32; private static final int ANIMATION_UPDATE_FREQUENCY = 25; private static final int COLLISION_BOX_OFFSET = (GHOST_SIZE - COLLISION_BOX_SIZE) / 2; private static final BufferedImage COLLISION_BOX = MiscUtil.createOutlinedBox(COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, Color.black, 2); private final Game game; private final GhostCollisionChecker collisionChecker; private Point position; private boolean moving = true; private int aniTick = 0; private int aniIndex = 0; private BufferedImage[] animation; private Direction direction = Direction.LEFT; private int movementTick = 0; public Ghost(Game game, GhostCollisionChecker collisionChecker, GhostStrategy strategy) { this.game = game; this.collisionChecker = collisionChecker; position = new Point(13 * 16 + 8 + GameMap.OFFSET_X, 4 * 16 + GameMap.OFFSET_Y); loadAnimation(); } private void loadAnimation() { BufferedImage[][] image = new BufferedImage[10][4]; BufferedImage img = LoadSave.GetSpriteAtlas("sprites/PacManAssets-Ghosts.png"); for (int row = 0; row < 3; row++) { for (int col = 0; col < 4; col++) { image[row][col] = img.getSubimage(32 * col, 32 * row, GHOST_SIZE, GHOST_SIZE); } } animation = image[0]; } public void draw(Graphics g) { g.drawImage( animation[aniIndex], position.x - COLLISION_BOX_OFFSET, position.y - COLLISION_BOX_OFFSET, GHOST_SIZE, GHOST_SIZE, null); g.drawImage(COLLISION_BOX, position.x, position.y, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, null); } public void update() { updateAnimationTick(); if(movementTick >= GHOST_MOVEMENT_UPDATE_FREQUENCY) { // if intersection - decide direction // else if direction isPassible List i = collisionChecker.isIntersection(position); if(!i.isEmpty()){ // Change direction if(i.contains(Direction.DOWN)) direction = Direction.DOWN; else if(i.contains(Direction.RIGHT)) direction = Direction.RIGHT; else if(i.contains(Direction.UP)) direction = Direction.UP; else direction = Direction.LEFT; } //Point target = strategy.chooseTarget(pacman, this, blinky); Point newPosition = switch (direction){ case RIGHT -> new Point(position.x += GHOST_SPEED, position.y); case LEFT -> new Point(position.x -= GHOST_SPEED, position.y); case DOWN -> new Point(position.x, position.y += GHOST_SPEED); case UP -> new Point(position.x, position.y -= GHOST_SPEED); default -> throw new IllegalStateException("Illegal direction"); }; Point destination = collisionChecker.getValidDestination(direction, newPosition, GHOST_SIZE, GHOST_SIZE); // if (position.x + direction.dx * GHOST_SPEED < GameMap.OFFSET_X) direction = direction.opposite(); // if (position.x + GHOST_SIZE + (direction.dx * GHOST_SPEED) > GamePanel.SCREEN_WIDTH - GameMap.OFFSET_X) // direction = direction.opposite(); if(destination != null) { position = destination; } movementTick = 0; } else movementTick++; } private Direction chooseDirection(List options, Point target) { Direction best = options.get(0); double bestDist = Double.MAX_VALUE; for (Direction d : options) { int nx = position.x + d.dx * GameMap.MAP_TILESIZE; int ny = position.y + d.dy * GameMap.MAP_TILESIZE; double dist = target.distance(nx, ny); if (dist < bestDist) { bestDist = dist; best = d; } } return best; } private void updateAnimationTick() { if (moving) { aniTick++; if (aniTick >= ANIMATION_UPDATE_FREQUENCY) { aniTick = 0; aniIndex++; if (aniIndex >= 3) { aniIndex = 0; } } } } }