2010-04-23 75 views
4

我正在用Java編寫遊戲,現在它是Swing + JOGL - 帶有GLCanvas的JFrame。Java遊戲的鍵盤輸入

我處理輸入使用keyPressed等事件(jframe.addKeyListener(...)),它似乎並沒有正常工作:

  • 當我有3+鍵按下的同時,他們沒有正確註冊 - 顯然這是鍵盤的故障,我不得不尋找一個備用控制方案。
  • 窗口丟失後,重新獲得焦點,輸入停止工作完全...

我在做什麼錯?

有沒有更好的方式來處理Java中的鍵盤輸入?

(我寧願不切換到另一個庫,如LWJGL ...除非我別無選擇)。

+2

3個關鍵問題可能是硬件限制。 – Beothorn 2010-04-23 22:06:53

+1

http://en.wikipedia.org/wiki/Keyboard_%28computing%29#Control_processor 閱讀下半部分,可能會解釋3鍵問題。 – Paul 2010-04-23 22:34:05

+0

@Lucass:我懷疑3個關鍵問題是硬件限制。我在下面發佈的解決方案中使用三個以上的密鑰沒有問題,並且已經在多臺計算機上進行了測試。 – aioobe 2010-04-23 22:49:20

回答

5

爲了保持依賴關係,我會去「內置」鍵盤處理。如果你知道你在做什麼,它就會很好。我會粘貼我的遊戲中的一些代碼:

它使用自定義重複延遲/速率處理鍵重複,並且在鍵盤焦點在哪個組件中沒有問題。

public class GameKeyController implements KeyEventDispatcher { 

    private final int MAX_REPEAT_RATE = 100; // Hz 

    private final LocalGame game; 
    private final GamingContext context; 
    private final Account account; 
    Timer keyRepeatTimer; 
    Map<Move, TimerTask> repeatingTasks = new EnumMap<Move, TimerTask>(Move.class); 

    public GameKeyController(LocalGame game, GamingContext context, 
      Account account) { 
     this.game = game; 
     this.context = context; 
     this.account = account; 
    } 


    public boolean dispatchKeyEvent(KeyEvent e) { 

     assert EventQueue.isDispatchThread(); 

     int kc = e.getKeyCode(); 

     if (e.getID() == KeyEvent.KEY_PRESSED) { 

      // If repeat is activated, ignore KEY_PRESSED events. 
      // Should actually not occur, since KEY_RELEASED *should* have been 
      // intercepted since last KEY_PRESSED. 
      if (kc == account.getInt(KC_MOVE_LEFT) && !isRepeating(LEFT))  move(LEFT); 
      if (kc == account.getInt(KC_MOVE_RIGHT) && !isRepeating(RIGHT))  move(RIGHT); 
      if (kc == account.getInt(KC_SOFT_DROP) && !isRepeating(SOFT_DROP)) move(SOFT_DROP); 

      // Regular moves 
      if (kc == account.getInt(KC_ROT_CW))  move(ROT_CW); 
      if (kc == account.getInt(KC_ROT_CW2))  move(ROT_CW); 
      if (kc == account.getInt(KC_ROT_CCW))  move(ROT_CCW); 
      if (kc == account.getInt(KC_ROT_CCW2))  move(ROT_CCW); 
      if (kc == account.getInt(KC_HARD_DROP))  move(HARD_DROP); 
      if (kc == account.getInt(KC_SLIDE_DROP)) move(SLIDE_DROP); 
      if (kc == account.getInt(KC_FULL_LEFT))  move(FULL_LEFT); 
      if (kc == account.getInt(KC_FULL_RIGHT)) move(FULL_RIGHT); 
      if (kc == account.getInt(KC_HOLD))   move(HOLD); 

      if (kc == account.getInt(KC_SEND_TO_ME)) useSpecial(0); 
      if (kc == account.getInt(KC_SEND_TO_1))  useSpecial(1); 
      if (kc == account.getInt(KC_SEND_TO_2))  useSpecial(2); 
      if (kc == account.getInt(KC_SEND_TO_3))  useSpecial(3); 
      if (kc == account.getInt(KC_SEND_TO_4))  useSpecial(4); 
      if (kc == account.getInt(KC_SEND_TO_5))  useSpecial(5); 
      if (kc == account.getInt(KC_SEND_TO_6))  useSpecial(6); 
      if (kc == account.getInt(KC_SEND_TO_7))  useSpecial(7); 
      if (kc == account.getInt(KC_SEND_TO_8))  useSpecial(8); 
      if (kc == account.getInt(KC_SEND_TO_9))  useSpecial(9); 


      // Reported bug: Key repeat "lags on releases", that is, the key 
      // continues to repeat a few ms after it has been released. 
      // The following two lines gives one "upper" approximation of 
      // when someone really wants to release the key. 
      if (kc == account.getInt(KC_MOVE_RIGHT)) stopRepeating(LEFT); 
      if (kc == account.getInt(KC_MOVE_LEFT)) stopRepeating(RIGHT); 
     } 


     if (e.getID() == KeyEvent.KEY_RELEASED) { 
      if (kc == account.getInt(KC_MOVE_LEFT)) stopRepeating(LEFT); 
      if (kc == account.getInt(KC_MOVE_RIGHT)) stopRepeating(RIGHT); 
      if (kc == account.getInt(KC_SOFT_DROP)) stopRepeating(SOFT_DROP); 
     } 

     return false; 
    } 


