Initial commit
This commit is contained in:
94
src/main/java/se/urmo/game/Game.java
Normal file
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
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
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
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
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
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
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
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
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
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
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user