2013-02-21 5 views
12

Ich versuche eine Kollisionserkennung durchzuführen. Für diesen Test verwende ich einfache rechteckige Shape, und überprüfen Sie ihre Bound, um herauszufinden, ob sie kollidieren. Obwohl die Erkennung nicht wie erwartet funktioniert. Ich habe versucht, verschiedene Möglichkeiten zu verwenden, um das Objekt zu verschieben (relocate, setLayoutX, Y) und auch verschiedene gebundene Prüfungen (boundsInLocal, boundsInParrent usw.), aber ich kann immer noch nicht funktionieren. Wie Sie sehen können, funktioniert die Erkennung nur für ein Objekt, selbst wenn Sie drei Objekte haben, erkennt nur eins die Kollision. Dies ist einige Arbeitscode demonstriert das Problem:Überprüfen der Kollision von Formen mit 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

Versuchen Sie, mit dieser [Kreuzung-Demo-Anwendung] (https://gist.github.com/jewelsea/1441960) herumzuspielen, die ich geschrieben habe, um Kreuzungsbeziehungen verschiedener Grenzen in JavaFX zu demonstrieren. – jewelsea

+0

Ok, es sieht so aus, als ob alles, was mich interessiert, gerade in der ersten Klasse in dieser Datei ist. Eine wichtige Sache, die ich aufnehme, ist changeListener für die Überprüfung auf Kollisionen. Verwenden Sie auch LayoutBounds für Checks (??). Sollte ich setLayoutX oder translateX für das Rechteck verwenden? Ich sehe, dass Sie setX verwenden, aber das ist privat ich denke und auf doc ist nicht klar, welche die öffentliche Methode ist, die dasselbe Attribut ändert. – Giannis

+0

Aktualisierte Antwort, um weitere Fragen zu beantworten. – jewelsea

Antwort

23

Sieht aus wie Sie einen leichten logischen Fehler in der Checkbounds haben Routine - Sie Erkennung richtig Kollisionen (basierend auf Grenzen), sondern überschreiben die Füllung des Blocks, wenn Sie ausführen nachfolgende Kollisionsprüfungen in der gleichen Routine.

so etwas wie dies versuchen - es fügt eine Flagge, so dass die Routine nicht „vergessen“, dass eine Kollision erkannt wurde:

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

Beachten Sie, dass die Prüfung Sie (basierend auf Grenzen in Eltern) tun wird berichte Kreuzungen des Rechtecks, das die sichtbaren Grenzen von Knoten innerhalb derselben Elterngruppe einschließt.

Alternative Implementierung

Falls Sie sie brauchen, ich Ihre ursprüngliche Probe aktualisiert, so dass es in der Lage ist, basierend zu überprüfen auf der visuellen Form der Knoten anstelle dem Begrenzungsrahmen der visuellen Form. Auf diese Weise können Sie Kollisionen für nicht rechteckige Formen wie Kreise genau erkennen. Der Schlüssel dafür ist die Shape.intersects(shape1, shape2) Methode.

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

Beispielprogrammausgabe. Im Beispiel wurden die Kreise herumgezogen, und der Benutzer zieht gerade einen Kreis, der als kollidierend mit einem anderen Kreis markiert wurde (indem er blau gezeichnet wurde). Zu Demonstrationszwecken ist nur der gerade gezogene Kreis mit einer Kollisionsfarbe markiert.

collisions

Kommentare basierend auf weitere Fragen

Der Link, den ich in einem früheren Kommentar zu einem intersection demo application gepostet wurde die Verwendung verschiedener Grenzen Typen nicht als eine spezifische Art von Kollisionserkennung Probe zu veranschaulichen . Für Ihren Anwendungsfall benötigen Sie nicht die zusätzliche Komplexität des Änderungslisteners und die Überprüfung verschiedener Arten von Begrenzungstypen - es genügt, sich auf einen Typ festzulegen. Die meisten Kollisionserkennungen sind nur an der Überschneidung visueller Grenzen interessiert und nicht an anderen JavaFX-Grenzen, z. B. den Layoutgrenzen oder lokalen Grenzen eines Knotens. So können Sie entweder:

  1. prüfen Kreuzung von getBoundsInParent (wie Sie in Ihrer ursprünglichen Frage tat), die auf dem kleinsten rechteckigen Box funktioniert, die die visuellen Extremitäten des Knotens umfassen wird ODER
  2. Verwenden Sie die Shape.intersect(shape1, shape2) Routine, wenn Sie müssen anhand der visuellen Form des Knotens und nicht anhand der Begrenzungsbox der visuellen Form überprüfen.

Soll ich setLayoutX oder translateX für das Rechteck

Die layoutX und layoutY Eigenschaften bestimmt sind, für die Positionierung oder Auslegen Knoten werden. Die Eigenschaften translateX und translateY sind für vorübergehende Änderungen am visuellen Speicherort eines Knotens vorgesehen (z. B. wenn der Knoten eine Animation durchläuft). Für Ihr Beispiel, obwohl eine der beiden Eigenschaften funktioniert, ist es vielleicht besser, die Layout-Eigenschaften zu verwenden als die Translate, wenn Sie auf den Knoten so etwas wie einen TranslateTransition ausführen möchten, wird es offensichtlicher sein, was der Start und ist end translate-Werte sollten so sein, dass diese Werte relativ zur aktuellen Layoutposition des Knotens sind und nicht zur Position in der übergeordneten Gruppe.

Eine andere Möglichkeit, diese Layouts zu verwenden und die Koordinaten im Tandem in Ihrem Beispiel zu übersetzen, besteht darin, dass Sie während eines Ziehvorgangs so etwas wie einen ESC-Befehl abbrechen mussten. Sie könnten layoutX, Y auf die anfängliche Position Ihres Knotens setzen, eine Ziehoperation starten, die translateX-, Y-Werte setzt, und wenn der Benutzer ESC drückt, setzen Sie translateX, Y zurück auf 0, um den Ziehvorgang abzubrechen oder wenn der Benutzer die Maustaste loslässt setze layoutX, Y auf layoutX, Y + translateX, Y und setze translateX, Y zurück auf 0. Die Idee ist, dass die Übersetzung für eine temporäre Änderung der visuellen Koordinaten des Knotens von seiner ursprünglichen Layoutposition verwendet wird.

funktioniert die Schnittmenge, obwohl die Kreise animiert sind? Ich meine, ohne den Kreis mit der Maus zu ziehen, was passieren wird, wenn ich sie dazu bringen würde, sich zufällig zu bewegen. Ändert sich in diesem Fall auch die Farbe?

Um dies zu tun, nur ändern, in dem die Kollisionserkennung Funktion aufgerufen wird und die Kollisions Handler aufgerufen. Anstatt nach Schnittpunkten zu suchen, die auf einem Maus-Drag-Ereignis basieren (wie im obigen Beispiel), prüfen Sie stattdessen auf Kollisionen innerhalb eines Change-Listeners auf jedem Knoten boundsInParentProperty().

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

Hinweis: Wenn Sie viele Formen haben animiert werden, dann auf Kollisionen überprüft einmal pro Frame innerhalb eines game loop wird effizienter als eine Kollisionsprüfung ausgeführt wird, wenn alle Knoten bewegt (wie in der boundsInParentProperty Änderungsempfänger erfolgt über).

+0

Vielen Dank für Ihre Hilfe, es hat wirklich einige Konzepte geklärt. – Giannis

+0

Noch arbeiten an diesem Projekt und ich habe ein anderes Problem. Kannst du einen besseren Weg vorschlagen, als mehrere Formen im Scenebuilder zu kombinieren, um Blöcke zu erzeugen, die ähnlich wie Scratch Blöcke oder Google Blocky verwendet werden? Das Problem besteht darin, Blöcke so zu transformieren, dass sie in andere hineinpassen. – Giannis

+0

Form Kombination ist eine andere Frage als das Original und die Form Kombination Frage ist auch nicht so klar, würde ich raten, eine neue Frage mit mehr Beschreibung und klare Beispielbilder, die genau zeigen, einige Formen und Kombinationen von Formen, die Sie benötigen. – jewelsea