Как избежать задержки привязки клавиш при быстром переключении между двумя ключами - PullRequest
0 голосов
/ 06 января 2019

Вот моя проблема: когда я быстро переключаюсь между двумя клавишами, он блокирует полсекунды между ними (например, с двумя клавишами, левой и правой клавишами, которые используются для перемещения игрового персонажа, во-первых, я нажимаю на левая клавиша, затем я хочу быстро переключиться на правую клавишу, и даже если время, которое я потратил на переключение, довольно мало, оно в любом случае остановит моего игрового персонажа на ~ 0,5 с, пока он не сдвинется вправо). Пример в этом коротком видео

На самом деле я не могу найти способ правильно использовать клавиатурные входы в игре, чтобы остановить эту странную проблему. Самой вещью, которую я наконец нашел после нескольких часов исследований, является эта тема: здесь , и, как кажется, говорит автор, нет никакого способа узнать, сработает ли он после всех изменений в проекте, которые я бы сделал нужно сделать, чтобы реализовать эту библиотеку 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: здесь

Итак, есть ли кто-нибудь, кто знает, как решить эту проблему, может быть, это просто проблема с ОС, которая не может быть решена, но даже если это так, пожалуйста, оставьте ответ и сообщите его ^^.

1 Ответ

0 голосов
/ 06 января 2019

Ошибка в вашей логике. Если вы нажмете одну кнопку, затем другую (у вас есть две активные клавиши), затем отпустите одну, вы получите состояние «нет», тогда вам придется подождать, пока начальное состояние нажатия не перейдет в состояние повторения

Так, например ...

  • Нажмите Влево (MOVE_LEFT)
  • Нажмите Справа (MOVE_RIGHT)
  • Выпуск Слева (MOVE_NONE)
  • Задержка между начальным нажатием вправо и повторением события, когда ничего не происходит

Лучшей идеей является установка состояния, при котором "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);
        }
    }

}
...