2014-03-30 164 views
16

我在我的代碼(遊戲或其他)中使用KeyListener作爲我的屏幕對象對用戶密鑰輸入作出反應的方式。這裏是我的代碼:如何使用密鑰綁定而不是密鑰監聽器

public class MyGame extends JFrame { 

    static int up = KeyEvent.VK_UP; 
    static int right = KeyEvent.VK_RIGHT; 
    static int down = KeyEvent.VK_DOWN; 
    static int left = KeyEvent.VK_LEFT; 
    static int fire = KeyEvent.VK_Q; 

    public MyGame() { 

//  Do all the layout management and what not... 
     JLabel obj1 = new JLabel(); 
     JLabel obj2 = new JLabel(); 
     obj1.addKeyListener(new MyKeyListener()); 
     obj2.addKeyListener(new MyKeyListener()); 
     add(obj1); 
     add(obj2); 
//  Do other GUI things... 
    } 

    static void move(int direction, Object source) { 

     // do something 
    } 

    static void fire(Object source) { 

     // do something 
    } 

    static void rebindKey(int newKey, String oldKey) { 

//  Depends on your GUI implementation. 
//  Detecting the new key by a KeyListener is the way to go this time. 
     if (oldKey.equals("up")) 
      up = newKey; 
     if (oldKey.equals("down")) 
      down = newKey; 
//  ... 
    } 

    public static void main(String[] args) { 

     new MyGame(); 
    } 

    private static class MyKeyListener extends KeyAdapter { 

     @Override 
     public void keyPressed(KeyEvent e) { 

      Object source = e.getSource(); 
      int action = e.getExtendedKeyCode(); 

/* Will not work if you want to allow rebinding keys since case variables must be constants. 
      switch (action) { 
       case up: 
        move(1, source); 
       case right: 
        move(2, source); 
       case down: 
        move(3, source); 
       case left: 
        move(4, source); 
       case fire: 
        fire(source); 
       ... 
      } 
*/ 
      if (action == up) 
       move(1, source); 
      else if (action == right) 
       move(2, source); 
      else if (action == down) 
       move(3, source); 
      else if (action == left) 
       move(4, source); 
      else if (action == fire) 
       fire(source); 
     } 
    } 
} 

我有響應的問題:

  • 我需要在對象上點擊爲它工作。
  • 我得到的按下其中一個按鍵的響應並不是我希望它工作的方式 - 響應太快或響應太遲。

爲什麼會發生這種情況,我該如何解決這個問題?

回答

44

這個答案解釋和演示瞭如何使用鍵綁定而不是鍵監聽器用於教育目的。它不是

  • 如何用Java編寫遊戲。
  • 代碼的書寫效果應該如何(例如可見度)。
  • 實現密鑰綁定的最有效的(性能或代碼方式)方法。

這是

  • 我會發布什麼作爲回答誰是有與主要聽衆困難的人。

答案;閱讀Swing tutorial on key bindings

我不想看手冊,告訴我爲什麼我想用鍵綁定而不是美麗的代碼我已經!

好了,Swing指南解釋說,

  • 鍵綁定不要求你點擊組件(給它重點):
    • 從用戶的刪除意外的行爲觀點看法。
    • 如果您有2個對象,它們不能同時移動,因爲只有1個對象可以在給定時間擁有焦點(即使您將它們綁定到不同的鍵)。
  • 鍵綁定更易於維護和操作:
    • 禁用,重新綁定,重新分配用戶的操作更容易。
    • 該代碼更易於閱讀。

OK,你說服我試試吧。它是如何工作的?

tutorial有一個關於它良好的部分。密鑰綁定涉及2個對象InputMapActionMapInputMap將用戶輸入映射到動作名稱,ActionMap將動作名稱映射到Action。當用戶按下某個鍵時,會在輸入映射中搜索鍵並查找一個動作名稱,然後在動作映射中搜索動作名稱並執行動作。

看起來很麻煩。爲什麼不把用戶輸入直接綁定到動作並擺脫動作名稱?那麼你只需要一張地圖而不是兩張地圖。

好問題!您會看到這是使鍵綁定更易於管理(禁用,重新綁定等)的一件事情。

我想讓你給我一個完整的工作代碼。

否(在Swing指南working examples)。

你吮吸!我恨你!

下面是如何使一個按鍵綁定:

myComponent.getInputMap().put("userInput", "myAction"); 
myComponent.getActionMap().put("myAction", action); 

注意,有3個InputMap小號反應,不同的聚焦狀態:

myComponent.getInputMap(JComponent.WHEN_FOCUSED); 
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 
  • WHEN_FOCUSED,這是當組件具有焦點時,也會使用沒有提供參數的情況。這與關鍵聽衆的情況很相似。
  • WHEN_ANCESTOR_OF_FOCUSED_COMPONENT當聚焦組件位於註冊接收動作的組件內部時使用。如果你有一個太空船內的許多船員,並且你希望太空船在任何船員都聚焦的情況下繼續接收輸入,使用這個。
  • WHEN_IN_FOCUSED_WINDOW當註冊接收動作的組件位於聚焦組件內部時使用。如果在聚焦窗口中有許多坦克,並且希望所有坦克同時接收輸入,請使用此功能。

中的問題提出的代碼看起來像這樣假設兩個對象都在同一時間進行控制:

public class MyGame extends JFrame { 

    private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW; 
    private static final String MOVE_UP = "move up"; 
    private static final String MOVE_DOWN = "move down"; 
    private static final String FIRE = "move fire"; 

    static JLabel obj1 = new JLabel(); 
    static JLabel obj2 = new JLabel(); 

    public MyGame() { 

//  Do all the layout management and what not... 

     obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("UP"), MOVE_UP); 
     obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("DOWN"), MOVE_DOWN); 
