Detecting multiple keypresses in java
Solution 1:
This is a little more convoluted then I would normal do. Normally I would allow each Action
to directly change the game state.
This example shows how to use the key bindings API to monitor press/release of certain keys and add/remove them from a central pool.
The main reason for using this API is that it overcomes the limitations of the KeyListener
when it comes to focus context. This example will retrieve notifications of key presses so long as the window has focus.
public class TestMultyKeyPress {
public static void main(String[] args) {
new TestMultyKeyPress();
}
public TestMultyKeyPress() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new KeyPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class KeyPane extends JPanel implements KeyManager {
private TrianglePane upPane;
private TrianglePane downPane;
private TrianglePane leftPane;
private TrianglePane rightPane;
private List<Integer> keysList = new ArrayList<>(25);
public KeyPane() {
setLayout(new GridBagLayout());
upPane = new TrianglePane(0);
leftPane = new TrianglePane(1);
downPane = new TrianglePane(2);
rightPane = new TrianglePane(3);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 0;
add(upPane, gbc);
gbc.gridx = 0;
gbc.gridy++;
add(leftPane, gbc);
gbc.gridx += 2;
add(rightPane, gbc);
gbc.gridx = 1;
gbc.gridy++;
add(downPane, gbc);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "KeyPressed.Left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "KeyPressed.Right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "KeyPressed.Up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "KeyPressed.Down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "KeyReleased.Left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "KeyReleased.Right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "KeyReleased.Up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "KeyReleased.Down");
am.put("KeyPressed.Left", new KeyAction(KeyEvent.VK_LEFT, false, this));
am.put("KeyPressed.Right", new KeyAction(KeyEvent.VK_RIGHT, false, this));
am.put("KeyPressed.Up", new KeyAction(KeyEvent.VK_UP, false, this));
am.put("KeyPressed.Down", new KeyAction(KeyEvent.VK_DOWN, false, this));
am.put("KeyReleased.Left", new KeyAction(KeyEvent.VK_LEFT, true, this));
am.put("KeyReleased.Right", new KeyAction(KeyEvent.VK_RIGHT, true, this));
am.put("KeyReleased.Up", new KeyAction(KeyEvent.VK_UP, true, this));
am.put("KeyReleased.Down", new KeyAction(KeyEvent.VK_DOWN, true, this));
}
@Override
public void keyPressed(int keyCode) {
if (!keysList.contains(keyCode)) {
keysList.add(new Integer(keyCode));
updateKeyState();
}
}
@Override
public void keyReleased(int keyCode) {
keysList.remove(new Integer(keyCode));
updateKeyState();
}
public void updateKeyState() {
upPane.setActive(keysList.contains(KeyEvent.VK_UP));
downPane.setActive(keysList.contains(KeyEvent.VK_DOWN));
leftPane.setActive(keysList.contains(KeyEvent.VK_LEFT));
rightPane.setActive(keysList.contains(KeyEvent.VK_RIGHT));
}
}
public interface KeyManager {
public void keyPressed(int keyCode);
public void keyReleased(int keyCode);
}
public class KeyAction extends AbstractAction {
private int keyCode;
private boolean released;
private KeyManager manager;
public KeyAction(int keyCode, boolean released, KeyManager manager) {
this.keyCode = keyCode;
this.released = released;
this.manager = manager;
}
@Override
public void actionPerformed(ActionEvent e) {
if (released) {
manager.keyReleased(keyCode);
} else {
manager.keyPressed(keyCode);
}
}
}
public class TrianglePane extends JPanel {
private boolean active;
private TriangleShape shape;
public TrianglePane(int direction) {
shape = new TriangleShape(20);
AffineTransform at = null;
switch (direction) {
case 0:
at = AffineTransform.getRotateInstance(Math.toRadians(-90), 10, 10); // UP
break;
case 1:
at = AffineTransform.getRotateInstance(Math.toRadians(180), 10, 10); // UP
break;
case 2:
at = AffineTransform.getRotateInstance(Math.toRadians(90), 10, 10); // UP
break;
case 3:
at = AffineTransform.getRotateInstance(Math.toRadians(0)); // UP
break;
}
shape.transform(at);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(20, 20);
}
public void setActive(boolean value) {
if (active != value) {
this.active = value;
repaint();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - shape.getBounds().width) / 2;
int y = (getHeight() - shape.getBounds().height) / 2;
if (active) {
g2d.setColor(Color.BLACK);
} else {
g2d.setColor(Color.GRAY);
}
g2d.fill(shape);
}
}
public class TriangleShape extends Path2D.Float {
public TriangleShape(int size) {
moveTo(1, 1);
lineTo(size - 1, (size - 1) / 2);
lineTo(1, size - 1);
closePath();
}
}
}
Solution 2:
Try this custom class which uses a HashSet
to keep a track of the keys pressed...
class MultiKeyPressListener implements KeyListener {
// Set of currently pressed keys
private final Set<Character> pressed = new HashSet<Character>();
@Override
public synchronized void keyPressed(KeyEvent e) {
pressed.add(e.getKeyChar());
if (pressed.size() > 1) {
// More than one key is currently pressed.
// Iterate over pressed to get the keys.
}
}
@Override
public synchronized void keyReleased(KeyEvent e) {
pressed.remove(e.getKeyChar());
}
@Override
public void keyTyped(KeyEvent e) {/* Not used */ }
}