如何从后台线程安全地更新JavaFX应用程序的GUI?

时间:2017-11-20 17:00:13

标签: java multithreading javafx concurrency

我正在尝试使用 JavaFX 2.2 编写Asteroids游戏,但是当我尝试移动游戏对象(即岩石,太空飞船和横梁)或检测到时,我遇到了问题他们之间的碰撞。

最初,我尝试使用 ScheduledThreadPoolExecutor 类的 scheduleAtFixedRate(Runnable,long,long,TimeUnit)方法从后台线程执行所有移动和碰撞检测,但是这导致了可怕的运行时异常甚至在我的代码中,因为我试图修改GUI形成后台线程。

我的下一个方法是使用 AnimationTimer 类从UI线程本身更新游戏对象。虽然这种方法解决了例外情况。问题,在UI线程上运行,导致严重滞后。

所以,我想知道是否有一种可行的方法来更新游戏对象而不会导致例外或滞后?

这是我的应用程序的Main类:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.media.AudioClip;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;

import java.util.ArrayList;

public class Main extends Application {
    private ArrayList<Rock> rocks = new ArrayList<>();
    private ArrayList<Beam> beams = new ArrayList<>();
    private SpaceShip spaceShip = null;
    private Group group;
    private final int SCENE_WIDTH = 900, SCENE_HEIGHT = 600;
    private final int ROCK_COUNT = 20;
    private boolean upKeyPressed, upKeyReleased, zKeyPressed, leftKeyPressed, rightKeyPressed;
    private int bulletsFired = 0, skipCount = 10;
    private AudioClip explosion = new AudioClip(Main.class.getResource("explosion.wav").toString());
    private AudioClip destroy = new AudioClip(Main.class.getResource("destroy.mp3").toString());

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        ImageView spaceBackground = new ImageView("space.jpg");
        spaceBackground.setFitHeight(SCENE_HEIGHT);
        spaceBackground.setFitWidth(SCENE_WIDTH);

        group = new Group(spaceBackground);
        Scene scene = new Scene(group, SCENE_WIDTH, SCENE_HEIGHT);

        initializeGameObjects();

        // add event listeners for the spaceShip controls
        scene.setOnKeyPressed((keyEvent) -> {
            switch(keyEvent.getCode()) {
                case UP:
                    upKeyPressed = true;
                    break;
                case Z:
                    zKeyPressed = true;
                    break;
                case LEFT:
                    leftKeyPressed = true;
                    break;
                case RIGHT:
                    rightKeyPressed = true;
            }
        });

        scene.setOnKeyReleased((keyEvent) -> {
            switch(keyEvent.getCode()) {
                case UP:
                    upKeyPressed = false;
                    upKeyReleased = true;
                    break;
                case Z:
                    zKeyPressed = false;
                    break;
                case LEFT:
                    leftKeyPressed = false;
                    break;
                case RIGHT:
                    rightKeyPressed = false;
            }
        });

        AnimationTimer updater = new AnimationTimer() {
            @Override
            public void handle(long now) {
                updateGameObjects();
            }
        };

        primaryStage.setScene(scene);
        primaryStage.setTitle("Asteroids");
        primaryStage.setResizable(false);
        primaryStage.getIcons().add(new Image(Main.class.getResource("icon.png").toString()));
        primaryStage.show();

        updater.start();
    }

    private void initializeGameObjects() {
        // initialize the Rock ArrayList
        for(int i=0; i<ROCK_COUNT; i++) {
            Rock rock = new Rock();
            rocks.add(rock);
            group.getChildren().add(rock);
        }

        // add the space ship to the center
        spaceShip = new SpaceShip();
        group.getChildren().add(spaceShip);
    }

    private void updateGameObjects() {
        // move the rocks
        for(Rock rock: rocks) {
            rock.move(rocks);
        }

        // check for collision among rocks
        for(int i=0; i<rocks.size(); i++) {
            for(int j=i+1; j<rocks.size(); j++) {
                Rock rock1 = rocks.get(i), rock2 = rocks.get(j);

                // if two rocks collide, interchange their speeds
                if(rock1.getBoundsInParent().intersects(rock2.getBoundsInParent())) {
                    int tmpSpeedX = rock1.getSpeedX();
                    int tmpSpeedY = rock1.getSpeedY();

                    rock1.setSpeedX(rock2.getSpeedX());
                    rock1.setSpeedY(rock2.getSpeedY());

                    rock2.setSpeedX(tmpSpeedX);
                    rock2.setSpeedY(tmpSpeedY);
                }
            }
        }

        // control the spaceShip
        if(upKeyPressed) {
            spaceShip.accelerate();
            //System.out.println(spaceShip.getSpeed());
        }
        else if(upKeyReleased) {
            if(spaceShip.getSpeed() > 0)
                spaceShip.decelerate();
            else {
                spaceShip.nullifySpeed();
                upKeyReleased = false;
            }
            //System.out.println(spaceShip.getSpeed());
        }

        if(leftKeyPressed)
            spaceShip.rotateLeft();
        if(rightKeyPressed)
            spaceShip.rotateRight();
        if(zKeyPressed) {
            if(bulletsFired < 4) {
                beams = spaceShip.fire(group);
                bulletsFired++;
                skipCount = 15;
            } else {
                skipCount--;

                if(skipCount == 0)
                    bulletsFired = 0;
            }
        }

        // move the beams
        for(int i=0; i<beams.size(); i++) {
            Beam beam = beams.get(i);

            if(!beam.isAlive()) {
                beams.remove(beam);
                continue;
            }

            beam.move();
        }

        // check if the ship hits a rock
        for(int i=0; i<rocks.size(); i++) {
            Rock rock = rocks.get(i);

            if(Shape.intersect(spaceShip, rock).getLayoutBounds().getWidth() > 0) {
                rock.setVisible(false);
                rocks.remove(rock);
                explosion.play(0.04, 0, 1.5, 0, 1);
            }
        }

        // check if a beam hits a rock
        for(int i=0; i<beams.size(); i++) {
            for(int j=0; j<rocks.size(); j++) {
                Beam beam = beams.get(i);
                Rock rock = rocks.get(j);

                if(Shape.intersect(beam, rock).getLayoutBounds().getWidth() > 1) {
                    rock.setVisible(false);
                    rocks.remove(rock);
                    beam.setVisible(false);
                    beams.remove(beam);

                    destroy.play(0.04, 0, 1.5, 0, 1);
                }
            }
        }
    }
}

为了简洁,我省略了SpaceShip,Beam和Rock课程。

1 个答案:

答案 0 :(得分:0)

我发现了问题。我按照James_D的建议对我的代码进行了分析,发现所有滞后都是由 Shape.intersect(Shape,Shape)方法引起的,我用它进行更精确的碰撞检查。我用常规的Bounds检查方法替换它,现在它运行顺利。