setOnMouseMoved between specific x - javafx

I am making the game Breakout and want to move the paddle by it following my mouse-movements. When the mouse leaves my AnchorPane named windowPane, the paddle is halfway gone, too. I would like to ask for help: how do I make the paddle not follow the mouse any longer when I reach the bounds of my pane?
windowPane.setOnMouseMoved(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
/**move Paddle with mouse *#author Can #author SziSzi */
paddle.setLayoutX(event.getY());
paddle.setLayoutX(event.getX()- paddle.getWidth()/2);
}
});

Check, if the position would make the paddle violate the bounds of the pane:
double h = paddle.getHeight();
double pW = paddle.getWidth() / 2;
double x = event.getX();
double y = event.getY();
if (x >= pW && x + pW <= windowPane.getWidth() && y + h <= windowPane.getHeight()) {
paddle.setLayoutY(y);
paddle.setLayoutX(x - pW);
} // otherwise ignore the event

Related

Preventing overlapping shapes while dragging on a Pane

I've looked at similar questions but they all are concerned with collision detection rather than preventing overlap. I've gotten most of it to work with the below code:
private final EventHandler<MouseEvent> onPress = mouseEvent -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : getAllShapes()) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
However, the problem is, once there is a tiniest bit of overlap, the Shape is no longer draggable at all. Meaning, if I drag a shape to another, once they become essentially tangent, neither of them are draggable anymore. What I want to happen is just that, for example, if you try to drag a circle onto another, the circle won't follow the mouse position as long as the future position of the drag will cause an overlap.
I can't figure out exactly how to accomplish this.
EDIT: Minimum Reproducible Example:
Main.java
public class Main extends Application {
static Circle circle1 = new DraggableCircle(100, 200);
static Circle circle2 = new DraggableCircle(200, 300);
static Circle[] circleList = new Circle[]{circle1, circle2};
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World");
Pane pane = new Pane();
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
pane.getChildren().addAll(circle1, circle2);
}
public static void main(String[] args) {
launch(args);
}
}
DraggableCircle.java
public class DraggableCircle extends Circle {
private double xDrag, yDrag;
public DraggableCircle(double x, double y) {
super(x, y, 30);
this.setFill(Color.WHITE);
this.setStroke(Color.BLACK);
this.setStrokeWidth(1.5);
this.addEventHandler(MouseEvent.MOUSE_PRESSED, onPress);
this.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDrag);
}
private final EventHandler<MouseEvent> onPress = (mouseEvent) -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
}
This also has an issue where dragging too quickly causes a noticeable overlap between the circles, before the drag detection ends.
A simple (imperfect) solution
The following algorithm will allow a node to continue to be dragged after an intersection has occurred:
Record the current draggable shape position.
Set the new position.
Check the intersection.
If an intersection is detected, reset the position to the original position.
An implementation replaces the drag handler in the supplied minimal example code from the question.
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
this.setCenterX(priorCenterX);
this.setCenterY(priorCenterY);
return;
}
}
}
};
This handler does work better than what you had, it does at least allow you to continue dragging after the intersection.
But, yes, if you drag quickly it will leave a visible space between nodes when it has detected that the drag operation would cause an intersection, which isn't ideal.
Also, the additional requirement you added in your comment about having the dragged shape glide along a border would require a more sophisticated solution.
Other potential solutions
I don't offer code for these more sophisticated solutions here.
One potential brute force solution is to interpolate the prior center with the new center and then, in a loop, slowly move the dragged object along the interpolated line until an intersection is detected, then just back it out to the last interpolated value to prevent the intersection. You can do this by calculating and applying a normalized (1 unit distance) movement vector. That might fix space between intersected nodes.
Similarly to get the gliding, on the intersection, you could just update either the interpolated x or y value rather than both.
There may be more sophisticated methods with geometry math applied, especially if you know shape geometry along with movement vectors and surface normals.
+1 for #jewelsea answer.
On top of #jewelsea answer, I would like to provide a fix for the "space between nodes" issue.
So you might have already observed that when you drag fast, it will not cover each and every pixel in the drag path. It varies with the speed of the drag. So when you decide to move it to the previous recorded point, we will do a quick math, to see if there is any gap between the two nodes, if yes:
We will do a math "to determine a point along a line which is at distance d" and move the drag circle to that point. Here..
start point of line is : previous recorded point
end point of line is : the intersected shape center
d is : the gap between the two shapes.
So the updated code to the #jewelsea answer is as below:
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Circle shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
Point2D cx = new Point2D(priorCenterX, priorCenterY);
Point2D px = new Point2D(shape.getCenterX(), shape.getCenterY());
double d = cx.distance(px);
if (d > getRadius() + shape.getRadius()) {
cx = pointAtDistance(cx, px, d - getRadius() - shape.getRadius());
}
this.setCenterX(cx.getX());
this.setCenterY(cx.getY());
return;
}
}
}
};
private Point2D pointAtDistance(Point2D p1, Point2D p2, double distance) {
double lineLength = p1.distance(p2);
double t = distance / lineLength;
double dx = ((1 - t) * p1.getX()) + (t * p2.getX());
double dy = ((1 - t) * p1.getY()) + (t * p2.getY());
return new Point2D(dx, dy);
}

