如何使用Key Bindings而不是Key Listeners
我在我的代码(游戏或其他)中使用KeyListener
作为我的屏幕对象对用户键input做出反应的方式。 这是我的代码:
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); } } }
我在响应方面遇到问题:
- 我需要点击对象才能工作。
- 我得到的按下其中一个键的响应不是我想要它的工作 – 太响应或太没有反应。
为什么会发生这种情况,我该如何解决这个问题?
这个答案解释和演示如何使用键绑定而不是关键听众的教育目的。 不是这样
- 如何用Java编写游戏。
- 代码的写法应该是什么样的(比如可见性)。
- 实现密钥绑定的最有效的(性能或代码方式)方法。
它是
- 我会作为一个答案给任何关键的听众有麻烦的人 。
回答; 阅读关于键绑定的Swing教程 。
我不想阅读手册,告诉我为什么我想要使用键绑定,而不是我已经漂亮的代码!
那么, Swing教程就解释了这一点
- 键绑定不需要您单击该组件(以使其焦点):
- 从用户的angular度去除意外的行为。
- 如果您有两个对象,则它们不能同时移动,因为只有一个对象可以在给定时间拥有焦点(即使您将它们绑定到不同的键)。
- 键绑定更容易维护和操作:
- 禁用,重新绑定,重新分配用户操作要容易得多。
- 代码更容易阅读。
好的,你说服我试试看。 它是如何工作的?
该教程有一个很好的部分。 键绑定涉及2个对象InputMap
和ActionMap
。 InputMap
将用户input映射到操作名称, ActionMap
将操作名称映射到Action
。 当用户按下某个键时,会在input映射中search键并查找一个动作名称,然后在动作映射中search动作名称并执行动作。
看起来很麻烦。 为什么不把用户input直接绑定到动作上,并摆脱动作名称? 那么你只需要一个地图,而不是两个。
好问题! 你会看到这是使键绑定更易于pipe理(禁用,重新绑定等)的事情之一。
我希望你给我一个完整的工作代码。
否( Swing教程有工作示例 )。
你好烂!我恨你!
这里是如何使一个键绑定:
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
被使用,当一个焦点组件在一个被注册接收这个动作的组件中。 如果你有一个宇宙飞船里面有许多船员,并且你希望宇宙飞船在任何一个船员都聚焦的情况下继续接收input,就用这个。 -
WHEN_IN_FOCUSED_WINDOW
用于注册接收动作的组件在聚焦组件内。 如果在聚焦的窗口中有多个坦克,并且希望所有坦克同时接收input,请使用此窗口。
问题中提供的代码看起来像这样,假设两个对象将被同时控制:
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. } } }
您可以看到,将input映射与操作映射分开允许可重用的代码,并更好地控制绑定。 另外,如果你需要这个function,你也可以直接控制一个Action。 例如:
FireAction p1Fire = new FireAction(1); p1Fire.setEnabled(false); // Disable the action (for both players in this case).
有关更多信息,请参阅操作教程 。
我看到你用了1个动作,移动了4个键(方向)和1个动作,射击了1个键。 为什么不给每个键自己的行动,或给所有的键相同的行动,并解决在行动内部做什么(如在移动的情况下)?
好点子。 从技术上讲,你可以做到这一点,但你必须考虑什么是有意义的,什么允许简单的pipe理和可重用的代码。 在这里,我假设所有方向的移动都是相似的,而且射击是不同的,所以我select了这种方法。
我看到很多
KeyStroke
被使用,那是什么? 他们是不是一个KeyEvent
?
是的,他们有类似的function,但是更适合在这里使用。 查看他们的API获取信息和如何创build它们。
有问题吗? 改进? build议? 发表评论。 有更好的答案吗? 发表它。
注意:这不是一个答案,只是太多的代码注释:-)
通过getKeyStroke(String)获取keyStrokes是正确的方法 – 但需要仔细阅读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, ie the name following "VK_".
最后一行最好是确切的名称 ,这是大小写的问题:对于下键,确切的键码名称是VK_DOWN
,所以参数必须是“DOWN”(而不是“Down”或任何其他大写/小写字母的变体)
不完全直观的(阅读:不得不自己挖掘一点)获得修改键的KeyStroke。 即使正确的拼写,以下将无法正常工作:
KeyStroke control = getKeyStroke("CONTROL");
在awt事件队列中更深入一点,单个修改键的keyEvent被创build,并且自己作为修饰符。 绑定到控制键,你需要中风:
KeyStroke control = getKeyStroke("ctrl CONTROL");