看起来你的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) {
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; }
}
示例程序输出。在示例中,圆圈已被拖动,并且用户当前正在拖动一个被标记为与另一个圆圈碰撞的圆圈(通过将其涂成蓝色)- 仅用于演示目的,当前正在拖动的圆圈具有标记的碰撞颜色。
根据额外问题的评论
我在之前的评论中发帖的链接到一个交叉点演示应用程序,是为了说明使用各种边界类型,而不是作为特定类型的碰撞检测示例。对于您的用例,您不需要更改侦听器的额外复杂性和检查各种不同的边界类型-只需选择一种类型即可。大多数碰撞检测只关心视觉边界的交集,而不关心其他JavaFX边界类型,例如节点的布局边界或本地边界。因此,您可以执行以下任一操作:
- 检查
getBoundsInParent
的交集(与您在原始问题中所做的相同),该方法适用于将包围节点的视觉极限的最小矩形框,或者
- 如果您需要基于节点的视觉形状而不是视觉形状的边界框进行检查,则使用
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(block)
);
注意:如果有很多形状正在进行动画,那么在
游戏循环内每帧检查一次碰撞比在任何节点移动时运行碰撞检查更有效(如上面的boundsInParentProperty更改监听器中所做的那样)。
处理非矩形形状输入的其他信息
对于输入检测而非碰撞检测,因此与您的问题无直接关系,请查看
node.pickOnBounds
设置,如果需要与非矩形节点进行鼠标或触摸交互。