Compare commits

..

3 Commits

Author SHA1 Message Date
7c79a309e7 Refactored Pacman positioning and speed - not uses Double x,y 2025-08-30 20:52:22 +02:00
014c9ea2ce Added LevelManager 2025-08-30 13:44:54 +02:00
4597279d9e changed UPS to 200 2025-08-30 13:44:39 +02:00
7 changed files with 139 additions and 49 deletions

View File

@ -2,6 +2,7 @@ package se.urmo.game.collision;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import se.urmo.game.entities.MyPoint;
import se.urmo.game.main.GamePanel; import se.urmo.game.main.GamePanel;
import se.urmo.game.util.Direction; import se.urmo.game.util.Direction;
import se.urmo.game.map.GameMap; import se.urmo.game.map.GameMap;
@ -19,52 +20,89 @@ public class CollisionChecker {
this.map = map; this.map = map;
} }
public Point getValidDestination(Direction direction, Point position, int agent_width, int agent_height) { // public Point getValidDestination(Direction direction, Point position, int agent_width, int agent_height) {
List<Point> boundaries = switch (direction) { // List<Point> boundaries = switch (direction) {
// case NONE -> Collections.emptyList();
// case RIGHT, LEFT -> List.of(
// new Point(position.x + (direction.dx * agent_width/2), position.y - agent_height/2),
// new Point(position.x + (direction.dx * agent_width/2), position.y + agent_height/2)
// );
// case UP, DOWN -> List.of(
// new Point(position.x - agent_width/2, position.y + (direction.dy * agent_height/2)),
// new Point(position.x + agent_width/2, position.y + (direction.dy * agent_height/2))
// );
// };
//
// List<Pair> bs = boundaries.stream().map(p -> new Pair(p.x, p.y, GameMap.screenToRow(p.y), GameMap.screenToCol(p.x))).toList();
// log.debug("{} boundaries for {} are {}", direction, position, bs);
//
// List<Point> normalized = boundaries.stream()
// .map(p -> normalizePosition(direction, p, agent_width, agent_height))
// .toList();
//
// if (map.isPassable(normalized)) {
// return normalizePosition(direction, position, agent_width, agent_height);
// }
// return null; // Blocked
// }
// /**
// * Applies specific rules to movement
// * This, for instance, makes sure the tunnel left/right works.
// *
// * @param dir
// * @param pos
// * @param agent_width
// * @param agent_height
// * @return
// */
// public Point normalizePosition(Direction dir, Point pos, int agent_width, int agent_height) {
// int x = pos.x;
// int y = pos.y;
// int width = GamePanel.SCREEN_WIDTH;
// int height = GamePanel.SCREEN_HEIGHT;
//
// // tunnel
// if (x < GameMap.OFFSET_X) x = width - agent_width/2 - GameMap.OFFSET_X; // right
// if (x >= (width - GameMap.OFFSET_X)) x = GameMap.OFFSET_X; // left
//
// return new Point(x, y);
// }
public MyPoint getValidDestination(Direction direction, MyPoint position, int agent_width, int agent_height) {
List<MyPoint> boundaries = switch (direction) {
case NONE -> Collections.emptyList(); case NONE -> Collections.emptyList();
case RIGHT, LEFT -> List.of( case RIGHT, LEFT -> List.of(
new Point(position.x + (direction.dx * agent_width/2), position.y - agent_height/2), new MyPoint(position.x + (direction.dx * agent_width/2), position.y - agent_height/2),
new Point(position.x + (direction.dx * agent_width/2), position.y + agent_height/2) new MyPoint(position.x + (direction.dx * agent_width/2), position.y + agent_height/2)
); );
case UP, DOWN -> List.of( case UP, DOWN -> List.of(
new Point(position.x - agent_width/2, position.y + (direction.dy * agent_height/2)), new MyPoint(position.x - agent_width/2, position.y + (direction.dy * agent_height/2)),
new Point(position.x + agent_width/2, position.y + (direction.dy * agent_height/2)) new MyPoint(position.x + agent_width/2, position.y + (direction.dy * agent_height/2))
); );
}; };
List<Pair> bs = boundaries.stream().map(p -> new Pair(p.x, p.y, GameMap.screenToRow(p.y), GameMap.screenToCol(p.x))).toList(); List<MyPoint> normalized = boundaries.stream()
log.debug("{} boundaries for {} are {}", direction, position, bs); .map(p -> normalizePosition(direction, p.x, p.y, agent_width, agent_height))
List<Point> normalized = boundaries.stream()
.map(p -> normalizePosition(direction, p, agent_width, agent_height))
.toList(); .toList();
if (map.isPassable(normalized)) { boolean passable = normalized.stream().allMatch(myPoint -> map.isPassable((int) myPoint.x, (int) myPoint.y));
return normalizePosition(direction, position, agent_width, agent_height); if (passable) {
return normalizePosition(direction, position.x, position.y, agent_width, agent_height);
} }
return null; // Blocked return null; // Blocked
} }
/** private MyPoint normalizePosition(Direction direction, double x, double y, int agent_width, int agent_height) {
* Applies specific rules to movement double x1 = x;
* This, for instance, makes sure the tunnel left/right works. double y1 = y;
*
* @param dir
* @param pos
* @param agent_width
* @param agent_height
* @return
*/
public Point normalizePosition(Direction dir, Point pos, int agent_width, int agent_height) {
int x = pos.x;
int y = pos.y;
int width = GamePanel.SCREEN_WIDTH; int width = GamePanel.SCREEN_WIDTH;
int height = GamePanel.SCREEN_HEIGHT; int height = GamePanel.SCREEN_HEIGHT;
// tunnel // tunnel
if (x < GameMap.OFFSET_X) x = width - agent_width/2 - GameMap.OFFSET_X; // right if (x < GameMap.OFFSET_X) x1 = width - agent_width/2 - GameMap.OFFSET_X; // right
if (x >= (width - GameMap.OFFSET_X)) x = GameMap.OFFSET_X; // left if (x>= (width - GameMap.OFFSET_X)) x1 = GameMap.OFFSET_X; // left
return new Point(x, y); return new MyPoint(x1, y1);
} }
} }

