P. Károlyi P. Károlyi - 29 days ago 9
Java Question

How to start game on Canvas from a JPanel?

I'm writing a game as a university project. It uses JCanvas to display the game and that part is working fully, but I want to add a menu where you can choose from things like "play" "highscores" "exit" etc.

I have a JFrame that contains the JCanvas and a JPanel (the latter is the menu). But if I start the game from the panel the frame freezes.

I ran some tests and the problem is that when the playGame() method is called from the JPanels actionListener the canvas can't update until that method returns.

Here are the relevant parts:

public class Main {

public static Game game = new Game();
private static JFrame mainFrame = new JFrame("Snake");
private static MenuPanel menuPanel = new MenuPanel();

public static void main(String[] args) {
mainFrame.setLayout(new BorderLayout());

mainFrame.add(menuPanel, BorderLayout.SOUTH);
mainFrame.pack();
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setVisible(true);

}

static void playGame(){
mainFrame.setVisible(false);
mainFrame.remove(menuPanel);
menuPanel.setEnabled(false);

ScoreboardPanel scoreboardPanel = new ScoreboardPanel();
mainFrame.add(scoreboardPanel, BorderLayout.NORTH);

game.addPanel(scoreboardPanel);
mainFrame.add(game, BorderLayout.CENTER);
mainFrame.pack();
mainFrame.setVisible(true);

// ANYTHING here stops the frame's rendering until playGame() returns (like game.play() or sleep(x)
}


}

public class MenuPanel extends JPanel {

private class MenuActionListener implements ActionListener {
public void actionPerformed(ActionEvent actionEvent){
switch (actionEvent.getActionCommand()){
case "play":
// TODO This somehow breaks everything. The game loop runs but the window freezes.
Main.playGame();
break;
case "highscore":
break;
case "leveleditor":
break;
case "exit":
System.exit(0);
break;
}
}
}


And here's the full code if that helps more: https://github.com/nodar86/prog3-snake

Answer

Both repaints and event dispatch is performed on The Event Dispatch Thread (EDT). The Game.play method is called from the event listener and blocks EDT using sleep, so Swing has no chance to repaint your frame.

The easiest way to fix this -- replace hand-made timer in Game.play with javax.swing.Timer. On each tick of the timer you should emulate on tick of the game. This will allow Swing to repaint as needed. If you prepare timer beforehand, you menu listeners could just start and stop it.

Side note: probably, it worth using special menu components for you menu.

Comments