如何检查 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);
}
}
我正在创建一个游戏,我在其中检查两个 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);
}
}