View File

@ -0,0 +1,34 @@
package se.urmo.game.entities;
public class MyPoint {
public final double x;
public final double y;
public MyPoint(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "MyPoint{" +
"x=" + x +
", y=" + y +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
MyPoint myPoint = (MyPoint) o;
return Double.compare(x, myPoint.x) == 0 && Double.compare(y, myPoint.y) == 0;
}
@Override
public int hashCode() {
int result = Double.hashCode(x);
result = 31 * result + Double.hashCode(y);
return result;
}
}

View File

@ -22,16 +22,16 @@ public class PacMan {
private static final int COLLISION_BOX_SIZE = 16; 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 COLLISION_BOX_OFFSET = (PACMAN_SIZE - COLLISION_BOX_SIZE) / 2;
private final Game game; private final Game game;
private final Point startPosition; //private final Point startPosition;
private final MyPoint startPosition;
private int aniTick = 0; private int aniTick = 0;
private int aniIndex = 0; private int aniIndex = 0;
private static final int ANIMATION_UPDATE_FREQUENCY = 10; private static final int ANIMATION_UPDATE_FREQUENCY = 10;
private int speed = 1; private static final double BASE_SPEED = 0.40;
@Setter @Setter
private boolean moving; private boolean moving;
private final BufferedImage[][] movmentImages = new BufferedImage[4][4]; private final BufferedImage[][] movmentImages = new BufferedImage[4][4];
@Getter private MyPoint position;
private Point position;
private static final BufferedImage COLLISION_BOX = MiscUtil.createOutlinedBox(COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, Color.yellow, 2); private static final BufferedImage COLLISION_BOX = MiscUtil.createOutlinedBox(COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, Color.yellow, 2);
private final CollisionChecker collisionChecker; private final CollisionChecker collisionChecker;
@Setter @Setter
@ -41,9 +41,9 @@ public class PacMan {
public PacMan(Game game, CollisionChecker collisionChecker) { public PacMan(Game game, CollisionChecker collisionChecker) {
this.game = game; this.game = game;
this.collisionChecker = collisionChecker; this.collisionChecker = collisionChecker;
this.position = new Point( this.position = new MyPoint(
26 * GameMap.MAP_TILESIZE + GameMap.OFFSET_X, 26 * GameMap.MAP_TILESIZE + GameMap.OFFSET_X,
13 * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y + (GameMap.MAP_TILESIZE / 2)); 13 * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y + ((double) GameMap.MAP_TILESIZE / 2));
this.startPosition = this.position; this.startPosition = this.position;
loadAnimation(); loadAnimation();
} }
@ -72,8 +72,8 @@ public class PacMan {
public void draw(Graphics g) { public void draw(Graphics g) {
g.drawImage( g.drawImage(
movmentImages[direction==Direction.NONE ? 0 : direction.ordinal()][aniIndex], movmentImages[direction==Direction.NONE ? 0 : direction.ordinal()][aniIndex],
position.x - PACMAN_OFFSET, (int) position.x - PACMAN_OFFSET,
position.y - PACMAN_OFFSET, (int) position.y - PACMAN_OFFSET,
PACMAN_SIZE, PACMAN_SIZE,
PACMAN_SIZE, null); 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);
@ -84,15 +84,16 @@ public class PacMan {
public void update() { public void update() {
updateAnimationTick(); updateAnimationTick();
if(moving) { if(moving) {
Point newPosition = switch (direction) { log.debug("Moving to {}", direction);
case RIGHT -> new Point(position.x + speed, position.y); MyPoint mpoint = switch (direction) {
case LEFT -> new Point(position.x - speed, position.y); case RIGHT -> new MyPoint(position.x + getSpeed(), position.y);
case UP -> new Point(position.x, position.y - speed); case LEFT -> new MyPoint(position.x - getSpeed(), position.y);
case DOWN -> new Point(position.x, position.y + speed); case UP -> new MyPoint(position.x, position.y - getSpeed());
case DOWN -> new MyPoint(position.x, position.y + getSpeed());
default -> throw new IllegalStateException("Unexpected value: " + direction); default -> throw new IllegalStateException("Unexpected value: " + direction);
}; };
log.debug("At: {},trying to move {} to {}", position, direction.name(), newPosition);
Point destination = collisionChecker.getValidDestination(direction, newPosition, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE); MyPoint destination = collisionChecker.getValidDestination(direction, mpoint, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE);
if (destination != null) { if (destination != null) {
position = destination; position = destination;
@ -100,6 +101,10 @@ public class PacMan {
} }
} }
private double getSpeed() {
return BASE_SPEED * 0.8;
}
private void updateAnimationTick() { private void updateAnimationTick() {
if (moving) { if (moving) {
aniTick++; aniTick++;
@ -115,7 +120,7 @@ public class PacMan {
} }
public double distanceTo(Point point) { public double distanceTo(Point point) {
return position.distance(point); return new Point((int) position.x, (int) position.y).distance(point);
} }
public void loseLife() { public void loseLife() {
@ -131,6 +136,10 @@ public class PacMan {
} }
public Rectangle getBounds() { public Rectangle getBounds() {
return new Rectangle(position.x - COLLISION_BOX_OFFSET, position.y - COLLISION_BOX_OFFSET, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE); 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);
} }
} }

View File

@ -9,7 +9,7 @@ import javax.swing.*;
@Slf4j @Slf4j
public class Game implements Runnable { public class Game implements Runnable {
public final static int FPS_SET = 120; public final static int FPS_SET = 120;
public final static int UPS_SET = 120; public final static int UPS_SET = 200;
private final static double timePerFrame = 1000000000.0 / FPS_SET; private final static double timePerFrame = 1000000000.0 / FPS_SET;
private final static double timePerUpdate = 1000000000.0 / UPS_SET; private final static double timePerUpdate = 1000000000.0 / UPS_SET;

View File

@ -8,13 +8,18 @@ import java.awt.Graphics;
@Slf4j @Slf4j
public class FruitManager { public class FruitManager {
private final LevelManager levelManager;
private Fruit activeFruit; private Fruit activeFruit;
private int dotsEaten = 0; private int dotsEaten = 0;
public FruitManager(LevelManager levelManager) {
this.levelManager = levelManager;
}
public void dotEaten() { public void dotEaten() {
dotsEaten++; dotsEaten++;
if (dotsEaten == 10 || dotsEaten == 170) { if (dotsEaten == 10 || dotsEaten == 170) {
spawnFruit(1); spawnFruit(levelManager.getLevel());
} }
} }

View File

@ -1,4 +1,8 @@
package se.urmo.game.state; package se.urmo.game.state;
import lombok.Getter;
public class LevelManager { public class LevelManager {
@Getter
private int level = 1;
} }

View File

@ -37,8 +37,8 @@ public class PlayingState implements GameState {
this.map = new GameMap("maps/map1.csv"); this.map = new GameMap("maps/map1.csv");
this.pacman = new PacMan(game, new CollisionChecker(map)); this.pacman = new PacMan(game, new CollisionChecker(map));
this.ghostManager = new GhostManager(new GhostCollisionChecker(map)); this.ghostManager = new GhostManager(new GhostCollisionChecker(map));
this.fruitManager = new FruitManager();
this.levelManager = new LevelManager(); this.levelManager = new LevelManager();
this.fruitManager = new FruitManager(levelManager);
this.arcadeFont = loadArcadeFont(); this.arcadeFont = loadArcadeFont();
} }