Подтвердить что ты не робот

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

Я пытаюсь выполнить обнаружение конфликтов. Для этого теста я использую простой прямоугольник Shape и проверяю их Bound, чтобы понять, если они сталкиваются. Хотя обнаружение не работает должным образом. Я попытался использовать разные способы перемещения объекта (переместить, 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;
    }
}
4b9b3361

Ответ 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);
  }
}

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

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

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

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

collisions

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

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

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

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

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

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

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

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

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

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