简体   繁体   中英

JavaFX, countdown to run a method

So I'm designing a game similar to Galaga or Space Invaders in JavaFX and while the collision does work, just having it run constantly checking makes the game slow down and if you fire more than 2-3 shots at a time the game freezes and crashes. So my though was create a timer that counts down and when it reaches 0 it runs the method it is supposed to.

So here is what I have: when I add an enemy in the addEnemy() method it calls countdownToSmallCollisionCheck() which runs the smallCollisionCounter() method the given number of times, the smallCollisionCounter() method reduces the counter (an int for the milliseconds) until it reaches 0, which at that time it calls the smallCollisionTimeline() method and resets itself to it's original value, the smallCollisionTimeline() method contains the animation that controls the collision check and actually checks for collisions.

So the route this takes is: addEnemy() > countdownToSmallCollisionCheck() > smallCollisionCounter() > smallCollisionTimeline() > checkCollisionSmall();

I know I have some sort of logic error in here somewhere as the collisions are never being detected, but I can't seem to figure out what it is. Also there might be a better way to do this that I haven't figured out. I've looked through the API and other book and web sources but nothing seems like a direct way to do this where I can just use one function to call the method after X time passes. Anyway, I'm grateful for any and all help with this. Below is my code for my GamePane class that extends Pane which I am doing this part of the coding in.

/**
* 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

I add your function countdownToSmallCollisionCheck() to function 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

There are error on checkCollisionLarge function and the same on checkCollisionSmall

first you remove from a List enemy and shot so if you use a for loop is best a reverse loop example: for (int i = 0; i < shotList.size(); i++) use for (int i = shotList.size(); i >= 0 ; i--) another error is this line LargeEnemy tempLarge = largeList.get(i); you use j for loop so the correct line is LargeEnemy tempLarge = largeList.get(j); same error for line largeList.remove(i); and when was detected a collision i think that you can break the loop j so i add break on your code.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM