【问题标题】:Isometric tiles drawing and picking - JAVA等距瓷砖的绘制和挑选 - JAVA
【发布时间】:2017-09-07 20:28:58
【问题描述】:

我正在尝试在 Java 中绘制等距瓷砖并使用鼠标光标实现瓷砖拾取系统。我使用我找到并适应我的瓷砖纹理的这些数学公式绘制瓷砖,您可以在下面找到。瓷砖是 64x64 像素,但即使我使用 64x64 精灵绘制平面瓷砖,它们的高度也只有 32 像素。

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

这是我使用 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
    
    }
}

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

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

128*64 tiles

【问题讨论】:

  • “如果您需要更高的精度,我很乐意为您提供更多信息。” 您可以提供的最高精度是minimal reproducible example,请发一个。另外,我不能完全理解你的问题,你到底想达到什么目的?顺便说一句,你在使用 Swing 吗?
  • 你的鼠标在哪里?将它们悬停时会发生什么?现在发生了什么?如果你能提供一个 MCVE 会更容易理解
  • 好的,视频让我清楚地知道了问题,但我们仍然需要minimal reproducible example,这样我们就可以复制粘贴并自己查看错误并尝试解决它:) 你能试试吗?做一个?这不是您的全部工作代码,它是一个新的简短示例(不是上面的代码 sn-ps),我们可以复制粘贴,不需要导入任何内容(即在您的 MCVE 中包含导入),这样我们就可以提供更多信息/答案和更高质量
  • 什么是代码 sn-p?它们是显示程序特定部分的代码部分,通常很短。什么是整体程序?它包括程序需要运行的所有类,但它真的很大。什么是 MCVE?它是两者之间的中间点,它是一个完整的程序,只需最少的代码即可复制您的问题,我们可以轻松地将其复制粘贴到我们的 IDE 中并实际查看问题。您可以阅读 Minimal, Complete... 我在上面发布两次和Short, Self Contained, Correct Example (SSCCE) 的链接以了解我在说什么
  • 非常感谢@Frakcool 的帮助,我会尽量简化我的代码,使其更短且易于执行!什么时候完成我会通知你的!谢谢 ! :)

标签: java swing drawing tiles isometric


【解决方案1】:

只想说我终于解决了。这只是转换为 int 问题。这些是我使用的最终方法。希望它能帮助那些试图使用等距瓷砖的人。谢谢!

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

【讨论】:

    【解决方案2】:

    在用带有128x64 像素图块的新精灵表替换精灵表后,我已经能够部分实现所需的输出...

    为什么我说“部分地”?因为我只从地图的右半部分设法得到了想要的结果。

    我相信这可能与地图的绘制方式有关,我不是以英语为母语的人,所以我可能会误解 OP 的 @987654322 中的 “Notes” 部分所说的内容@:

    请注意,等轴测图块的“原点”是顶角。但通常当我们绘制一个精灵时,它是从左上角开始的

    我在程序开头调用了toGrid()toIso()方法如下:

    int[] coordinates = Game.toIso(2, 1);
    System.out.println(coordinates[0] + "-" + coordinates[1]);
    
    int[] coordinates2 = Game.toGrid(coordinates[0], coordinates[1]);
    System.out.println(coordinates2[0] + "-" + coordinates2[1]);
    

    得到了以下结果,(这确实是我们所期望的),所以我们知道这些方法可以正常工作:

    64-96
    2-1
    

    我确定要修改Assets 文件:

    public static final int WIDTH = 128, HEIGHT = 64;
    

    我还更改了Java naming conventions (ALL_WORDS_UPPER_CASE_CONSTANTS) 之后的变量名,并将其改为public 而不是private

    我还更改了Game 文件:

    public static final int TILE_WIDTH = Assets.WIDTH;
    public static final int TILE_HEIGHT = Assets.HEIGHT;
    public static final int TILE_WIDTH_HALF = TILE_WIDTH / 2;
    public static final int TILE_HEIGHT_HALF = TILE_HEIGHT / 2;
    public static final int TILE_WIDTH_QUARTER = TILE_WIDTH / 4;
    public static final int TILE_HEIGHT_QUARTER = TILE_HEIGHT / 4;
    

    Assets 文件上使用这些常量并计算HALFQUARTER,而不是对其进行硬编码。

    我也认为xOffset 不应该是public 而应该是private 以及程序中的其他一些变量...

    tick()方法,不需要每次都计算xOffset,所以我们可以去掉里面的这行:

    xOffset = frame.getWidth() / 2 - 65;
    

    我还更改了您选择的图块的绘制方式:

    // Selected tile render
    int[] coordsIsoSelected = toIso(x, y);
    g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
    

    对于 Tolso 方程,我将它们更改为:

    public static int[] toIso(int x, int y) {
        int i = (x - y) * TILE_WIDTH_HALF;
        int j = (x + y) * TILE_HEIGHT_HALF;
    
        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_HALF)) / 2;
        int j = ((y / TILE_HEIGHT_HALF) - (x / TILE_WIDTH_HALF)) / 2;
    
        return new int[] { i, j };
    }
    

    【讨论】:

    • 哦,谢谢你的回答!您是否更改了 toIso 和 toGrid 函数?因为当我启动程序时,瓷砖以正常高度的一半渲染(我在帖子中放了一个屏幕截图),如果我改变方程式并将 Tile_HEIGHT_QUARTER 替换为 TILE_HEIGHT_HALF,地图会正确渲染,但瓷砖检测仍然完全被破坏所以我想知道您是如何取得这样的结果的:/ 很抱歉无法解决这个问题。 ://
    • 我刚刚调整了括号,我也将添加那个代码......哦,我改变了你单独绘制它的Tile大小......
    • 非常感谢您的帮助!感谢您提供的代码,我将尝试修复地图的左侧!
    • 不用担心,如果您仍有疑问,请尝试使用改进的代码提出新问题并尝试将其最小化(资产文件可以删除,鼠标管理器相同,启动器和游戏可以在同一个文件上等等)
    • 感谢@Frakcool 所做的一切!我想我会试试,是的,这可能是个好主意:)
    猜你喜欢
    • 2021-10-22
    • 2021-05-23
    • 2019-07-04
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多