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,Color.RED)); rectangleArrayList.add(new Rectangle(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; } }
解决方法
尝试这样的东西 – 它添加一个标志,以便例程不会“忘记”检测到冲突:
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); } }
请注意,您正在做的检查(基于父项中的边界)将报告包含同一父组中节点可见边界的矩形的交集.
替代执行
如果需要,我更新了原始样本,以便能够根据节点的视觉形状检查,而不是视觉形状的边界框.这样可以准确地检测非矩形形状(如圆形)的碰撞.这是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); 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; } }
示例程序输出.在示例中,圈子已被拖动,用户正在拖动一个已被标记为与另一个圆相撞的圆(通过绘制蓝色) – 为了演示的目的,只有当前被拖动的圆具有碰撞颜色标记.
基于其他问题的评论
在之前的评论中,我发布到intersection demo application的链接是为了说明各种边界类型的使用,而不是作为特定类型的碰撞检测样本.对于您的用例,您不需要更改侦听器的其他复杂性,并检查各种不同类型的边界类型 – 只需一种类型即可.大多数碰撞检测只会对可视边界的交叉感兴趣,而不是其他JavaFX边界类型,如节点的布局界限或局部边界.所以你可以:
>检查getBoundsInParent的交集(如您在原始问题中所做的那样),它在最小的矩形框上工作,该矩形框将包含节点的视觉四肢
>如果需要根据节点的视觉形状检查,而不是视觉形状的边框,请使用Shape.intersect(shape1,shape2)例程.
Should I be using setLayoutX or translateX for the rectangle
layoutX和layoutY属性用于定位或布局节点. translateX和translateY属性旨在临时更改节点的可视位置(例如,当节点正在进行动画时).对于您的示例,尽管任何一种属性都可以正常工作,但使用布局属性比翻译版本更好的形式就是这样,如果你想在节点上运行像TranslateTransition这样的方式,那么开始和结束转换值应该是这些值将相对于节点的当前布局位置而不是父组中的位置.
您可以使用这些布局和在示例中一起翻译坐标的另一种方法是在拖动操作过程中,如果您有某种类似于ESC来取消的坐标.您可以将layoutX,Y设置为节点的初始位置,开始拖动操作,设置translateX,Y值,如果用户按ESC键,则将translateX,Y设置为0以取消拖动操作,或者如果用户释放鼠标将layoutX,Y设置为layoutX,Y translateX,Y并将translateX,Y设置为0.想法是,转换值用于从其原始布局位置临时修改节点的视觉坐标.
will the intersect work even though the circles are animated? I mean without dragging the circle by mouse,what will happen if I made them to move around randomly. Will the colour change in this case also?
为此,只需更改调用碰撞检测功能的位置并调用碰撞处理程序.而不是基于鼠标拖动事件检查交叉点(如上面的示例),而是检查每个节点的boundsInParentProperty()
上的更改监听器中的冲突.
block.boundsInParentProperty().addListener((observable,oldValue,newValue) -> checkShapeIntersection(block) );
注意:如果您有很多形状是动画的,那么在game loop中每帧框架检查一次会比每当任何节点移动时执行冲突检查(如上面的boundsInParentProperty更改监听器中所做的那样)更有效.