使用 JavaFX 检查形状的碰撞

Posted

技术标签:

【中文标题】使用 JavaFX 检查形状的碰撞【英文标题】:Checking Collision of Shapes with JavaFX 【发布时间】:2013-02-07 11:09:24 【问题描述】:

我正在尝试进行一些碰撞检测。对于这个测试,我使用简单的矩形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;
    

【问题讨论】:

尝试使用我写的这个intersection demo application 来演示JavaFX 中各种边界类型的交集关系。 好吧,看起来我现在感兴趣的所有内容都在该文件的第一个类中。我拿起的一件重要的事情是 changeListener 用于检查碰撞。还要在检查时使用 LayoutBounds (??)。我应该对矩形使用 setLayoutX 还是 translateX ?我看到您正在使用 setX 但我猜那是私有的,并且在文档中不清楚哪个是更改相同属性的公共方法。 更新答案以解决其他问题。 【参考方案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);
  

请注意,您正在执行的检查(基于父项中的边界)将报告包含同一父组中节点可见边界的矩形的交点。

替代实现

如果您需要,我更新了您的原始示例,以便它能够根据节点的视觉形状而不是视觉形状的边界框进行检查。这使您可以准确地检测非矩形形状(例如圆形)的碰撞。关键是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; 

示例程序输出。在示例中,圆圈已被拖动,用户当前正在拖动一个圆圈,该圆圈已标记为与另一个圆圈碰撞(通过将其涂成蓝色) - 出于演示目的,只有当前被拖动的圆圈标记了它的碰撞颜色。

基于其他问题的评论

我在之前的评论中发布到intersection demo application 的链接是为了说明各种边界类型的使用,而不是作为特定类型的碰撞检测示例。对于您的用例,您不需要更改侦听器的额外复杂性并检查各种不同类型的边界类型 - 只需选择一种类型就足够了。大多数碰撞检测只对视觉边界的交集感兴趣,而不是其他 JavaFX 边界类型,例如布局边界或节点的局部边界。所以你可以:

    检查getBoundsInParent 的交集(就像您在原始问题中所做的那样),它适用于包含节点的视觉末端的最小矩形框或 如果您需要根据节点的视觉形状而不是视觉形状的边界框进行检查,请使用Shape.intersect(shape1, shape2) 例程。

我应该对矩形使用 setLayoutX 还是 translateX

layoutX 和 layoutY 属性用于定位或布局节点。 translateX 和 translateY 属性用于临时更改节点的可视位置(例如,当节点正在播放动画时)。对于您的示例,尽管任一属性都可以使用,但使用布局属性可能比使用翻译属性更好,这样,如果您确实想在节点上运行类似 TranslateTransition 的东西,那么更明显的是start 和 end translate 值应该是相对于节点的当前布局位置而不是父组中的位置。

您可以在示例中使用这些布局并同时转换坐标的另一种方法是,如果您在拖动操作过程中有类似 ESC 的东西要取消。您可以将 layoutX,Y 设置为节点的初始位置,启动设置 translateX,Y 值的拖动操作,如果用户按下 ESC,则将 translateX,Y 设置回 0 以取消拖动操作或用户释放鼠标将 layoutX,Y 设置为 layoutX,Y+translateX,Y 并将 translateX,Y 设置回 0。其想法是,平移值用于临时修改节点从其原始布局位置的视觉坐标。

即使圆圈是动画的,相交也会起作用吗?我的意思是不用鼠标拖动圆圈,如果我让它们随机移动会发生什么。这种情况下颜色也会改变吗?

为此,只需更改调用碰撞检测函数和调用碰撞处理程序的位置。不是基于鼠标拖动事件检查交叉点(如上面的示例),而是检查每个节点的 boundsInParentProperty() 上的更改侦听器内的冲突。

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

注意:如果您有很多动画形状,那么在 game loop 中每帧检查一次碰撞将比在任何节点移动时运行碰撞检查更有效(如上面的 boundsInParentProperty 更改侦听器中所做的那样) .

用于处理非矩形形状输入的附加信息

对于输入检测而不是碰撞检测,因此与您的问题没有直接关系,如果您需要与非矩形节点进行鼠标或触摸交互,请查看node.pickOnBounds 设置。

【讨论】:

非常感谢您的帮助,它确实理清了一些概念。 仍在做这个项目,我面临另一个问题。您能否提出比在场景构建器中组合多个形状更好的方法,以便创建类似于 Scratch 块或 google Blocky 使用的块?问题在于转换块以适应其他人。 形状组合是一个与原始问题不同的问题,形状组合问题也不是很清楚,我建议提出一个新问题,提供更多描述和清晰的示例图像,准确展示一些形状和您需要的形状组合。 嗨,即使圆圈是动画的,相交也会起作用吗?我的意思是不用鼠标拖动圆圈,如果我让它们随机移动会发生什么。这种情况下颜色也会改变吗?

以上是关于使用 JavaFX 检查形状的碰撞的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 HTML5 Javascript Canvas 获得三个碰撞形状的交集并删除未碰撞的部分?

复杂形状的碰撞检测

复杂形状的碰撞检测

俄罗斯方块游戏开发系列教程5:形状碰撞检测(下)

形状碰撞 C++ 的设计模式

p2之碰撞