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

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

a [暂停] aaaaaaaaaaaaaaaa

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

【问题讨论】:

    标签: java swing keylistener lag keypad


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

    例如...

    用简单的例子更新

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

    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 事件上激活其中一个布尔值,并在释放键时停用它。它将消除按键的滞后并允许多次按键!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-16
      • 2012-08-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多