Я пытаюсь нарисовать изометрические плитки на Java и реализовать систему выбора плитки с помощью курсора мыши. Я рисую плитки, используя эти математические формулы, которые я нашел и адаптировал к своим текстурам плитки, которые вы можете найти ниже. Плитки имеют размер 64x64px, но плоские плитки имеют высоту всего 32px, даже если я рисую их с помощью спрайта 64x64.
Карта представляет собой простой 2-мерный массив, в котором мои плитки представлены своим идентификатором.
Вот класс, который я использую для преобразования координат карты в координаты экрана с помощью функции toIso (). Я передаю свои экранные координаты, которые представляют положение курсора на экране, функции toGrid (), чтобы преобразовать их в координаты карты.
public class Utils {
private static int TILE_WIDTH = Tile.TILE_WIDTH;
private static int TILE_HEIGHT = Tile.TILE_HEIGHT;
private static int TILE_WIDTH_HALF = TILE_WIDTH/2;
private static int TILE_HEIGHT_HALF = TILE_HEIGHT/2;
private static int TILE_WIDTH_QUARTER = TILE_WIDTH_HALF/2;
private static int TILE_HEIGHT_QUARTER = TILE_HEIGHT_HALF/2;
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
//800 and 100 are temporary offsets I apply to center the map.
i+=800;
j+=100;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
//800 and 100 are temporary offsets I apply to center the map.
x-=800;
y-=100;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}}
В настоящее время я визуализирую свои плитки с помощью двух циклов for и конвертирую координаты карты в координаты экрана с помощью функции toIso ().
public void render(Graphics g){
for(int x = 0;x<width;x++){
for(int y = 0;y<height;y++){
int[] isoCoords = Utils.toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
Tile grass = new GrassTile(0);
grass.render(g, grass.getId(), fx, fy);
}else if(world[x][y] == 1){
Tile water = new WaterTile(1);
water.render(g, water.getId(), fx, fy);
}
}
}
}
Я получаю ромбовидную форму, которую хотел отобразить на экране.
Наконец, я обновляю каждый тик, который является фактическими координатами мыши на экране.
int[] coords = Utils.toGrid(mouseManager.getMouseX(), mouseManager.getMouseY());
tileX = coords[0];
tileY = coords[1];
Выбранная плитка окончательно отрисована:
BufferedImage selectedTexture = Assets.selected;
int[] coordsIsoSelected = Utils.toIso(this.tileX, this.tileY);
g.drawImage(selectedTexture, coordsIsoSelected[0], coordsIsoSelected[1], Tile.TILE_WIDTH, Tile.TILE_HEIGHT, null);
g.drawRect(Utils.toIso(tileX, tileY)[0], Utils.toIso(tileX, tileY)[1]+Tile.TILE_HEIGHT/2, Tile.TILE_WIDTH, Tile.TILE_HEIGHT/2);//I draw a rectangle to visualize what's happening.
Наконец, мое обнаружение плитки работает не так, как ожидалось, оно не идеально подходит для плитки, однако, похоже, это связано с прямоугольником, который я рисую. Я не могу придумать решение этой проблемы, заранее благодарю вас за чтение или за любой совет, который вы могли мне дать. Если вам нужна дополнительная точность, я буду рад предоставить вам дополнительную информацию.
Вот видео, показывающее, что на самом деле происходит: youtu.be/baCVIfJz2Wo
РЕДАКТИРОВАТЬ:
Вот часть моего кода, который вы могли бы использовать для запуска такого приложения, как мое. Извините за очень запутанный код, но я постарался сделать его как можно короче, не нарушая поведения игры.
Вам нужно будет поместить предоставленный ранее лист в папку текстур, созданную в папке ресурсов проекта.
Пакет gfx:
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class Assets {
private static final int width = 64, height = 64;
public static BufferedImage grass, water, selected;
public static void init(){
//Temp
SpriteSheet tileSheet = new SpriteSheet(ImageLoader.loadImage("/textures/sheet.png"));
grass = tileSheet.crop(width*2, 0, width, height);
water = tileSheet.crop(width*9, height*5, width, height);
selected = tileSheet.crop(0, height*5, width, height);
//
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageLoader {
public static BufferedImage loadImage(String path){
try {
return ImageIO.read(ImageLoader.class.getResource(path));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
return null;
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class SpriteSheet {
private BufferedImage sheet;
public SpriteSheet(BufferedImage sheet){
this.sheet = sheet;
}
public BufferedImage crop(int x, int y, int width, int height){
return sheet.getSubimage(x, y, width, height);
}
}
В остальном по проекту:
package fr.romainimberti.isometric;
public class Launcher {
public static void main(String args[]){
System.setProperty("sun.awt.noerasebackground", "true");
Game game = new Game("Isometric", 1280, 720);
game.start();
}
}
package fr.romainimberti.isometric;
import java.awt.Canvas;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Display {
private JFrame frame;
private Canvas canvas;
private String title;
private int width, height;
public Display(String title, int width, int height){
this.title = title;
this.width = width;
this.height = height;
createDisplay();
}
private void createDisplay(){
frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
canvas = new Canvas();
canvas.setPreferredSize(new Dimension(width, height));
canvas.setMaximumSize(new Dimension(width, height));
canvas.setMinimumSize(new Dimension(width, height));
canvas.setFocusable(true);
frame.add(canvas);
frame.pack();
}
public Canvas getCanvas(){
return canvas;
}
public JFrame getFrame(){
return frame;
}
}
package fr.romainimberti.isometric;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import fr.romainimberti.isometric.gfx.Assets;
public class Game implements Runnable {
private Display display;
private int width, height;
public JFrame frame;
private boolean running = false;
private Thread thread;
public String title;
private BufferStrategy bs;
private Graphics g;
public int x, y;
public int[][] world;
public static final int TILE_WIDTH = 64;
public static final int TILE_HEIGHT = 64;
public static final int TILE_WIDTH_HALF = 32;
public static final int TILE_HEIGHT_HALF = 32;
public static final int TILE_WIDTH_QUARTER = 16;
public static final int TILE_HEIGHT_QUARTER = 16;
public int xOffset;
//Input
private MouseManager mouseManager;
public Game(String title, int width, int height){
this.width = width;
this.height = height;
this.mouseManager = new MouseManager(this);
this.world = new int[10][10];
}
private void init(){
display = new Display(title, width, height);
display.getFrame().addMouseListener(mouseManager);
display.getFrame().addMouseMotionListener(mouseManager);
display.getCanvas().addMouseListener(mouseManager);
display.getCanvas().addMouseMotionListener(mouseManager);
this.frame = display.getFrame();
Assets.init();
xOffset = frame.getWidth()/2;
//Fill the world
for(int i = 0;i<world.length;i++){
for(int j=0;j<world[0].length;j++){
int r = ThreadLocalRandom.current().nextInt(0,1+1);
if(r == 0)
world[i][j] = 0;
else
world[i][j] = 1;
}
}
}
private void tick(){
mouseManager.tick();
xOffset = frame.getWidth()/2;
}
private void render(){
bs = display.getCanvas().getBufferStrategy();
if(bs == null){
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Clear Screen
g.clearRect(0, 0, frame.getWidth(), frame.getHeight());
//Draw Here
//World render
for(int x = 0;x<world.length;x++){
for(int y = 0;y<world[0].length;y++){
int[] isoCoords = toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
g.drawImage(Assets.grass, fx, fy, null);
}else if(world[x][y] == 1){
g.drawImage(Assets.water, fx, fy, null);
}
}
}
//Selected tile render
int[] coordsIsoSelected = toIso(x, y);
g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
//End Drawing
bs.show();
g.dispose();
}
public void run(){
init();
int fps = 120;
double timePerTick = 1000000000 / fps;
double delta = 0;
long now;
long lastTime = System.nanoTime();
while(running){
now = System.nanoTime();
delta += (now - lastTime) / timePerTick;
lastTime = now;
if(delta >= 1){
tick();
render();
delta--;
}
}
stop();
}
public MouseManager getMouseManager(){
return mouseManager;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
public synchronized void start(){
if(running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop(){
if(!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
i+=xOffset;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
x-=xOffset;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}
}
package fr.romainimberti.isometric;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
public class MouseManager implements MouseListener, MouseMotionListener {
private boolean leftPressed, rightPressed;
private int mouseX, mouseY;
private Game game;
public MouseManager(Game game){
this.game = game;
}
public void tick(){
game.x = game.toGrid(mouseX, mouseY)[0];
game.y = game.toGrid(mouseX, mouseY)[1];
}
// Getters
public boolean isLeftPressed(){
return leftPressed;
}
public boolean isRightPressed(){
return rightPressed;
}
public int getMouseX(){
return mouseX;
}
public int getMouseY(){
return mouseY;
}
// Implemented methods
@Override
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = true;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = true;
}
@Override
public void mouseReleased(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = false;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = false;
}
@Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
}
@Override
public void mouseDragged(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
Если вам это нужно, вы можете найти здесь мою архитектуру проекта, чтобы вы могли правильно организовать все файлы.
Опять же, извините за этот очень, очень запутанный код, но мне пришлось разделить все полезные части моей игры, чтобы уменьшить ее размер. Также не забудьте скачать и правильно разместить файл листа. Надеюсь, это поможет.