如何检查 JavaFX 中两个 ImageView 对象之间的碰撞

How to check collision between two ImageView objects in JavaFX

我正在创建一个游戏,我在其中检查两个 ImageView 对象之间的碰撞,但是当我使用相交方法时,它并没有完全按照我想要的方式工作。 checkCollision() 方法中的分数被计算到巨大的尺寸,即使第一张图片没有接触第二次碰撞已经发生,我不知道为什么 - 我的程序应该在一个对象接触另一个对象时添加 +1。

此处是基于最小可重现示例进行改进后的代码示例:

import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.ArrayList;

public class TestCollision extends Application {

    private Thread collisionThread;
    private Scene scene;
    private Pane pane;
    private ImageView  wolfIv;
    private ArrayList<ImageView> eggsList;
    private int score;

    @Override
    public void start(Stage primaryStage) throws Exception {

        pane = new Pane();
        scene = new Scene(pane,800, 600);

        Image wolf = new Image("/images/wolf.png");
        wolfIv = new ImageView(wolf);

        eggsList = new ArrayList<>();
        eggsList.add(new ImageView(new Image(getClass().getResourceAsStream("/images/egg.png"))));
        eggsList.get(0).setFitHeight(45);
        eggsList.get(0).setFitWidth(35);
        pane.getChildren().add(eggsList.get(0));
        MoveTo moveToEgg = new MoveTo();
        moveToEgg.setX(60.0f);
        moveToEgg.setY(95.0f);
        Path eggPath = new Path();
        eggPath.getElements().add(moveToEgg);
        eggPath.getElements().add(new CubicCurveTo(190,200,190, 200,190,480));

        PathTransition pathTransition = new PathTransition();
        pathTransition.setDuration(Duration.millis(4000));
        pathTransition.setNode(eggsList.get(0));
        pathTransition.setPath(eggPath);
        pathTransition.setCycleCount(PathTransition.INDEFINITE);
        pathTransition.setAutoReverse(false);
        pathTransition.play();


        wolfIv.setFitWidth(200);
        wolfIv.setFitHeight(250);

        wolfIv.setX(140);
        wolfIv.setY(218);

        pane.getChildren().addAll(wolfIv);

        primaryStage.setResizable(false);
        primaryStage.setScene(scene);
        primaryStage.show();

        collisionCheckThread();

    }

    public void checkCollision(ImageView imageView, ImageView imageView2){
        if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
            score++;
            System.out.println(score);
            System.out.println("Boom");
        }
    }

    public void collisionCheckThread()
    {
        collisionThread = new Thread(){
            @Override
            public void run(){
                while(scene.getWindow().isShowing() == true){
                    checkCollision(wolfIv,eggsList.get(0));
                }

            }
        };
        collisionThread.start();
    }

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


}

分数“变得很大”的原因是后台线程尽可能快且尽可能频繁地反复检查碰撞。每当它检查并且边界相交时,它都会将分数加一。

您有时会看到不正确结果的原因更为复杂,例如在 none 应该发生时立即检测到碰撞。发生这种情况是因为值(特别是两个图像视图的 boundsInParent)在一个线程(FX 应用程序线程)上被更改,而在另一个线程(您的后台线程)上被观察到而没有适当的同步。 JavaFX 与大多数 UI 工具包一样,设计为单线程工具包,因此无法为其添加同步。

这里实际发生的事情是由于所谓的“提升”。后台线程中的代码本质上是

public void run(){
    while(scene.getWindow().isShowing() == true){

        if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
            score++;
            System.out.println(score);
            System.out.println("Boom");
        }
    }
}

根据 Java 语言规范,JVM 以某些方式优化代码是合法的。对于未声明 [​​=15=] 且没有同步的变量,假设每个线程彼此独立,则允许 JVM 对代码重新排序。在此假设下,由于在此线程中没有对 boundsInParent 进行任何更改,因此可以将代码视为等同于

