web-dev-qa-db-fra.com

Vérification de la collision des formes avec JavaFX

J'essaie de faire une détection de collision. Pour ce test, j'utilise un simple Shape rectangulaire et vérifie leur Bound, pour déterminer s'ils entrent en collision. Bien que la détection ne fonctionne pas comme prévu. J'ai essayé d'utiliser différentes façons de déplacer l'objet (déplacer, setLayoutX, Y) et également différentes vérifications liées (boundsInLocal, boundsInParrent, etc.) mais je ne parviens toujours pas à faire fonctionner cela. Comme vous pouvez le voir, la détection ne fonctionne que pour un seul objet, même lorsque vous avez trois objets, un seul détecte la collision. Voici un code de travail illustrant le problème:

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

Il semble que vous ayez une légère erreur logique dans votre routine checkBounds - vous détectez correctement les collisions (en fonction des limites) mais vous écrasez le remplissage de votre bloc lorsque vous effectuez des vérifications de collision ultérieures dans la même routine.

Essayez quelque chose comme ça - il ajoute un indicateur pour que la routine n'oublie pas qu'une collision a été détectée:

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

Notez que la vérification que vous faites (basée sur les limites du parent) signalera les intersections du rectangle entourant les limites visibles des nœuds dans le même groupe parent.

Autre implémentation

Si vous en avez besoin, j'ai mis à jour votre échantillon d'origine afin qu'il puisse vérifier en fonction de la forme visuelle du Node plutôt que de la boîte englobante de la forme visuelle. Cela vous permet de détecter les collisions pour les formes non rectangulaires telles que les cercles. La clé pour cela est la méthode 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; }
}

Exemple de sortie de programme. Dans l'exemple, les cercles ont été déplacés et l'utilisateur fait actuellement glisser un cercle qui a été marqué comme entrant en collision avec un autre cercle (en le peignant en bleu) - à des fins de démonstration, seul le cercle en cours de déplacement a sa couleur de collision marquée.

collisions

Commentaires basés sur des questions supplémentaires

Le lien que j'ai publié vers une application de démonstration d'intersection dans un commentaire précédent était d'illustrer l'utilisation de différents types de limites plutôt que comme un type spécifique d'échantillon de détection de collision. Pour votre cas d'utilisation, vous n'avez pas besoin de la complexité supplémentaire de l'écouteur de changement et de la vérification de différents types de types de limites - il suffit de s'installer sur un type. La plupart des détections de collision ne seront intéressées que par l'intersection des limites visuelles plutôt que d'autres types de limites JavaFX tels que les limites de disposition ou les limites locales d'un nœud. Vous pouvez donc soit:

  1. Vérifiez l'intersection de getBoundsInParent (comme vous l'avez fait dans votre question d'origine) qui fonctionne sur la plus petite boîte rectangulaire qui englobera les extrémités visuelles du nœud OU
  2. Utilisez la routine Shape.intersect(shape1, shape2) si vous devez vérifier en vous basant sur la forme visuelle de Node plutôt que sur la boîte englobante de la forme visuelle.

Dois-je utiliser setLayoutX ou translateX pour le rectangle

Les propriétés layoutX et layoutY sont destinées au positionnement ou à la disposition des nœuds. Les propriétés translateX et translateY sont destinées à des modifications temporaires de l'emplacement visuel d'un nœud (par exemple lorsque le nœud subit une animation). Pour votre exemple, bien que l'une ou l'autre propriété fonctionne, il est peut-être préférable d'utiliser les propriétés de disposition que celles de traduction, de cette façon, si vous vouliez exécuter quelque chose comme TranslateTransition sur les nœuds, cela il est plus évident de définir les valeurs de traduction de début et de fin car ces valeurs seront relatives à la position de mise en page actuelle du nœud plutôt qu'à la position dans le groupe parent.

Vous pouvez également utiliser ces dispositions et traduire les coordonnées en tandem dans votre exemple si vous avez quelque chose comme un ESC à annuler au cours d'une opération de glissement. Vous pouvez définir layoutX, Y à l'emplacement initial de votre nœud, démarrer une opération de glissement qui définit les valeurs translateX, Y et si l'utilisateur appuie sur ESC, redéfinir translateX, Y sur 0 pour annuler l'opération de glissement ou si l'utilisateur relâche la souris définissez layoutX, Y sur layoutX, Y + translateX, Y et remettez translateX, Y sur 0. L'idée est que les valeurs de traduction sont utilisées pour une modification temporaire des coordonnées visuelles du nœud à partir de sa position de disposition d'origine.

l'intersection fonctionnera-t-elle même si les cercles sont animés? Je veux dire sans faire glisser le cercle avec la souris, que se passera-t-il si je les fais bouger de façon aléatoire. La couleur changera-t-elle également dans ce cas?

Pour ce faire, changez simplement où la fonction de détection de collision est appelée et le gestionnaire de collision invoqué. Plutôt que de rechercher des intersections basées sur un événement de glissement de souris (comme dans l'exemple ci-dessus), vérifiez plutôt les collisions au sein d'un écouteur de changement sur chaque nœud boundsInParentProperty() .

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

Remarque: si vous avez beaucoup de formes en cours d'animation, puis vérifier les collisions une fois par image dans une boucle de je sera plus efficace que d'exécuter une vérification de collision chaque fois qu'un nœud se déplace (comme cela se fait dans le changement boundsInParentProperty auditeur ci-dessus).

31
jewelsea