Compare commits
2 Commits
f001cf0a41
...
63b2b7ae68
| Author | SHA1 | Date | |
|---|---|---|---|
| 63b2b7ae68 | |||
| 23fce8dded |
45
src/main/java/se/urmo/game/entities/Fruit.java
Normal file
45
src/main/java/se/urmo/game/entities/Fruit.java
Normal file
@ -0,0 +1,45 @@
|
||||
package se.urmo.game.entities;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import se.urmo.game.map.GameMap;
|
||||
import se.urmo.game.state.FruitType;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Slf4j
|
||||
public class Fruit {
|
||||
private final Point position;
|
||||
private final BufferedImage sprite;
|
||||
@Getter
|
||||
private final int score;
|
||||
private final long spawnTime;
|
||||
private final long lifetimeMs = 9000; // ~9 seconds
|
||||
|
||||
public Fruit(FruitType type) {
|
||||
this.position = new Point(GameMap.colToScreen(13), GameMap.rowToScreen(16)); ;
|
||||
this.sprite = type.getSprite();
|
||||
this.score = type.getScore();
|
||||
this.spawnTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void draw(Graphics g) {
|
||||
g.drawImage(sprite, position.x + GameMap.MAP_TILESIZE / 2, position.y, null);
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() - spawnTime > lifetimeMs;
|
||||
}
|
||||
|
||||
public boolean collidesWith(PacMan pacman) {
|
||||
//return pacman.distanceTo(position) < GameMap.MAP_TILESIZE / 2.0;
|
||||
|
||||
Rectangle pacmanBounds = pacman.getBounds();
|
||||
Rectangle fruitBounds = new Rectangle(position.x, position.y, sprite.getWidth(), sprite.getHeight());
|
||||
return pacmanBounds.intersects(fruitBounds);
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,6 +3,8 @@ package se.urmo.game.entities;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import se.urmo.game.collision.GhostCollisionChecker;
|
||||
import se.urmo.game.graphics.SpriteLocation;
|
||||
import se.urmo.game.graphics.SpriteSheetManager;
|
||||
import se.urmo.game.main.Game;
|
||||
import se.urmo.game.map.GameMap;
|
||||
import se.urmo.game.state.GhostManager;
|
||||
@ -52,12 +54,13 @@ public class Ghost {
|
||||
|
||||
|
||||
|
||||
public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy strategy, GhostStrategy scaterStrategy, BufferedImage[] animation, BufferedImage[] fearAnimation) {
|
||||
public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy strategy, GhostStrategy scaterStrategy, int animation) {
|
||||
this.collisionChecker = collisionChecker;
|
||||
this.chaseStrategy = strategy;
|
||||
this.scaterStrategy = scaterStrategy;
|
||||
this.baseAnimation = animation;
|
||||
this.fearAnimation = fearAnimation;
|
||||
this.baseAnimation = SpriteSheetManager.get(SpriteLocation.GHOST).getAnimation(animation);;
|
||||
this.fearAnimation = SpriteSheetManager.get(SpriteLocation.GHOST).getAnimation(8);;
|
||||
|
||||
position = new Point(
|
||||
13 * GameMap.MAP_TILESIZE + GameMap.OFFSET_X + (GameMap.MAP_TILESIZE / 2),
|
||||
4 * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y + (GameMap.MAP_TILESIZE / 2) );
|
||||
|
||||
@ -76,7 +76,7 @@ public class PacMan {
|
||||
position.y - PACMAN_OFFSET,
|
||||
PACMAN_SIZE,
|
||||
PACMAN_SIZE, null);
|
||||
g.drawImage(COLLISION_BOX, position.x - COLLISION_BOX_OFFSET, position.y - COLLISION_BOX_OFFSET, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, null);
|
||||
//g.drawImage(COLLISION_BOX, position.x - COLLISION_BOX_OFFSET, position.y - COLLISION_BOX_OFFSET, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, null);
|
||||
//g.setColor(Color.BLUE);
|
||||
//g.fillRect(position.x-1, position.y-1, 3, 3);
|
||||
}
|
||||
@ -129,4 +129,8 @@ public class PacMan {
|
||||
public Image getLifeIcon() {
|
||||
return movmentImages[0][1];
|
||||
}
|
||||
|
||||
public Rectangle getBounds() {
|
||||
return new Rectangle(position.x - COLLISION_BOX_OFFSET, position.y - COLLISION_BOX_OFFSET, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
package se.urmo.game.map;
|
||||
package se.urmo.game.graphics;
|
||||
|
||||
import lombok.Getter;
|
||||
import se.urmo.game.entities.Ghost;
|
||||
import se.urmo.game.map.GameMap;
|
||||
import se.urmo.game.util.LoadSave;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Getter
|
||||
public enum SpriteLocation {
|
||||
MAP("sprites/PacMan-custom-spritemap-0-3.png", 5, 11),
|
||||
ITEM("sprites/PacManAssets-Items.png", 2, 8),
|
||||
NONE("", 0, 0) { // Special case for tiles without sprites
|
||||
MAP("sprites/PacMan-custom-spritemap-0-3.png", 5, 11, GameMap.MAP_TILESIZE),
|
||||
ITEM("sprites/PacManAssets-Items.png", 2, 8, GameMap.MAP_TILESIZE),
|
||||
GHOST("sprites/PacManAssets-Ghosts.png", 11, 4, Ghost.GHOST_SIZE),
|
||||
NONE("", 0, 0, 0) { // Special case for tiles without sprites
|
||||
@Override
|
||||
public BufferedImage[][] loadSprites(int tileSize) {
|
||||
return new BufferedImage[][] {{ null }}; // Single null sprite
|
||||
@ -19,11 +22,13 @@ public enum SpriteLocation {
|
||||
private final String path;
|
||||
private final int rows;
|
||||
private final int cols;
|
||||
private final int size;
|
||||
|
||||
SpriteLocation(String path, int rows, int cols) {
|
||||
SpriteLocation(String path, int rows, int cols, int size) {
|
||||
this.path = path;
|
||||
this.rows = rows;
|
||||
this.cols = cols;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public BufferedImage[][] loadSprites(int tileSize) {
|
||||
@ -1,6 +1,7 @@
|
||||
package se.urmo.game.map;
|
||||
package se.urmo.game.graphics;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
|
||||
@ -11,15 +12,7 @@ public class SpriteSheet {
|
||||
|
||||
public SpriteSheet(SpriteLocation location) {
|
||||
this.location = location;
|
||||
this.spriteSheet = location.loadSprites(GameMap.MAP_TILESIZE);
|
||||
}
|
||||
|
||||
public BufferedImage getSprite(TileType tileType) {
|
||||
if (tileType.getSpriteSheet() != location) {
|
||||
throw new IllegalArgumentException("Tile type belongs to different sprite sheet");
|
||||
}
|
||||
// NONE will always return null without additional checks
|
||||
return getSprite(tileType.getRow(), tileType.getCol());
|
||||
this.spriteSheet = location.loadSprites(location.getSize());
|
||||
}
|
||||
|
||||
public BufferedImage getSprite(int row, int col) {
|
||||
@ -31,4 +24,12 @@ public class SpriteSheet {
|
||||
}
|
||||
return spriteSheet[row][col];
|
||||
}
|
||||
|
||||
public BufferedImage[] getAnimation(int row) {
|
||||
if(location == SpriteLocation.NONE) return null;
|
||||
|
||||
if(row>= spriteSheet.length) return null;
|
||||
|
||||
return spriteSheet[row];
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
package se.urmo.game.map;
|
||||
package se.urmo.game.graphics;
|
||||
|
||||
import se.urmo.game.map.GameMap;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
@ -1,6 +1,7 @@
|
||||
package se.urmo.game.map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import se.urmo.game.graphics.SpriteSheetManager;
|
||||
import se.urmo.game.util.Direction;
|
||||
|
||||
import java.awt.*;
|
||||
@ -31,28 +32,26 @@ public class GameMap {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) {
|
||||
|
||||
String line;
|
||||
int rowIndex = 0;
|
||||
while ((line = br.readLine()) != null && rowIndex < mapRowSize) {
|
||||
int row = 0;
|
||||
while ((line = br.readLine()) != null && row < mapRowSize) {
|
||||
String[] tokens = line.split(",");
|
||||
if (tokens.length != mapColSize) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid map format: row " + rowIndex + " has " + tokens.length +
|
||||
"Invalid map format: row " + row + " has " + tokens.length +
|
||||
" columns, expected " + mapColSize
|
||||
);
|
||||
}
|
||||
|
||||
for (int col = 0; col < mapColSize; col++) {
|
||||
int value = Integer.parseInt(tokens[col].trim());
|
||||
TileType type = TileType.fromValue(value);
|
||||
BufferedImage sprite = SpriteSheetManager.get(type.getSpriteSheet()).getSprite(type);
|
||||
data[rowIndex][col] = new MapTile(sprite, type);
|
||||
data[row][col] = MapTile.byType(TileType.fromValue(value));
|
||||
}
|
||||
rowIndex++;
|
||||
row++;
|
||||
}
|
||||
|
||||
if (rowIndex != mapRowSize) {
|
||||
if (row != mapRowSize) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid map format: found " + rowIndex + " rows, expected " + mapRowSize
|
||||
"Invalid map format: found " + row + " rows, expected " + mapRowSize
|
||||
);
|
||||
}
|
||||
|
||||
@ -78,11 +77,6 @@ public class GameMap {
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage getSprite(int value) {
|
||||
TileType type = TileType.fromValue(value);
|
||||
return SpriteSheetManager.get(type.getSpriteSheet()).getSprite(type);
|
||||
}
|
||||
|
||||
public boolean isPassable(List<Point> list){
|
||||
return list.stream().allMatch(p -> isPassable(p.x, p.y));
|
||||
}
|
||||
@ -189,6 +183,13 @@ public class GameMap {
|
||||
return (screenY - OFFSET_Y) / MAP_TILESIZE;
|
||||
}
|
||||
|
||||
public static int colToScreen(int col) {
|
||||
return col * MAP_TILESIZE + OFFSET_X;
|
||||
}
|
||||
public static int rowToScreen(int row) {
|
||||
return row * MAP_TILESIZE + OFFSET_Y;
|
||||
}
|
||||
|
||||
public MapTile getTile(Point screenPos) {
|
||||
int r = screenToRow(screenPos);
|
||||
int c = screenToCol(screenPos);
|
||||
|
||||
@ -2,6 +2,7 @@ package se.urmo.game.map;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import se.urmo.game.graphics.SpriteSheetManager;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@ -12,12 +13,19 @@ public class MapTile {
|
||||
private BufferedImage image;
|
||||
private final boolean[][] collisionMask;
|
||||
|
||||
public MapTile(BufferedImage image, TileType tileType) {
|
||||
this.tileType = tileType;
|
||||
public MapTile(BufferedImage image, TileType type) {
|
||||
this.tileType = type;
|
||||
this.image = image;
|
||||
this.collisionMask = tileType.isSolid() ? createCollisionMask(image) : null;
|
||||
}
|
||||
|
||||
public static MapTile byType(TileType type) {
|
||||
BufferedImage img = SpriteSheetManager
|
||||
.get(type.getSpriteSheet())
|
||||
.getSprite(type.getRow(), type.getCol()); // row and col in sprite - not in map!
|
||||
return new MapTile(img, type) ;
|
||||
}
|
||||
|
||||
private boolean[][] createCollisionMask(BufferedImage img) {
|
||||
if(img == null) return null;
|
||||
int w = img.getWidth();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package se.urmo.game.map;
|
||||
|
||||
import lombok.Getter;
|
||||
import se.urmo.game.graphics.SpriteLocation;
|
||||
|
||||
@Getter
|
||||
public enum TileType {
|
||||
|
||||
46
src/main/java/se/urmo/game/state/FruitManager.java
Normal file
46
src/main/java/se/urmo/game/state/FruitManager.java
Normal file
@ -0,0 +1,46 @@
|
||||
package se.urmo.game.state;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import se.urmo.game.entities.Fruit;
|
||||
import se.urmo.game.entities.PacMan;
|
||||
|
||||
import java.awt.Graphics;
|
||||
|
||||
@Slf4j
|
||||
public class FruitManager {
|
||||
private Fruit activeFruit;
|
||||
private int dotsEaten = 0;
|
||||
|
||||
public void dotEaten() {
|
||||
dotsEaten++;
|
||||
if (dotsEaten == 10 || dotsEaten == 170) {
|
||||
spawnFruit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnFruit(int level) {
|
||||
FruitType type = FruitType.forLevel(level);
|
||||
|
||||
activeFruit = new Fruit(type);
|
||||
}
|
||||
|
||||
public void update(PacMan pacman, PlayingState playingState) {
|
||||
if (activeFruit != null) {
|
||||
if (activeFruit.isExpired()) {
|
||||
activeFruit = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeFruit.collidesWith(pacman)) {
|
||||
playingState.setScore(activeFruit.getScore());
|
||||
activeFruit = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(Graphics g) {
|
||||
if (activeFruit != null) {
|
||||
activeFruit.draw(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/main/java/se/urmo/game/state/FruitType.java
Normal file
29
src/main/java/se/urmo/game/state/FruitType.java
Normal file
@ -0,0 +1,29 @@
|
||||
package se.urmo.game.state;
|
||||
|
||||
import lombok.Getter;
|
||||
import se.urmo.game.graphics.SpriteLocation;
|
||||
import se.urmo.game.graphics.SpriteSheetManager;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Getter
|
||||
public enum FruitType {
|
||||
CHERRY(1, SpriteSheetManager.get(SpriteLocation.ITEM).getSprite(0,0), 100);
|
||||
|
||||
private final int level;
|
||||
private final BufferedImage sprite;
|
||||
private final int score;
|
||||
|
||||
FruitType(int level, BufferedImage sprite, int score) {
|
||||
this.level = level;
|
||||
this.sprite = sprite;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
public static FruitType forLevel(int i) {
|
||||
return Arrays.stream(values())
|
||||
.filter(fruitType -> fruitType.level ==i)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
@ -26,10 +26,12 @@ import java.util.List;
|
||||
public class GhostManager {
|
||||
public static final int SPRITE_SHEET_ROWS = 10;
|
||||
public static final int MAX_SPRITE_FRAMES = 4;
|
||||
public static final int BLINKY_ANIMATION = 0;
|
||||
public static final int PINKY_ANIMATION = 2;
|
||||
public static final int INKY_ANIMATION = 1;
|
||||
public static final int CLYDE_ANIMATION = 3;
|
||||
@Getter
|
||||
private final List<Ghost> ghosts = new ArrayList<>();
|
||||
private BufferedImage[][] image;
|
||||
private GhostMode globalMode;
|
||||
|
||||
private long lastModeSwitchTime;
|
||||
private int phaseIndex = 0;
|
||||
@ -43,31 +45,17 @@ public class GhostManager {
|
||||
};
|
||||
|
||||
public GhostManager(GhostCollisionChecker ghostCollisionChecker) {
|
||||
loadAnimation();
|
||||
|
||||
// Create ghosts with their strategies
|
||||
Ghost blinky = new Ghost(ghostCollisionChecker, new BlinkyStrategy(), new ScatterToTopRight(), image[0], image[9]);
|
||||
Ghost blinky = new Ghost(ghostCollisionChecker, new BlinkyStrategy(), new ScatterToTopRight(), BLINKY_ANIMATION);
|
||||
ghosts.add(blinky);
|
||||
//ghosts.add(new Ghost(ghostCollisionChecker, new PinkyStrategy(),new ScatterToTopLeft(), image[2], image[9]));
|
||||
//ghosts.add(new Ghost(ghostCollisionChecker,new InkyStrategy(blinky), new ScatterToBottomRight(), image[1], image[9]));
|
||||
//ghosts.add(new Ghost(ghostCollisionChecker, new ClydeStrategy(), new ScatterToBottomLeft(), image[3], image[8]));
|
||||
// ghosts.add(new Ghost(ghostCollisionChecker, new PinkyStrategy(), new ScatterToTopLeft(), PINKY_ANIMATION));
|
||||
// ghosts.add(new Ghost(ghostCollisionChecker,new InkyStrategy(blinky), new ScatterToBottomRight(), INKY_ANIMATION));
|
||||
// ghosts.add(new Ghost(ghostCollisionChecker, new ClydeStrategy(), new ScatterToBottomLeft(), CLYDE_ANIMATION));
|
||||
setMode(GhostMode.CHASE);
|
||||
}
|
||||
|
||||
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;
|
||||
log.debug("Mode changed to {}", globalMode);
|
||||
log.debug("Mode changed to {}", mode);
|
||||
for (Ghost g : ghosts) {
|
||||
g.setMode(mode);
|
||||
}
|
||||
|
||||
4
src/main/java/se/urmo/game/state/LevelManager.java
Normal file
4
src/main/java/se/urmo/game/state/LevelManager.java
Normal file
@ -0,0 +1,4 @@
|
||||
package se.urmo.game.state;
|
||||
|
||||
public class LevelManager {
|
||||
}
|
||||
@ -23,6 +23,8 @@ public class PlayingState implements GameState {
|
||||
private final GameStateManager gameStateManager;
|
||||
private final GhostManager ghostManager;
|
||||
private final Font arcadeFont;
|
||||
private final FruitManager fruitManager;
|
||||
private final LevelManager levelManager;
|
||||
private PacMan pacman;
|
||||
@Getter
|
||||
private GameMap map;
|
||||
@ -35,6 +37,8 @@ public class PlayingState implements GameState {
|
||||
this.map = new GameMap("maps/map1.csv");
|
||||
this.pacman = new PacMan(game, new CollisionChecker(map));
|
||||
this.ghostManager = new GhostManager(new GhostCollisionChecker(map));
|
||||
this.fruitManager = new FruitManager();
|
||||
this.levelManager = new LevelManager();
|
||||
this.arcadeFont = loadArcadeFont();
|
||||
}
|
||||
|
||||
@ -42,6 +46,7 @@ public class PlayingState implements GameState {
|
||||
public void update() {
|
||||
pacman.update();
|
||||
ghostManager.update(pacman, map);
|
||||
fruitManager.update(pacman, this);
|
||||
checkCollisions();
|
||||
handleDots();
|
||||
}
|
||||
@ -54,6 +59,7 @@ public class PlayingState implements GameState {
|
||||
ghostManager.setFrightMode();
|
||||
}
|
||||
if(wasRemoved){
|
||||
fruitManager.dotEaten();
|
||||
score+=tile.getTileType().getScore();
|
||||
}
|
||||
}
|
||||
@ -63,6 +69,7 @@ public class PlayingState implements GameState {
|
||||
map.draw(g);
|
||||
pacman.draw(g);
|
||||
ghostManager.draw(g);
|
||||
fruitManager.draw(g);
|
||||
drawUI(g);
|
||||
}
|
||||
|
||||
@ -129,4 +136,8 @@ public class PlayingState implements GameState {
|
||||
return new Font("Monospaced", Font.BOLD, 16);
|
||||
}
|
||||
}
|
||||
|
||||
public void setScore(int score) {
|
||||
this.score += score;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user