Создаю простую игру с качелями. Я пытаюсь обрабатывать ввод с помощью, ActionMap
и InputMap
поэтому я использую, Action
чтобы сделать то, что мне нужно сделать, в этом конкретном случае переключиться в главное меню и сбросить игровую панель. Теперь я заметил, что функция showMenu()
вызывается несколько раз двумя потоками, а клавиша нажимается только один раз. Почему это так? И как я могу это предотвратить?
Чтобы воспроизвести ошибку, просто запустите основную программу в режиме Startup.java
щелчка мышью, а затем нажмите m
клавишу на клавиатуре; что должно воспроизводить в консоли вывод, аналогичный моему.
Вот результат с именами потоков, который я получаю:
I'M PRESSING M - THREAD: AWT-EventQueue-0
CALLING showMenu OF GUIHANLDER - THREAD AWT-EventQueue-0
I am in stop - GameController - THREAD:AWT-EventQueue-0
Start of reset Game - GameController - THREAD:AWT-EventQueue-0
CALLING showMenu OF GUIHANLDER - THREAD Thread-0
I am in stop - GameController - THREAD:Thread-0
Start of reset Game - GameController - THREAD:Thread-0
Created a player X: 24 Y:40
resetted
Created a player X: 49 Y:5
End of reset Game - GameController - THREAD:AWT-EventQueue-0
resetted
End of reset Game - GameController - THREAD:Thread-0
Called resetGame() - GameController - THREAD:AWT-EventQueue-0
Called resetGame() - GameController - THREAD:Thread-0
pressed M - THREAD: AWT-EventQueue-0
Вот основные области интересов:
GamePanel.java
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class GamePanel extends JPanel {
public static final String PANEL_ID = "game_panel";
public static final int DEFAULT_WIDTH = 100;
public static final int DEFAULT_HEIGHT = 100;
private int mapWidth = -1;
private int mapHeight = -1;
private Random r = new Random();
public GamePanel() {
setBackground(Color.BLACK);
createPlayer();
initSetup();
}
public void createPlayer() {
int x = r.nextInt(100);
int y = r.nextInt(100);
System.out.println("Created a player X: " + x + " Y:" + y);
}
public void initSetup() {
repaint();
initInput();
}
private void initInput() {
InputMap iMap = getInputMap();
ActionMap aMap = getActionMap();
Action goToMenu = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("I'M PRESSING M - THREAD: "+Thread.currentThread().getName());
GUIHandler.getInstance().showMenu();
System.out.println("pressed M - THREAD: "+Thread.currentThread().getName());
}
};
aMap.put("goToMenu", goToMenu);
iMap.put(KeyStroke.getKeyStroke('m'), "goToMenu");
}
public void reset() {
if (mapHeight == -1 || mapWidth == -1) {
mapWidth = DEFAULT_WIDTH;
mapHeight = DEFAULT_HEIGHT;
}
createPlayer();
initSetup();
System.out.println("resetted");
}
}
GameController.java
public class GameController {
private GamePanel gamePanel;
private GameLoop gameLoop;
private static GameController instance = null;
private GameController() {
gamePanel = GUIHandler.getInstance().getGamePanel();
gameLoop = new GameLoop();
}
public void start() {
if (!gameLoop.isGameRunning())
gameLoop.run();
}
public void stop() {
System.out.println("I am in stop - GameController - THREAD:"+Thread.currentThread().getName());
if (gameLoop.isGameRunning())
gameLoop.stop();
resetGame();
System.out.println("Called resetGame() - GameController - THREAD:"+Thread.currentThread().getName());
}
private void resetGame() {
System.out.println("Start of reset Game - GameController - THREAD:"+Thread.currentThread().getName());
gamePanel.reset();
System.out.println("End of reset Game - GameController - THREAD:"+Thread.currentThread().getName());
}
public GamePanel getGamePanel() {
return gamePanel;
}
public static GameController getInstance() {
if (instance == null)
instance = new GameController();
return instance;
}
}
GameLoop.java
public class GameLoop {
private static final String STOPPED = "stopped";
private static final String RUNNING = "running";
private static final int FPS_GOAL = 60;
private static final int MS_PER_FRAME = 1000/FPS_GOAL;
protected String status;
private Thread gameThread;
protected GameLoop() {
status = STOPPED;
}
public void run() {
status = RUNNING;
gameThread = new Thread(this::processGameLoop);
gameThread.start();
}
public void stop() {
status = STOPPED;
gameThread.interrupt();
gameThread = null;
}
public boolean isGameRunning() {
return status == RUNNING;
}
private void render() {
GameController.getInstance().getGamePanel().repaint();
}
protected void processGameLoop() {
while (isGameRunning())
{
long start = System.currentTimeMillis();
render();
try {
long tiemout = start + MS_PER_FRAME - System.currentTimeMillis();
if (tiemout > 0)
Thread.sleep(tiemout);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
GUIHandler.getInstance().showMenu();
}
}
}
}
GUIHandler.java
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Dimension;
public class GUIHandler {
private JPanel contentPane;
private Menu menu;
private GamePanel game;
public static final Dimension PREFERRED_DIMENSION = new Dimension(1200, 800);
private static GUIHandler instance = null;
private GUIHandler() {}
public void displayGUI() {
JFrame frame = new JFrame("Square Territory");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane = new JPanel();
contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new CardLayout());
menu = new Menu();
game = new GamePanel();
contentPane.add(menu, Menu.PANEL_ID);
contentPane.add(game, GamePanel.PANEL_ID);
frame.setPreferredSize(PREFERRED_DIMENSION);
frame.getContentPane().add(contentPane, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public GamePanel getGamePanel() {return game;}
public void startGame() {
((CardLayout) contentPane.getLayout()).show(contentPane, GamePanel.PANEL_ID);
game.grabFocus();
GameController.getInstance().start();
}
public void showMenu() {
System.out.println("CALLING showMenu OF GUIHANLDER - THREAD "+Thread.currentThread().getName());
GameController.getInstance().stop();
((CardLayout) contentPane.getLayout()).show(contentPane, Menu.PANEL_ID);
}
public static GUIHandler getInstance() {
if (instance == null)
instance = new GUIHandler();
return instance;
}
}
Menu.java
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextPane;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.JSpinner.NumberEditor;
import javax.swing.event.MouseInputAdapter;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.awt.BorderLayout;
import java.awt.Dimension;
public class Menu extends JPanel {
public static final String PANEL_ID = "menu_panel";
/**
* Creating the menu and its components.
*/
public Menu() {
setOpaque(true);
setBackground(Color.CYAN.darker());
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JTextPane logo = new JTextPane();
logo.setText("SquareTerritory");
logo.setFont(new Font(Font.SANS_SERIF, Font.ITALIC + Font.BOLD, 60));
SimpleAttributeSet center = new SimpleAttributeSet();
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
logo.getStyledDocument().setParagraphAttributes(0, logo.getStyledDocument().getLength(), center, false);
logo.setBackground(Color.CYAN.darker());
logo.setEditable(false);
logo.setFocusable(false);
JPanel dimensionChoose = new JPanel(new BorderLayout());
SpinnerModel widthModel = new SpinnerNumberModel(100, 10, 800, 1);
JSpinner width = new JSpinner(widthModel);
SpinnerModel heightModel = new SpinnerNumberModel(100, 10, 800, 1);
JSpinner height = new JSpinner(heightModel);
width.getEditor().setPreferredSize(new Dimension(200, 50));
width.setToolTipText("Width of the grid");
((NumberEditor) width.getEditor()).getTextField().setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
((NumberEditor) width.getEditor()).getTextField().setBackground(Color.CYAN.darker().darker());
height.getEditor().setPreferredSize(new Dimension(200, 50));
height.setToolTipText("Height of the grid");
((NumberEditor) height.getEditor()).getTextField().setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
((NumberEditor) height.getEditor()).getTextField().setBackground(Color.CYAN.darker().darker());
JPanel labelPane = new JPanel();
JLabel widthLabel = new JLabel("Width");
widthLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
JLabel heightLabel = new JLabel("Height");
heightLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
labelPane.setBackground(Color.CYAN.darker());
labelPane.add(widthLabel);
labelPane.add(Box.createGlue());
labelPane.add(heightLabel);
widthLabel.setLabelFor(width);
heightLabel.setLabelFor(height);
dimensionChoose.add(width, BorderLayout.WEST);
dimensionChoose.add(labelPane);
dimensionChoose.add(height, BorderLayout.EAST);
JButton play = new JButton("Play");
play.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
play.setForeground(Color.WHITE);
play.setBackground(Color.DARK_GRAY);
play.addMouseListener(new MouseInputAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
play.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 40));
play.setBackground(Color.LIGHT_GRAY);
}
@Override
public void mouseExited(MouseEvent e) {
play.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
play.setBackground(Color.DARK_GRAY);
}
});
play.addActionListener(e -> GUIHandler.getInstance().startGame());
JButton exit = new JButton("Exit");
exit.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
exit.setForeground(Color.WHITE);
exit.setBackground(Color.DARK_GRAY);
exit.addMouseListener(new MouseInputAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
exit.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 40));
exit.setBackground(Color.LIGHT_GRAY);
}
@Override
public void mouseExited(MouseEvent e) {
exit.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 40));
exit.setBackground(Color.DARK_GRAY);
}
});
exit.addActionListener(e -> System.exit(0));
logo.setAlignmentX(Component.CENTER_ALIGNMENT);
logo.setMaximumSize(new Dimension(logo.getMaximumSize().width, 500));
dimensionChoose.setAlignmentX(Component.CENTER_ALIGNMENT);
dimensionChoose.setMaximumSize(new Dimension(800, 200));
dimensionChoose.setBackground(Color.CYAN.darker());
play.setAlignmentX(Component.CENTER_ALIGNMENT);
exit.setAlignmentX(Component.CENTER_ALIGNMENT);
add(logo);
add(Box.createVerticalGlue());
add(dimensionChoose);
add(Box.createVerticalGlue());
add(play);
add(Box.createVerticalGlue());
add(exit);
}
}
Startup.java
public class Startup {
public static void main(String[] args) {
GUIHandler gh = GUIHandler.getInstance();
gh.displayGUI();
}
}
Ваш второй вызов showMenu()
выполняется здесь, в вашем «игровом цикле», а не из-за того, что действие вызывается дважды:
protected void processGameLoop() {
while (isGameRunning()) {
long start = System.currentTimeMillis();
render();
try {
long tiemout = start + MS_PER_FRAME - System.currentTimeMillis();
if (tiemout > 0)
Thread.sleep(tiemout);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// ** println inserted by me **
System.out.println("thread interrupted");
GUIHandler.getInstance().showMenu(); // **** here ****
}
}
}
Вызывается блок catch и выполняется код внутри него. Ваш игровой цикл не является потокобезопасным для Swing, поскольку вы выполняете вызовы Swing из фонового потока.
Это код, который прерывает:
class GameLoop {
// ....
public void stop() {
status = STOPPED;
gameThread.interrupt(); // *****
gameThread = null;
}
который вызывается:
class GameController {
// ....
public void stop() {
System.out.println("I am in stop - GameController - THREAD:" + Thread.currentThread().getName());
if (gameLoop.isGameRunning())
gameLoop.stop(); // *****
resetGame();
System.out.println("Called resetGame() - GameController - THREAD:" + Thread.currentThread().getName());
}
который вызывается самим showMenu:
public void showMenu() {
System.out.println("CALLING showMenu OF GUIHANLDER - THREAD " + Thread.currentThread().getName());
GameController.getInstance().stop(); // *****
((CardLayout) contentPane.getLayout()).show(contentPane, Menu.PANEL_ID);
}
Эта статья взята из Интернета, укажите источник при перепечатке.
Если есть какие-либо нарушения, пожалуйста, свяжитесь с[email protected] Удалить.
я говорю два предложения