在两个键之间快速交替时如何避免键绑定延迟

时间:2019-01-06 00:02:02

标签: java swing input keyboard-events keyevent

这是我的问题:当我在两个键之间快速切换时,它们之间的间隔为半秒(例如,用于移动游戏角色的两个键,一个左键和一个右键),首先我单击左键,那么我想快速切换到右键,即使我花了很短的时间,它仍然会使我的游戏角色停止约0.5秒,直到它向右移动为止。 short video

中的示例

实际上,我找不到任何方法可以正确使用游戏中的键盘输入来阻止该怪异的问题。 经过数小时的研究,我终于找到了一个与之相关的主题:here,正如作者似乎所说的那样,在我必须对项目进行所有更改之后,无法知道它是否会起作用以便实现该JInput库。

以下是使用键盘输入的代码部分:

class MainClient {
private static String clientName;
private static GameClient gameClient;
private static List<Object> allSolidObjects = new ArrayList<>();
private static boolean stopRight = false, stopLeft = false;
private static GameFrame gameFrame;
private static Character character;
private static CharacterView characterView;
private static final String MOVE_LEFT = "move left", MOVE_RIGHT = "move right", MOVE_STOP = "move stop";
private static final int FPS = 60;

public static void main(String[] args) {    
        SwingUtilities.invokeLater(() -> {
            character = new Character();
            characterView = new CharacterView(
                    character.getRelativeX(),
                    character.getRelativeY(),
                    Character.getRelativeWidth(),
                    Character.getRelativeHeight());

            gameFrame.getGamePanel().setCharacterView(characterView);

            final InputMap IM = gameFrame.getGamePanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            final ActionMap AM = gameFrame.getGamePanel().getActionMap();
            MovementState movementState = new MovementState();

            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0, true), MOVE_STOP);
            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), MOVE_STOP);
            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), MOVE_STOP);
            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), MOVE_STOP);
            AM.put(MOVE_STOP, new MoveXAction(movementState, 0f));

            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), MOVE_RIGHT);
            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), MOVE_RIGHT);
            AM.put(MOVE_RIGHT, new MoveXAction(movementState, Character.getRelativeSpeed()));

            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0, false), MOVE_LEFT);
            IM.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), MOVE_LEFT);
            AM.put(MOVE_LEFT, new MoveXAction(movementState, -Character.getRelativeSpeed()));
            Timer timer = new Timer(1000/FPS, e -> {
                if (movementState.xDirection < 0) {
                    stopRight = false;
                    if (!stopLeft) {
                        character.setRelativeX(character.getRelativeX() + movementState.xDirection);
                        for (Object object : allSolidObjects) {
                            if (CollisionDetection.isCollisionBetween(character, object)) {
                                stopLeft = true;
                            }
                        }
                    }
                } else if (movementState.xDirection > 0) {
                    stopLeft = false;
                    if (!stopRight) {
                        character.setRelativeX(character.getRelativeX() + movementState.xDirection);
                        for (Object object : allSolidObjects) {
                            if (CollisionDetection.isCollisionBetween(character, object)) {
                                stopRight = true;
                            }
                        }
                    }
                }

                characterView.setRelativeX(character.getRelativeX());
                characterView.setRelativeY(character.getRelativeY());
                gameFrame.getGamePanel().setCharacterView(characterView);

                gameFrame.getGamePanel().repaint();
            });
            timer.start();

        });
    }
}

// Not important method
private static void launchGameClient() {}

static class MoveXAction extends AbstractAction {
    private final MovementState movementState;
    private final float value;

    MoveXAction(MovementState movementState, float value) {
        this.movementState = movementState;
        this.value = value;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        this.movementState.xDirection = this.value;
    }
}

static class MovementState {
    float xDirection;
}
}

如果您想自己进行测试以查看问题,则整个项目位于GitHub上: here

