2017-05-29 45 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