Java mouse picking on diamond tiled map
Ok, im at my wits end. I am trying to create a small isometric tile map thats bigger then the screen witha viewpoint i can modify with mouse dragging. I got the drawing right (i think), i got the dragging working, just do't seem to be able to get the mouse picking right. I have made it so far that i get the tile nearly right, but its off by about half a tiles size and i can't find a way to make up that offset.
Here is the code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MapView {
public static void main(String[] args) {
JFrame test = new JFrame("IsoView");
test.setSize(800, 600);
MapViewPane pane = new MapViewPane();
test.getContentPane().setLayout(new BorderLayout());
test.getContentPane().add(pane, BorderLayout.CENTER);
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setVisible(true);
}
private static class MapViewPane extends JPanel
implements MouseMotionListener, MouseListener {
private BufferedImage BackImage;
BufferedImage GrassTile, SelectedBorder;
private Point MousePoint, PrevView, ViewLocation, Selected;
private boolean Dragging;
private int mapwidth, mapheight, tilecount;
public MapViewPane() {
super();
this.setOpaque(true);
createAssets();
tilecount = 30;
mapwidth = GrassTile.getWidth() * tilecount;
mapheight = GrassTile.getHeight() * tilecount;
ViewLocation = new Point(0, mapheight / 2);
Selected = new Point(-1, -1);
addMouseListener(this);
addMouseMotionListener(this);
}
private void createAssets() {
GraphicsConfiguration gc =
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
GrassTile = gc.createCompatibleImage(128,
64, Transparency.TRANSLUCENT);
Graphics g = GrassTile.getGraphics();
Polygon poly = new Polygon();
poly.addPoint(0, 32);
poly.addPoint(64, 0);
poly.addPoint(128, 32);
poly.addPoint(64, 64);
g.setColor(Color.GREEN);
g.fillPolygon(poly);
g.setColor(Color.BLUE);
g.drawPolygon(poly);
g.dispose();
SelectedBorder = gc.createCompatibleImage(128,
64, Transparency.TRANSLUCENT);
g = SelectedBorder.getGraphics();
g.setColor(Color.red);
g.drawPolygon(poly);
g.dispose();
}
@Override
public void paint(Graphics g) {
//super.paint(g);
Rectangle visiblerec = this.getVisibleRect();
g.setColor(Color.BLACK);
g.fillRect(visiblerec.x, visiblerec.y,
visiblerec.width, visiblerec.height);
checkBackImage();
Graphics bg = BackImage.getGraphics();
drawGrassGrid(bg);
bg.dispose();
g.drawImage(BackImage, 0, 0, this);
}
private void drawGrassGrid(Graphics g) {
int dx = 0;
int dy = 0;
g.setColor(Color.BLACK);
g.fillRect(0, 0, BackImage.getWidth(), BackImage.getHeight());
for (int x = 0; x < tilecount; x++) {
for (int y = 0; y < tilecount; y++) {
dx = x * GrassTile.getWidth() / 2
- y * GrassTile.getWidth() / 2;
dy = x * GrassTile.getHeight() / 2
+ y * GrassTile.getHeight() / 2;
dx -= ViewLocation.x;
dy -= ViewLocation.y;
g.drawImage(GrassTile, dx, dy, this);
if ((x == Selected.x) && (y == Selected.y)) {
g.drawImage(SelectedBorder, dx, dy, this);
}
g.drawString("(" + x + "," + y + ")", dx, dy
+ GrassTile.getHeight() / 2);
}
}
}
private void checkBackImage() {
if ((BackImage == null) || (BackImage.getWidth() != this.getWidth())
|| (BackImage.getHeight() != this.getHeight())) {
GraphicsConfiguration gc =
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BackImage = gc.createCompatibleImage(this.getWidth(),
this.getHeight(), Transparency.BITMASK);
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (Dragging) {
ViewLocation.x = PrevView.x + MousePoint.x - e.getX();
ViewLocation.y = PrevView.y + MousePoint.y - e.getY();
if (ViewLocation.x < -mapwidth / 2) {
ViewLocation.x = -mapwidth / 2;
}
if (ViewLocation.y < -mapheight / 2 + this.getHeight()) {
ViewLocation.y = -mapheight / 2 + this.getHeight();
}
if (ViewLocation.x > mapwidth / 2 - this.getWidth()
+ GrassTile.getWidth()) {
ViewLocation.x = mapwidth / 2 - this.getWidth()
+ GrassTile.getWidth();
}
if (ViewLocation.y > mapheight / 2 + this.getHeight()) {
ViewLocation.y = mapheight / 2 + this.getHeight();
}
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
public void 开发者_如何学编程mouseClicked(MouseEvent e) {
if (!Dragging) {
int x = (GrassTile.getWidth() * (e.getY() + ViewLocation.y)
+ GrassTile.getHeight() * (e.getX() + ViewLocation.x))
/ (GrassTile.getWidth() * GrassTile.getHeight());
int y = (GrassTile.getWidth() * (e.getY() + ViewLocation.y)
- GrassTile.getHeight() * (e.getX() + ViewLocation.x))
/ (GrassTile.getWidth() * GrassTile.getHeight());
// int x = (int) Math.floor((e.getY() + ViewLocation.y)
// / (double) GrassTile.getHeight() - (e.getX() + ViewLocation.x)
// / (double) GrassTile.getWidth());
// int y = (int) Math.floor((e.getY() + ViewLocation.y)
// / (double) GrassTile.getHeight() + (e.getX() + ViewLocation.x)
// / (double) GrassTile.getWidth());
Selected.setLocation(x, y);
repaint();
System.out.println("(" + x + "," + y + ")");
}
}
@Override
public void mousePressed(MouseEvent e) {
if ((e.getButton() == MouseEvent.BUTTON1) && !Dragging) {
MousePoint = e.getPoint();
PrevView = new Point(ViewLocation);
Dragging = true;
}
}
@Override
public void mouseReleased(MouseEvent e) {
Dragging = false;
MousePoint = null;
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
}
I have some formulas in the click method commented out wich i tried, but they dont work (x and y axis are inverted that way, havent tried to figure out why yet). Would realy appreciate if someone can point me to the mistake i'm making.
I managed to fix it for you. Firstly I did a bit of algebra (hopefully explained by the inline comments) to simplify the calculation of which tile is hit. The next bit you did somewhat realise; there's issues casting. You need to convert everything to a double before you use it. If you do int/int
then you already made a cast and lost precision.
The trick to making it hit the right tile is 1) casting early and 2) the +/- 0.5
which is used to force the return int
cast to go one way or the other. To elaborate, (int)6.9 == 6
.
Here's the working answer:
@Override
public void mouseClicked(MouseEvent e) {
if (!Dragging) {
/*
// copy of the tile location assignment code as a reminder
dx = x * GrassTile.getWidth() / 2
- y * GrassTile.getWidth() / 2;
dy = x * GrassTile.getHeight() / 2
+ y * GrassTile.getHeight() / 2;
dx -= ViewLocation.x;
dy -= ViewLocation.y;
*/
int pickX = e.getX() + ViewLocation.x;
int pickY = e.getY() + ViewLocation.y;
int tileW = GrassTile.getWidth();
int tileH = GrassTile.getHeight();
/*
// assignment code refactored
x - y = 2 * pickX / tileW;
x + y = 2 * pickY / tileH;
// x+y= refactored to y=
y = (2*pickY / tileH) - x;
// substitute into x-y + refactor
2x = (2 * pickX / tileW) + (2 * pickY / tileH);
// x+y= refactored to x=
x = (2*pickY / tileH) - y;
// substitute x-y + refactor
-2y = (2 * pickX / tileW) - (2 * pickY / tileH);
2y = (2 * pickY / tileH) - (2 * pickX / tileW);
*/
int hitx = (int)(((double)pickX / (double)tileW) + ((double)pickY / (double)tileH) - 0.5);
int hity = (int)(((double)pickY / (double)tileH) - ((double)pickX / (double)tileW) + 0.5);
Selected.setLocation(hitx, hity);
repaint();
//System.out.println("(" + x + "," + y + ")");
}
}
Polygon
implements the Shape
interface, so one of the several contain()
variations may simplify your calculation. The createTransformedShape()
method of AffineTransform
may also be helpful, as suggested in this example.
精彩评论