    private synchronized void stopRepeating(Move m) { 
     if (!isRepeating(m)) 
      return; 
     repeatingTasks.get(m).cancel(); 
     repeatingTasks.remove(m); 
    } 


    private synchronized boolean isRepeating(Move m) { 
     return repeatingTasks.get(m) != null; 
    } 


    private synchronized void move(Move move) { 
     assert EventQueue.isDispatchThread(); 

     context.notIdleSinceStart(); 

     PlayfieldEvent pfe = game.move(move); 

     // Fake wall kicks 
     if ((move == ROT_CW || move == ROT_CCW) && 
       account.getBool(USE_FAKE_WALL_KICKS) && !pfe.pfChanged) { 

      // Try RIGHT and ROT, then LEFT and ROT. 
      Playfield pf = game.getPlayfield(); 
      if (pf.isFakeRotPossible(true, move == ROT_CW)) { 
       game.move(RIGHT); 
       game.move(move); 
      } else if (pf.isFakeRotPossible(false, move == ROT_CW)) { 
       game.move(LEFT); 
       game.move(move); 
      } 
     } 


     // Initiate key repeats 
     int delay = account.getInt(KEY_REPEAT_DELAY); 
     int rate = account.getInt(KEY_REPEAT_RATE); 
     if (delay > 0 && rate > 0 && isRepeatable(move)) 
      startRepeating(move); 
    } 


    private boolean isRepeatable(Move m) { 
     return m == LEFT || m == RIGHT || m == SOFT_DROP; 
    } 


    private synchronized void startRepeating(Move move) { 
     assert EventQueue.isDispatchThread(); 

     if (isRepeating(move)) 
      return; 

     long delay = account.getInt(KEY_REPEAT_DELAY); 
     int rate = account.getInt(KEY_REPEAT_RATE); 

     Move repeatMove = move; 
     if (rate >= MAX_REPEAT_RATE) { 
      rate = MAX_REPEAT_RATE; 
      repeatMove = move == LEFT  ? FULL_LEFT 
         : move == RIGHT  ? FULL_RIGHT 
         : move == SOFT_DROP ? SLIDE_DROP 
         : null; // not a repeatable move! 
     } 

     long period = (long) (1000.0/rate); 

     if (move == SOFT_DROP) 
      delay = period; 

     final Move m = repeatMove; 
     TimerTask tt = new TimerTask() { 

      // Should only be executed by keyRepeatTimer thread. 
      public void run() { 

       // Remove the if-branch below and you get old school GB behavior 
       // With the if-branch it's more TDS-ish. 
       // TODO: Make this depend on an account-setting 
       if (m == SOFT_DROP && game.getPlayfield().isTetOnSurface()) { 
        stopRepeating(SOFT_DROP); 
        return; 
       } 

       game.move(m); 

       // Attempt to make it more responsive to key-releases. 
       // Even if there are multiple this-tasks piled up (due to 
       // "scheduleAtFixedRate") we don't want this thread to take 
       // precedence over AWT thread. 
       Thread.yield(); 
      } 
     }; 
     repeatingTasks.put(move, tt); 
     keyRepeatTimer.scheduleAtFixedRate(tt, delay, period); 
    } 


    public synchronized void init() { 
     if (!isInited()) { 
      keyRepeatTimer = new Timer("Key Repeat Timer"); 
      KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); 
     } 
    } 


    public synchronized boolean isInited() { 
     return keyRepeatTimer != null; 
    } 


    public synchronized void uninit() { 
     if (isInited()) { 
      KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this); 

      keyRepeatTimer.cancel(); 
      keyRepeatTimer = null; 
     } 
    } 


    private void useSpecial(int target) { 
     context.notIdleSinceStart(); 
     context.useSpecial(target); 
    } 

} 
1

本文中有關global event listeners的一些提示包括使用KeyboardFocusManager來捕捉關鍵事件,並可能有助於從失去焦點回來。

關於3+鍵,這將是棘手的,因爲KeyEvent說明了修飾符,但不適用於其API中的多個(常規)鍵。您可能必須自己管理按下狀態,因爲如果您獲得KEY_PRESSED,則會存儲該按鍵並構建當前按下的一組按鍵。但是,如果按下3個或更多按鍵時根本沒有收到任何活動,我不確定你能做什麼。

編輯另外,JGame庫有一個JOGL目標。看看它如何處理關鍵事件可能會有所幫助。我知道它可以同時處理至少兩個鍵。