使用按钮重新启动游戏后,与最初的游戏启动相比,我遇到了不一致的游戏速度和流星坠落率。最初,游戏运行流畅,有快速的流星坠落和战斗机的移动。然而,重新开始游戏后,流星的坠落速度似乎变慢了,战斗机的移动速度也减慢了。我怀疑这种不一致可能是由于我处理游戏循环或线程使用的方式造成的。任何有关解决此问题的帮助将不胜感激!
我将展示 Game.java,其中实现了游戏循环和重置功能。
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 和 Swing 是单线程的。所有 Swing 方法都需要在一个且仅有一个线程(AWT 事件调度线程)中调用。如果您在任何其他线程中调用它们,则行为是未定义的 - 这意味着它有时可能有效,有时可能失败,可能在其他计算机上失败,等等。简而言之,违反线程规则会让你的程序不稳定。
这意味着您无法从您自己创建的线程中调用 update、checkCollisions 和 displayGameOver 等方法。
你可以做的是使用 javax.swing.Timer (它与 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();
}
}
您的旧版本
run()
是一个循环,其中没有睡眠或等待。它不断地运行,消耗 CPU 时间并占用 Swing 的 CPU 时间。 (理论上,每个线程都会使用自己的 CPU 核心……当然,除非其他软件正在使用这些 CPU 核心,包括操作系统、正在运行的任何后台服务以及恰好正在运行的任何 Web 浏览器或邮件客户端) .)
定时器不会有这些问题,因为它等待间隔时间而不是不断运行和轮询时间。
其他一些注意事项:
g2.dispose();
。除非您自己创建图形,否则切勿丢弃它。传递给绘画方法的图形属于系统,不属于您处理的。if (gameOver)
方法中删除 paintComponent
检查及其关联的主体。一方面,run 方法已经执行了该检查;另一方面,绘画方法可以出于多种原因随时被系统调用,其中大多数原因不在您的控制范围内,因此程序逻辑永远不应该在绘画方法中。