【问题标题】:JavaFX, countdown to run a methodJavaFX,倒计时运行一个方法
【发布时间】:2014-12-10 05:48:16
【问题描述】:

所以我正在设计一个类似于 JavaFX 中的 Galaga 或 Space Invaders 的游戏,虽然碰撞确实有效,但只是让它不断运行检查会使游戏变慢,如果你一次射击超过 2-3 次游戏死机和崩溃。所以我的想法是创建一个倒计时的计时器,当它达到 0 时,它会运行它应该运行的方法。

所以这就是我所拥有的:当我在 addEnemy() 方法中添加一个敌人时,它调用 countdownToSmallCollisionCheck() 运行 smallCollisionCounter() 方法给定的次数,smallCollisionCounter() 方法减少计数器(一个 int毫秒)直到它达到 0,此时它调用 smallCollisionTimeline() 方法并将自身重置为其原始值,smallCollisionTimeline() 方法包含控制碰撞检查并实际检查碰撞的动画。

所以这里采取的路线是:addEnemy() > countdownToSmallCollisionCheck() > smallCollisionCounter() > smallCollisionTimeline() > checkCollisionSmall();

我知道我在这里的某个地方存在某种逻辑错误,因为从未检测到碰撞,但我似乎无法弄清楚它是什么。还有可能有更好的方法来做到这一点,我还没有想出来。我查看了 API 和其他书籍和网络资源,但似乎没有什么是直接的方法,我可以在 X 时间过去后使用一个函数调用该方法。无论如何,我很感激这方面的任何帮助。下面是我的 GamePane 类的代码,它扩展了我正在执行这部分编码的 Pane。

/**
* This class adds objects to the pane, controls all animations for adding and
* moving enemies, adding and moving shots, and checking for collisions.
* NOTE:  All methods for winning the game, losing the game, and such are to be
* implemented in future versions ;)
*/
package CaltabellottaProject6;

import java.util.ArrayList;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Screen;
import javafx.util.Duration;
import javax.swing.JOptionPane;