//  ... 
     obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("control CONTROL"), FIRE); 
     obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("W"), MOVE_UP); 
     obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("S"), MOVE_DOWN); 
//  ... 
     obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("T"), FIRE); 

     obj1.getActionMap().put(MOVE_UP, new MoveAction(1, 1)); 
     obj1.getActionMap().put(MOVE_DOWN, new MoveAction(2, 1)); 
//  ... 
     obj1.getActionMap().put(FIRE, new FireAction(1)); 
     obj2.getActionMap().put(MOVE_UP, new MoveAction(1, 2)); 
     obj2.getActionMap().put(MOVE_DOWN, new MoveAction(2, 2)); 
//  ... 
     obj2.getActionMap().put(FIRE, new FireAction(2)); 

//  In practice you would probably create your own objects instead of the JLabels. 
//  Then you can create a convenience method obj.inputMapPut(String ks, String a) 
//  equivalent to obj.getInputMap(IFW).put(KeyStroke.getKeyStroke(ks), a); 
//  and something similar for the action map. 

     add(obj1); 
     add(obj2); 
//  Do other GUI things... 
    } 

    static void rebindKey(KeyEvent ke, String oldKey) { 

//  Depends on your GUI implementation. 
//  Detecting the new key by a KeyListener is the way to go this time. 
     obj1.getInputMap(IFW).remove(KeyStroke.getKeyStroke(oldKey)); 
//  Removing can also be done by assigning the action name "none". 
     obj1.getInputMap(IFW).put(KeyStroke.getKeyStrokeForEvent(ke), 
       obj1.getInputMap(IFW).get(KeyStroke.getKeyStroke(oldKey))); 
//  You can drop the remove action if you want a secondary key for the action. 
    } 

    public static void main(String[] args) { 

     new MyGame(); 
    } 

    private class MoveAction extends AbstractAction { 

     int direction; 
     int player; 

     MoveAction(int direction, int player) { 

      this.direction = direction; 
      this.player = player; 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 

      // Same as the move method in the question code. 
      // Player can be detected by e.getSource() instead and call its own move method. 
     } 
    } 

    private class FireAction extends AbstractAction { 

     int player; 

     FireAction(int player) { 

      this.player = player; 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 

      // Same as the fire method in the question code. 
      // Player can be detected by e.getSource() instead, and call its own fire method. 
      // If so then remove the constructor. 
     } 
    } 
} 

你可以看到,從動作圖分離輸入映射允許重複使用代碼和更好的綁定控制。另外,如果您需要該功能,還可以直接控制Action。例如:

FireAction p1Fire = new FireAction(1); 
p1Fire.setEnabled(false); // Disable the action (for both players in this case). 

查看Action tutorial瞭解更多信息。

我看到你用1分的動作,移動,4個鍵(方向)和1個動作,火,1個鍵。爲什麼不給每個關鍵點自己的行爲,或給所有關鍵點採取相同的行動,並理清行動內部要做什麼(就像在移動的情況下)?

好點。從技術上講,你可以同時做這兩件事,但你必須考慮什麼是有意義的,什麼允許簡單的管理和可重用的代碼。在這裏,我假設所有方向的移動都是相似的,而且射擊是不同的,所以我選擇了這種方法。

我看到很多KeyStroke的使用,那些是什麼?他們喜歡KeyEvent

是的,他們有一個類似的功能,但更適合在這裏使用。有關信息和如何創建它們,請參閱他們的API


有問題?改進?建議?發表評論。 有更好的回答?發表它。

+2

_「改進?」_ - 我會使用常量,而不是硬編碼的字符串值。 –

+0

@peeskillet謝謝,將操作名稱更改爲常量。爲了清楚起見,我留下了關鍵筆劃字符串。 – user1803551

+0

[例如](http://stackoverflow.com/a/7940227/714968),使用的JPanel用正確的焦點(不知道你能指望什麼),良柱(一束)的鍵綁定解釋(原本應該是通過的KeyListener)由@Hovercraft全部鰻魚標記,他的抽象的主人在這裏與約 – mKorbel

5

注:這是不是一個回答,只是太多的代碼:-)

獲得通過按鍵getKeyStroke(字符串)註釋的正確方法 - 但需要的API文檔的仔細閱讀:

modifiers := shift | control | ctrl | meta | alt | altGraph 
typedID := typed <typedKey> 
typedKey := string of length 1 giving Unicode character. 
pressedReleasedID := (pressed | released) key 
key := KeyEvent key code name, i.e. the name following "VK_". 

最後一行最好是確切名稱,這是區分事項:爲向下鍵確切的關鍵代號爲VK_DOWN,因此參數必須是「DOWN」(而不是「向下」或任何上/下的其他變化r大寫字母)

不完全直觀(閱讀:必須自己挖一點)正在獲取修改鍵的KeyStroke。即使正確拼寫,以下將不起作用:

KeyStroke control = getKeyStroke("CONTROL"); 

在AWT事件隊列更深下來,單個修改鍵的KeyEvent與本身作爲改性劑產生。綁定到控制鍵,你需要中風:

KeyStroke control = getKeyStroke("ctrl CONTROL"); 
+0

哦,廢話我注意到我自己走過去的擊鍵字符串(我只是把佔位符有),但與所有其他的事情,我不得不去了我只是忘了。我甚至在最後寫道:「**請參閱他們的API瞭解**信息和**如何創建它們**。」並沒有自己做。 「UP」和「DOWN」是顯而易見的(常規VK_值),但我不知道綁定控制。我知道作爲一個修飾符就足以寫出「控制M」了。我會馬上改正這些,謝謝。 – user1803551