Проверка столкновения фигур с JavaFX

Я пытаюсь сделать некоторые обнаружения столкновений. Для этого теста я использую простой прямоугольный Shape, и просмотрев их Bound, чтобы понять, сталкиваются ли они. Хотя обнаружение не работает, как ожидалось. Я пробовал использовать разные способы перемещения объекта (relocate, setLayoutX,Y), а также различные связанные проверки (boundsInLocal, boundsInParrent и т. д.), Но я все еще не могу заставить это работать. Как вы можете видеть, обнаружение работает только для одного объекта, даже если у вас есть только три объекта один обнаруживает столкновение. Это некоторый рабочий код, демонстрирующий проблему:

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

1 ответов


похоже, у вас есть небольшая логическая ошибка в вашей процедуре checkBounds - вы правильно обнаруживаете столкновения (на основе границ), но перезаписываете заполнение вашего блока при выполнении последующих проверок столкновений в той же процедуре.

попробуйте что - то вроде этого-он добавляет флаг, чтобы процедура не "забывала", что было обнаружено столкновение:

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

обратите внимание, что проверка, которую вы делаете (на основе границ в родителе), сообщит о пересечениях прямоугольник, заключающий видимые границы узлов в пределах одной родительской группы.

Альтернативная Реализация

в случае, если вам это нужно, я обновил ваш исходный образец, чтобы он мог проверять на основе визуальной формы узла, а не ограничивающей рамки визуальной формы. Это позволяет точно обнаруживать столкновения для непрямоугольных фигур, таких как круги. Ключом к этому является формы.пересекается(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; }
}

пример вывода программы. В образце круги были перетащены, и пользователь в настоящее время перетаскивает круг, который был отмечен как сталкивающийся с другим кругом (покрасив его синим цветом) - для демонстрационных целей только круг, который в настоящее время перетаскивается, имеет цвет столкновения.

collisions

комментарии на основе дополнительных вопросов

ссылка, которую я разместил в перекресток демонстрационное приложение в предыдущем комментарии было проиллюстрировать использование различных типов границ, а не в качестве конкретного типа образца обнаружения столкновения. Для вашего случая использования вам не нужна дополнительная сложность прослушивателя изменений и проверка различных типов границ - достаточно просто остановиться на одном типе. Большинство обнаружения столкновений будет интересоваться только пересечением визуальных границ, а не другими типами границ JavaFX, такими как границы макета или локальные границы узла. Так что вы можете либо:

  1. проверка на пересечение getBoundsInParent (как и в исходном вопросе), который работает на самом маленьком прямоугольном ящике, который будет охватывать визуальные конечности узла или
  2. использовать Shape.intersect(shape1, shape2) подпрограмма, если вам нужно проверить на основе визуальной формы узла, а не ограничивающую рамку визуальной формы.

должен ли я использовать setLayoutX или translateX для прямоугольник

на layoutX и свойства layoutY предназначены для позиционирования или размещения узлов. The translateX и translateY свойства предназначены для временных изменений визуального расположения узла (например, когда узел проходит анимацию). Для вашего примера, хотя любое свойство будет работать, возможно, лучше использовать свойства макета, чем переводить, таким образом, если вы хотите что-то запустить как TranslateTransition на узлах будет более очевидно, какими должны быть начальные и конечные значения перевода, поскольку эти значения будут относительно текущей позиции макета узла, а не позиции в родительской группе.

другой способ, которым вы могли бы использовать этот макет и перевести координаты в тандеме в вашем примере,-это если у вас было что-то вроде ESC для отмены во время операции перетаскивания. Вы можете установить layoutX, Y в исходное местоположение узла, начать операцию перетаскивания, который устанавливает транслатекс,значения y и если пользователь нажимает клавишу Esc, набор транслатекс,y обратно в 0, чтобы отменить операцию перетаскивания или если пользователь отпускает кнопку мыши установить layoutX,г до layoutX,г+транслатекс,Y и установить транслатекс,г до 0. Идея заключается в том, что значения translation is используются для временной модификации визуальных координат узла из его исходной позиции макета.

будет пересекаться, хотя круги анимированные? Я имею в виду, не перетаскивая круг мышью, что произойдет, если я заставлю их двигаться случайным образом. Изменится ли цвет и в этом случае?

для этого просто измените, где вызывается функция обнаружения столкновений и вызывается обработчик столкновений. Вместо того, чтобы проверять пересечения на основе события перетаскивания мыши (как в примере выше), вместо этого проверьте коллизии в прослушивателе изменений на каждом узле boundsInParentProperty().

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

Примечание: Если у вас есть много анимированных фигур, а затем проверка столкновений один раз за кадр в игровой цикл будет более эффективным, чем выполнение проверки столкновения при каждом перемещении любого узла (как это сделано в прослушивателе изменений boundsInParentProperty выше).