2013-02-21 156 views
12

我正在尝试做一些碰撞检测。对于这个测试,我使用简单的长方形Shape,并检查他们的Bound,以确定它们是否发生碰撞。虽然检测不能按预期工作。我尝试过使用不同的方法来移动对象(relocate,setLayoutX,Y),还有不同的绑定检查(boundsInLocal,boundsInParrent等),但我仍然无法得到这个工作。正如你所看到的,检测只适用于一个物体,即使你有三个物体只有一个物体检测到碰撞。这是一些工作的代码演示问题:检查形状与JavaFX的碰撞

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

试着玩弄我写的这个[intersection demo application](https://gist.github.com/jewelsea/1441960)来演示JavaFX中各种边界类型的交集关系。 – jewelsea 2013-02-21 23:11:47

+0

好吧,看起来我现在感兴趣的所有内容都在该文件的第一课。我选择的一件重要事情是用于检查冲突的changeListener。在检查上也使用LayoutBounds(??)。我应该使用setLayoutX还是translateX作为矩形?我看到你正在使用setX,但那是私人的,我猜测,在doc上它不清楚哪一个是改变相同属性的公共方法。 – Giannis 2013-02-21 23:34:23

+0

更新回答解决其他问题。 – jewelsea 2013-02-22 00:58:05

回答

23

看起来你在你的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; } 
} 

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

collisions

基于其他问题

我张贴到intersection demo application在现有评论的链接

评论是示出使用各种边界类型的,而不是作为特定类型的碰撞检测样品的。对于您的用例,您不需要更改侦听器的额外复杂性并检查各种不同类型的边界类型 - 只需要解决一种类型就足够了。大多数碰撞检测只会对可视边界的交集感兴趣,而不是其他JavaFX边界类型,例如节点的布局边界或局部边界。所以,你可以:

  1. 检查的getBoundsInParent路口(如你在原来的问题所做的那样),其上最小的矩形框,这将包括该节点的视觉四肢或
  2. 使用Shape.intersect(shape1, shape2)例行程序的工作原理您需要根据节点的视觉形状而不是视觉形状的边界框进行检查。

我应该使用setLayoutX或平移X为layoutX和layoutY属性旨在用于定位或铺设节点矩形

translateX和translateY属性用于临时更改节点的可视位置(例如节点正在进行动画时)。对于你的例子,虽然两个属性都可以工作,但使用布局属性可能比翻译属性更好,那样的话,如果你想在节点上运行一个类似TranslateTransition的东西, end转换值应该是这些值将相对于节点的当前布局位置而不是父组中的位置。

另一种可以在样本中使用这些布局和坐标转换的方法是,如果您在拖动操作过程中有类似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变化监听器完成以上)。

+0

非常感谢您的帮助,它确实清除了一些概念。 – Giannis 2013-02-22 01:19:20

+0

仍然在这个项目上工作,我面临着另一个问题。你能否建议比在scenebuilder中组合几个形状更好的方法,以创建类似于Scratch块或Google Blocky的块?问题在于将块转换​​为适合其他人。 – Giannis 2013-03-20 17:51:17

+0

形状组合是一个与原始不同的问题,形状组合问题也不是那么清晰,我建议提出一个新问题,提供更多描述和清晰的示例图像,以展示您需要的某些形状和形状组合。 – jewelsea 2013-03-20 18:46:07