java - 等距瓷砖绘制和拾取 - JAVA

标签 java swing drawing tiles isometric

我正在尝试用 Java 绘制等距图 block 并使用鼠标光标实现图 block 拾取系统。我使用我发现的这些数学公式绘制瓷砖,并根据我的瓷砖纹理进行调整,您可以在下面找到这些数学公式。瓷砖是 64x64 像素,但平面瓷砖只有 32 像素高度,即使我使用 64x64 Sprite 绘制它们也是如此。

Tile sheet

map 是一个简单的二维数组,其中我的图 block 由它们的 id 表示。

这是我使用 toIso() 函数将 map 坐标转换为屏幕坐标的类。我将表示屏幕上光标位置的屏幕坐标传递给 toGrid() 函数,以将它们转换为 map 坐标。

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() 函数将 map 坐标转换为屏幕坐标来渲染我的图 block 。

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];

最终渲染选定的图 block :

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.

最后,我的瓷砖检测没有按预期工作,它没有完全适合瓷砖,但它似乎与我绘制的矩形有关。我想不出解决这个问题的办法,我提前感谢你的阅读或你能给我的任何建议。如果您需要更高的精度,我很乐意为您提供更多信息。

Tile picking

这是一段视频,展示了实际发生的事情:youtu.be/baCVIfJz2Wo


编辑:

这是我的一些代码,您可以使用它来运行像我这样的应用程序。很抱歉这段非常困惑的代码,但我试图在不影响“游戏”行为的情况下使其尽可能短。

您需要将之前提供的工作表放入项目资源文件夹中创建的“textures”文件夹中。

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
    
    }
}

如果你需要的话,你可以在这里找到我的项目架构,这样你就可以正确地组织所有的文件。

Project architecture

再次为这段非常、非常困惑的代码感到抱歉,但我不得不拆分游戏中所有有用的部分以减小它的大小。也不要忘记下载并正确放置图纸文件。希望这会有所帮助。

128*64 tiles

最佳答案

只想说终于解决了。这只是一个转换为 int 的问题。这些是我使用的 final方法。希望它能帮助那些尝试使用等轴测图 block 的人。谢谢!

public static int[] toIso(int x, int y){

    int i = (x - y) * TILE_WIDTH_HALF;
    int j = (x + y) * TILE_HEIGHT_QUARTER;

    i += xOffset-TILE_WIDTH_HALF;
    j+=yOffset;

    return new int[]{i,j};
}

public static int[] toGrid(double i, double j){

    i-=xOffset;
    j-=yOffset;

    double tx = Math.ceil(((i / TILE_WIDTH_HALF) + (j / TILE_HEIGHT_QUARTER))/2);
    double ty = Math.ceil(((j / TILE_HEIGHT_QUARTER) - (i / TILE_WIDTH_HALF))/2);

    int x = (int) Math.ceil(tx)-1;
    int y = (int) Math.ceil(ty)-1;

    return new int[]{x, y};
}

关于java - 等距瓷砖绘制和拾取 - JAVA,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46104610/

相关文章:

java - JTable - 在列末尾点击返回后不应移动到下一列

java - 使用序列化文件作为 Oracle Coherence 中的数据存储的分布式缓存

java - 如何清理tomcat缓存?

java - GUI 应用程序显示然后立即退出

java - GridBagLayout 内的 JScrollPane 内的 JPanel 无法正确滚动

.net - 如何在无法访问 System.Windows.Forms.PaintEventArgs 命名空间的情况下在 Winform 上绘图?

java - 如何用颜色填充自由绘制的形状

javascript - 在 Canvas (HTML5) 上绘制好看的(如在 Flash 中)线条 - 可能吗?

java - 将textview中的数字转换为int

java - `Class.getDeclaredClasses()` 返回的数组中的元素是否有任何特定顺序?