Ответ 1
Этот ответ объясняет и демонстрирует, как использовать привязки клавиш вместо ключевых слушателей для образовательной цели. Это не
- Как написать игру на Java.
- Как должно выглядеть хорошее написание кода (например, видимость).
- Самый эффективный (с точки зрения производительности или кода) способ реализации привязок ключей.
Это
- То, что я опубликую в качестве ответа любому, у кого возникнут проблемы с ключевыми слушателями.
Ответ; Прочитайте руководство Swing по привязкам клавиш.
Я не хочу читать руководства, скажите, почему я хотел бы использовать привязки клавиш вместо красивого кода, который у меня уже есть!
В учебнике Swing объясняется, что
- Связывание клавиш не требует щелчка по компоненту (для его фокусировки):
- Удаляет неожиданное поведение с точки зрения пользователя.
- Если у вас есть 2 объекта, они не могут перемещаться одновременно, так как только один из объектов может иметь фокус в данный момент времени (даже если вы привязываете их к разным клавишам).
- Связывание клавиш проще в обслуживании и управлении:
- Отключение, повторное связывание, переназначение действий пользователя намного проще.
- Код легче читать.
Хорошо, вы убедили меня попробовать. Как это работает?
tutorial содержит хороший раздел. Ключевые привязки включают в себя 2 объекта InputMap
и ActionMap
. InputMap
отображает вход пользователя в имя действия, ActionMap
сопоставляет имя действия с Action
. Когда пользователь нажимает клавишу, карта ввода ищет ключ и находит имя действия, затем в карте действий выполняется поиск имени действия и выполняется действие.
Выглядит громоздко. Почему бы не связать пользовательский ввод напрямую с действием и избавиться от имени действия? Тогда вам понадобится только одна карта, а не две.
Хороший вопрос! Вы увидите, что это одна из тех вещей, которые делают привязки клавиш более управляемыми (отключить, переустановить и т.д.).
Я хочу, чтобы вы дали мне полный рабочий код этого.
Нет (учебник 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
используется, когда сфокусированный компонент находится внутри компонента, который зарегистрирован для получения действия. Если у вас есть много членов экипажа внутри космического корабля, и вы хотите, чтобы космический корабль продолжал получать вход, в то время как любой из членов экипажа сосредоточен, используйте это. -
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.
}
}
}
Вы можете видеть, что разделение карты ввода с картой действий позволяет использовать многократный код и лучший контроль привязок. Кроме того, вы можете также управлять действием напрямую, если вам нужна функциональность. Например:
FireAction p1Fire = new FireAction(1);
p1Fire.setEnabled(false); // Disable the action (for both players in this case).
Дополнительную информацию см. в Учебном пособии.
Я вижу, что вы использовали 1 действие, движение, для 4 ключей (направлений) и 1 действие, огонь, для 1 клавиши. Почему бы не дать каждому ключу его собственное действие или дать всем клавишам одно и то же действие и разобраться, что делать внутри действия (например, в случае перемещения)?
Хорошая точка. Технически вы можете сделать то и другое, но вы должны думать, что имеет смысл и что позволяет легко управлять и использовать многоразовый код. Здесь я предположил, что перемещение аналогично для всех направлений, и стрельба различна, поэтому я выбрал этот подход.
Я вижу много используемых
KeyStroke
, что это такое? Являются ли они похожими наKeyEvent
?
Да, они имеют аналогичную функцию, но более подходят для использования здесь. См. Их API для информации и о том, как их создать.
Вопросы? Улучшения? Предложения? Оставить комментарий. Получите лучший ответ? Отправьте его.