Tartamudeo KeyListener Java

Estoy haciendo un juego de pong muy simple en Java y estoy haciendo esto usando KeyListener. Lo quiero así que cuando el usuario presiona las teclas derecha o izquierda en el teclado, el bloque pong va en esa dirección. Esta es una tarea bastante simple, pero lo que estoy descubriendo es que cuando el usuario mantiene presionada la tecla, el bloque se mueve una vez, se detiene por un corto tiempo y luego continúa moviéndose hasta que el usuario suelta la tecla. Noto que esto sucede cuando intentas mantener presionada una tecla de letra en una computadora. Si trato de mantener presionada la tecla ‘a’, la computadora hará:

a [pausa] aaaaaaaaaaaaaaaa

¿Existe algún modo de desactivar este tartamudeo, porque se está metiendo en el camino de una jugabilidad fluida para mi pequeño juego? Una solución rápida sería muy apreciada.

Originalmente tenía una respuesta sobre Key Bindings, pero después de un poco de prueba descubrí que todavía tenían el mismo problema de tartamudeo.

No confíe en la tasa de repetición del sistema operativo. Puede ser diferente para cada plataforma y un usuario también puede personalizarlo.

En su lugar, use un temporizador para progtwigr el evento. Inicia el temporizador en una tecla presionado y detiene el temporizador en la tecla liberada.

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 pressedKeys = new HashMap(); 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://sofes.miximages.com/java/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); } } 

Este código fue probado en Windows donde el orden de los eventos es keyPressed, keyPressed, keyPressed … keyReleased.

Sin embargo, creo que en una Mac (o Unix) el orden de los eventos es keyPressed, keyReleased, keyPressed, keyReleased … Así que no estoy seguro de si este código funcionaría mejor que tu código actual.

  1. Debería usar enlaces clave ya que resuelven la mayoría de los problemas relacionados con el enfoque y generalmente son más flexibles …
  2. Necesita definir un indicador para indicar cuándo se presiona una tecla. Mientras está presionado, no debe realizar ninguna tarea adicional …

Por ejemplo…

  • Movimiento de objetos Java
  • Usando el teclado para mover un círculo en angularjs en java
  • Problemas con el método de pintura de Java, velocidad de actualización ridícula que muestra un mecanismo para acelerar un objeto mientras se presiona una tecla;)

Actualizado con un simple ejemplo

En la mayoría de los juegos, debes reactjsr a los “cambios de estado” en lugar de a los eventos clave reales. Esto significa que el evento que realmente cambia el estado puede ser variable (piense en claves personalizadas)

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

Una buena idea es establecer valores booleanos para las teclas que desea rastrear, y luego, en el evento presionado por tecla, activar uno de los booleanos, y en la tecla liberada, desactivarlo. ¡Eliminará el retraso de las teclas y permitirá múltiples pulsaciones de teclas también!