class GamePane extends Pane {

private Timeline animation;
ArrayList<LargeEnemy> largeList = new ArrayList<>();
ArrayList<SmallEnemy> smallList = new ArrayList<>();
public static ArrayList<Shot> shotList = new ArrayList<>(1);
double x, y, smallDX, largeDX, dy, width, height;
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
static double GAME_WIDTH = 1000;
static double GAME_HEIGHT = 650;
int addToPlayerScore;
public TextField showScore;
public static int gameOverCounter = 0;
public static int playerPointCounter = 0;
public static Launcher launcher;
Rectangle topOfGame;
Image BACKGROUND_IMAGE = new Image("file:purpleSpaceBackground.jpeg");

/**
 * This constructor for the GamePane calls the method that adds the
 * launcher, calls the method that adds the invisible object for the top of
 * the game, and houses the animations that control when a new enemy is
 * added, the rate that each SmallEnemy is moved, the rate that each
 * LargeEnemy is moved, the rate that each Shot is moved, and how often the
 * game will check for a collision.
 */
public GamePane() {
    //Adds a launcher
    addLauncher();
    addTopOfGame();

    //Animation to call new enemies every 10 seconds.
    animation = new Timeline(new KeyFrame(Duration.seconds(5), e -> addEnemy()));
    animation.setCycleCount(Timeline.INDEFINITE);
    animation.play();

    //Animation to move SmallEnemies
    animation = new Timeline(new KeyFrame(Duration.millis(5), e -> moveSmallEnemy()));
    animation.setCycleCount(Timeline.INDEFINITE);
    animation.play();

    //Animation to move Large Enemies
    animation = new Timeline(new KeyFrame(Duration.millis(8), e -> moveLargeEnemy()));
    animation.setCycleCount(Timeline.INDEFINITE);
    animation.play();

    //Animation to move Shots
    animation = new Timeline(new KeyFrame(Duration.millis(1), e -> moveShot()));
    animation.setCycleCount(Timeline.INDEFINITE);
    animation.play();

    this.setStyle("-fx-background-image: url(\"file:purpleSpaceBackground.jpeg\");-fx-background-size: cover");
} //End GamePane Constructor

/**
 * This method adds a JOptionPane window informing the player of the
 * instructions of how to play the game.
 */
public static void playerInstructionsPane() {
    String playerInstructionsMsg = "You are the captain of the Starship Destiny at the bottom of the screen.\n"
            + "Fire your Forward Guns at the enemy ships above you with the Space Bar.\n"
            + "The larger Asgard Ships are worth 10 points, while the smaller Kino Srones are worth twice that at 20 points.\n"
            + "\n"
            + "Good Luck!";
    JOptionPane.showMessageDialog(null, playerInstructionsMsg, "How To Play", JOptionPane.INFORMATION_MESSAGE);
} //end playerInstructionsPane method

/**
 * When this method is called it begins playing the animations.
 */
public void play() {
    animation.play();
} //End play method

/**
 * When this method is called it stops playing the animations.
 */
public void stop() {
    animation.stop();
} //End stop method

/**
 * This method adds an invisible rectangle to the top that ends the game
 * when it is hit 10 times... (aka: when the player misses 10 times).
 */
public void addTopOfGame() {
    topOfGame = new Rectangle(); //Set slightly above game view so shot will be offscreen before it dissapears.
    topOfGame.setX(0);
    topOfGame.setY(-100);
    topOfGame.setHeight(10);
    topOfGame.setWidth(1000);
    topOfGame.setFill(Color.TRANSPARENT);
    this.getChildren().add(topOfGame);
} //End addTopOfGame methpd

/**
 * This method adds a new Launcher to the pane.
 */
public void addLauncher() {
    launcher = new Launcher();
    launcher.setWidth(70);
    launcher.setHeight(103);
    launcher.setX((GAME_WIDTH * 0.5) - (launcher.getWidth() * 0.5) + 10);
    launcher.setY(GAME_HEIGHT - 100);
    this.getChildren().add(launcher);
} //End addLauncher method

/**
 * This method adds new enemies. It has a random variable that generates a
 * number between 0 and 100. If this random variable is less than 60 a new
 * LargeEnemy is created and added to the pane, and if the random variable
 * is higher than 60 it crates a new SmallEnemy and adds it to the pane.
 * This means that there is a 60% chance of a new LargeEnemy being created
 * and a 40% chance of a new SmallEnemy being created.
 */
public void addEnemy() {
    addToPlayerScore = 0;
    int random = (int) (0 + Math.random() * 100);
    if (random < 60) { //60:40 chance LargeEnemy:SmallEnemy
        addToPlayerScore = 10;
        LargeEnemy newLargeEnemy = new LargeEnemy();
        newLargeEnemy.setWidth(182);
        newLargeEnemy.setHeight(102);
        newLargeEnemy.setX(0);
        newLargeEnemy.setY(60 + Math.random() * 200);
        largeList.add(newLargeEnemy);
        this.getChildren().add(newLargeEnemy);
        System.out.println("LargeEnemy Added");
        countdownToLargeCollisionCheck();
    } //End boolean if statement to add a newLargeEnemy
    else {
        addToPlayerScore = 20;
        SmallEnemy newSmallEnemy = new SmallEnemy();
        newSmallEnemy.setX(0);
        newSmallEnemy.setY(60 + Math.random() * 250);
        newSmallEnemy.setWidth(60);
        newSmallEnemy.setHeight(60);
        smallList.add(newSmallEnemy);
        this.getChildren().add(newSmallEnemy);
        System.out.println("SmallEnemy Added");
        countdownToSmallCollisionCheck();
    } //End boolean else statemet to add a newSmallEnemy
} //End addEnemy method

/**
 * This method adds a new Shot to the screen. It is called whenever the
 * player hits the Space Bar.
 */
public void fire() {
    Shot newShot = new Shot();
    newShot.setX(GAME_WIDTH * 0.5);
    newShot.setY(launcher.getY() - launcher.getHeight() - 30);
    newShot.setWidth(18);
    newShot.setHeight(134);
    shotList.add(newShot);
    this.getChildren().add(newShot);

    System.out.println("Shot fired ");
} //End fire method

/**
 * This method updates the TextField with the new score information, removes
 * the old TextField from the Pane, and adds the new TextField with the
 * updated score to the pane. It is called every time a collision is
 * detected.
 */
public void setTextField() {
    showScore = new TextField("Score:  " + String.valueOf(playerPointCounter)
            + "\nMisses:  " + String.valueOf(gameOverCounter));
    showScore.setLayoutX(50);
    showScore.setLayoutY(50);
    showScore.setEditable(false);
    this.getChildren().clear();
    addLauncher();
    this.getChildren().add(10, showScore);
    System.out.println("TextField updated");
} //End setTextField method

/**
 * This method sets how much each SmallEnemy will be moved each time the
 * animation timer calls the method, as well as removing the SmallEnemy if
 * it's x-axis value is greater than or equal to 1200.
 */
public void moveSmallEnemy() {
    for (int i = 0; i < smallList.size(); i++) {
        SmallEnemy tempSmall = smallList.get(i);
        double x = tempSmall.getX();
        double width = tempSmall.getWidth();
        double smallDX = 1;
        if (x >= 1200) {
            smallList.remove(tempSmall);
        } //end boolean if statement to change direction
        x += smallDX;
        tempSmall.setX(x);
    } //end for loop
} //End moveSmallEnemy method

/**
 * This method sets how much each LargeEnemy will be moved each time the
 * animation timer calls the method, as well as removing the LargeEnemy if
 * it's x-axis value is greater than or equal to 1200.
 */
public void moveLargeEnemy() {
    for (int i = 0; i < largeList.size(); i++) {
        LargeEnemy tempLarge = largeList.get(i);
        double x = tempLarge.getX();
        double width = tempLarge.getWidth();
        double largeDX = 1;
        if (x >= 1200) {
            largeList.remove(tempLarge);
        } //end boolean if statement to change direction
        x += largeDX;
        tempLarge.setX(x);
    } //end for loop
} //End moveLargeEnemy method

/**
 * This method sets how much each Shot will be moved each time the animation
 * timer calls the method, as well as removing the Shot if it's y-axis value
 * is less than or equal to -100.
 */
public static void moveShot() {
    if (shotList.isEmpty()) {
        return;
    }
    for (int i = 0; i < shotList.size(); i++) {
        Shot shot = shotList.get(i);
        double y = shot.getY();
        double dy = 0.21;
        //double height = shot.getHeight();
        if (y <= -100) { //Setting above stage size so it will go offscreen completely before it dissapears.
            shot.setVisible(false);
            shotList.remove(shot);
        } //End boolean if statement to remove shot.
        y -= dy;
        shot.setY(y);
    } //End for loop
} //End moveShot method

/**
 * When called this animation method runs the smallCollisionCounter method ever millisecond for 2250 times.
 */
public void countdownToSmallCollisionCheck() {
    animation = new Timeline(new KeyFrame(Duration.millis(1), e -> smallCollisionCounter()));
    animation.setCycleCount(1);
    animation.play();
    System.out.println("countdownToSmallCollisionCheck() reached");
} //End countdownToSmallCollisionCheck method

/**
 * When called this animation method runs the largeCollisionCounter every millisecond for 3750 times.
 */
public void countdownToLargeCollisionCheck() {
    animation = new Timeline(new KeyFrame(Duration.millis(1), e -> largeCollisionCounter()));
    animation.setCycleCount(1);
    animation.play();
    System.out.println("countdownToLargeCollisionCheck() reached");
} //End countdownToLargeCollisionCheck method

/**
 * Each time this method is run by the animation that calls it it reduces the counter by one.
 * When the counter reaches 0 it calls the smallCollisionTimeline method.
 */
public void smallCollisionCounter() {
    int count = 2250;
    int subtract = 1;
    count -= subtract;
    if (count ==0) {
        smallCollisionTimeline();
    }                 
    System.out.println("smallCollisionCounter() reached");
} //End smallCollisionCounter method

/**
 * Each time this method is run by the animation that calls it it reduces the counter by one.
 * When the counter reaches 0 it calls the largeCollisionTimeline method.
 */
public void largeCollisionCounter() {
    int count = 3750;
    int subtract = 1;
    count -= subtract;
    if (count ==0) {
        smallCollisionTimeline();
    }              
    System.out.println("largeCollisionCounter() reached");
} //End smallCollisionCounter method

/**
 * When this method is called it runs for the specified duration and number of cycles,
 * each time checking for a collision with a SmallEnemy.
 */
public void smallCollisionTimeline() {
    animation = new Timeline(new KeyFrame(Duration.millis(1), e -> checkCollisionSmall()));
    animation.setCycleCount(1500);
    animation.play();   
    System.out.println("smallCollisionTimeline() reached");
} //End smallCollisionTimeline method

/**
 * When this method is called it runs for the specified duration and number of cycles,
 * each time checking for a collision with a LargeEnemy.
 */
public void largeCollisionTimeline() {
    animation = new Timeline(new KeyFrame(Duration.millis(1), e -> checkCollisionLarge()));
    animation.setCycleCount(2000);
    animation.play(); 
    System.out.println("largeCollisionTimeline() reached");
} //End largeCollisionTimeline method

/**
 * This method checks for collisions with each SmallEnemy, LargeEnemy, and
 * with the invisible object at the top of the game.
 */
public void checkCollisionSmall() {
    //This segment of code checks for collisions with each SmallEnemy.
    for (int i = 0; i < shotList.size(); i++) {
        Shot tempShot = shotList.get(i);
        for (int j = 0; j < smallList.size(); j++) {
            SmallEnemy tempSmall = smallList.get(i);
            if (tempShot.isVisible() && tempSmall.isVisible()) {
                if (tempShot.getBoundsInLocal().intersects(tempSmall.getBoundsInLocal())) {
                    System.out.println("Small Collision Detected");
                    tempShot.setVisible(false);
                    tempSmall.setVisible(false);
                    setScore();
                    setTextField();
                    smallList.remove(i);
                    shotList.remove(i);
                } //End inner boolean if statement
            } //End of outer boolean if statement
        } //End inner for loop
    } //End outer for loop
} //End checkCollisionSmall method

public void checkCollisionLarge() {
    //This segment of code checks for collisions with each LargeEnemy.
    for (int i = 0; i < shotList.size(); i++) {
        Shot tempShot = shotList.get(i);
        for (int j = 0; j < largeList.size(); j++) {
            LargeEnemy tempLarge = largeList.get(i);
            if (tempShot.isVisible() && tempLarge.isVisible()) {
                if (tempShot.getBoundsInParent().intersects(tempLarge.getBoundsInParent())) {
                    System.out.println("Large Collision Detected");
                    tempShot.setVisible(false);
                    tempLarge.setVisible(false);
                    setScore();
                    setTextField();
                    largeList.remove(i);
                    shotList.remove(i);
                } //End inner boolean if statement
            } //End outer boolean if statement
        } //End inner for loop
    } //End outer for loop
} //End checkCollisionLarge method

public void checkTopCollision() {        
    //This segment of code checks for collisions with the invisible topOfGame Rectangle object.
    for (int i = 0; i < shotList.size(); i++) {
        Shot tempShot = shotList.get(i);
        if (tempShot.isVisible() && topOfGame.isVisible()) {
            if (tempShot.getBoundsInParent().intersects(topOfGame.getBoundsInParent())) {
                tempShot.setVisible(false);
                shotList.remove(i);
            } //end inner boolean if statement
        } //end outer boolean if statement
    } //end for loop
} //End checkTopCollision method

/**
 * This method updates the counter for the player's points.
 */
public void setScore() {
    playerPointCounter = playerPointCounter + addToPlayerScore;
    System.out.println("Score updated:  " + playerPointCounter);
} //End setScore method

/**
 * This method determines if the player loses the game. The player loses the
 * game when they miss a target 10 times. When the game is lost a
 * JOptionPane window displays telling them the game is over and how many
 * points they had when they lost. This method also stops all animations.
 */
public void loseGame() {
    if (gameOverCounter >= 10) {
        String playerInstructionsMsg = "You Missed 10 times and Lost!\n"
                + "Your Score:  " + playerPointCounter;
        JOptionPane.showMessageDialog(null, playerInstructionsMsg, "Game Over!", JOptionPane.INFORMATION_MESSAGE);
        animation.stop();
    }
} //End loseGame method

/**
 * This method determines if the player wins the game. The player wins the
 * game when they earn 1000 points. When the game is lost a JOptionPane
 * window displays telling them they won the game and how many points they
 * had at the end and how many misses they had. This method also stops all
 * animations.
 */
public void winGame() {
    if (playerPointCounter >= 1000) {
        String playerInstructionsMsg = "Congratulations!!  You Win!\n"
                + "Your Score:  " + playerPointCounter
                + "Misses:  " + gameOverCounter;
        JOptionPane.showMessageDialog(null, playerInstructionsMsg, "Game Over!", JOptionPane.INFORMATION_MESSAGE);
        animation.stop();
    }
} //End winGame method
} //End of GamePane Class

【问题讨论】:

  • 我没有对所有代码进行校对,但我强烈建议重用分数 TextField 而不是每次更新时都创建一个新的。这可能非常耗时。
  • 我试过了,但问题是它只覆盖旧的,所以你会在新的顶部看到前一个分数的数字。所以看起来它就写在上面,然后你就无法辨认出数字。我向我的教授和助教询问了我的课程,他们都说唯一的方法是删除最后一个并用新的替换它。不知道为什么,但这显然是唯一的方法。虽然老实说最后我无法让分数 TextField 工作,即使我确实在某个时候工作过,但最终我转向了其他事情。

标签: java timer javafx javafx-8 countdown


【解决方案1】:

我将你的函数 countdownToSmallCollisionCheck() 添加到函数 moveShot() ...

public static void moveShot() {
        if (shotList.isEmpty()) {
            return;
        }
        for (int i = 0; i < shotList.size(); i++) {
            Shot shot = shotList.get(i);
            double y = shot.getY();
            double dy = 0.21;
            //double height = shot.getHeight();
            if (y <= -100) { //Setting above stage size so it will go offscreen completely before it dissapears.
                shot.setVisible(false);
                shotList.remove(shot);
            } //End boolean if statement to remove shot.
            y -= dy;
            shot.setY(y);
        } //End for loop
        countdownToSmallCollisionCheck();

    } //End mo

