2017-05-29 52 views
0

我正在用Swing制作一个老派的Java游戏。我已经读过,为了实时捕获输入,我需要在新线程中运行我的游戏循环,以便它的方法不会干扰输入捕捉。我做了InputCapture类实现KeyListener,我已经实现keyPressed()方式类似:KeyListener在游戏输入捕捉的新线程

public class InputCapture implements KeyListener { 

    private Direction capturedDirection; 

    //Methods 
    @Override 
    public void keyPressed(KeyEvent e) { 
     boolean inputConsoleDebug = true; 
     if (e.getKeyCode() == KeyEvent.VK_LEFT) { 
      capturedDirection = Direction.left; 
      if (inputConsoleDebug) System.out.println("LEFT"); 
     } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { 
      capturedDirection = Direction.right; 
      if (inputConsoleDebug) System.out.println("RIGHT"); 
     } else if (e.getKeyCode() == KeyEvent.VK_UP) { 
      capturedDirection = Direction.up; 
      if (inputConsoleDebug) System.out.println("UP"); 
     } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { 
      capturedDirection = Direction.down; 
      if (inputConsoleDebug) System.out.println("DOWN"); 
     } 
    } 

    @Override 
    public void keyReleased(KeyEvent e) { 
    } 

    @Override 
    public void keyTyped(KeyEvent e) { 
    } 

    public Direction getCapturedDirection() { 
     return capturedDirection; 
    } 
} 

然后我做了Game类扩展Thread,我已经把游戏循环代码到run()方法:

public class Game extends Thread { 

    private Board board; 
    private Snake snake; 
    private JFrame frame; 
    private long waitTime; 
    private int difficultyStep; 
    private Direction inputDirection; 
    private InputCapture inputManager; 

    //Constructors 
    Game(Dimension boardSize) { 
     //Set difficulty 
     int applesToWin = boardSize.width * boardSize.height - 1; 
     final int easiestWaitTime = 1000; 
     final int hardestWaitTime = 100; 
     difficultyStep = (easiestWaitTime - hardestWaitTime)/applesToWin; 
     waitTime = easiestWaitTime; 
     //Set starting point 
     final int startingPointX = boardSize.width/2; 
     final int startingPointy = boardSize.height/2; 
     //Set board and snake 
     board = new Board(boardSize); 
     snake = new Snake(board, startingPointX, startingPointy); 
     //Set window Frame 
     frame = new JFrame(SnakeApplication.getApplicationName()); 
     frame.setContentPane(board); 
     frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 
     frame.pack(); 
     frame.setResizable(false); 
     frame.addWindowListener(new WindowAdapter() { 
      @Override 
      public void windowClosing(WindowEvent e) { 
       super.windowClosing(e); 
       interrupt(); 
      } 
     }); 
     //Set input manager 
     inputManager = new InputCapture(); 
     frame.addKeyListener(inputManager); 
     inputDirection = null; 
    } 

    //Methods 
    public void run() { 
     board.spawnApple(); 
     while (!isWon()) { 
      try { 
       sleep(waitTime); 
      } catch (InterruptedException e) { 
       return; 
      } 
      try { 
       inputDirection = inputManager.getCapturedDirection(); 
       snake.move(inputDirection); 
      } catch (LosingMove e) { 
       showGameOverDialog(); 
       return; 
      } 
      board.repaint(); 
     } 
     showWinDialog(); 
    } 

    JFrame getFrame() { 
     return frame; 
    } 

    private boolean isWon() { 
     for (int row = 0; row < board.getFields().length; row++) { 
      for (int col = 0; col < board.getFields()[0].length; col++) { 
       if (!(board.getFields()[row][col].getContent() instanceof Snake.SnakeNode)) return false; 
      } 
     } 
     return true; 
    } 

    private void showGameOverDialog() { 
     JFrame gameOverFrame = new JFrame(); 
     JOptionPane.showMessageDialog(gameOverFrame, "Game Over!"); 
    } 

    private void showWinDialog() { 
     JFrame gameOverFrame = new JFrame(); 
     JOptionPane.showMessageDialog(gameOverFrame, "You Win!"); 
    } 
} 

在我的MainMenu类中,我做了startNewGame()方法,当单击New Game按钮时调用。此方法创建Game对象,并通过调用start()方法启动新线程。

public class MainMenu { 

    //Form components references 
    private JButton exitButton; 
    private JFrame frame; 
    private JPanel mainPanel; 
    private JButton newGameButton; 
    private JLabel titleLabel; 

    //Constructors 
    MainMenu() { 
     //Set window Frame 
     frame = new JFrame(SnakeApplication.getApplicationName()); 
     frame.setContentPane(mainPanel); 
     frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 
     frame.setResizable(false); 
     frame.pack(); 
     newGameButton.addActionListener(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       startNewGame(); 
      } 
     }); 
     exitButton.addActionListener(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       exitGame(); 
      } 
     }); 
    } 

    JFrame getFrame() { 
     return frame; 
    } 

    private Dimension showBoardSizeDialog() { 
     Frame boardSizeFrame = new Frame(); 
     int width = Integer.parseInt(JOptionPane.showInputDialog(boardSizeFrame, "Set board's width:")); 
     int height = Integer.parseInt(JOptionPane.showInputDialog(boardSizeFrame, "Set board's height:")); 
     return new Dimension(width, height); 
    } 

    private void startNewGame() { 
     Dimension boardSize = showBoardSizeDialog(); 
     frame.setVisible(false); 
     Game game = new Game(boardSize); 
     game.getFrame().setVisible(true); 
     //Starting game loop in a new thread 
     game.start(); 
     try { 
      game.join(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     frame.setVisible(true); 
    } 
} 

但是,当测试应用程序时,它被卡在游戏循环中,根本没有捕获输入。为什么?我试图调试它,但每次启动新线程时,它都会陷入游戏循环。 Board本身只在主线程结束执行时才被绘制。为什么?不应该在游戏循环中多次重绘,如果执行被阻塞在那里?点击框架的关闭按钮(红色的X按钮),我已经做了线程中断,因此执行可以返回到MainMenu并重新显示它,但点击红色关闭按钮没有任何影响。

+1

可能的复制[*与按键绑定*线程(https://stackoverflow.com/q/13999506/230513)。 – trashgod

+0

[如何使用密钥绑定](http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) – MadProgrammer

回答

2

程序冻结,因为startNewGame调用game.join()的。 join保持从被称为的线程继续执行,直到它被称为的线程在死亡。在你的情况下,join击败使用另一个线程的目的,所以你应该删除它。

但还有其他问题。你可能不应该使用线程。 You should probably use a Swing TimerSwing isn't thread-safe,我已经可以看到一些代码不是线程安全的地方。 (例如,您需要声明capturedDirectionvolatile。)使用Swing编写正确的多线程代码有点复杂,而且使用计时器会更简单。

否则,如果您不使用计时器,则需要使用例如游戏线程(写入共享游戏状态)和执行绘画的Swing线程(大概从共享游戏状态读取)之间的同步。如果你不这样做,你可能会遇到难以诊断的问题。

也看到了The Use of Multiple JFrames: Good or Bad Practice?

+0

使用Timer为我工作,谢谢:) – Mesayah

0

你应该让你的Game类扩展Runnable而不是Thread

然后在不同的线程游戏:

Game theGame = ... // initialization code here 
new Thread(theGame).start(); 
+0

使'Game'实现'Runnable'没有帮助。它仍然卡住,关闭按钮仍然不响应。 – Mesayah