public void run(){
    if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
        while(scene.getWindow().isShowing() == true){

            score++;
            System.out.println(score);
            System.out.println("Boom");
        }
    }
}

此外,这种优化可能会在任意时间进行(当 JVM 决定它可能有益时),因此如果您从 FX 应用程序线程以外的线程访问 boundsInParent,行为此代码的本质上变得不确定。

要了解更多信息,我建议阅读 Joshua Bloch 的书中的相关项目 有效 Java。 (任何在 Java 中花费超过一个小时编程的人都应该没有这本书。)

无论如何,在这里使用后台线程是完全错误的方法。

我假设,当图像的状态从“不相交”变为“相交”时,您真正想要做的是在分数上加一(并可能执行其他操作)。您可以通过创建具有正确值的 BooleanBinding 并将其绑定到两个 boundsInParent 属性来实现。然后使用该绑定注册一个侦听器,并在它从 false 变为 true:

时做出反应
    BooleanBinding collision = Bindings.createBooleanBinding(
            () -> wolfIv.getBoundsInParent().intersects(eggsList.get(0).getBoundsInParent()), 
            wolfIv.boundsInParentProperty(), 
            eggsList.get(0).boundsInParentProperty()
    );
    
    collision.addListener((obs, wasCollision, isNowCollision) -> {
        if (isNowCollision) {
            score++;
            System.out.println(score);
            System.out.println("Boom");
        }
    });

这是一个完整的可运行示例,它演示了这一点:

import java.util.ArrayList;

import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TestCollision extends Application {

    private Scene scene;
    private Pane pane;
    private ImageView  wolfIv;
    private ArrayList<ImageView> eggsList;
    private int score;

    @Override
    public void start(Stage primaryStage) throws Exception {

        pane = new Pane();
        scene = new Scene(pane,800, 600);

        WritableImage wolf = new WritableImage(1, 1);
        wolf.getPixelWriter().setColor(0, 0, Color.RED);
        wolfIv = new ImageView(wolf);

        eggsList = new ArrayList<>();
        WritableImage egg = new WritableImage(1, 1);
        egg.getPixelWriter().setColor(0, 0, Color.YELLOW);
        eggsList.add(new ImageView(egg));
        
        
        BooleanBinding collision = Bindings.createBooleanBinding(
                () -> wolfIv.getBoundsInParent().intersects(eggsList.get(0).getBoundsInParent()), 
                wolfIv.boundsInParentProperty(), 
                eggsList.get(0).boundsInParentProperty()
        );
        
        collision.addListener((obs, wasCollision, isNowCollision) -> {
            if (isNowCollision) {
                score++;
                System.out.println(score);
                System.out.println("Boom");
            }
        });
        
        
        eggsList.get(0).setFitHeight(45);
        eggsList.get(0).setFitWidth(35);
        pane.getChildren().add(eggsList.get(0));
        MoveTo moveToEgg = new MoveTo();
        moveToEgg.setX(60.0f);
        moveToEgg.setY(95.0f);
        Path eggPath = new Path();
        eggPath.getElements().add(moveToEgg);
        eggPath.getElements().add(new CubicCurveTo(190,200,190, 200,190,480));

        PathTransition pathTransition = new PathTransition();
        pathTransition.setDuration(Duration.millis(4000));
        pathTransition.setNode(eggsList.get(0));
        pathTransition.setPath(eggPath);
        pathTransition.setCycleCount(PathTransition.INDEFINITE);
        pathTransition.setAutoReverse(false);
        pathTransition.play();


        wolfIv.setFitWidth(200);
        wolfIv.setFitHeight(250);

        wolfIv.setX(140);
        wolfIv.setY(218);


        
        pane.getChildren().addAll(wolfIv);

        
        
        primaryStage.setResizable(false);
        primaryStage.setScene(scene);
        primaryStage.show();
        
    }

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


}