Controllo collisione di forms con JavaFX

Sto provando a fare un po ‘di rilevamento delle collisioni. Per questo test sto usando una semplice Shape rettangular e controllando il loro Bound , per capire se si scontrano. Sebbene il rilevamento non funzioni come previsto. Ho provato a utilizzare diversi modi per spostare l’object (riposizionare, setLayoutX, Y) e anche diversi controlli vincolati (boundsInLocal, boundsInParrent ecc.) Ma non riesco ancora a farlo funzionare. Come puoi vedere il rilevamento funziona solo per un object, anche quando hai tre oggetti solo uno rileva la collisione. Questo è un codice funzionante che dimostra il 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; } } 

    Sembra che tu abbia un leggero errore logico nella routine checkBounds: stai rilevando correttamente le collisioni (in base ai limiti) ma stai sovrascrivendo il riempimento del blocco quando esegui i successivi controlli di collisione nella stessa routine.

    Prova qualcosa di simile – aggiunge un flag in modo che la routine non “dimentichi” che è stata rilevata una collisione:

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

    Si noti che il controllo che si sta eseguendo (in base ai limiti in parent) riporterà le intersezioni del rettangolo che racchiudono i limiti visibili dei nodes all’interno dello stesso gruppo padre.

    Implementazione alternativa

    Nel caso ne abbiate bisogno, ho aggiornato il vostro campione originale in modo che sia in grado di controllare in base alla forma visiva del Nodo piuttosto che al riquadro di delimitazione della forma visiva. Ciò consente di rilevare con precisione le collisioni per forms non rettangolari come cerchi. La chiave per questo è il metodo 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; } } 

    Esempio di output del programma. Nel campione i cerchi sono stati trascinati e l’utente sta attualmente trascinando un cerchio che è stato contrassegnato come in collisione con un altro cerchio (dipingendolo in blu) – a scopo dimostrativo solo il cerchio attualmente trascinato ha il colore del colore della collisione.

    collisioni

    Commenti basati su domande aggiuntive

    Il link che ho postato su un’applicazione demo di intersezione in un commento precedente era per illustrare l’uso di vari tipi di limiti piuttosto che come un tipo specifico di campione di rilevamento di collisione. Per il tuo caso d’uso, non hai bisogno della complessità aggiuntiva dell’ascoltatore delle modifiche e del controllo di vari tipi di tipi di limiti: basterà semplicemente stabilirsi su un tipo. La maggior parte del rilevamento delle collisioni interesserà solo l’intersezione dei limiti visivi piuttosto che altri tipi di limiti JavaFX come i limiti di layout oi limiti locali di un nodo. Quindi puoi:

    1. Controlla l’intersezione di getBoundsInParent (come hai fatto nella domanda originale) che funziona sulla casella rettangular più piccola che comprenda le estremità visive del nodo OPPURE
    2. Utilizzare la Shape.intersect(shape1, shape2) se è necessario verificare in base alla forma visiva del nodo anziché al riquadro di delimitazione della forma visiva.

    Dovrei usare setLayoutX o translateX per il rettangolo

    Le proprietà layoutX e layoutY sono pensate per posizionare o disporre i nodes. Le proprietà translateX e translateY sono intese per modifiche temporanee alla posizione visiva di un nodo (ad esempio quando il nodo è in fase di animazione). Per il tuo esempio, anche se entrambe le proprietà funzioneranno, è forse meglio usare le proprietà di layout rispetto a quelle di traduzione, in questo modo se vuoi eseguire qualcosa come una TranslateTransition sui nodes, sarà più ovvio quale sia l’inizio e i valori di fine traduzione devono essere tali in quanto tali valori saranno relativi alla posizione di layout corrente del nodo anziché alla posizione nel gruppo principale.

    Un altro modo in cui puoi usare questi layout e tradurre le coordinate in tandem nel tuo esempio è se avevi qualcosa come un ESC da annullare durante un’operazione di trascinamento. È ansible impostare layoutX, Y nella posizione iniziale del nodo, avviare un’operazione di trascinamento che imposta valori translateX, Y e se l’utente preme ESC, impostare translateX, Y di nuovo su 0 per annullare l’operazione di trascinamento o se l’utente rilascia il mouse imposta layoutX, Y su layoutX, Y + translateX, Y e imposta translateX, Y torna a 0. L’idea è che la traduzione è valori usati per una modifica temporanea delle coordinate visive del nodo dalla sua posizione di layout originale.

    l’intersezione funzionerà anche se i cerchi sono animati? Intendo senza trascinare il cerchio con il mouse, cosa succederà se li faccio spostare casualmente. Anche il colore cambierà in questo caso?

    Per fare questo, basta cambiare dove viene chiamata la funzione di rilevamento delle collisioni e invocato il gestore di collisione. Anziché controllare le intersezioni basate su un evento di trascinamento del mouse (come nell’esempio precedente), controlla invece le collisioni all’interno di un listener di modifiche sui boundsInParentProperty() ogni boundsInParentProperty() .

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

    Nota: se hai molte forms animate, controllare le collisioni una volta per fotogramma all’interno di un ciclo di gioco sarà più efficiente di eseguire un controllo di collisione ogni volta che un nodo si muove (come avviene nel listener di boundsInParentProperty sopra).