Added mode-management and second ghost

This commit is contained in:
Urban Modig
2025-08-17 22:10:10 +02:00
parent a2b9b2264e
commit 9d06b038e0
8 changed files with 125 additions and 40 deletions

View File

@ -4,7 +4,7 @@ import java.awt.Point;
public class BlinkyStrategy implements GhostStrategy {
@Override
public Point chooseTarget(PacMan pacman, Ghost self, Ghost blinky) {
public Point chooseTarget(PacMan pacman) {
return pacman.getTilePosition();
}
}

View File

@ -2,10 +2,9 @@ package se.urmo.game.entities;
import lombok.extern.slf4j.Slf4j;
import se.urmo.game.collision.GhostCollisionChecker;
import se.urmo.game.main.Game;
import se.urmo.game.map.GameMap;
import se.urmo.game.state.GhostManager;
import se.urmo.game.util.Direction;
import se.urmo.game.util.LoadSave;
import se.urmo.game.util.MiscUtil;
import java.awt.Color;
@ -13,53 +12,40 @@ import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
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;
public static final int GHOST_SIZE = 32;
private static final int ANIMATION_UPDATE_FREQUENCY = 25;
private static final int COLLISION_BOX_OFFSET = 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 final GhostStrategy strategy;
private final GhostStrategy chaseStrategy;
private Point position;
private boolean moving = true;
private int aniTick = 0;
private int aniIndex = 0;
private BufferedImage[] animation;
private final BufferedImage[] animation;
private int movementTick = 0;
private Direction direction;
private Direction prevDirection;
private GhostMode mode;
private GhostStrategy currentStrategy;
public Ghost(Game game, GhostCollisionChecker collisionChecker, GhostStrategy strategy) {
this.game = game;
public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy strategy, BufferedImage[] animation) {
this.collisionChecker = collisionChecker;
this.strategy = strategy;
this.chaseStrategy = strategy;
this.currentStrategy = strategy;
this.animation = animation;
position = new Point(
13 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + GameMap.OFFSET_X,
4 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + 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) {
@ -88,7 +74,7 @@ public class Ghost {
prevDirection = direction;
direction = chooseDirection(
collisionChecker.calculateDirectionAlternatives(position),
strategy.chooseTarget(pacman, null, null),
currentStrategy.chooseTarget(pacman),
prevDirection);
log.info("selecting direction {}", direction);
}
@ -139,7 +125,7 @@ public class Ghost {
if (aniTick >= ANIMATION_UPDATE_FREQUENCY) {
aniTick = 0;
aniIndex++;
if (aniIndex >= 3) {
if (aniIndex >= GhostManager.MAX_SPRITE_FRAMES) {
aniIndex = 0;
}
@ -147,4 +133,13 @@ public class Ghost {
}
}
public void setMode(GhostMode mode) {
this.mode = mode;
switch (mode) {
case CHASE -> currentStrategy = chaseStrategy;
case SCATTER -> currentStrategy = null;
case FRIGHTENED -> currentStrategy = null;
case EATEN -> currentStrategy = null;
}
}
}

View File

@ -0,0 +1,8 @@
package se.urmo.game.entities;
public enum GhostMode {
CHASE,
SCATTER,
FRIGHTENED,
EATEN
}

View File

@ -3,5 +3,5 @@ package se.urmo.game.entities;
import java.awt.Point;
public interface GhostStrategy {
Point chooseTarget(PacMan pacman, Ghost self, Ghost blinky);
Point chooseTarget(PacMan pacman);
}

View File

@ -1,5 +1,7 @@
package se.urmo.game.entities;
import lombok.Getter;
import lombok.Setter;
import se.urmo.game.collision.CollisionChecker;
import se.urmo.game.util.Direction;
import se.urmo.game.main.Game;
@ -21,11 +23,14 @@ public class PacMan {
private int aniIndex = 0;
private static final int ANIMATION_UPDATE_FREQUENCY = 25;
private int speed = 1;
@Setter
private boolean moving;
private final BufferedImage[][] movmentImages = new BufferedImage[4][4];
private Point position;
private static final BufferedImage COLLISION_BOX = MiscUtil.createOutlinedBox(COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, Color.yellow, 2);
private CollisionChecker collisionChecker;
@Setter
@Getter
private Direction direction = Direction.NONE;
public PacMan(Game game, CollisionChecker collisionChecker) {
@ -102,14 +107,6 @@ public class PacMan {
}
}
public void setMoving(boolean moving) {
this.moving = moving;
}
public void setDirection(Direction direction) {
this.direction = direction;
}
public Point getTilePosition() {
return position;
}

View File

@ -0,0 +1,21 @@
package se.urmo.game.entities;
import se.urmo.game.util.Direction;
import java.awt.Point;
public class PinkyStrategy implements GhostStrategy{
@Override
public Point chooseTarget(PacMan pacman) {
Direction pacmanDir = pacman.getDirection();
Point pacmanPos = pacman.getTilePosition();
return switch (pacmanDir){
case RIGHT -> new Point(pacmanPos.x + 4 * 16, pacmanPos.y);
case LEFT -> new Point(pacmanPos.x - 4 * 16, pacmanPos.y);
case DOWN -> new Point(pacmanPos.x, pacmanPos.y + 4 * 16);
case UP -> new Point(pacmanPos.x, pacmanPos.y - 4 * 16);
case NONE -> pacmanPos;
default -> throw new IllegalStateException("Illegal direction");
};
}
}

View File

@ -0,0 +1,63 @@
package se.urmo.game.state;
import lombok.Getter;
import se.urmo.game.collision.GhostCollisionChecker;
import se.urmo.game.entities.BlinkyStrategy;
import se.urmo.game.entities.Ghost;
import se.urmo.game.entities.GhostMode;
import se.urmo.game.entities.PacMan;
import se.urmo.game.entities.PinkyStrategy;
import se.urmo.game.util.LoadSave;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
public class GhostManager {
public static final int SPRITE_SHEET_ROWS = 10;
public static final int MAX_SPRITE_FRAMES = 4;
@Getter
private final List<Ghost> ghosts = new ArrayList<>();
private BufferedImage[][] image;
private GhostMode globalMode = GhostMode.CHASE;
public GhostManager(GhostCollisionChecker ghostCollisionChecker) {
loadAnimation();
// Create ghosts with their strategies
ghosts.add(new Ghost(ghostCollisionChecker, new BlinkyStrategy(),image[0]));
ghosts.add(new Ghost(ghostCollisionChecker, new PinkyStrategy(), image[1]));
//ghosts.add(new Ghost(240, 200, new InkyStrategy(), loader.getSprite("inky")));
//ghosts.add(new Ghost(260, 200, new ClydeStrategy(), loader.getSprite("clyde")));
}
private void loadAnimation() {
image = new BufferedImage[SPRITE_SHEET_ROWS][MAX_SPRITE_FRAMES];
BufferedImage img = LoadSave.GetSpriteAtlas("sprites/PacManAssets-Ghosts.png");
for (int row = 0; row < SPRITE_SHEET_ROWS; row++) {
for (int col = 0; col < MAX_SPRITE_FRAMES; col++) {
image[row][col] = img.getSubimage(Ghost.GHOST_SIZE * col, Ghost.GHOST_SIZE * row, Ghost.GHOST_SIZE, Ghost.GHOST_SIZE);
}
}
}
public void setMode(GhostMode mode) {
this.globalMode = mode;
for (Ghost g : ghosts) {
g.setMode(mode);
}
}
public void update(PacMan pacman) {
for (Ghost g : ghosts) {
g.update(pacman);
}
}
public void draw(Graphics2D g) {
for (Ghost ghost : ghosts) {
ghost.draw(g);
}
}
}

View File

@ -16,29 +16,30 @@ import java.awt.event.KeyEvent;
public class PlayingState implements GameState {
private final Game game;
private final GameStateManager gameStateManager;
private final Ghost ghost;
private final GhostManager ghostManager;
private PacMan pacman;
@Getter
private GameMap map;
public PlayingState(Game game, GameStateManager gameStateManager) {
this.game = game;
this.gameStateManager = gameStateManager;
this.map = new GameMap();
this.pacman = new PacMan(game, new CollisionChecker(map));
this.ghost = new Ghost(game, new GhostCollisionChecker(map), new BlinkyStrategy());
this.ghostManager = new GhostManager(new GhostCollisionChecker(map));
}
@Override
public void update() {
pacman.update();
ghost.update(pacman);
ghostManager.update(pacman);
}
@Override
public void render(Graphics2D g) {
map.draw(g);
pacman.draw(g);
ghost.draw(g);
ghostManager.draw(g);
}
@Override