199 lines
6.8 KiB
Java
199 lines
6.8 KiB
Java
package se.urmo.game.entities.pacman;
|
|
|
|
import lombok.Getter;
|
|
import lombok.Setter;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import se.urmo.game.collision.CollisionChecker;
|
|
import se.urmo.game.entities.BaseAnimated;
|
|
import se.urmo.game.graphics.SpriteLocation;
|
|
import se.urmo.game.graphics.SpriteSheetManager;
|
|
import se.urmo.game.main.LevelManager;
|
|
import se.urmo.game.map.GameMap;
|
|
import se.urmo.game.util.Direction;
|
|
import se.urmo.game.util.LoadSave;
|
|
import se.urmo.game.util.MyPoint;
|
|
|
|
import java.awt.Graphics;
|
|
import java.awt.Image;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.awt.image.BufferedImage;
|
|
import java.util.Arrays;
|
|
import java.util.stream.Stream;
|
|
|
|
|
|
@Slf4j
|
|
public class PacMan extends BaseAnimated {
|
|
public static final int PACMAN_SIZE = 32;
|
|
public static final int PACMAN_OFFSET = PACMAN_SIZE / 2;
|
|
private static final int COLLISION_BOX_SIZE = 16;
|
|
private static final int COLLISION_BOX_OFFSET = (PACMAN_SIZE - COLLISION_BOX_SIZE) / 2;
|
|
private static final int ANIMATION_UPDATE_FREQUENCY = 10;
|
|
private static final double BASE_SPEED = 0.40;
|
|
private static final long FRAME_NS = 80_000_000L; // 80 ms
|
|
public static final int PACMAN_SPRITE_FRAMES = 4;
|
|
private final MyPoint startPosition;
|
|
private final CollisionChecker collisionChecker;
|
|
private final LevelManager levelManager;
|
|
private final Sprites sprites;
|
|
private boolean moving = false;
|
|
private MyPoint position;
|
|
@Setter
|
|
@Getter
|
|
private Direction direction = Direction.NONE;
|
|
private BufferedImage[] deathFrames; // working copy
|
|
private long lastChangeNs;
|
|
// animation state
|
|
@Setter
|
|
private PacmanState state = PacmanState.ALIVE;
|
|
private int deathFrameIdx = 0;
|
|
|
|
public PacMan(CollisionChecker collisionChecker, LevelManager levelManager) {
|
|
super(ANIMATION_UPDATE_FREQUENCY, PACMAN_SPRITE_FRAMES);
|
|
this.collisionChecker = collisionChecker;
|
|
this.levelManager = levelManager;
|
|
this.position = new MyPoint(
|
|
26 * GameMap.MAP_TILESIZE + GameMap.OFFSET_X,
|
|
13 * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y + ((double) GameMap.MAP_TILESIZE / 2));
|
|
this.startPosition = this.position;
|
|
this.sprites = loadAnimation();
|
|
}
|
|
|
|
private Sprites loadAnimation() {
|
|
BufferedImage[][] spriteMap = new BufferedImage[6][PACMAN_SPRITE_FRAMES];
|
|
BufferedImage[] deathFrames;
|
|
|
|
BufferedImage[] animation = SpriteSheetManager.get(SpriteLocation.PACMAN).getAnimation(0);
|
|
spriteMap[Direction.RIGHT.ordinal()] = animation;
|
|
spriteMap[Direction.LEFT.ordinal()] = Arrays.stream(animation)
|
|
.map(i -> LoadSave.rotate(i, Direction.LEFT.angel))
|
|
.toArray(BufferedImage[]::new);
|
|
spriteMap[Direction.DOWN.ordinal()] = Arrays.stream(animation)
|
|
.map(i -> LoadSave.rotate(i, Direction.DOWN.angel))
|
|
.toArray(BufferedImage[]::new);
|
|
spriteMap[Direction.UP.ordinal()] = Arrays.stream(animation)
|
|
.map(i -> LoadSave.rotate(i, Direction.UP.angel))
|
|
.toArray(BufferedImage[]::new);
|
|
deathFrames = Stream.concat(
|
|
Arrays.stream(SpriteSheetManager.get(SpriteLocation.PACMAN).getAnimation(1)),
|
|
Arrays.stream(SpriteSheetManager.get(SpriteLocation.PACMAN).getAnimation(2)))
|
|
.toArray(BufferedImage[]::new);
|
|
return new Sprites(spriteMap, deathFrames);
|
|
}
|
|
|
|
public void draw(Graphics g) {
|
|
switch (state) {
|
|
case ALIVE -> drawAlive(g);
|
|
case DYING -> drawDead(g);
|
|
}
|
|
}
|
|
|
|
private void drawAlive(Graphics g) {
|
|
if (state != PacmanState.ALIVE) return; // ignore if not dying/dead
|
|
g.drawImage(
|
|
sprites.spriteSheets[direction == Direction.NONE ? 0 : direction.ordinal()][aniIndex],
|
|
(int) position.x - PACMAN_OFFSET,
|
|
(int) position.y - PACMAN_OFFSET,
|
|
PACMAN_SIZE,
|
|
PACMAN_SIZE, null);
|
|
}
|
|
|
|
private void drawDead(Graphics g) {
|
|
if (state == PacmanState.ALIVE) return; // ignore if not dying/dead
|
|
|
|
g.drawImage(
|
|
deathFrames[deathFrameIdx],
|
|
(int) position.x - PACMAN_OFFSET,
|
|
(int) position.y - PACMAN_OFFSET,
|
|
PACMAN_SIZE,
|
|
PACMAN_SIZE,
|
|
null
|
|
);
|
|
}
|
|
|
|
public void update() {
|
|
switch (state) {
|
|
case ALIVE -> updateAlive();
|
|
case DYING -> updateDead();
|
|
}
|
|
}
|
|
|
|
private void updateDead() {
|
|
if (state != PacmanState.DYING) return;
|
|
|
|
long now = System.nanoTime();
|
|
while (now - lastChangeNs >= FRAME_NS && deathFrameIdx < sprites.deathFrames.length - 1) { // FRAME_NS has passed and not all frames has been drawn
|
|
deathFrameIdx++;
|
|
lastChangeNs += FRAME_NS; // carry over exact cadence
|
|
}
|
|
}
|
|
|
|
private void updateAlive() {
|
|
if (state != PacmanState.ALIVE) return;
|
|
|
|
if (moving) {
|
|
MyPoint destination = collisionChecker.getValidDestination(direction, getNewPosition(), COLLISION_BOX_SIZE, COLLISION_BOX_SIZE);
|
|
|
|
if (destination != null) {
|
|
position = destination;
|
|
}
|
|
}
|
|
}
|
|
|
|
private MyPoint getNewPosition() {
|
|
return new MyPoint(position.x + direction.dx * getSpeed(), position.y + direction.dy * getSpeed());
|
|
}
|
|
|
|
public void startDeathAnimation() {
|
|
state = PacmanState.DYING;
|
|
deathFrameIdx = 0;
|
|
lastChangeNs = System.nanoTime(); // reset stopwatch right now
|
|
deathFrames = Arrays.stream(sprites.deathFrames)
|
|
.map(img -> LoadSave.rotate(img, direction.angel))
|
|
.toArray(BufferedImage[]::new);
|
|
}
|
|
|
|
private double getSpeed() {
|
|
return BASE_SPEED * levelManager.getPacmanLevelSpeed();
|
|
}
|
|
|
|
public double distanceTo(Point point) {
|
|
return new Point((int) position.x, (int) position.y).distance(point);
|
|
}
|
|
|
|
public void reset() {
|
|
position = startPosition;
|
|
aniIndex = 0; // reset animation to start
|
|
state = PacmanState.ALIVE;
|
|
deathFrameIdx = 0;
|
|
}
|
|
|
|
public Image getLifeIcon() {
|
|
return sprites.spriteSheets[0][1];
|
|
}
|
|
|
|
public Rectangle getBounds() {
|
|
return new Rectangle(
|
|
(int) (position.x - COLLISION_BOX_OFFSET),
|
|
(int) (position.y - COLLISION_BOX_OFFSET),
|
|
COLLISION_BOX_SIZE,
|
|
COLLISION_BOX_SIZE);
|
|
}
|
|
|
|
public Point getPosition() {
|
|
return new Point((int) position.x, (int) position.y);
|
|
}
|
|
|
|
public void setMoving(boolean b) {
|
|
moving = b;
|
|
paused = !b;
|
|
}
|
|
|
|
private enum PacmanState {
|
|
ALIVE, DYING, DEAD
|
|
}
|
|
|
|
record Sprites(BufferedImage[][] spriteSheets, BufferedImage[] deathFrames) {
|
|
}
|
|
}
|