Comprobación de la colisión de formas con JavaFX

Estoy tratando de hacer algo de detección de colisión. Para esta prueba, estoy usando Shape rectangular simple y revisando su Bound , para ver si están colisionando. Aunque la detección no funciona como se esperaba. He intentado usar diferentes formas para mover el objeto (relocalizar, establecerLayoutX, Y) y también diferentes controles encuadernados (boundsInLocal, boundsInParrent, etc.) pero todavía no puedo hacer que esto funcione. Como puede ver, la detección funciona solo para un objeto, incluso cuando tiene tres objetos, solo uno detecta una colisión. Este es un código de trabajo que demuestra el problema:

 import javafx.application.Application; import javafx.event.EventHandler; import javafx.scene.Cursor; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import java.util.ArrayList; public class CollisionTester extends Application { private ArrayList rectangleArrayList; public static void main(String[] args) { launch(args); } public void start(Stage primaryStage) { primaryStage.setTitle("The test"); Group root = new Group(); Scene scene = new Scene(root, 400, 400); rectangleArrayList = new ArrayList(); rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.GREEN)); rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.RED)); rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.CYAN)); for(Rectangle block : rectangleArrayList){ setDragListeners(block); } root.getChildren().addAll(rectangleArrayList); primaryStage.setScene(scene); primaryStage.show(); } public void setDragListeners(final Rectangle block) { final Delta dragDelta = new Delta(); block.setOnMousePressed(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { // record a delta distance for the drag and drop operation. dragDelta.x = block.getTranslateX() - mouseEvent.getSceneX(); dragDelta.y = block.getTranslateY() - mouseEvent.getSceneY(); block.setCursor(Cursor.NONE); } }); block.setOnMouseReleased(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { block.setCursor(Cursor.HAND); } }); block.setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { block.setTranslateX(mouseEvent.getSceneX() + dragDelta.x); block.setTranslateY(mouseEvent.getSceneY() + dragDelta.y); checkBounds(block); } }); } private void checkBounds(Rectangle block) { for (Rectangle static_bloc : rectangleArrayList) if (static_bloc != block) { if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) { block.setFill(Color.BLUE); //collision } else { block.setFill(Color.GREEN); //no collision } } else { block.setFill(Color.GREEN); //no collision -same block } } class Delta { double x, y; } } 

Parece que tiene un ligero error de lógica en su rutina CheckBounds: está detectando colisiones correctamente (según los límites) pero está sobrescribiendo el relleno de su bloque cuando realiza controles de colisión posteriores en la misma rutina.

Pruebe algo como esto: agrega una bandera para que la rutina no “olvide” que se detectó una colisión:

 private void checkBounds(Shape block) { boolean collisionDetected = false; for (Shape static_bloc : nodes) { if (static_bloc != block) { static_bloc.setFill(Color.GREEN); if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) { collisionDetected = true; } } } if (collisionDetected) { block.setFill(Color.BLUE); } else { block.setFill(Color.GREEN); } } 

Tenga en cuenta que la comprobación que está realizando (según los límites en el elemento primario) informará las intersecciones del rectángulo que encierra los límites visibles de los nodos dentro del mismo grupo principal.

Implementación alternativa

En caso de que lo necesite, actualicé su muestra original para que pueda verificar en función de la forma visual del nodo en lugar del cuadro delimitador de la forma visual. Esto le permite detectar con precisión las colisiones de formas no rectangulares como círculos. La clave para esto es el método Shape.intersects (shape1, shape2) .

 import javafx.application.Application; import javafx.event.EventHandler; import javafx.scene.*; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.stage.Stage; import java.util.ArrayList; import javafx.scene.shape.*; public class CircleCollisionTester extends Application { private ArrayList nodes; public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Drag circles around to see collisions"); Group root = new Group(); Scene scene = new Scene(root, 400, 400); nodes = new ArrayList<>(); nodes.add(new Circle(15, 15, 30)); nodes.add(new Circle(90, 60, 30)); nodes.add(new Circle(40, 200, 30)); for (Shape block : nodes) { setDragListeners(block); } root.getChildren().addAll(nodes); checkShapeIntersection(nodes.get(nodes.size() - 1)); primaryStage.setScene(scene); primaryStage.show(); } public void setDragListeners(final Shape block) { final Delta dragDelta = new Delta(); block.setOnMousePressed(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { // record a delta distance for the drag and drop operation. dragDelta.x = block.getLayoutX() - mouseEvent.getSceneX(); dragDelta.y = block.getLayoutY() - mouseEvent.getSceneY(); block.setCursor(Cursor.NONE); } }); block.setOnMouseReleased(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { block.setCursor(Cursor.HAND); } }); block.setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { block.setLayoutX(mouseEvent.getSceneX() + dragDelta.x); block.setLayoutY(mouseEvent.getSceneY() + dragDelta.y); checkShapeIntersection(block); } }); } private void checkShapeIntersection(Shape block) { boolean collisionDetected = false; for (Shape static_bloc : nodes) { if (static_bloc != block) { static_bloc.setFill(Color.GREEN); Shape intersect = Shape.intersect(block, static_bloc); if (intersect.getBoundsInLocal().getWidth() != -1) { collisionDetected = true; } } } if (collisionDetected) { block.setFill(Color.BLUE); } else { block.setFill(Color.GREEN); } } class Delta { double x, y; } } 

Muestra de salida del progtwig. En la muestra, los círculos han sido arrastrados y el usuario está arrastrando un círculo que se ha marcado como colisión con otro círculo (pintándolo de azul); para fines de demostración, solo el círculo que se está arrastrando tiene marcado su color de colisión.

colisiones

Comentarios basados ​​en preguntas adicionales

El enlace que publiqué en una aplicación de demostración de intersección en un comentario anterior fue para ilustrar el uso de varios tipos de límites en lugar de un tipo específico de muestra de detección de colisión. Para su caso de uso, no necesita la complejidad adicional del detector de cambios y la comprobación de varios tipos diferentes de tipos de límites; basta con establecerse en un tipo. La mayoría de las detecciones de colisiones solo estarán interesadas en la intersección de los límites visuales en lugar de otros tipos de límites de JavaFX, como los límites de diseño o los límites locales de un nodo. Entonces puedes:

  1. Compruebe la intersección de getBoundsInParent (como lo hizo en su pregunta original) que funciona en la caja rectangular más pequeña que abarcará los extremos visuales del nodo O
  2. Utilice la Shape.intersect(shape1, shape2) si necesita verificar en función de la forma visual del nodo en lugar del cuadro delimitador de la forma visual.

¿Debo usar setLayoutX o translateX para el rectángulo?

Las propiedades layoutX y layoutY están destinadas a posicionar o diseñar nodos. Las propiedades translateX y translateY están destinadas a cambios temporales en la ubicación visual de un nodo (por ejemplo, cuando el nodo está en proceso de animación). Para su ejemplo, aunque cualquiera de las propiedades funcionará, quizás sea mejor utilizar las propiedades de diseño que las de traducción, de modo que si desea ejecutar algo así como una Transcripción de traducción en los nodos, será más obvio que el inicio y el los valores de traducción del final deben ser como esos valores serán relativos a la posición de disposición actual del nodo en lugar de la posición en el grupo padre.

Otra forma en que podría usar estas coordenadas de diseño y traducción en conjunto en su muestra es si tenía algo así como un ESC para cancelar durante el curso de una operación de arrastre. Puede establecer layoutX, Y en la ubicación inicial de su nodo, iniciar una operación de arrastre que establece translateX, Y values ​​y si el usuario presiona ESC, establezca translateX, Y en 0 para cancelar la operación de arrastre o si el usuario suelta el mouse establece layoutX, Y en layoutX, Y + translateX, Y y establece translateX, Y en 0. La idea es que la traducción de los valores se usa para una modificación temporal de las coordenadas visuales del nodo desde su posición de diseño original.

¿la intersección funcionará aunque los círculos estén animados? Quiero decir, sin arrastrar el círculo con el mouse, ¿qué pasará si los hago moverse al azar? ¿Cambiará el color también en este caso?

Para hacer esto, simplemente cambie el lugar donde se llama a la función de detección de colisión y se invoca el controlador de colisión. En lugar de buscar intersecciones basadas en un evento de arrastre del mouse (como el ejemplo anterior), en su lugar, compruebe las colisiones dentro de un detector de cambios en los boundsInParentProperty() cada elemento boundsInParentProperty() .

 block.boundsInParentProperty().addListener((observable, oldValue, newValue) -> checkShapeIntersection(block) ); 

Nota: si tiene muchas formas animadas, entonces la comprobación de colisiones una vez por cuadro dentro de un bucle de juego será más eficiente que ejecutar una prueba de colisión cada vez que se mueva un nodo (como se hace en los límitesInParentProperty cambia el oyente más arriba).

    Intereting Posts