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



我的问题是:当我在两个键之间快速切换时,它会在两者之间阻挡半秒(例如,用两个键,一个左键和一个右键来移动游戏角色,首先我点击左键,然后我想快速切换到右键,即使我切换的时间很短,它也会让我的游戏角色停止约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上:此处

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

错误在于您的逻辑。如果你按下一个按钮,然后按下另一个按钮(你有两个活动键),然后释放一个,你会进入"无"状态,然后你必须等待初始按下状态,然后重复状态才会进入

例如。。。

  • (MOVE_LEFT)
  • (MOVE_RIGHT)
  • 释放(MOVE_NONE)
  • 初始按下Right和在没有发生任何事情的情况下发生重复事件之间的延迟

一个更好的想法是设置一个状态,其中"无"是指没有"活动"状态,而不是状态。

为此,我使用Directionenum。。。

public static enum Direction {
LEFT(-1), RIGHT(1);
private int delta;
private Direction(int delta) {
this.delta = delta;
}
public int getDelta() {
return delta;
}
}

我已经预先播种了delta,但您不必这样做,但它确实展示了一个可能很简单的实现

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

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

当按下某个键时,会添加相应的enum,当松开时,会将其删除。

这是通过"新闻"one_answers"发布"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中,就应该应用其delta。

这样做的一个很好的副作用是,如果你同时按下左键和右键,增量会相互抵消

可运行的示例

所以,我去掉了你的"例子",变成了一个可运行的例子,展示了基本原理。

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

最新更新