2013-02-21 12 views
12

Sto tentando di eseguire il rilevamento delle collisioni. Per questo test sto usando il semplice rettangolare Shape e controllando il loro Bound, per capire se sono in collisione. Sebbene il rilevamento non funzioni come previsto. Ho provato a utilizzare diversi modi per spostare l'oggetto (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 oggetto, anche quando hai tre oggetti solo uno rileva la collisione. Questo è un codice di lavoro che dimostra il problema:Controllo collisione di forme con JavaFX

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<Rectangle> 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<Rectangle>(); 
     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<MouseEvent>() { 
      @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<MouseEvent>() { 
      @Override 
      public void handle(MouseEvent mouseEvent) { 
       block.setCursor(Cursor.HAND); 
      } 
     }); 
     block.setOnMouseDragged(new EventHandler<MouseEvent>() { 
      @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; 
    } 
} 
+2

Provare a giocare con questa [applicazione demo intersezione] (https://gist.github.com/jewelsea/1441960) che ho scritto per dimostrare le relazioni di intersezione di vari tipi di limiti in JavaFX. – jewelsea

+0

Ok sembra che tutto quello che mi interessa in questo momento sia sulla prima classe in quel file. Una cosa importante che prendo è changeListener per verificare le collisioni. Usa anche LayoutBounds su checks (??). Dovrei usare setLayoutX o translateX per il rettangolo? Vedo che stai usando setX ma questo è privato credo e su doc ​​non è chiaro quale sia il metodo pubblico che cambia lo stesso attributo. – Giannis

+0

Risposta aggiornata per rispondere a ulteriori domande. – jewelsea

risposta

23

Sembra che tu abbia un leggero errore logico nei vostri checkBounds di routine - si sta correttamente Rilevamento di collisioni (sulla base di limiti), ma sta sovrascrivendo il riempimento del blocco quando si esegue successivi controlli di collisione nella stessa routine.

provare qualcosa di simile - si aggiunge una bandiera in modo che la routine non "dimentica" che la collisione è stato rilevato:

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 si sta facendo (in base a limiti di genitore) saranno segnalare le intersezioni del rettangolo che racchiudono i limiti visibili dei nodi all'interno dello stesso gruppo principale.

Attuazione alternativo

In caso di necessità, ho aggiornato il vostro campione originale in modo che sia in grado di controllare in base alla forma visiva del nodo piuttosto che la casella di delimitazione della forma visiva. Ciò consente di rilevare con precisione le collisioni per forme 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<Shape> 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<MouseEvent>() { 
     @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<MouseEvent>() { 
     @Override public void handle(MouseEvent mouseEvent) { 
     block.setCursor(Cursor.HAND); 
     } 
    }); 
    block.setOnMouseDragged(new EventHandler<MouseEvent>() { 
     @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 presenta il colore del colore della collisione.

collisions

Commenti sulla base di ulteriori domande

Il link che ho postato a un intersection demo application in un commento precedente era per illustrare l'uso di vari tipi bounds piuttosto che come un tipo specifico di campione rilevamento delle collisioni . 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 è possibile:

  1. Verificare la presenza di intersezione di getBoundsInParent (come avete fatto nella vostra domanda originale) che lavora sulla più piccola scatola rettangolare che comprenderà le estremità visive del nodo o
  2. Utilizzare i Shape.intersect(shape1, shape2) di routine, se è necessario verificare in base alla forma visiva del nodo piuttosto che al riquadro di delimitazione della forma visiva.

Dovrei usare setLayoutX o translateX per il rettangolo

I layoutX e layoutY sono destinati posizionamento o distendersi nodi. Le proprietà translateX e translateY sono destinate a modifiche temporanee all'ubicazione visiva di un nodo (ad esempio quando il nodo è in fase di animazione). Per il tuo esempio, sebbene entrambe le proprietà funzionino, è forse meglio usare le proprietà di layout rispetto a quelle di traduzione, in questo modo se vuoi eseguire qualcosa come un TranslateTransition sui nodi, 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 è possibile utilizzare questi layout e tradurre coordinate in tandem nel campione è se si ha qualcosa come un ESC per annullare nel corso di un'operazione di trascinamento. È possibile 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 funziona 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 eseguire questa operazione, è sufficiente modificare la posizione in cui viene richiamata la funzione di rilevamento delle collisioni e richiamato 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 su ogni nodo boundsInParentProperty().

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

Nota: se hai un sacco di forme di essere animato, quindi il controllo per le collisioni, una volta per frame all'interno di un game loop sarà più efficiente di esecuzione di un controllo di collisione ogni volta che qualsiasi mossa nodi (come si fa nel cambiamento ascoltatore boundsInParentProperty sopra).

+0

Grazie mille per il tuo aiuto ha chiarito alcuni concetti. – Giannis

+0

Ancora lavorando a questo progetto e sto affrontando un altro problema. Puoi suggerire un modo migliore rispetto alla combinazione di diverse forme in Scenebuilder, al fine di creare blocchi che saranno utilizzati in modo simile ai blocchi Scratch o google Blocky? Il problema è con la trasformazione dei blocchi per adattarli agli altri. – Giannis

+0

La combinazione di forme è una domanda diversa dall'originale e anche la domanda sulla combinazione di forme non è del tutto chiara. Suggerirei di fare una nuova domanda fornendo più descrizioni e immagini di esempio chiare che dimostrino esattamente alcune forme e combinazioni di forme necessarie. – jewelsea