使用 JavaFX 检查形状的碰撞 [英] Checking Collision of Shapes with JavaFX

查看:31
本文介绍了使用 JavaFX 检查形状的碰撞的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试进行一些碰撞检测.对于这个测试,我使用简单的矩形 Shape,并检查它们的 Bound,以确定它们是否发生碰撞.虽然检测没有按预期工作.我尝试使用不同的方式来移动对象(重定位、setLayoutX、Y)以及不同的边界检查(boundsInLocal、boundsInParrent 等),但我仍然无法让它工作.如您所见,检测仅适用于一个物体,即使您有三个物体,也只有一个物体检测碰撞.这是一些演示问题的工作代码:

import javafx.application.Application;导入 javafx.event.EventHandler;导入 javafx.scene.Cursor;导入 javafx.scene.Group;导入 javafx.scene.Scene;导入 javafx.scene.input.MouseEvent;导入 javafx.scene.paint.Color;导入 javafx.scene.shape.Rectangle;导入 javafx.stage.Stage;导入 java.util.ArrayList;公共类 CollisionTester 扩展应用程序 {私有 ArrayList<矩形>矩形数组列表;公共静态无效主(字符串 [] args){发射(参数);}公共无效开始(阶段primaryStage){primaryStage.setTitle("测试");组根 = new Group();场景场景 = 新场景(root, 400, 400);矩形阵列列表 = 新阵列列表<矩形>();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(矩形块:矩形阵列列表){setDragListeners(block);}root.getChildren().addAll(rectangleArrayList);primaryStage.setScene(场景);primaryStage.show();}public void setDragListeners(最终矩形块){最终 Delta dragDelta = new Delta();block.setOnMousePressed(new EventHandler() {@覆盖公共无效句柄(MouseEvent mouseEvent){//记录拖放操作的增量距离.dragDelta.x = block.getTranslateX() - mouseEvent.getSceneX();dragDelta.y = block.getTranslateY() - mouseEvent.getSceneY();块.setCursor(Cursor.NONE);}});block.setOnMouseReleased(new EventHandler() {@覆盖公共无效句柄(MouseEvent mouseEvent){块.setCursor(Cursor.HAND);}});block.setOnMouseDragged(new EventHandler() {@覆盖公共无效句柄(MouseEvent mouseEvent){block.setTranslateX(mouseEvent.getSceneX() + dragDelta.x);block.setTranslateY(mouseEvent.getSceneY() + dragDelta.y);检查边界(块);}});}私有无效检查边界(矩形块){for (Rectangle static_bloc : rectangleArrayList)如果(静态块!=块){如果 (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {块.setFill(颜色.蓝色);//碰撞} 别的 {块.setFill(颜色.绿色);//无碰撞}} 别的 {块.setFill(颜色.绿色);//无碰撞-相同的块}}类 Delta {双 x, y;}}

解决方案

看起来您的 checkBounds 例程中存在轻微的逻辑错误 - 您正确地检测了碰撞(基于边界),但在您执行此操作时覆盖了块的填充在同一个例程中执行后续的碰撞检查.

尝试这样的事情 - 它添加了一个标志,以便例程不会忘记"检测到碰撞:

private void checkBounds(Shape block) {布尔碰撞检测 = 假;对于(形状静态块:节点){如果(静态块!=块){static_bloc.setFill(Color.GREEN);如果 (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {碰撞检测 = 真;}}}如果(碰撞检测){块.setFill(颜色.蓝色);} 别的 {块.setFill(颜色.绿色);}}

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

替代实现

如果您需要它,我更新了您的原始示例,以便它能够根据节点的视觉形状而不是视觉形状的边界框进行检查.这使您可以准确检测非矩形形状(例如圆形)的碰撞.其关键是 的链接是为了说明使用各种边界类型,而不是作为特定类型的碰撞检测样本.对于您的用例,您不需要更改侦听器和检查各种不同类型的边界类型的额外复杂性 - 只需解决一种类型就足够了.大多数碰撞检测只对视觉边界的交集感兴趣,而不是其他 JavaFX 边界类型,例如节点的布局边界或局部边界.所以你可以:

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

<块引用>

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

layoutX和 layoutY 属性用于定位或布置节点.translateX 和 translateY 属性用于临时更改节点的视觉位置(例如,当节点正在播放动画时).对于您的示例,尽管任一属性都可以使用,但使用布局属性可能比翻译属性更好,如果您确实想运行类似 TranslateTransition 在节点上,开始和结束翻译值应该是什么会更明显,因为这些值将是相对的到节点的当前布局位置而不是父组中的位置.

您可以在示例中使用这些布局并同时转换坐标的另一种方法是,如果您在拖动操作过程中需要取消 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(块));

注意:如果你有很多形状被动画化,那么在 游戏循环 将比在任何节点移动时运行碰撞检查更有效(如在上面的 boundsInParentProperty 更改侦听器中所做的那样).

I am trying to do some collision detection. For this test I am using simple rectangular Shape, and checking their Bound, to figure if they are colliding. Although the detection does not work as expected. I have tried using different ways to move the object(relocate, setLayoutX,Y) and also different bound checks (boundsInLocal,boundsInParrent etc) but I still cannot get this to work. As you can see the detection works only for one object, even when you have three objects only one detects collision. This is some working code demonstrating the problem:

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

解决方案

Looks like you have a slight logic error in your checkBounds routine - you are correctly detecting collisions (based on bounds) but are overwriting the fill of your block when you perform subsequent collision checks in the same routine.

Try something like this - it adds a flag so that the routine does not "forget" that a collision was detected:

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

Note that the check you are doing (based on bounds in parent) will report intersections of the rectangle enclosing the visible bounds of nodes within the same parent group.

Alternate Implementation

In case you need it, I updated your original sample so that it is able to check based on the visual shape of the Node rather than the bounding box of the visual shape. This lets you to accurately detect collisions for non-rectangular shapes such as Circles. The key for this is the Shape.intersects(shape1, shape2) method.

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

Sample program output. In the sample the circles have been dragged around and the user is currently dragging a circle which has been marked as colliding with another circle (by painting it blue) - for demonstration purposes only the circle currently being dragged has it's collision color marked.

Comments based on additional questions

The link I posted to an intersection demo application in a prior comment was to illustrate the use of various bounds types rather than as a specific type of collision detection sample. For your use case, you don't need the additional complexity of the change listener and checking on various different kinds of bounds types - just settling on one type will be enough. Most collision detection is only going to be interested in intersection of visual bounds rather than other JavaFX bounds types such as the layout bounds or local bounds of a node. So you can either:

  1. Check for intersection of getBoundsInParent (as you did in your original question) which works on the smallest rectangular box which will encompass the visual extremities of the node OR
  2. Use the Shape.intersect(shape1, shape2) routine if you need to check based on the visual shape of the Node rather than the bounding box of the visual shape.

Should I be using setLayoutX or translateX for the rectangle

The layoutX and layoutY properties are intended for positioning or laying out nodes. The translateX and translateY properties are intended for temporary changes to the visual location of a node (for example when the node is undergoing an animation). For your example, though either property will work, it is perhaps better form to use the layout properties than the translate ones, that way if you did want to run something like a TranslateTransition on the nodes, it will be more obvious what the start and end translate values should be as those values will be relative to the current layout position of the node rather than the position in the parent group.

Another way you could use these layout and translate co-ordinates in tandem in your sample is if you had something like an ESC to cancel during the course of a drag operation. You could set layoutX,Y to the initial location of your node, start a drag operation which sets translateX,Y values and if the user presses ESC, set translateX,Y back to 0 to cancel the drag operation or if the user releases the mouse set layoutX,Y to layoutX,Y+translateX,Y and set translateX,Y back to 0. The idea is that the translation is values are used for a temporary modification of the visual co-ordinates of the node from it's original layout position.

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?

To do this, just change where the collision detection function is called and the collision handler invoked. Rather than checking for intersections based upon a mouse drag event (like the example above), instead check for collisions within a change listener on each node's boundsInParentProperty().

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

Note: if you have lots of shapes being animated, then checking for collisions once per frame within a game loop will be more efficient than running a collision check whenever any node moves (as is done in the boundsInParentProperty change listener above).

这篇关于使用 JavaFX 检查形状的碰撞的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