Java KeyListener 口吃

Posted

技术标签:

【中文标题】Java KeyListener 口吃【英文标题】:Java KeyListener stutters 【发布时间】:2013-04-26 02:07:12 【问题描述】:

我正在用 java 制作一个非常简单的乒乓球游戏,我正在使用 KeyListener 来做这个。我想要它,所以当用户按下键盘上的左右键时,乒乓球块会朝那个方向移动。这是一个足够简单的任务,但我发现当用户按住键时,块移动一次,停止一小段时间,然后继续移动直到用户释放键。我注意到当您尝试按住计算机上的字母键时会发生这种情况。如果我尝试按住“a”键,计算机会这样做:

a [暂停] aaaaaaaaaaaaaaaa

有什么办法可以禁用这种口吃,因为它妨碍了我的小游戏流畅的游戏玩法。快速修复将不胜感激。

【问题讨论】:

【参考方案1】:
    您应该使用Key Bindings,因为它们解决了大多数与焦点相关的问题,并且通常更灵活... 您需要定义一个标志来指示何时按下某个键。按下时,您不应执行任何其他任务...

例如...

Java object movement Using keypad to move a circle at angles in java Problems with Java's Paint method, ridiculous refresh velocity 显示了一种在按下键时加速对象的机制;)

用简单的例子更新

在大多数游戏中,您应该对“状态变化”而不是实际的关键事件做出反应。这意味着实际改变状态的事件可以是可变的(想想自定义键)

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

    

【讨论】:

我最初有一个关于 Key Bindings 的答案,但经过一些测试后,我发现他们仍然有同样的口吃问题。但是,为其他内容 +1。 @syb0rg 这就是你需要那个小标志的原因;)。最后一个例子展示了一个关于允许按键事件应用加速度增量的巧妙想法,按下它的时间越长,增量增加的越多,直到它被释放并且增量反转,随着时间的推移减慢对象;)跨度> @syb0rg 添加了一个简单的例子来演示原理;)【参考方案2】:

我最初有一个关于 Key Bindings 的答案,但经过一些测试后,我发现他们仍然有同样的口吃问题。

不要依赖操作系统的重复率。每个平台都可能不同,用户也可以对其进行自定义。

改为使用计时器来安排活动。您在 keyPressed 上启动 Timer,在 keyReleased 上停止 Timer。

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


此代码在 Windows 上进行了测试,其中事件顺序为 keyPressed、keyPressed、keyPressed...keyReleased。

但是,我认为在 Mac(或 Unix)上,事件的顺序是 keyPressed、keyReleased、keyPressed、keyReleased...所以我不确定这段代码是否会比您当前的代码更好。

【讨论】:

仅供参考,这句话来自我,但我不是 OP。【参考方案3】:

一个好主意是为要跟踪的键设置布尔值,然后在 keypressed 事件上激活其中一个布尔值,并在释放键时停用它。它将消除按键的滞后并允许多次按键!

【讨论】:

以上是关于Java KeyListener 口吃的主要内容,如果未能解决你的问题,请参考以下文章

不移动鼠标光标时Java动画口吃

java keylistener 未调用

如何在 Java 中正确使用 keyListener

改进我的键盘输入 (KeyListener) Java

Java KeyListener 与键绑定

解释 Java 中的 KeyListener