Initial commit
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
10
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Environment-dependent path to Maven home directory
|
||||||
|
/mavenHomeManager.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
7
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/misc.xml
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-24" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
17
pom.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.example</groupId>
|
||||||
|
<artifactId>MyPacman</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
||||||
94
src/main/java/se/urmo/game/Game.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package se.urmo.game;
|
||||||
|
|
||||||
|
import se.urmo.game.state.Playing;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class Game implements Runnable {
|
||||||
|
public final static int FPS_SET = 120;
|
||||||
|
public final static int UPS_SET = 200;
|
||||||
|
private final static double timePerFrame = 1000000000.0 / FPS_SET;
|
||||||
|
private final static double timePerUpdate = 1000000000.0 / UPS_SET;
|
||||||
|
|
||||||
|
|
||||||
|
private Thread gameThread;
|
||||||
|
private final JFrame window = new JFrame();
|
||||||
|
private final GamePanel gamePanel = new GamePanel(this);
|
||||||
|
private Playing playing = new Playing(this);
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
window.setResizable(false);
|
||||||
|
window.setTitle("2d");
|
||||||
|
|
||||||
|
|
||||||
|
window.add(gamePanel);
|
||||||
|
|
||||||
|
window.pack();
|
||||||
|
|
||||||
|
window.setLocationRelativeTo(null);
|
||||||
|
window.setVisible(true);
|
||||||
|
|
||||||
|
startGameThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startGameThread() {
|
||||||
|
this.gameThread = new Thread(this);
|
||||||
|
gameThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long previousTime = System.nanoTime();
|
||||||
|
|
||||||
|
int frames = 0;
|
||||||
|
int updates = 0;
|
||||||
|
long lastCheck = System.currentTimeMillis();
|
||||||
|
|
||||||
|
double deltaU = 0;
|
||||||
|
double deltaF = 0;
|
||||||
|
|
||||||
|
while (gameThread !=null && gameThread.isAlive()) {
|
||||||
|
long currentTime = System.nanoTime();
|
||||||
|
deltaU += (currentTime - previousTime) / timePerUpdate;
|
||||||
|
deltaF += (currentTime - previousTime) / timePerFrame;
|
||||||
|
|
||||||
|
previousTime = currentTime;
|
||||||
|
|
||||||
|
if (deltaU >= 1) {
|
||||||
|
update();
|
||||||
|
updates++;
|
||||||
|
deltaU--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltaF >= 1) {
|
||||||
|
gamePanel.repaint(); // triggers JPanel paintComponent
|
||||||
|
frames++;
|
||||||
|
deltaF--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() - lastCheck >= 1000) {
|
||||||
|
lastCheck = System.currentTimeMillis();
|
||||||
|
System.out.println("FPS: " + frames + " | UPS: " + updates);
|
||||||
|
frames = 0;
|
||||||
|
updates = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update() {
|
||||||
|
playing.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Playing getPlaying() {
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
public GamePanel getGamePanel() {
|
||||||
|
return gamePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render(Graphics g) {
|
||||||
|
playing.draw(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/main/java/se/urmo/game/GameMap.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package se.urmo.game;
|
||||||
|
|
||||||
|
import se.urmo.game.map.MapTile;
|
||||||
|
import se.urmo.game.util.LoadSave;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
public class GameMap {
|
||||||
|
public static final int MAP_TILESIZE = 16;// 16px from left
|
||||||
|
public static final int OFFSET_Y = 7 * MAP_TILESIZE; // 160px from top
|
||||||
|
public static final int OFFSET_X = MAP_TILESIZE; // 16px from left
|
||||||
|
private BufferedImage[][] image = new BufferedImage[13][19];
|
||||||
|
private MapTile[][] mapData;
|
||||||
|
|
||||||
|
public GameMap() {
|
||||||
|
loadMap("maps/map1.csv");
|
||||||
|
loadSprites();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMap(String path) {
|
||||||
|
List<int[]> rows = new ArrayList<>();
|
||||||
|
try (InputStream is = getClass().getClassLoader().getResourceAsStream(path);
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) {
|
||||||
|
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
String[] tokens = line.split(",");
|
||||||
|
int[] row = new int[tokens.length];
|
||||||
|
for (int i = 0; i < tokens.length; i++) {
|
||||||
|
row[i] = Integer.parseInt(tokens[i].trim());
|
||||||
|
}
|
||||||
|
rows.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException | NullPointerException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
mapData = rows.stream()
|
||||||
|
.map(row -> IntStream.of(row)
|
||||||
|
.mapToObj(MapTile::new)
|
||||||
|
.toArray(MapTile[]::new))
|
||||||
|
.toArray(MapTile[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSprites() {
|
||||||
|
BufferedImage img = LoadSave.GetSpriteAtlas("sprites/PacManAssets_Map_TileSet2.png");//473B78
|
||||||
|
for (int row = 0; row < 13; row++) {
|
||||||
|
for (int col = 0; col < 19; col++) {
|
||||||
|
image[row][col] = img.getSubimage(MAP_TILESIZE * col, MAP_TILESIZE * row, MAP_TILESIZE, MAP_TILESIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Graphics g) {
|
||||||
|
for (int row = 0; row < mapData.length; row++) {
|
||||||
|
for (int col = 0; col < mapData[row].length; col++) {
|
||||||
|
MapTile tile = mapData[row][col];
|
||||||
|
|
||||||
|
BufferedImage tileImage = tile.getImage();
|
||||||
|
|
||||||
|
if (tileImage != null) {
|
||||||
|
int x = OFFSET_X + col * MAP_TILESIZE;
|
||||||
|
int y = OFFSET_Y + row * MAP_TILESIZE;
|
||||||
|
g.drawImage(tileImage, x, y, MAP_TILESIZE, MAP_TILESIZE, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPassable(int x, int y) {
|
||||||
|
int row = (y - OFFSET_Y) / MAP_TILESIZE;
|
||||||
|
int col = (x - OFFSET_X) / MAP_TILESIZE;
|
||||||
|
|
||||||
|
if (row > mapData.length - 1) row = 0;
|
||||||
|
if (col > mapData[row].length - 1) col = 0;
|
||||||
|
if (row < 0) row = mapData.length - 1;
|
||||||
|
if (col < 0) col = mapData[row].length - 1;
|
||||||
|
|
||||||
|
boolean passable = mapData[row][col].isPassable();
|
||||||
|
System.out.println(row + "," + col + "is" +(passable?"":" not") + " passable");
|
||||||
|
return passable;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/main/java/se/urmo/game/GamePanel.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package se.urmo.game;
|
||||||
|
|
||||||
|
import se.urmo.game.input.KeyHandler;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class GamePanel extends JPanel {
|
||||||
|
public static final int ORIGINAL_TILE_SIZE = 8;
|
||||||
|
public static final int SCALE = 1;
|
||||||
|
public static final int TILE_SIZE = ORIGINAL_TILE_SIZE * SCALE;
|
||||||
|
public static final int MAX_SCREEN_COL = 60;
|
||||||
|
public static final int MAX_SCREEN_ROW = 80;
|
||||||
|
public static final int SCREEN_WIDTH = MAX_SCREEN_COL * TILE_SIZE;
|
||||||
|
public static final int SCREEN_HEIGHT = MAX_SCREEN_ROW * TILE_SIZE;
|
||||||
|
private final Game game;
|
||||||
|
|
||||||
|
|
||||||
|
KeyHandler keyHandler = new KeyHandler(this);
|
||||||
|
|
||||||
|
public GamePanel(Game game) {
|
||||||
|
this.game = game;
|
||||||
|
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
|
||||||
|
this.setBackground(Color.decode("#473B78"));
|
||||||
|
this.setDoubleBuffered(true);
|
||||||
|
this.addKeyListener(keyHandler);
|
||||||
|
this.setFocusable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void paintComponent(Graphics g) {
|
||||||
|
super.paintComponent(g);
|
||||||
|
// Cast to Graphics2D for better control (optional)
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
// Fill full background with purple border
|
||||||
|
g2d.setColor(new Color(71, 59, 120)); // #473B78
|
||||||
|
g2d.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
|
// Fill the inner map background with dark (almost black)
|
||||||
|
int border = 16;
|
||||||
|
g2d.setColor(new Color(20, 20, 28)); // Dark bluish-black like in the image
|
||||||
|
g2d.fillRect(border, border, getWidth() - 2 * border, getHeight() - 2 * border);
|
||||||
|
game.render(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
game.getPlaying().keyPressed(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keyReleased(KeyEvent e) {
|
||||||
|
game.getPlaying().keyReleased();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/main/java/se/urmo/game/Main.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package se.urmo.game;
|
||||||
|
|
||||||
|
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
|
||||||
|
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new Game().start();
|
||||||
|
}
|
||||||
|
}
|
||||||
148
src/main/java/se/urmo/game/PacMan.java
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package se.urmo.game;
|
||||||
|
|
||||||
|
import se.urmo.game.state.CollisionChecker;
|
||||||
|
import se.urmo.game.util.LoadSave;
|
||||||
|
import se.urmo.game.util.MiscUtil;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
|
public class PacMan {
|
||||||
|
private static final int RIGHT = 0;
|
||||||
|
private static final int LEFT = 1;
|
||||||
|
private static final int DOWN = 2;
|
||||||
|
private static final int UP = 3;
|
||||||
|
public static final int PACMAN_SIZE = GamePanel.TILE_SIZE;
|
||||||
|
private final Game game;
|
||||||
|
private int aniTick = 0;
|
||||||
|
private int aniIndex = 0;
|
||||||
|
private static final int ANIMATION_UPDATE_FREQUENCY = 25;
|
||||||
|
private int speed = 1;
|
||||||
|
private boolean moving;
|
||||||
|
private final BufferedImage[][] movmentImages = new BufferedImage[4][4];
|
||||||
|
private int direction = 0;
|
||||||
|
private int xPos = 14*16 + 8, yPos = 29*16; // top left of object
|
||||||
|
private static final BufferedImage innerBox = MiscUtil.createOutlinedBox(8,8, Color.yellow, 2);
|
||||||
|
private CollisionChecker collisionChecker;
|
||||||
|
|
||||||
|
public PacMan(Game game, CollisionChecker collisionChecker) {
|
||||||
|
this.game = game;
|
||||||
|
this.collisionChecker = collisionChecker;
|
||||||
|
loadAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAnimation() {
|
||||||
|
BufferedImage[][] image = new BufferedImage[3][4];
|
||||||
|
|
||||||
|
BufferedImage img = LoadSave.GetSpriteAtlas("sprites/PacManAssets-PacMan.png");
|
||||||
|
for (int row = 0; row < 3; row++) {
|
||||||
|
for (int col = 0; col < 4; col++) {
|
||||||
|
image[row][col] = img.getSubimage(32 * col, 32 * row, PACMAN_SIZE, PACMAN_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
movmentImages[RIGHT] = image[0];
|
||||||
|
movmentImages[LEFT] = Arrays.stream(image[0])
|
||||||
|
.map(i -> LoadSave.rotate(i, 180))
|
||||||
|
.toArray(BufferedImage[]::new);
|
||||||
|
movmentImages[DOWN] = Arrays.stream(image[0])
|
||||||
|
.map(i -> LoadSave.rotate(i, 90))
|
||||||
|
.toArray(BufferedImage[]::new);
|
||||||
|
movmentImages[UP] = Arrays.stream(image[0])
|
||||||
|
.map(i -> LoadSave.rotate(i, 270))
|
||||||
|
.toArray(BufferedImage[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void draw(Graphics g) {
|
||||||
|
g.drawImage(innerBox, xPos, yPos, PACMAN_SIZE, PACMAN_SIZE, null);
|
||||||
|
//g.setColor(Color.RED);
|
||||||
|
//g.drawLine(xPos, yPos, xPos, yPos);
|
||||||
|
//g.drawImage(movmentImages[direction][aniIndex], xPos, yPos, PACMAN_SIZE, PACMAN_SIZE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeft() {
|
||||||
|
direction = LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRight() {
|
||||||
|
direction = RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUp() {
|
||||||
|
direction = UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDown() {
|
||||||
|
direction = DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
//System.out.println("Pacman current pos: " + xPos + ", " + yPos);
|
||||||
|
updateAnimationTick();
|
||||||
|
int nextTileX = xPos;
|
||||||
|
int nextTileY = yPos;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case RIGHT: {
|
||||||
|
nextTileX++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LEFT:
|
||||||
|
nextTileX--;
|
||||||
|
break;
|
||||||
|
case UP:
|
||||||
|
nextTileY--;
|
||||||
|
break;
|
||||||
|
case DOWN:
|
||||||
|
nextTileY++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (moving && collisionChecker.canMoveTo(direction, nextTileX, nextTileY, PACMAN_SIZE, PACMAN_SIZE)) {
|
||||||
|
//boolean b = collisionChecker.canMoveTo(direction, nextTileX, nextTileY, PACMAN_SIZE, PACMAN_SIZE);
|
||||||
|
//System.out.println("Can" + (b ? " " : "not ") + "move to (" +nextTileX+ "," + nextTileY + ")");
|
||||||
|
updatePosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePosition() {
|
||||||
|
if(moving) {
|
||||||
|
switch (direction) {
|
||||||
|
case RIGHT -> {
|
||||||
|
if(xPos + PACMAN_SIZE < GamePanel.SCREEN_WIDTH) xPos += speed;
|
||||||
|
}
|
||||||
|
case LEFT -> {
|
||||||
|
if(xPos > 0) xPos -= speed;
|
||||||
|
else xPos = 0;
|
||||||
|
}
|
||||||
|
case DOWN -> {
|
||||||
|
if(yPos + PACMAN_SIZE < GamePanel.SCREEN_HEIGHT) yPos += speed;
|
||||||
|
}
|
||||||
|
case UP ->{
|
||||||
|
if(yPos > 0) yPos -= speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAnimationTick() {
|
||||||
|
if (moving) {
|
||||||
|
aniTick++;
|
||||||
|
if (aniTick >= ANIMATION_UPDATE_FREQUENCY) {
|
||||||
|
aniTick = 0;
|
||||||
|
aniIndex++;
|
||||||
|
if (aniIndex >= 3) {
|
||||||
|
aniIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMoving(boolean moving) {
|
||||||
|
this.moving = moving;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/se/urmo/game/input/KeyHandler.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package se.urmo.game.input;
|
||||||
|
|
||||||
|
import se.urmo.game.GamePanel;
|
||||||
|
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.KeyListener;
|
||||||
|
|
||||||
|
public class KeyHandler implements KeyListener {
|
||||||
|
private final GamePanel gamePanel;
|
||||||
|
|
||||||
|
public KeyHandler(GamePanel gamePanel) {
|
||||||
|
this.gamePanel = gamePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyTyped(KeyEvent e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
gamePanel.keyPressed(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyReleased(KeyEvent e) {
|
||||||
|
gamePanel.keyReleased(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/main/java/se/urmo/game/map/MapTile.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package se.urmo.game.map;
|
||||||
|
|
||||||
|
import se.urmo.game.util.MiscUtil;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public class MapTile {
|
||||||
|
private final int value;
|
||||||
|
private final BufferedImage image;
|
||||||
|
private final boolean solid;
|
||||||
|
|
||||||
|
public MapTile(int value) {
|
||||||
|
this.value = value;
|
||||||
|
this.image = value != 0 ? MiscUtil.createOutlinedBox(16, 16, Color.blue, 2) : null;
|
||||||
|
this.solid = value != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getImage() {
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPassable() {
|
||||||
|
return ! this.solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/main/java/se/urmo/game/state/CollisionChecker.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package se.urmo.game.state;
|
||||||
|
|
||||||
|
import se.urmo.game.GameMap;
|
||||||
|
|
||||||
|
public class CollisionChecker {
|
||||||
|
private GameMap map;
|
||||||
|
|
||||||
|
public CollisionChecker(GameMap map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canMoveTo(int dir, int objectLeft, int objectTop, int width, int height) {
|
||||||
|
int objectRight = objectLeft + width;
|
||||||
|
int objectBotton = objectTop + height;
|
||||||
|
return switch (dir){
|
||||||
|
case 0 -> isPassibleRight(objectRight, objectTop, objectBotton);// right
|
||||||
|
case 1 -> isPassibleLeft(objectLeft, objectTop, objectBotton);// right
|
||||||
|
case 2 -> isPassibleBottom(objectLeft, objectRight, objectBotton);
|
||||||
|
case 3 -> isPassibleTop(objectLeft, objectRight, objectTop);// right
|
||||||
|
default -> throw new IllegalArgumentException("Invalid dir: " + dir);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPassibleBottom(int objectLeft, int objectRight, int objectBotton) {
|
||||||
|
return map.isPassable(objectLeft, objectBotton) && map.isPassable(objectRight, objectBotton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPassibleTop(int objectLeft, int objectRight, int objectTop) {
|
||||||
|
return map.isPassable(objectLeft, objectTop) && map.isPassable(objectRight, objectTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPassibleLeft(int objectLeft, int objectTop, int objectBotton) {
|
||||||
|
return map.isPassable(objectLeft, objectTop) && map.isPassable(objectLeft, objectBotton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPassibleRight(int objectRight, int objectTop, int objectBotton) {
|
||||||
|
return map.isPassable(objectRight, objectTop) && map.isPassable(objectRight, objectBotton);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/main/java/se/urmo/game/state/Playing.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package se.urmo.game.state;
|
||||||
|
|
||||||
|
import se.urmo.game.Game;
|
||||||
|
import se.urmo.game.GameMap;
|
||||||
|
import se.urmo.game.PacMan;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
|
||||||
|
public class Playing {
|
||||||
|
private PacMan pacMan;
|
||||||
|
private GameMap map = new GameMap();
|
||||||
|
private final CollisionChecker collisionChecker = new CollisionChecker(map);
|
||||||
|
public Playing(Game game) {
|
||||||
|
this.pacMan = new PacMan(game, collisionChecker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
pacMan.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
pacMan.setMoving(true);
|
||||||
|
switch (e.getKeyCode()) {
|
||||||
|
case KeyEvent.VK_A -> pacMan.setLeft();
|
||||||
|
case KeyEvent.VK_D -> pacMan.setRight();
|
||||||
|
case KeyEvent.VK_W -> pacMan.setUp();
|
||||||
|
case KeyEvent.VK_S -> pacMan.setDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Graphics g) {
|
||||||
|
map.draw(g);
|
||||||
|
pacMan.draw(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keyReleased() {
|
||||||
|
pacMan.setMoving(false);
|
||||||
|
}
|
||||||
|
public GameMap getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/main/java/se/urmo/game/util/LoadSave.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package se.urmo.game.util;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class LoadSave {
|
||||||
|
|
||||||
|
public static BufferedImage GetSpriteAtlas(String fileName) {
|
||||||
|
BufferedImage img = null;
|
||||||
|
InputStream is = LoadSave.class.getResourceAsStream("/" + fileName);
|
||||||
|
try {
|
||||||
|
img = ImageIO.read(is);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedImage rotate(BufferedImage img, double angleDegrees) {
|
||||||
|
double angleRadians = Math.toRadians(angleDegrees);
|
||||||
|
|
||||||
|
// Bildens dimensioner
|
||||||
|
int w = img.getWidth();
|
||||||
|
int h = img.getHeight();
|
||||||
|
|
||||||
|
// Skapa en ny bild med samma storlek och stöd för transparens
|
||||||
|
BufferedImage rotated = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = rotated.createGraphics();
|
||||||
|
|
||||||
|
// För bästa kvalitet
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
// Skapa transform: rotera runt bildens mittpunkt
|
||||||
|
AffineTransform transform = new AffineTransform();
|
||||||
|
transform.rotate(angleRadians, w / 2.0, h / 2.0);
|
||||||
|
|
||||||
|
// Rita originalbilden med transform
|
||||||
|
g2d.setTransform(transform);
|
||||||
|
g2d.drawImage(img, 0, 0, null);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
return rotated;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/se/urmo/game/util/MiscUtil.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package se.urmo.game.util;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public class MiscUtil {
|
||||||
|
public static BufferedImage createOutlinedBox(int width, int height, Color outlineColor, float lineThickness) {
|
||||||
|
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = image.createGraphics();
|
||||||
|
|
||||||
|
// Optional: Disable anti-aliasing for pixel-perfect edges
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||||
|
|
||||||
|
// Set color and stroke
|
||||||
|
g2d.setColor(outlineColor);
|
||||||
|
g2d.setStroke(new BasicStroke(lineThickness));
|
||||||
|
|
||||||
|
// To avoid clipping, draw inside the bounds.
|
||||||
|
// Adjust for stroke thickness to stay within image bounds.
|
||||||
|
float halfStroke = lineThickness / 2f;
|
||||||
|
g2d.drawRect(
|
||||||
|
Math.round(halfStroke),
|
||||||
|
Math.round(halfStroke),
|
||||||
|
Math.round(width - lineThickness),
|
||||||
|
Math.round(height - lineThickness)
|
||||||
|
);
|
||||||
|
|
||||||
|
g2d.dispose();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/resources/maps/map1.csv
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,18,19, 21, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3
|
||||||
|
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1,20, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1,17, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 9
|
||||||
|
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 9
|
||||||
|
7, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 9
|
||||||
|
4, 5, 5, 5, 5,10, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0,11, 5, 5, 5, 5, 6
|
||||||
|
0, 0, 0, 0, 0,12, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0,13, 0, 0, 0, 0, 0
|
||||||
|
0, 0, 0, 0, 0,12, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,13, 0, 0, 0, 0, 0
|
||||||
|
0, 0, 0, 0, 0,12, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,13, 0, 0, 0, 0, 0
|
||||||
|
15,15,15,15,15,14, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,16,15,15,15,15,15
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
5, 5, 5, 5, 5,10, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,11, 5, 5, 5, 5, 5
|
||||||
|
0, 0, 0, 0, 0,12, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,13, 0, 0, 0, 0, 0
|
||||||
|
0, 0, 0, 0, 0,12, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,13, 0, 0, 0, 0, 0
|
||||||
|
0, 0, 0, 0, 0,12, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,13, 0, 0, 0, 0, 0
|
||||||
|
1,15,15,15,15,14, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,16, 2, 2, 2, 2, 3
|
||||||
|
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 9
|
||||||
|
21, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 9
|
||||||
|
22,24,25, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 9
|
||||||
|
23, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 9
|
||||||
|
7, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9
|
||||||
|
7, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9
|
||||||
|
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9
|
||||||
|
4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6
|
||||||
|
BIN
src/main/resources/sprites/PacManAssets-Ghosts.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/main/resources/sprites/PacManAssets-Items.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/sprites/PacManAssets-PacMan.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/main/resources/sprites/PacManAssets_Map.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/sprites/PacManAssets_Map_1.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src/main/resources/sprites/PacManAssets_Map_2.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
src/main/resources/sprites/PacManAssets_Map_3.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src/main/resources/sprites/PacManAssets_Map_Back&Maze.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/main/resources/sprites/PacManAssets_Map_TileSet.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/main/resources/sprites/PacManAssets_Map_TileSet2.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/main/resources/sprites/pear36-8x.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
src/main/resources/sprites/sprite_atlas.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |