问题
I am currently trying to develop a puzzle. All I got is my application with my playing-field and gaming-pieces. Next step is to click on one of my gaming-piece to select it and be able to move it with the arrow-keys (furthermore I want to them only to move, if the next step - which will be 100 pixels - does not contain any other gaming-piece).
The problem I'm currently running into: Using addMouseListener()
on my main JPanel
and then using getSource()
only returns my playing-field (called View
in my code) but I need it to return the desired gaming-piece (such as topLeft
). I already tried casting getSource()
to Piece
but this does not work (Cannot cast View to Piece
).
So, I need to find a way to add a mouse listener which returns the gaming-piece that was clicked on so that I can change the location and check for any collision with any other gaming-piece. Thanks in advance!
Edited code thanks to @camickr.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Puzzle {
public static void main(String[] args) {
SwingUtilities.invokeLater(Puzzle::new);
}
private final static int[] SHAPE_X = { -100, 100, 100, 0, 0, -100 };
private final static int[] SHAPE_Y = { -100, -100, 0, 0, 100, 100 };
public Puzzle() {
JFrame frame = new JFrame("Puzzle");
frame.setSize(400, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
View view = new View();
frame.setContentPane(view);
view.addMouseListener(new MouseAdapterMod(view));
Shape polyShape = new Polygon(SHAPE_X, SHAPE_Y, SHAPE_X.length);
Piece topLeft = new Piece(Color.YELLOW, polyShape, 0, 100, 100);
view.pieces.add(topLeft);
Piece topRight = new Piece(Color.CYAN, polyShape, 90, 300, 100);
view.pieces.add(topRight);
Piece bottomRight = new Piece(Color.GREEN, polyShape, 180, 300, 300);
view.pieces.add(bottomRight);
Piece bottomLeft = new Piece(Color.RED, polyShape, 270, 100, 300);
view.pieces.add(bottomLeft);
Piece square = new Piece(Color.ORANGE, new Rectangle(200, 200), 0, 100, 100);
view.pieces.add(square);
frame.setVisible(true);
}
}
class View extends JPanel {
final List<Piece> pieces = new ArrayList<>();
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gc = (Graphics2D) g;
for (Piece piece : pieces) {
piece.draw(gc);
}
}
}
class Piece {
final Color color;
final Shape shape;
final int angle;
int x;
int y;
Piece(Color color, Shape shape, int angle, int x, int y) {
this.color = color;
this.shape = shape;
this.angle = angle;
this.x = x;
this.y = y;
}
void draw(Graphics2D gc) {
AffineTransform tf = gc.getTransform();
gc.translate(x, y);
gc.rotate(Math.toRadians(angle));
gc.setColor(color);
gc.fill(shape);
gc.setTransform(tf);
}
Shape getShape() {
return shape;
}
}
class MouseAdapterMod extends MouseAdapter {
final View view;
public MouseAdapterMod(View view) {
this.view = view;
}
@Override
public void mousePressed(MouseEvent e) {
for(Piece piece : view.pieces) {
if(piece.getShape().contains(e.getX(), e.getY())) {
System.out.println("yes");
}
}
}
}
回答1:
So, I need to find a way to add a mouse listener which returns the gaming-piece that was clicked
You use the getX() and getY() methods from the MouseEvent.
Then you iterate through your "pieces" ArrayList and invoke the contains(...
method on the Shape
contained in each Piece
to see if the mouse point is contained in the piece.
So you will also need to add a getShape(...)
method to your "Piece" class so you can access the Shape
of each Piece
.
Edit:
So your basic logic might be something like:
//Shape polyShape = new Polygon(SHAPE_X, SHAPE_Y, SHAPE_X.length);
//Piece topLeft = new Piece(Color.YELLOW, polyShape, 0, 100, 100);
Polygon topLeftPolygon = new Polygon(SHAPE_X, SHAPE_Y, SHAPE_X.length);
topLeftPolygon.translate(100, 100);
//topLeftPolygon = ShapeUtils.rotate(...); // do rotation when required
Piece topLeft = new Piece(Color.YELLOW, topLeftPolygon);
Then the painting code in the draw(..) method is just:
gc.setColor(color);
gc.fill(shape);
No need for the transform or the translate.
Edit 2:
So use the Shape:
//topLeftPolygon = ShapeUtils.rotate(...); // do rotation when required
//Piece topLeft = new Piece(Color.YELLOW, topLeftPolygon);
Shape topLeftShape = ShapeUtils.rotate(...); // do rotation when required
Piece topLeft = new Piece(Color.YELLOW, topLeftShape);
This currently matches your Piece class which expects a Shape object anyway. Please think about the concept being suggested and don't assume posted code is perfect since it obviously hasn't been tested.
回答2:
I see you have used my answer from this question, as the basis of your puzzle game, instead of continuing with your own code. Understand, I gave you a Minimal, Complete, Verifiable Example for drawing shapes on a JPanel
. I never said it was suitable to use directly; if fact, by making the example "minimal" it may have caused extending the code to support things like hit-testing to be more difficult. But since you have chosen to proceed with my code as the basis...
Your hit-test issue comes from the fact that the mouse clicks are in the view's coordinate system, but the shapes are being translated and rotated to various other positions for drawing. You can solve this by reversing the transformation, translating the mouse position into the shape's coordinate system, and then using Shape.contains(Point2D)
.
class Piece {
// ... omitted for brevity ...
public boolean hitTest(Point point) {
AffineTransform tf = new AffineTransform();
tf.translate(x, y);
tf.rotate(Math.toRadians(angle));
try {
Point2D pnt = tf.inverseTransform(point, null);
return shape.contains(pnt);
} catch (NoninvertibleTransformException e) {
return false;
}
}
}
Then, simply loop over the each piece, and ask it if it is under the mouse's location.
class View extends JPanel {
// ... omitted for brevity ...
Piece hitTest(MouseEvent e) {
Point pnt = e.getPoint();
for (Piece piece : pieces) {
if (piece.hitTest(pnt))
return piece;
}
return null;
}
}
Note that I'm returning the Piece
here, not the Shape
. Since 4 out of the 5 pieces use the same polyShape
shape, that isn't very useful.
The hitTest()
could be shorten using Java8 streams:
Piece hitTest(MouseEvent e) {
final Point pnt = e.getPoint();
return pieces.stream()
.filter(piece -> piece.hitTest(pnt))
.findFirst()
.orElse(null);
}
If the pieces can overlap, the one that appears to be on top is the last one drawn. findFirst()
will return the bottom-most piece drawn at the given mouse point, not the top most. The following change will correct the behaviour:
Piece hitTest(MouseEvent e) {
final Point pnt = e.getPoint();
return pieces.stream()
.filter(piece -> piece.hitTest(pnt))
.reduce((first, second) -> second)
.orElse(null);
}
来源:https://stackoverflow.com/questions/55003607/java-swing-mousepressed-and-getsource-not-showing-drawed-shapes-on-jpanel