那么,有没有人知道如何解决该问题,也许就是那样,一个无法解决的操作系统问题,但是即使是这样,也请留下答案并告诉它^^。

1 个答案:

答案 0 :(得分:0)

错误是您的逻辑。如果您按下一个按钮,然后按下另一个按钮(您有两个活动键),然后松开一个按钮,最后得到一个“无”状态,那么您必须等待初始按下状态,然后再进入重复状态

例如,...

  • MOVE_LEFT
  • MOVE_RIGHT
  • 释放MOVE_NONE
  • 初次按下和重复事件之间发生的延迟,什么也没发生

一个更好的主意是设置一个状态,其中“无”是指不存在“活动”状态而不是状态。

为此,我使用Direction enum ...

public static enum Direction {
    LEFT(-1), RIGHT(1);

    private int delta;

    private Direction(int delta) {
        this.delta = delta;
    }

    public int getDelta() {
        return delta;
    }

}

我已经预先植入了增量,但您没有“必须”执行此操作,但是它确实演示了一个可能简单的实现方式

然后我使用Set来管理状态...

private Set<Direction> movement = new TreeSet<>();

按下某个键时,将添加相应的enum,并在释放时将其删除。

这是通过“按下”和“释放” Action ...

完成的
static public class PressAction extends AbstractAction {

    private final Set<Direction> movement;
    private final Direction value;

    public PressAction(Set<Direction> movementState, Direction value) {
        this.movement = movementState;
        this.value = value;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        movement.add(value);
    }
}
static public class ReleaseAction extends AbstractAction {

    private final Set<Direction> movement;
    private final Direction value;

    public ReleaseAction(Set<Direction> movementState, Direction value) {
        this.movement = movementState;
        this.value = value;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        movement.remove(value);
    }
}

这意味着,只要enum中存在Set,就应应用其增量。

一个不错的副作用是,如果同时按住向左和向右键,则增量会相互抵消

可运行的示例...

因此,我剥离了“示例”,并变成了一个可运行的示例,该示例演示了基本原理。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static enum Direction {
        LEFT(-1), RIGHT(1);

        private int delta;

        private Direction(int delta) {
            this.delta = delta;
        }

        public int getDelta() {
            return delta;
        }

    }

    public static class TestPane extends JPanel {

        private int xPos = 95;

        private Set<Direction> movement = new TreeSet<>();

        public TestPane() {
            final InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            final ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0, true), "Release.left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "Release.left");

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Release.right");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "Release.right");

            am.put("Release.left", new ReleaseAction(movement, Direction.LEFT));
            am.put("Release.right", new ReleaseAction(movement, Direction.RIGHT));

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Press.right");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "Press.right");
            am.put("Press.right", new PressAction(movement, Direction.RIGHT));

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0, false), "Press.left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "Press.left");
            am.put("Press.left", new PressAction(movement, Direction.LEFT));

            Timer timer = new Timer(5, e -> {
                for (Direction dir :  movement) {
                    xPos += dir.getDelta();
                }
                repaint();
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            g2d.fillRect(xPos, 95, 10, 10);
            g2d.dispose();
        }

    }

    private static final Instant ANCHOR = Instant.now();
    private static Instant switchAd = Instant.now();

    protected static long switchDelay() {
        return Duration.between(switchAd, Instant.now()).toMillis();
    }

    protected static long tick() {
        return Duration.between(ANCHOR, Instant.now()).toMillis();
    }

    static public class PressAction extends AbstractAction {

        private final Set<Direction> movement;
        private final Direction value;

        public PressAction(Set<Direction> movementState, Direction value) {
            this.movement = movementState;
            this.value = value;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            movement.add(value);
        }
    }
    static public class ReleaseAction extends AbstractAction {

        private final Set<Direction> movement;
        private final Direction value;

        public ReleaseAction(Set<Direction> movementState, Direction value) {
            this.movement = movementState;
            this.value = value;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            movement.remove(value);
        }
    }

}