You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现JavaFX中StackPane集合的全局碰撞检测?

实现集合内所有元素的两两碰撞检测问题

我正在尝试对一组图形进行两两碰撞检测,但目前仅能实现邻近三个节点间的碰撞检测。其中addToRectCollection方法用于向集合中添加StackPane,拖拽功能由相关方法实现。请问需要修改或添加哪些代码,才能实现集合内所有元素间的碰撞检测?

以下是我的代码:

public class CollisionDetection extends Application {
    final ObjectProperty<Point2D> mousePosition = new SimpleObjectProperty<>();
    Group gp = new Group();

    // 尝试检测该列表中元素的碰撞
    ArrayList<StackPane> stackPanes = new ArrayList<>();

    @Override
    public void start(Stage stage) throws Exception {
        Scene sc = new Scene(gp, 700, 300);

        Button btn = new Button();
        btn.setText("CREATE REct");

        btn.setOnAction(event -> {
            Rectangle r = createRect(0);
            r.setFill(Color.RED);

            Text text = new Text(".");
            StackPane stack = new StackPane();

            StackPane.setMargin(r, new Insets(8, 8, 8, 8));
            stack.getChildren().addAll(r, text);

            ChoiceBox<String> dropdown = new ChoiceBox<>();
            stack.getChildren().add(dropdown);

            makeDraggable(stack);
            addToRectCollection(stack);

            gp.getChildren().add(stack);
        });

        gp.getChildren().add(btn);

        stage.setScene(sc);
        stage.show();
    }

    private void addToRectCollection(StackPane r) {
        stackPanes.add(r);
        stackPanes.forEach(rect -> {
            stackPanes.forEach(rect2 -> {
                if (!rect.equals(rect2)) {
                    setOnDragegd(rect2, rect);
                    setOnDragegd(rect, rect2);
                }
            });
        });
    }

