2014-03-30 167 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