我正在尝试用 Java 创建一个简单的游戏,但我一直坚持创建一个系统来处理不同的场景以及它们之间的过渡。
这些是我的课程:
使用 cardLayout 控制当前显示什么场景的场景管理器。
import javax.swing.*;
import java.awt.*;
public class SceneManager extends JFrame{
private static SceneManager single_instance = null;
public final CardLayout cardLayout;
private final Scene mainMenuScene;
private final Scene gameScene;
private SceneManager() {
super("My Game");
setSize(new Dimension(1000,350));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
cardLayout = new CardLayout();
this.setLayout(cardLayout);
mainMenuScene = new MainMenuScene(this);
mainMenuScene.setName("mainMenuScene");
this.add(mainMenuScene, "mainMenuScene");
gameScene = new GameScene(this);
gameScene.setName("gameScene");
this.add(gameScene, "gameScene");
setVisible(true);
}
public static synchronized SceneManager getInstance()
{
if (single_instance == null)
single_instance = new SceneManager();
return single_instance;
}
public void switchToMainMenu() {
cardLayout.show(getContentPane(), "mainMenuScene");
}
public void switchToGame() {
cardLayout.show(getContentPane(), "gameScene");
}
public static void main(String[] args) {
SceneManager sc = SceneManager.getInstance();
}
}
Scene 父类作为所有场景的模板。它使用线程游戏循环系统,因为我想即使是看似静态的场景也有动画,比如主菜单。但是,我不知道我这样做是否正确,因为这是我第一次使用线程和一般的 java。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public abstract class Scene extends JPanel implements Runnable{
private boolean isPaused = true;
private int FPS;
protected SceneManager sceneManager;
private Thread thread;
public Scene(SceneManager sceneManager){
this.FPS = 30;
this.sceneManager = sceneManager;
this.setPreferredSize(new Dimension(1000, 350));
this.setBackground(Color.black);
this.setDoubleBuffered(true);
this.setFocusable(true);
this.setVisible(true);
addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent evt) {
pause();
}
@Override
public void componentShown(ComponentEvent evt) {
resume();
}
});
startThread();
}
public void startThread(){
this.thread = new Thread(this);
this.thread.start();
}
public void pause() {
this.isPaused = true;
}
public void resume() {
this.isPaused = false;
}
public boolean isPaused(){
return isPaused;
}
@Override
public void run() {
double drawInterval = 1_000_000_000. / FPS;
double delta = 0;
long lastTime = System.nanoTime();
long currentTime;
long timer = 0;
while (thread != null){
currentTime = System.nanoTime();
delta += (currentTime - lastTime) / drawInterval;
timer += (currentTime - lastTime);
lastTime = currentTime;
if (delta >= 1) {
// Only update and repaint if the game is not paused
if(!isPaused) {
repaint();
update();
}
delta --;
}
if(timer >= 1_000_000_000){
timer = 0;
}
}
}
public abstract void update();
public abstract void draw(Graphics2D g2d);
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
draw(g2d);
g2d.dispose();
}
}
然后是两个场景:
主菜单:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class MainMenuScene extends Scene {
private JButton button;
public MainMenuScene(SceneManager sceneManager) {
super(sceneManager);
button = new JButton("Click Me");
button.addActionListener(this::onButtonClick);
button.setLocation(100,100);
button.setSize(200,100);
this.add(button, "button1");
this.validate();
this.setLayout(new BorderLayout());
}
public void update() {}
public void draw(Graphics2D g2d) {}
private void onButtonClick(ActionEvent event) {
sceneManager.switchToGame();
}
}
以及游戏场景:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class GameScene extends Scene {
private int x;
JButton button;
public GameScene(SceneManager sceneManager) {
super(sceneManager);
button = new JButton("Click Me");
button.addActionListener(this::onButtonClick);
button.setLocation(100,100);
button.setSize(200,100);
this.add(button, "button1");
this.validate();
this.setLayout(new BorderLayout());
}
public void update(){
x += 2;
}
public void draw(Graphics2D g2d) {
g2d.fillRect(x,50,200,200);
}
private void onButtonClick(ActionEvent event) {
sceneManager.switchToMainMenu();
}
}
我遇到的问题是,当我启动程序时,按钮是不可见的,直到我将鼠标悬停在它上面。然后,如果我调用 resume(),按钮在悬停时会闪烁(当我触摸它时它变得可见,然后下一个“帧”它再次变得不可见,直到我再次移动鼠标)。我不知道是什么导致了这种行为,所以任何帮助将不胜感激。
P.S.:我发现的一件事是,当我将 mainMenu 的布局设置为 SceneManager 的 cardLayout(我调用 this.setLayout(sceneManager.cardLayout); 在 MainMenu 构造函数中)时,按钮从开始直接显示,但它覆盖了整个窗口,而不是指定的大小。
P.P.S:我还尝试将 revalidate() 放在代码中的各种位置(update() 方法、菜单构造函数,两者……),但没有成功。可能是因为我不太确定它的作用,所以我可能一直在错误地使用它。
问题是在Scene类的PaintComponent()方法中调用了g2d.dispose()引起的。 kleopatra 在评论中发现了这个错误。