    private void setOnDragegd(StackPane pane, StackPane paneToSetDragged) {
        paneToSetDragged.setOnMouseDragged((MouseEvent event) -> {
            double deltaX = event.getSceneX() - mousePosition.get().getX();
            double deltaY = event.getSceneY() - mousePosition.get().getY();
            paneToSetDragged.setLayoutX(paneToSetDragged.getLayoutX() + deltaX);
            paneToSetDragged.setLayoutY(paneToSetDragged.getLayoutY() + deltaY);
            mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));

            var intersect = paneToSetDragged.getBoundsInParent().intersects(pane.getBoundsInParent());
            if (intersect) {
                pane.setStyle("-fx-background-color: #6a5246");
                paneToSetDragged.setStyle("-fx-background-color: #6a5246");

                Group g = new Group();
                g.getChildren().add(paneToSetDragged);
                g.getChildren().add(pane);
                gp.getChildren().add(g);

                paneToSetDragged.setLayoutX(paneToSetDragged.getLayoutX() - deltaX);
                paneToSetDragged.setLayoutY(paneToSetDragged.getLayoutY() - deltaY);
            } else {
                pane.setStyle("-fx-background-color: #ffffff");
                paneToSetDragged.setStyle("-fx-background-color: #ffffff");
            }
        });
    }

    private void makeDraggable(StackPane ractangleToSetDragged) {
        ractangleToSetDragged.setOnMousePressed((MouseEvent event) -> mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY())));

        ractangleToSetDragged.setOnMouseDragged((MouseEvent event) -> {
            double deltaX = event.getSceneX() - mousePosition.get().getX();
            double deltaY = event.getSceneY() - mousePosition.get().getY();
            ractangleToSetDragged.setLayoutX(ractangleToSetDragged.getLayoutX() + deltaX);
            ractangleToSetDragged.setLayoutY(ractangleToSetDragged.getLayoutY() + deltaY);
            mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
        });
    }

    public Rectangle createRect(int x) {
        Rectangle r = new Rectangle();
        r.setX(x);
        r.setY(60);
        r.setWidth(300);
        r.setHeight(150);
        return r;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

问题分析与修改方案

原代码核心问题

  1. 重复绑定拖拽事件:addToRectCollection中的双重循环会给所有元素重复绑定setOnMouseDragged事件,每次添加新元素都会覆盖旧的拖拽逻辑,导致只有最后绑定的单个元素碰撞检测生效。
  2. 碰撞检测范围有限:每次拖拽仅对比单个元素,没有遍历整个集合完成两两检测。
  3. 冗余逻辑:拖拽时创建新Group的操作会导致界面元素结构混乱,且回退位置的逻辑不符合正常拖拽碰撞的预期。

修改步骤

  1. 移除冗余的setOnDragegd方法:将碰撞检测逻辑整合到makeDraggable的拖拽事件中,确保每次拖拽时遍历整个集合。
  2. 简化addToRectCollection方法:仅负责将元素添加到集合,无需额外绑定事件。
  3. 实现全集合碰撞检测:拖拽时遍历stackPanes,与当前拖拽元素以外的所有元素逐一检测碰撞,并统一处理样式变化。
  4. 优化碰撞后的逻辑:移除创建新Group的操作,保留样式提示即可,如需阻止穿透可调整位置回退逻辑。

修改后的完整代码

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.geometry.Point2D;
import java.util.ArrayList;

public class CollisionDetection extends Application {
    final ObjectProperty<Point2D> mousePosition = new SimpleObjectProperty<>();
    Group gp = new Group();
    ArrayList<StackPane> stackPanes = new ArrayList<>();

    @Override
    public void start(Stage stage) throws Exception {
        Scene sc = new Scene(gp, 700, 300);

        Button btn = new Button();
        btn.setText("CREATE REct");

        btn.setOnAction(event -> {
            Rectangle r = createRect(0);
            r.setFill(Color.RED);

            Text text = new Text(".");
            StackPane stack = new StackPane();

            StackPane.setMargin(r, new Insets(8, 8, 8, 8));
            stack.getChildren().addAll(r, text);

            ChoiceBox<String> dropdown = new ChoiceBox<>();
            stack.getChildren().add(dropdown);

            makeDraggable(stack);
            addToRectCollection(stack);

            gp.getChildren().add(stack);
        });

        gp.getChildren().add(btn);
        stage.setScene(sc);
        stage.show();
    }

    private void addToRectCollection(StackPane r) {
        stackPanes.add(r);
    }

    private void makeDraggable(StackPane draggedPane) {
        draggedPane.setOnMousePressed((MouseEvent event) -> {
            mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
            // 重置所有元素样式为默认
            stackPanes.forEach(pane -> pane.setStyle("-fx-background-color: #ffffff"));
        });

        draggedPane.setOnMouseDragged((MouseEvent event) -> {
            double deltaX = event.getSceneX() - mousePosition.get().getX();
            double deltaY = event.getSceneY() - mousePosition.get().getY();
            
            // 记录拖拽前的位置,用于碰撞后回退
            double prevX = draggedPane.getLayoutX();
            double prevY = draggedPane.getLayoutY();
            
            // 更新拖拽位置
            draggedPane.setLayoutX(prevX + deltaX);
            draggedPane.setLayoutY(prevY + deltaY);
            mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));

            boolean hasCollision = false;
            // 遍历所有元素检测碰撞
            for (StackPane pane : stackPanes) {
                if (!draggedPane.equals(pane)) {
                    boolean intersect = draggedPane.getBoundsInParent().intersects(pane.getBoundsInParent());
                    if (intersect) {
                        pane.setStyle("-fx-background-color: #6a5246");
                        draggedPane.setStyle("-fx-background-color: #6a5246");
                        hasCollision = true;
                        // 回退到拖拽前的位置,阻止穿透
                        draggedPane.setLayoutX(prevX);
                        draggedPane.setLayoutY(prevY);
                    }
                }
            }
            
            // 无碰撞时重置当前拖拽元素样式
            if (!hasCollision) {
                draggedPane.setStyle("-fx-background-color: #ffffff");
            }
        });
    }

    public Rectangle createRect(int x) {
        Rectangle r = new Rectangle();
        r.setX(x);
        r.setY(60);
        r.setWidth(300);
        r.setHeight(150);
        return r;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

内容的提问来源于stack exchange,提问作者user12420288

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 10:39:34