Detecting multiple keypresses in java

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();
        }

    }

}

Leave a Comment