Java KeyListener stutters

I am making a very simple pong game in java and I am doing it with KeyListener. I want so that when the user presses the right or left keys on the keyboard, the pong block goes in that direction. This is a fairly simple task, but I found that when the user holds the key, the block moves once, stops for a short time, and then continues to move until the user releases the key. I notice that this happens when you try to keep an alphabetic key on a computer. If I try to hold the "a" key, the computer will do:

a [pause] aaaaaaaaaaaaaaaaa

Is there a way to disable this stutter because it interferes with a smooth game for my little game. A quick fix will be deeply appreciated.

+6
source share
3 answers

I initially had an answer about Key Bindings, but after a bit of testing, I found that they still had the same stuttering problem.

Do not rely on the OS repetition rate. It may be different for each platform, and the user can also configure it.

Instead, use a timer to schedule an event. You start the timer on the Raised key and stop the timer on keyReleased.

import java.awt.*; import java.awt.event.*; import java.net.*; import java.util.Map; import java.util.HashMap; import javax.imageio.ImageIO; import javax.swing.*; public class KeyboardAnimation implements ActionListener { private final static String PRESSED = "pressed "; private final static String RELEASED = "released "; private final static Point RELEASED_POINT = new Point(0, 0); private JComponent component; private Timer timer; private Map<String, Point> pressedKeys = new HashMap<String, Point>(); public KeyboardAnimation(JComponent component, int delay) { this.component = component; timer = new Timer(delay, this); timer.setInitialDelay( 0 ); } public void addAction(String keyStroke, int deltaX, int deltaY) { // InputMap inputMap = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); InputMap inputMap = component.getInputMap(); ActionMap actionMap = component.getActionMap(); String pressedKey = PRESSED + keyStroke; KeyStroke pressedKeyStroke = KeyStroke.getKeyStroke( pressedKey ); Action pressedAction = new AnimationAction(keyStroke, new Point(deltaX, deltaY)); inputMap.put(pressedKeyStroke, pressedKey); actionMap.put(pressedKey, pressedAction); String releasedKey = RELEASED + keyStroke; KeyStroke releasedKeyStroke = KeyStroke.getKeyStroke( releasedKey ); Action releasedAction = new AnimationAction(keyStroke, RELEASED_POINT); inputMap.put(releasedKeyStroke, releasedKey); actionMap.put(releasedKey, releasedAction); } private void handleKeyEvent(String keyStroke, Point moveDelta) { // Keep track of which keys are pressed if (RELEASED_POINT == moveDelta) pressedKeys.remove( keyStroke ); else pressedKeys.put(keyStroke, moveDelta); // Start the Timer when the first key is pressed if (pressedKeys.size() == 1) { timer.start(); } // Stop the Timer when all keys have been released if (pressedKeys.size() == 0) { timer.stop(); } } // Invoked when the Timer fires public void actionPerformed(ActionEvent e) { moveComponent(); } // Move the component to its new location private void moveComponent() { int componentWidth = component.getSize().width; int componentHeight = component.getSize().height; Dimension parentSize = component.getParent().getSize(); int parentWidth = parentSize.width; int parentHeight = parentSize.height; // Calculate new move int deltaX = 0; int deltaY = 0; for (Point delta : pressedKeys.values()) { deltaX += delta.x; deltaY += delta.y; } // Determine next X position int nextX = Math.max(component.getLocation().x + deltaX, 0); if ( nextX + componentWidth > parentWidth) { nextX = parentWidth - componentWidth; } // Determine next Y position int nextY = Math.max(component.getLocation().y + deltaY, 0); if ( nextY + componentHeight > parentHeight) { nextY = parentHeight - componentHeight; } // Move the component component.setLocation(nextX, nextY); } private class AnimationAction extends AbstractAction implements ActionListener { private Point moveDelta; public AnimationAction(String keyStroke, Point moveDelta) { super(PRESSED + keyStroke); putValue(ACTION_COMMAND_KEY, keyStroke); this.moveDelta = moveDelta; } public void actionPerformed(ActionEvent e) { handleKeyEvent((String)getValue(ACTION_COMMAND_KEY), moveDelta); } } public static void main(String[] args) { JPanel contentPane = new JPanel(); contentPane.setLayout( null ); Icon dukeIcon = null; try { dukeIcon = new ImageIcon( "dukewavered.gif" ); // dukeIcon = new ImageIcon( ImageIO.read( new URL("http://duke.kenai.com/iconSized/duke4.gif") ) ); } catch(Exception e) { System.out.println(e); } JLabel duke = new JLabel( dukeIcon ); duke.setSize( duke.getPreferredSize() ); duke.setLocation(100, 100); contentPane.add( duke ); KeyboardAnimation navigation = new KeyboardAnimation(duke, 24); navigation.addAction("LEFT", -3, 0); navigation.addAction("RIGHT", 3, 0); navigation.addAction("UP", 0, -3); navigation.addAction("DOWN", 0, 3); navigation.addAction("A", -5, 0); navigation.addAction("S", 5, 0); navigation.addAction("Z", 0, -5); navigation.addAction("X", 0, 5); navigation.addAction("V", 5, 5); JFrame frame = new JFrame(); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); // frame.getContentPane().add(new JTextField(), BorderLayout.SOUTH); frame.getContentPane().add(contentPane); frame.setSize(600, 600); frame.setLocationRelativeTo( null ); frame.setVisible(true); } } 

This code has been tested on Windows, where the order of events is keyPressed, keyPressed, keyPressed ... keyReleased.

However, on Mac (or Unix), I think the order of events will be keyPressed, keyReleased, keyPressed, keyReleased ... So I'm not sure if this code will work better than your current code.

+7
source
  • You should use Key Bindings as they solve most problems related to focus, and tend to be more flexible ...
  • You need to define a flag that indicates when the key is pressed. By clicking, you should not perform any additional tasks ...

For examples ...

Updated with a simple example.

In most games, you should respond to β€œstate changes,” not actual key events. This means that an event that actually changes state can be variable (think user keys)

 import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class SinglePressKeyBinding { public static void main(String[] args) { new SinglePressKeyBinding(); } public SinglePressKeyBinding() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(new TestPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TestPane extends JPanel { private JLabel message; private boolean spacedOut = false; public TestPane() { message = new JLabel("Waiting"); setLayout(new GridBagLayout()); add(message); InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW); ActionMap am = getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "space-pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), "space-released"); am.put("space-pressed", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (spacedOut) { message.setText("I'm ignoring you"); } else { spacedOut = true; message.setText("Spaced out"); } } }); am.put("space-released", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { spacedOut = false; message.setText("Back to earth"); } }); } @Override public Dimension getPreferredSize() { return new Dimension(200, 200); } } } 
+5
source

It’s a good idea to set logical values ​​for the keys that you want to track, and then in an event with a lock, activate one of the Boolean elements, and when you release the key, deactivate it. It will remove the key delay and allow you to press several keys at the same time.

+1
source

Source: https://habr.com/ru/post/944084/


All Articles