After restarting my game using a button, I'm encountering inconsistent game speed and meteor falling rates compared to the initial game launch. Initially, the game runs smoothly with quick meteor falls and fighter jet movement. However, upon restarting the game, the meteors seem to fall slower, and the fighter jet moves at a reduced speed. I suspect this inconsistency might be due to how I'm handling the game loop or thread usage. Any help on resolving this issue would be greatly appreciated!
I'm going to show Game.java, where the game loop and reset functionality are implemented.
package main;
import entity.FighterJet;
import entity.Meteor;
import javax.swing.*;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class Game extends JPanel implements Runnable {
final int windowWidth = 600;
final int windowHeight = 600;
int FPS = 60;
KeyHandler handler = new KeyHandler();
Thread gameLoopThread;
FighterJet fighterJet;
List<Meteor> meteors;
Random random;
boolean gameOver = false;
Menu menu;
JFrame gameWindow;
public Game(Menu menu, JFrame gameWindow) {
this.menu = menu;
this.gameWindow = gameWindow;
setPreferredSize(new Dimension(windowWidth, windowHeight));
setBackground(Color.black);
setDoubleBuffered(true);
addKeyListener(handler);
fighterJet = new FighterJet(300, 500, 8);
meteors = new ArrayList<>();
random = new Random();
}
public void startGameLoopThread() {
if (gameLoopThread != null && gameLoopThread.isAlive()) {
gameLoopThread.interrupt(); // Interrupt the previous game loop thread
}
gameLoopThread = new Thread(this);
gameLoopThread.start();
}
@Override
public void run() {
double drawInterval = (double) 1000000000 / FPS;
double delta = 0;
long lastTime = System.nanoTime();
long currentTime;
while (gameLoopThread != null) {
currentTime = System.nanoTime();
delta += (currentTime - lastTime) / drawInterval;
lastTime = currentTime;
if (delta >= 1) {
update();
repaint();
checkCollisions();
delta--;
}
if (gameOver) {
displayGameOver();
break; // Exit the loop when game is over
}
}
}
public void update() {
updateJet();
generateMeteors();
updateMeteors();
}
private void updateJet() {
if (handler.move_left && !handler.move_right) {
fighterJet.update(true, false); // Move left
fighterJet.updateImage(1);
} else if (handler.move_right && !handler.move_left) {
fighterJet.update(false, true); // Move right
fighterJet.updateImage(2);
} else {
fighterJet.update(false, false); // No movement
fighterJet.updateImage(0);
}
}
private void generateMeteors() {
int meteorSpawnChance = 6;
int maxNumMeteors = 8;
if (meteors.size() < maxNumMeteors && random.nextInt(100) < meteorSpawnChance) {
int randomX = random.nextInt(windowWidth);
int speed = random.nextInt(3) + 4; // Random speed between 4 and 6
meteors.add(new Meteor(randomX, 0, speed));
}
}
private void updateMeteors() {
List<Meteor> meteorsToRemove = new ArrayList<>();
for (Meteor meteor : new ArrayList<>(meteors)) {
meteor.update();
if (meteor.getY() > windowHeight) {
meteorsToRemove.add(meteor);
}
}
meteors.removeAll(meteorsToRemove);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
fighterJet.draw(g2);
// Create a copy of the meteors list to iterate over
List<Meteor> meteorsCopy = new ArrayList<>(meteors);
for (Meteor meteor : meteorsCopy) {
g2.drawImage(meteor.getCurrentImage(), meteor.getX(), meteor.getY(), 20, 50, null);
}
g2.dispose();
if (gameOver) {
displayGameOver();
}
}
public void checkCollisions() {
for (Meteor meteor : meteors) {
if (fighterJet.getBounds().intersects(meteor.getBounds())) {
gameOver = true;
break;
}
}
}
public void displayGameOver() {
if (gameOver) {
gameLoopThread.interrupt();
GameOver gameOverScreen = new GameOver(gameWindow, menu,this);
gameWindow.getContentPane().removeAll();
gameWindow.add(gameOverScreen);
gameWindow.pack();
}
}
public void resetGame() {
gameOver = false;
meteors.clear();
generateMeteors(); // Generate new meteors after clearing
handler.resetFighterJetMovement();
fighterJet = new FighterJet(300, 500, 8);
addKeyListener(handler);
requestFocusInWindow();
startGameLoopThread(); // Start the game loop again
}
}
AWT and Swing are single-threaded. All Swing methods need to called in one and only one thread, the AWT event dispatch thread. If you call them in any other thread, the behavior is undefined—which means it might work sometimes, might fail other times, might fail on other computers, and so on. Simply put, violating the threading rules will make your program unstable.
What this means is that you cannot call methods like update, checkCollisions, and displayGameOver from a Thread you yourself created.
What you can do instead is use a javax.swing.Timer (which is not the same class as java.util.Timer):
private Timer gameLoopTimer;
public void startGameLoopThread() {
int drawInterval = 1000 / FPS;
if (gameLoopTimer != null) {
gameLoopTimer.stop();
}
gameLoopTimer = new Timer(drawInterval, e -> run());
gameLoopTimer.start();
}
@Override
public void run() {
update();
repaint();
checkCollisions();
if (gameOver) {
displayGameOver();
gameLoopTimer.stop();
}
}
Your old version of run()
was a loop with no sleeps or waits in it. It was running constantly, chewing up CPU time and taking CPU time away from Swing. (In theory, each thread would use its own CPU core… unless, of course, other software is using those CPU cores, including the operating system, any background services which are running, and any web browsers or mail clients that happen to be running.)
A Timer won’t have those problems, because it waits for the interval time instead of constantly running and polling the time.
Some other notes:
g2.dispose();
. Never dispose of a Graphics unless you yourself created it. The Graphics passed to painting methods belongs to the system, and is not yours to dispose of.if (gameOver)
check and its associated body from the paintComponent
method. For one thing, the run method already does that check; for another thing, painting methods can be called by the system at any time for a variety of reasons, most of which are not under your control, so program logic should never be in painting methods.