checkCollisionLarge 函数有错误,checkCollisionSmall 也一样

首先你从列表中删除敌人并射击,所以如果你使用 for 循环最好是一个反向循环示例:for (int i = 0; i &lt; shotList.size(); i++) 使用 for (int i = shotList.size(); i &gt;= 0 ; i--) 另一个错误是这条线 LargeEnemy tempLarge = largeList.get(i); 你使用 j for 循环所以正确的线是LargeEnemy tempLarge = largeList.get(j);largeList.remove(i); 行相同的错误,当检测到冲突时,我认为您可以中断循环 j,因此我在您的代码中添加中断。

public void checkCollisionLarge() {
    //This segment of code checks for collisions with each LargeEnemy.
    for (int i = shotList.size(); i >= 0 ; i--) {
        Shot tempShot = shotList.get(i);
        for (int j = largeList.size(); j >= 0; j--) {
            LargeEnemy tempLarge = largeList.get(j);
            if (tempShot.isVisible() && tempLarge.isVisible()) {
                if (tempShot.getBoundsInParent().intersects(tempLarge.getBoundsInParent())) {
                    System.out.println("Large Collision Detected");
                    tempShot.setVisible(false);
                    tempLarge.setVisible(false);
                    setScore();
                    setTextField();
                    largeList.remove(j);
                    shotList.remove(i);
                    break;
                } //End inner boolean if statement
            } //End outer boolean if statement
        } //End inner for loop
    } //End outer for loop
} //End checkCollisionLarge method

【讨论】:

  • 那行得通,我发现我需要一种方法来运行所有碰撞检查动画,否则它会在所有 LargeEnemies 之前检查所有 SmallEnemies。问题是,现在只要发现碰撞,它就会移除屏幕上的所有敌人,而不仅仅是被击中的敌人。
  • 此外,它现在只会检测与敌人的一些碰撞,但不是全部。我一直无法辨别是什么导致它检测到一些而不是全部的模式。我确实注意到它只会更新玩家对你击中的敌人的分数。不过,它总是会检测到与游戏顶部的碰撞。
  • I insert new code on answer is a little size of your question ...我建议您很好地调试您的代码...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多