【发布时间】:2018-05-03 23:34:30
【问题描述】:
我正在尝试使用 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 类。
【问题讨论】:
-
看
JavaFX'sAnimatinTimer。浏览本教程:gamedevelopment.tutsplus.com/tutorials/… -
正如我在问题中提到的,我目前正在使用 AnimationTimer。
-
本教程会讲解如何正确使用。我没有注意到你已经在使用它了。
-
这应该不会导致任何性能问题,除非您在任何时候都有大量的游戏对象(尤其是岩石)。
-
AnimationTimer绝对是这里的正确方法;基本上,您的任何代码都不能/不应该在 FX 应用程序线程之外执行,因此在后台线程中无需执行任何操作。我有一个类似的例子here,它对于合理数量的对象运行得非常顺利。
标签: java multithreading javafx concurrency