Deathanimation working
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
package se.urmo.game.entities.ghost;
|
package se.urmo.game.entities.ghost;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import se.urmo.game.collision.GhostCollisionChecker;
|
import se.urmo.game.collision.GhostCollisionChecker;
|
||||||
import se.urmo.game.entities.BaseAnimated;
|
import se.urmo.game.entities.BaseAnimated;
|
||||||
@ -13,10 +14,8 @@ import se.urmo.game.map.GameMap;
|
|||||||
import se.urmo.game.state.GhostManager;
|
import se.urmo.game.state.GhostManager;
|
||||||
import se.urmo.game.state.LevelManager;
|
import se.urmo.game.state.LevelManager;
|
||||||
import se.urmo.game.util.Direction;
|
import se.urmo.game.util.Direction;
|
||||||
import se.urmo.game.util.MiscUtil;
|
|
||||||
import se.urmo.game.util.MyPoint;
|
import se.urmo.game.util.MyPoint;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
@ -49,6 +48,8 @@ public class Ghost extends BaseAnimated {
|
|||||||
private final GhostStrategy fearStrategy = new FearStrategy();
|
private final GhostStrategy fearStrategy = new FearStrategy();
|
||||||
private int frightenedTimer = 0;
|
private int frightenedTimer = 0;
|
||||||
private boolean isBlinking = false;
|
private boolean isBlinking = false;
|
||||||
|
@Setter
|
||||||
|
private boolean frozen;
|
||||||
|
|
||||||
|
|
||||||
public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy strategy, GhostStrategy scaterStrategy, int animation, LevelManager levelManager) {
|
public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy strategy, GhostStrategy scaterStrategy, int animation, LevelManager levelManager) {
|
||||||
@ -78,7 +79,7 @@ public class Ghost extends BaseAnimated {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update(PacMan pacman, GameMap map) {
|
public void update(PacMan pacman, GameMap map) {
|
||||||
//updateAnimationTick();
|
if (frozen) return;
|
||||||
if (mode == GhostMode.FRIGHTENED) {
|
if (mode == GhostMode.FRIGHTENED) {
|
||||||
updateInFrightendMode();
|
updateInFrightendMode();
|
||||||
}
|
}
|
||||||
@ -223,4 +224,5 @@ public class Ghost extends BaseAnimated {
|
|||||||
public void resetModes() {
|
public void resetModes() {
|
||||||
mode = GhostMode.CHASE;
|
mode = GhostMode.CHASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,15 @@ import se.urmo.game.util.MyPoint;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PacMan extends BaseAnimated {
|
public class PacMan extends BaseAnimated {
|
||||||
|
private enum PacmanState {
|
||||||
|
ALIVE, DYING, DEAD
|
||||||
|
}
|
||||||
|
|
||||||
public static final int PACMAN_SIZE = 32;
|
public static final int PACMAN_SIZE = 32;
|
||||||
public static final int PACMAN_OFFSET = PACMAN_SIZE / 2;
|
public static final int PACMAN_OFFSET = PACMAN_SIZE / 2;
|
||||||
private static final int COLLISION_BOX_SIZE = 16;
|
private static final int COLLISION_BOX_SIZE = 16;
|
||||||
@ -26,7 +31,7 @@ public class PacMan extends BaseAnimated {
|
|||||||
private static final int ANIMATION_UPDATE_FREQUENCY = 10;
|
private static final int ANIMATION_UPDATE_FREQUENCY = 10;
|
||||||
private static final double BASE_SPEED = 0.40;
|
private static final double BASE_SPEED = 0.40;
|
||||||
private boolean moving = false;
|
private boolean moving = false;
|
||||||
private final BufferedImage[][] spriteSheets;
|
private final BufferedImage[][] spriteSheets;// [row][col]
|
||||||
private MyPoint position;
|
private MyPoint position;
|
||||||
private final CollisionChecker collisionChecker;
|
private final CollisionChecker collisionChecker;
|
||||||
private final LevelManager levelManager;
|
private final LevelManager levelManager;
|
||||||
@ -35,6 +40,16 @@ public class PacMan extends BaseAnimated {
|
|||||||
private Direction direction = Direction.NONE;
|
private Direction direction = Direction.NONE;
|
||||||
private double pacmanLevelSpeed;
|
private double pacmanLevelSpeed;
|
||||||
|
|
||||||
|
private static BufferedImage[] deathFramesBase; // original, flattened [0..7]
|
||||||
|
private BufferedImage[] deathFrames; // working copy
|
||||||
|
private long lastChangeNs;
|
||||||
|
private static final long FRAME_NS = 80_000_000L; // 80 ms
|
||||||
|
|
||||||
|
// animation state
|
||||||
|
@Setter
|
||||||
|
private PacmanState state = PacmanState.ALIVE;
|
||||||
|
private int deathFrameIdx = 0;
|
||||||
|
|
||||||
public PacMan(CollisionChecker collisionChecker, LevelManager levelManager) {
|
public PacMan(CollisionChecker collisionChecker, LevelManager levelManager) {
|
||||||
super(ANIMATION_UPDATE_FREQUENCY, 4);
|
super(ANIMATION_UPDATE_FREQUENCY, 4);
|
||||||
this.collisionChecker = collisionChecker;
|
this.collisionChecker = collisionChecker;
|
||||||
@ -43,13 +58,19 @@ public class PacMan extends BaseAnimated {
|
|||||||
26 * GameMap.MAP_TILESIZE + GameMap.OFFSET_X,
|
26 * GameMap.MAP_TILESIZE + GameMap.OFFSET_X,
|
||||||
13 * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y + ((double) GameMap.MAP_TILESIZE / 2));
|
13 * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y + ((double) GameMap.MAP_TILESIZE / 2));
|
||||||
this.startPosition = this.position;
|
this.startPosition = this.position;
|
||||||
this.spriteSheets = loadAnimation();
|
Sprites spriteSheets1 = loadAnimation();
|
||||||
|
this.spriteSheets = spriteSheets1.spriteSheets;
|
||||||
|
this.deathFramesBase = spriteSheets1.deathFrames;
|
||||||
this.pacmanLevelSpeed = this.levelManager.getPacmanLevelSpeed();
|
this.pacmanLevelSpeed = this.levelManager.getPacmanLevelSpeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage[][] loadAnimation() {
|
record Sprites(BufferedImage[][] spriteSheets, BufferedImage[] deathFrames) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private Sprites loadAnimation() {
|
||||||
BufferedImage[][] image = new BufferedImage[3][4];
|
BufferedImage[][] image = new BufferedImage[3][4];
|
||||||
BufferedImage[][] spriteMap = new BufferedImage[6][4];
|
BufferedImage[][] spriteMap = new BufferedImage[6][4];
|
||||||
|
BufferedImage[] deathFrames;
|
||||||
|
|
||||||
BufferedImage img = LoadSave.GetSpriteAtlas("sprites/PacManAssets-PacMan.png");
|
BufferedImage img = LoadSave.GetSpriteAtlas("sprites/PacManAssets-PacMan.png");
|
||||||
for (int row = 0; row < 3; row++) {
|
for (int row = 0; row < 3; row++) {
|
||||||
@ -59,7 +80,7 @@ public class PacMan extends BaseAnimated {
|
|||||||
}
|
}
|
||||||
spriteMap[Direction.RIGHT.ordinal()] = image[0];
|
spriteMap[Direction.RIGHT.ordinal()] = image[0];
|
||||||
spriteMap[Direction.LEFT.ordinal()] = Arrays.stream(image[0])
|
spriteMap[Direction.LEFT.ordinal()] = Arrays.stream(image[0])
|
||||||
.map(i -> LoadSave.rotate(i, 180))
|
.map(i -> LoadSave.rotate(i, Direction.LEFT.angel))
|
||||||
.toArray(BufferedImage[]::new);
|
.toArray(BufferedImage[]::new);
|
||||||
spriteMap[Direction.DOWN.ordinal()] = Arrays.stream(image[0])
|
spriteMap[Direction.DOWN.ordinal()] = Arrays.stream(image[0])
|
||||||
.map(i -> LoadSave.rotate(i, 90))
|
.map(i -> LoadSave.rotate(i, 90))
|
||||||
@ -67,12 +88,22 @@ public class PacMan extends BaseAnimated {
|
|||||||
spriteMap[Direction.UP.ordinal()] = Arrays.stream(image[0])
|
spriteMap[Direction.UP.ordinal()] = Arrays.stream(image[0])
|
||||||
.map(i -> LoadSave.rotate(i, 270))
|
.map(i -> LoadSave.rotate(i, 270))
|
||||||
.toArray(BufferedImage[]::new);
|
.toArray(BufferedImage[]::new);
|
||||||
spriteMap[4] = image[1];
|
deathFrames = Stream.concat(Arrays.stream(image[1]), Arrays.stream(image[2]))
|
||||||
spriteMap[5] = image[2];
|
.toArray(BufferedImage[]::new);
|
||||||
return spriteMap;
|
|
||||||
|
|
||||||
|
return new Sprites(spriteMap, deathFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(Graphics g) {
|
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(
|
g.drawImage(
|
||||||
spriteSheets[direction == Direction.NONE ? 0 : direction.ordinal()][aniIndex],
|
spriteSheets[direction == Direction.NONE ? 0 : direction.ordinal()][aniIndex],
|
||||||
(int) position.x - PACMAN_OFFSET,
|
(int) position.x - PACMAN_OFFSET,
|
||||||
@ -81,7 +112,39 @@ public class PacMan extends BaseAnimated {
|
|||||||
PACMAN_SIZE, null);
|
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() {
|
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 < deathFramesBase.length - 1) {
|
||||||
|
deathFrameIdx++;
|
||||||
|
lastChangeNs += FRAME_NS; // carry over exact cadence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAlive() {
|
||||||
|
if (state != PacmanState.ALIVE) return;
|
||||||
|
|
||||||
if (moving) {
|
if (moving) {
|
||||||
MyPoint mpoint = switch (direction) {
|
MyPoint mpoint = switch (direction) {
|
||||||
case RIGHT -> new MyPoint(position.x + getSpeed(), position.y);
|
case RIGHT -> new MyPoint(position.x + getSpeed(), position.y);
|
||||||
@ -99,6 +162,21 @@ public class PacMan extends BaseAnimated {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// called by PlayingState when collision (non-frightened)
|
||||||
|
public void startDeathAnimation() {
|
||||||
|
log.info("Starting death animation");
|
||||||
|
state = PacmanState.DYING;
|
||||||
|
deathFrameIdx = 0;
|
||||||
|
lastChangeNs = System.nanoTime(); // reset stopwatch right now
|
||||||
|
deathFrames = Arrays.stream(deathFramesBase)
|
||||||
|
.map(img -> LoadSave.rotate(img, direction.angel))
|
||||||
|
.toArray(BufferedImage[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeathDone() {
|
||||||
|
return state == PacmanState.DEAD;
|
||||||
|
}
|
||||||
|
|
||||||
private double getSpeed() {
|
private double getSpeed() {
|
||||||
return BASE_SPEED * pacmanLevelSpeed;
|
return BASE_SPEED * pacmanLevelSpeed;
|
||||||
}
|
}
|
||||||
@ -114,6 +192,8 @@ public class PacMan extends BaseAnimated {
|
|||||||
public void reset() {
|
public void reset() {
|
||||||
resetPosition();
|
resetPosition();
|
||||||
aniIndex = 0; // reset animation to start
|
aniIndex = 0; // reset animation to start
|
||||||
|
state = PacmanState.ALIVE;
|
||||||
|
deathFrameIdx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Image getLifeIcon() {
|
public Image getLifeIcon() {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package se.urmo.game.state;
|
package se.urmo.game.state;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import se.urmo.game.collision.GhostCollisionChecker;
|
import se.urmo.game.collision.GhostCollisionChecker;
|
||||||
import se.urmo.game.entities.ghost.strategy.BlinkyStrategy;
|
import se.urmo.game.entities.ghost.strategy.BlinkyStrategy;
|
||||||
@ -41,6 +42,7 @@ public class GhostManager {
|
|||||||
5000, 20000, // scatter 5s, chase 20s
|
5000, 20000, // scatter 5s, chase 20s
|
||||||
5000, Integer.MAX_VALUE // scatter 5s, then chase forever
|
5000, Integer.MAX_VALUE // scatter 5s, then chase forever
|
||||||
};
|
};
|
||||||
|
private boolean frozen;
|
||||||
|
|
||||||
public GhostManager(GhostCollisionChecker ghostCollisionChecker, AnimationManager animationManager, LevelManager levelManager) {
|
public GhostManager(GhostCollisionChecker ghostCollisionChecker, AnimationManager animationManager, LevelManager levelManager) {
|
||||||
this.levelManager = levelManager;
|
this.levelManager = levelManager;
|
||||||
@ -64,6 +66,7 @@ public class GhostManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update(PacMan pacman, GameMap map) {
|
public void update(PacMan pacman, GameMap map) {
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (phaseIndex < cycleDurations.length) {
|
if (phaseIndex < cycleDurations.length) {
|
||||||
int duration = cycleDurations[phaseIndex];
|
int duration = cycleDurations[phaseIndex];
|
||||||
@ -97,4 +100,8 @@ public class GhostManager {
|
|||||||
setMode(GhostMode.SCATTER);
|
setMode(GhostMode.SCATTER);
|
||||||
ghosts.forEach(Ghost::resetPosition);
|
ghosts.forEach(Ghost::resetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
this.ghosts.forEach(ghost -> ghost.setFrozen(frozen));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,22 +88,19 @@ public class PlayingState implements GameState {
|
|||||||
case LIFE_LOST -> {
|
case LIFE_LOST -> {
|
||||||
// Freeze, then reset round (keep dot state)
|
// Freeze, then reset round (keep dot state)
|
||||||
if (phaseElapsed() >= LIFE_LOST_MS) {
|
if (phaseElapsed() >= LIFE_LOST_MS) {
|
||||||
|
pacman.reset();
|
||||||
deathInProgress = false;
|
deathInProgress = false;
|
||||||
resetAfterLifeLost();
|
ghostManager.setFrozen(false);
|
||||||
setPhase(RoundPhase.READY);
|
setPhase(RoundPhase.READY);
|
||||||
if (lives <= 0) {
|
if (lives <= 0) {
|
||||||
endGame();
|
endGame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pacman.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetAfterLifeLost() {
|
|
||||||
pacman.reset(); // to start tile, direction stopped
|
|
||||||
ghostManager.reset(); // to house
|
|
||||||
}
|
|
||||||
|
|
||||||
private void advanceLevel() {
|
private void advanceLevel() {
|
||||||
levelManager.nextLevel();
|
levelManager.nextLevel();
|
||||||
map.reset();
|
map.reset();
|
||||||
@ -142,8 +139,8 @@ public class PlayingState implements GameState {
|
|||||||
@Override
|
@Override
|
||||||
public void render(Graphics2D g) {
|
public void render(Graphics2D g) {
|
||||||
map.draw(g);
|
map.draw(g);
|
||||||
pacman.draw(g);
|
|
||||||
ghostManager.draw(g);
|
ghostManager.draw(g);
|
||||||
|
pacman.draw(g);
|
||||||
fruitManager.draw(g);
|
fruitManager.draw(g);
|
||||||
drawUI(g);
|
drawUI(g);
|
||||||
|
|
||||||
@ -215,6 +212,8 @@ public class PlayingState implements GameState {
|
|||||||
ghost.resetPosition();
|
ghost.resetPosition();
|
||||||
ghost.setMode(GhostMode.CHASE); // end frightend
|
ghost.setMode(GhostMode.CHASE); // end frightend
|
||||||
} else {
|
} else {
|
||||||
|
ghostManager.setFrozen(true);
|
||||||
|
pacman.startDeathAnimation();
|
||||||
deathInProgress = true;
|
deathInProgress = true;
|
||||||
// Pac-Man loses a life
|
// Pac-Man loses a life
|
||||||
lives--;
|
lives--;
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
package se.urmo.game.util;
|
package se.urmo.game.util;
|
||||||
|
|
||||||
public enum Direction {
|
public enum Direction {
|
||||||
RIGHT(1, 0),
|
RIGHT(1, 0, 0),
|
||||||
LEFT(-1, 0),
|
LEFT(-1, 0 , 180),
|
||||||
DOWN(0, 1),
|
DOWN(0, 1, 90),
|
||||||
UP(0, -1),
|
UP(0, -1, 270),
|
||||||
NONE(0, 0);
|
NONE(0, 0, 0);
|
||||||
|
|
||||||
public final int dx;
|
public final int dx;
|
||||||
public final int dy;
|
public final int dy;
|
||||||
|
public final int angel;
|
||||||
|
|
||||||
Direction(int dx, int dy) {
|
Direction(int dx, int dy, int angel) {
|
||||||
this.dx = dx;
|
this.dx = dx;
|
||||||
this.dy = dy;
|
this.dy = dy;
|
||||||
|
this.angel = angel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Direction opposite() {
|
public Direction opposite() {
|
||||||
|
|||||||
Reference in New Issue
Block a user