From 9d06b038e0c0c12643adfe6e9935167f066e5b64 Mon Sep 17 00:00:00 2001 From: Urban Modig Date: Sun, 17 Aug 2025 22:10:10 +0200 Subject: [PATCH] Added mode-management and second ghost --- .../se/urmo/game/entities/BlinkyStrategy.java | 2 +- .../java/se/urmo/game/entities/Ghost.java | 47 +++++++------- .../java/se/urmo/game/entities/GhostMode.java | 8 +++ .../se/urmo/game/entities/GhostStrategy.java | 2 +- .../java/se/urmo/game/entities/PacMan.java | 13 ++-- .../se/urmo/game/entities/PinkyStrategy.java | 21 +++++++ .../java/se/urmo/game/state/GhostManager.java | 63 +++++++++++++++++++ .../java/se/urmo/game/state/PlayingState.java | 9 +-- 8 files changed, 125 insertions(+), 40 deletions(-) create mode 100644 src/main/java/se/urmo/game/entities/GhostMode.java create mode 100644 src/main/java/se/urmo/game/entities/PinkyStrategy.java create mode 100644 src/main/java/se/urmo/game/state/GhostManager.java diff --git a/src/main/java/se/urmo/game/entities/BlinkyStrategy.java b/src/main/java/se/urmo/game/entities/BlinkyStrategy.java index d824520..8eed1f8 100644 --- a/src/main/java/se/urmo/game/entities/BlinkyStrategy.java +++ b/src/main/java/se/urmo/game/entities/BlinkyStrategy.java @@ -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(); } } diff --git a/src/main/java/se/urmo/game/entities/Ghost.java b/src/main/java/se/urmo/game/entities/Ghost.java index 06b0acf..0ca723b 100644 --- a/src/main/java/se/urmo/game/entities/Ghost.java +++ b/src/main/java/se/urmo/game/entities/Ghost.java @@ -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; + } + } } diff --git a/src/main/java/se/urmo/game/entities/GhostMode.java b/src/main/java/se/urmo/game/entities/GhostMode.java new file mode 100644 index 0000000..a144cbf --- /dev/null +++ b/src/main/java/se/urmo/game/entities/GhostMode.java @@ -0,0 +1,8 @@ +package se.urmo.game.entities; + +public enum GhostMode { + CHASE, + SCATTER, + FRIGHTENED, + EATEN +} diff --git a/src/main/java/se/urmo/game/entities/GhostStrategy.java b/src/main/java/se/urmo/game/entities/GhostStrategy.java index 6635f01..90a38ad 100644 --- a/src/main/java/se/urmo/game/entities/GhostStrategy.java +++ b/src/main/java/se/urmo/game/entities/GhostStrategy.java @@ -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); } diff --git a/src/main/java/se/urmo/game/entities/PacMan.java b/src/main/java/se/urmo/game/entities/PacMan.java index 8bbd812..b1ba5eb 100644 --- a/src/main/java/se/urmo/game/entities/PacMan.java +++ b/src/main/java/se/urmo/game/entities/PacMan.java @@ -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; } diff --git a/src/main/java/se/urmo/game/entities/PinkyStrategy.java b/src/main/java/se/urmo/game/entities/PinkyStrategy.java new file mode 100644 index 0000000..48aec6a --- /dev/null +++ b/src/main/java/se/urmo/game/entities/PinkyStrategy.java @@ -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"); + }; + } +} diff --git a/src/main/java/se/urmo/game/state/GhostManager.java b/src/main/java/se/urmo/game/state/GhostManager.java new file mode 100644 index 0000000..77ead2c --- /dev/null +++ b/src/main/java/se/urmo/game/state/GhostManager.java @@ -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 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); + } + } +} diff --git a/src/main/java/se/urmo/game/state/PlayingState.java b/src/main/java/se/urmo/game/state/PlayingState.java index 0b5ba85..ee0390c 100644 --- a/src/main/java/se/urmo/game/state/PlayingState.java +++ b/src/main/java/se/urmo/game/state/PlayingState.java @@ -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