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

查看:198
本文介绍了使用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;


公共类CollisionTester扩展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();
场景场景=新场景(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){
//记录delta的距离拖放操作。
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(
if) block.getBoundsInParent()。intersects(static_bloc.getBoundsInParent())){
block.setFill(Color.BLUE); // collision
} else {
block.setFill(Color.GREEN); //没有碰撞
}
}其他{
block.setFill(Color.GREEN); //没有碰撞-same block
}
}

class Delta {
double x,y;
}
}


解决方案

看起来就像你在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);
}
}

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



备用实现



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


  1. 检查 getBoundsInParent 的交集(正如你所做的那样)您的原始问题)适用于最小的矩形框,它将包含节点的视觉末端OR

  2. 使用 Shape.intersect(shape1,shape2)例程如果你需要根据Node的视觉形状而不是视觉形状的边界框进行检查。



< blockquote>

我是否应该使用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(block)
);

注意:如果你有很多形状的动画,那么在每个帧内检查一次碰撞< a href =http://svanimpe.be/blog/game-loops-fx.html\"rel =noreferrer>游戏循环将比任何节点移动时运行碰撞检查更有效(因为在上面的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天全站免登陆