【发布时间】: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