Java FX - Reset translateX and translateY of node when it reaches 0

I have 2 draggable StackPanes and between these is a line shape. When I drag one stack pane the end of the line next to the moving StackPane moves accordingly to the moving stack pane and the other side of the line stays still (which is what I want). However my problem is when I release the mouse i.e stop dragging the stack pane, the line goes back to its original position.
My event handler when you press the StackPane:
EventHandler<MouseEvent> circleOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane = ((StackPane)(t.getSource()));
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
layoutX = currentStackPane.getLayoutX();
layoutY = currentStackPane.getLayoutY();
}
};
My event handler when i drag the StackPane:
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
currentStackPane.setTranslateX(offsetX);
currentStackPane.setTranslateY(offsetY);
}
};
I tried make a event handler after the drag is finished:
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane.setLayoutX(layoutX + ((StackPane)(t.getSource())).getTranslateX());
currentStackPane.setLayoutY(layoutY + ((StackPane)(t.getSource())).getTranslateY());
currentStackPane.setTranslateX(0);
currentStackPane.setTranslateY(0);
}
};
Binding the start and end points to the stackpanes:
DoubleProperty startX = new SimpleDoubleProperty(vertexClickedOn.getLayoutX() + (vertexClickedOn.getWidth() / 2));
DoubleProperty startY = new SimpleDoubleProperty(vertexClickedOn.getLayoutY() + (vertexClickedOn.getHeight() / 2));
DoubleProperty endX = new SimpleDoubleProperty(vertexTo.getLayoutX() + (vertexTo.getWidth() / 2));
DoubleProperty endY = new SimpleDoubleProperty(vertexTo.getLayoutY() + (vertexTo.getHeight() / 2));
line.startXProperty().bind(startX.add(vertexClickedOn.translateXProperty()));
line.startYProperty().bind(startY.add(vertexClickedOn.translateYProperty()));
line.endXProperty().bind(endX.add(vertexTo.translateXProperty()));
line.endYProperty().bind(endY.add(vertexTo.translateYProperty()));
However if i take this out then the line stays where the mouse is released but the dragged stackpane goes back to its original position when the mouse released. If i keep this in then the stackpane stays where the mouse is released but the line goes back to it's original position.
How do I solve this?
Thank you.
The root cause for your problem is the startX/Y and endX/Y values are not updated with the new layoutX/Y values. Rather than taking into a separate variable, I would recommend to include them in the binding.
line.startXProperty().bind(vertexClickedOn.layoutXProperty().add(vertexClickedOn.translateXProperty()).add(vertexClickedOn.widthProperty().divide(2)));
line.startYProperty().bind(vertexClickedOn.layoutYProperty().add(vertexClickedOn.translateYProperty()).add(vertexClickedOn.heightProperty().divide(2)));
line.endXProperty().bind(vertexTo.layoutXProperty().add(vertexTo.translateXProperty()).add(vertexTo.widthProperty().divide(2)));
line.endYProperty().bind(vertexTo.layoutYProperty().add(vertexTo.translateYProperty()).add(vertexTo.heightProperty().divide(2)));

Slider Puzzle in javaFX - Swapping a button if it borders on the blank button

I am coding a slider puzzle, using a 2-D array of buttons where 1 button is blank at all times, and the remaining buttons can only move if they are next to the blank button. Would anyone be able to offer a way to check the surrounding buttons and determine if it borders on the blank one? (keeping in mind boundaries)
Create fields blankX and blankY that contain the current position of the blank.
For each button save the current position in the properties. This allows you to retrieve the coordinates of the button, check, if they are adjacent to the blank and swap the positions, if that's the case.
Example:
private int blankX = 0;
private int blankY = 0;
private static final int SIZE = 50;
private static final String X_KEY = "TileLocationX";
private static final String Y_KEY = "TileLocationY";
private void move(Node n, int x, int y) {
// adjust position
n.setLayoutX(blankX * SIZE);
n.setLayoutY(blankY * SIZE);
// save coordinates to property
n.getProperties().put(X_KEY, blankX);
n.getProperties().put(Y_KEY, blankY);
// save node pos as blank pos
blankX = x;
blankY = y;
}
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
EventHandler<ActionEvent> handler = evt -> {
Node n = (Node) evt.getSource();
int x = (Integer) n.getProperties().get(X_KEY);
int y = (Integer) n.getProperties().get(Y_KEY);
if (Math.abs(x - blankX) + Math.abs(y - blankY) == 1) {
// move button, if it's adjacent to the blank
move(n, x, y);
}
};
int count = 1;
// create grid
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i != blankX || j != blankY) {
Button button = new Button(Integer.toString(count++));
button.setPrefSize(SIZE, SIZE);
button.setLayoutX(SIZE * i);
button.setLayoutY(SIZE * j);
button.getProperties().put(X_KEY, i);
button.getProperties().put(Y_KEY, j);
button.setOnAction(handler);
pane.getChildren().add(button);
}
}
}
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
2D arrays make it easy. Check for null above, below, right, and left, making sure that you aren't going out of bounds (aka checking boxes off the actual game grid).
I'm not sure if JavaFX is different from Java, so I can only give pseudo code:
//initialize int[5][5] (position) with tile classes (Tile), setting one to null
//checking tile at position[a][b]
//check position[a-1][b], [a+1][b], [a][b-1], [a][b+1] for null...
//...also making sure the value is between 0 and 5
//if any of those is true, square is moveable!
There might be an even more efficient way to do this, however. If you add another variable (boolean isMoveable) to each tile, then you can start each turn by finding the empty square and simply checking off every tile it borders as moveable. This would even allow you to highlight them somehow at the beginning of the turn, so the player knows what he can move.
And if you don't want to search through the entire board for the empty square, you can simply do it once at the beginning and save it's position to an array. Next turn, look to each square bordering that position to find the empty one.
If you have a 2D array, then you have the x and y location of each button. Keep the location of the blank button in variables, say blankX and blankY.
Then the condition for a button to be adjacent to the blank button is
Either x==blankX and y and blankY differ by 1, or
y==blankY and x and blankX differ by 1
So if you do
int deltaX = Math.abs(x - blankX) ;
int deltaY = Math.abs(y - blankY) ;
the condition is
deltaX==0 && deltaY==1, or
deltaX==1 && deltaY==0
but since deltaX >= 0 and deltaY >= 0, this is just equivalent to
deltaX + deltaY == 1
If you make blankX and blankY JavaFX properties:
private final IntegerProperty blankX = new SimpleIntegerProperty();
private final IntegerProperty blankY = new SimpleIntegerProperty();
then you can easily disable the button when it's not adjacent to the blank buttons:
// creates a button with coordinates x,y and initializes it
private Button createButton(int x, int y) {
Button button = new Button(Integer.toString(x + y * numColumns + 1));
// disable if not adjacent to blank button:
button.disableProperty().bind(Bindings.createBooleanBinding(
() -> Math.abs(x - blankX.get()) + Math.abs(y - blankY.get()) != 1,
blankX, blankY));
// when pressed, swap with blank button and update blankX, blankY:
button.setOnAction(e -> {
buttonArray[blankX.get()][blankY.get()].setText(button.getText());
button.setText("");
blankX.set(x);
blankY.set(y);
});
return button ;
}

JavaFx Determining whether a mouse is clicking on the background or a Circle

I am creating a game where circles fall from the top of the screen to the bottom. When the circle is clicked its suppose to re-spawn in a random position on top of the screen and with a random color. I am pretty sure my problem has to do with my line to determine if the mouse click was on one of the circles or not is working correctly. So my questions are how would I determine if a mouse click happened on one of the circles or on the background screen? and What is wrong with the following line? (Because I am almost certain that my problem is from that line)
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
My entire code is here:
public class ShapesWindow extends Application{
final int WIDTH = 640;
final int HEIGHT = WIDTH / 12 * 9;
Random r = new Random();
Circle circle;
double yCord;
long startNanoTime;
Group root = new Group();
Scene scene = new Scene(root, WIDTH, HEIGHT);
Canvas can = new Canvas(WIDTH,HEIGHT);
GraphicsContext gc = can.getGraphicsContext2D();
ArrayList<Shape> shape = new ArrayList<>();
#Override
public void start(Stage theStage) throws Exception {
theStage.setTitle("Click the bubbles!");
theStage.setScene(scene);
root.getChildren().add(can);
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
/* This adds 10 circles to my Group */
for(int i = 0; i < 10; i++){
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
circle = new Circle(15,randomColor());
root.getChildren().add(circle);
circle.setLayoutX(r.nextInt(WIDTH+15));
circle.setLayoutY(0);
shape.add(circle);
}
/* This my attempt at trying to handle the Mouse Events for each thing */
for(int i = 0; i < 10; i++){
shape.get(i).setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
shapeClicked(e);
}
});
}
startNanoTime = System.nanoTime();
new AnimationTimer(){
public void handle(long currentNanoTime){
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
yCord = t*20;
for(int i = 0; i < 10; i++){
/* This if statment allows nodes to wrap around from bottom to top */
if(yCord >=HEIGHT){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
resetNan();
}
shape.get(i).setLayoutY(yCord);
}
}
}.start();
theStage.show();
}
/*
* This Function is suppose the change the color and position of the circle that was clicked
*/
public void shapeClicked(MouseEvent e){
for(int i = 0; i < shape.size();i++){
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
}
}
/*
* This allows the value of startNanoTime to be indrectly change it can not be changed diretly
* inside of handle() inside of the Animation class
*/
public void resetNan(){
startNanoTime = System.nanoTime();
}
public Color randomColor(){
double R = r.nextDouble();
double G = r.nextDouble();
double B = r.nextDouble();
double opacity = .6;
Color color = new Color(R, G, B, opacity);
return color.brighter();
}
public static void main(String[] args){
launch(args);
}
}
Why not just
for(int i = 0; i < 10; i++){
Shape s = shape.get(i);
s.setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
s.setLayoutX(r.nextInt(WIDTH+15));
s.setLayoutY(0);
s.setFill(randomColor());
}
});
}
I know long time passed by, but if anyone else need to check if a mouse click event was on a Circle or any other shape it is better to use the built in .contains method. Thanks to Point2D from JavaFx geometry class you can check if a click (x,y coordinate) is on a shape or not, you don't have to worry about the click position: in the center or border.
for (Circle circle:listOfCircles) {
Point2D point2D = new Point2D(event.getX(),event.getY());
if (circle.contains(point2D)){
System.out.println("circle clicked");
}
}

Getting Coordinates after Rotate Transition JavaFX

I'm writing a game like Wheel of Fortune, with the Wheel is actually an JavaFX ImageView lay outed to the right most of window. This wheel is made up of 15 piece (24 degree for each).
Basically, after RotateTransition finish I want to get the coordinate, or bounding box of something like this of the ImageView in order to calculate the angle between that coordinate, the center of the image, and the needle. From that angle I can determine the score returned.
However, I'm confused on how to do this. I've tried to play around with bounding box, but it just doesn't solve the problem.
The code for this is as follow :
public class FXMLRoundSceneController {
#FXML
private AnchorPane backgroundAnchorPane;
#FXML
private ImageView wheel;
/* Wheel background Image*/
private Image img = new Image(getClass().getResource("/resources/WOF.png").toExternalForm());
#FXML
public void initialize() {
wheel.setImage(img);
}
#FXML
private void rotateWheel(MouseEvent mv) {
rotate(1355,5); // Just an example, the rotate angle should be randomly calculated on each MouseClicked event.
}
public void rotate(double angle, double duration) {
RotateTransition rt = new RotateTransition();
rt.setNode(wheel);
rt.setByAngle(angle);
rt.setDuration(Duration.seconds(duration));
rt.setCycleCount(1);
rt.setAutoReverse(false);
rt.setInterpolator(Interpolator.EASE_OUT);
rt.play();
rt.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ae) {
wheelBoundingBox();
}
});
}
public void wheelBoundingBox() {
double MX = wheel.getBoundsInParent().getMaxX();
double MY = wheel.getBoundsInParent().getMaxY();
double mX = wheel.getBoundsInParent().getMinX();
double mY = wheel.getBoundsInParent().getMinY();
double width = MX - mX;
double height = MY - mY;
Rectangle bb = new Rectangle(mX, mY, Color.TRANSPARENT);
bb.setWidth(width);
bb.setHeight(height);
bb.setStroke(Color.WHITE);
bb.setStrokeWidth(1);
backgroundAnchorPane.getChildren().add(bb);
}
}
Can anyone tell me a proper way to determine the coordinate or something like that to help you determine the score.
public static void main(String[] args) {
String[] prizes = new String[] {"100","200","300","400","500","600"};
int slotSize = 360 / prizes.length;
int angle = 359;
for (int i = 0; i < 3600; i++) // 10 spins
{
angle++;
if (angle>359) {
angle = 0;
}
// based on the current angle, determine the entry:
int entry = angle / slotSize;
System.out.println("angle "+angle+" entry "+entry+" prize "+prizes[entry]);
}
}

Resources