Files
PacMan/src/main/java/se/urmo/game/entities/Ghost.java
2025-08-17 10:53:59 +02:00

136 lines
4.9 KiB
Java

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