Java键盘输入——游戏开发
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22730715/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java keyboard input - game development
提问by dingari
I have a specific "problem" with a game I'm creating for class.
我为课堂创建的游戏有一个特定的“问题”。
The game is an implementation of "Break it". To move the platform at the bottom I just used a key listener. The problem is that after the first key press there is a short "lag" or "stutter" before the platform starts moving. How could I prevent this to get a smooth response? Is there another way than KeyListener? KeyBindings?
该游戏是“Break it”的实现。为了移动底部的平台,我只使用了一个关键的监听器。问题在于,在第一次按键之后,平台开始移动之前会出现短暂的“滞后”或“停顿”。我怎样才能防止这种情况得到平滑的响应?除了 KeyListener 之外还有其他方法吗?键绑定?
Here is the key listener implementation
这是关键的监听器实现
private class KeyControl implements KeyListener {
private int dx = 20;
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
if(dx < 0 )
dx = -dx;
gamePanel.movePlatform(dx);
}
if(e.getKeyCode() == KeyEvent.VK_LEFT) {
if(dx > 0 )
dx = -dx;
gamePanel.movePlatform(dx);
}
if(e.getKeyCode() == KeyEvent.VK_SPACE) {
System.out.println("space");
gamePanel.play();
}
if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
gamePanel.pause();
}
}
}
and here is the method that moves the platform around
这是移动平台的方法
public void movePlatform(int dx) {
int nextDX = dx;
if(paused || init) {
dx = 0;
}
// make sure platform doesnt exceed right border
if(platform.getX() + platform.getWidth() + dx> size.getWidth()) {
if(nextDX < 0)
dx = nextDX;
else
dx = 0;
}
// make sure platform doesnt exceed left border
if(platform.getX() + dx <= 0) {
if(nextDX > 0)
dx = nextDX;
else
dx = 0;
}
platform.setFrame(platform.getX() + dx, platform.getY(), platform.getWidth(), platform.getHeight());
platformIntervalX = new Interval((int)platform.getX(), (int)(platform.getX() + platform.getWidth()));
platformIntervalY = new Interval((int)(platform.getY() - platform.getHeight()), (int)platform.getY());
repaint();
}
回答by Hovercraft Full Of Eels
The solution is not to use the KeyListener's key press for moving your sprite. The key is not to rely on the hardware-specific key press frequency, to use a Swing Timer to create your own frequency. Instead use Key Bindings and a Swing Timer. Start the time on key press and stop it on key release.
解决方案不是使用 KeyListener 的按键来移动您的精灵。关键是不要依赖特定于硬件的按键频率,而是使用 Swing Timer 来创建您自己的频率。而是使用键绑定和摆动计时器。按下按键开始计时,松开按键停止计时。
For example, run this code and press and release the up-arrow key:
例如,运行此代码并按下并释放向上箭头键:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.*;
@SuppressWarnings("serial")
public class KeyBindingEg extends JPanel {
private static final String UP_KEY_PRESSED = "up key pressed";
private static final String UP_KEY_RELEASED = "up key released";
private static final int UP_TIMER_DELAY = 50;
private static final Color FLASH_COLOR = Color.red;
private Timer upTimer;
private JLabel label = new JLabel();
public KeyBindingEg() {
label.setFont(label.getFont().deriveFont(Font.BOLD, 32));
label.setOpaque(true);
add(label);
setPreferredSize(new Dimension(400, 300));
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
KeyStroke upKeyPressed = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false);
KeyStroke upKeyReleased = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true);
inputMap.put(upKeyPressed, UP_KEY_PRESSED);
inputMap.put(upKeyReleased, UP_KEY_RELEASED);
actionMap.put(UP_KEY_PRESSED, new UpAction(false));
actionMap.put(UP_KEY_RELEASED, new UpAction(true));
}
private class UpAction extends AbstractAction {
private boolean onKeyRelease;
public UpAction(boolean onKeyRelease) {
this.onKeyRelease = onKeyRelease;
}
@Override
public void actionPerformed(ActionEvent evt) {
if (!onKeyRelease) {
if (upTimer != null && upTimer.isRunning()) {
return;
}
System.out.println("key pressed");
label.setText(UP_KEY_PRESSED);
upTimer = new Timer(UP_TIMER_DELAY, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Color c = label.getBackground();
if (FLASH_COLOR.equals(c)) {
label.setBackground(null);
label.setForeground(Color.black);
} else {
label.setBackground(FLASH_COLOR);
label.setForeground(Color.white);
}
}
});
upTimer.start();
} else {
System.out.println("Key released");
if (upTimer != null && upTimer.isRunning()) {
upTimer.stop();
upTimer = null;
}
label.setText("");
}
}
}
private static void createAndShowGui() {
KeyBindingEg mainPanel = new KeyBindingEg();
JFrame frame = new JFrame("KeyBindingEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Edit
编辑
Or a better example, one that moves a sprite in any direction based on a key press of one of the arrow keys. No delay encountered:
或者一个更好的例子,一个基于箭头键之一的按键向任何方向移动精灵的例子。没有遇到延迟:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.*;
@SuppressWarnings("serial")
public class KeyBindingEg2 extends JPanel {
enum Dir {
LEFT("Left", KeyEvent.VK_LEFT, -1, 0),
RIGHT("Right", KeyEvent.VK_RIGHT, 1, 0),
UP("Up", KeyEvent.VK_UP, 0, -1),
DOWN("Down", KeyEvent.VK_DOWN, 0, 1);
private String name;
private int keyCode;
private int deltaX;
private int deltaY;
private Dir(String name, int keyCode, int deltaX, int deltaY) {
this.name = name;
this.keyCode = keyCode;
this.deltaX = deltaX;
this.deltaY = deltaY;
}
public String getName() {
return name;
}
public int getKeyCode() {
return keyCode;
}
public int getDeltaX() {
return deltaX;
}
public int getDeltaY() {
return deltaY;
}
}
public static final int TIMER_DELAY = 10;
public static final int DELTA_X = 2;
public static final int DELTA_Y = DELTA_X;
public static final int SPRITE_WIDTH = 10;
public static final int SPRITE_HEIGHT = SPRITE_WIDTH;
private static final String PRESSED = "pressed";
private static final String RELEASED = "released";
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private Map<Dir, Boolean> dirMap = new EnumMap<>(Dir.class);
private int spriteX = 0;
private int spriteY = 0;
private BufferedImage sprite;
private Timer animationTimer = new Timer(TIMER_DELAY, new AnimationListener());
public KeyBindingEg2() {
for (Dir dir : Dir.values()) {
dirMap.put(dir, Boolean.FALSE);
}
sprite = createSprite();
setKeyBindings();
animationTimer.start();
}
private BufferedImage createSprite() {
BufferedImage sprt = new BufferedImage(SPRITE_WIDTH, SPRITE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics g = sprt.getGraphics();
g.setColor(Color.RED);
g.fillRect(0, 0, SPRITE_WIDTH, SPRITE_HEIGHT);
g.dispose();
return sprt;
}
@Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (sprite != null) {
g.drawImage(sprite, spriteX, spriteY, this);
}
}
private void setKeyBindings() {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
for (Dir dir : Dir.values()) {
KeyStroke keyPressed = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, false);
KeyStroke keyReleased = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, true);
inputMap.put(keyPressed, dir.toString() + PRESSED);
inputMap.put(keyReleased, dir.toString() + RELEASED);
actionMap.put(dir.toString() + PRESSED, new DirAction(dir, PRESSED));
actionMap.put(dir.toString() + RELEASED, new DirAction(dir, RELEASED));
}
}
private class AnimationListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int newX = spriteX;
int newY = spriteY;
for (Dir dir : Dir.values()) {
if (dirMap.get(dir)) {
newX += dir.getDeltaX() * DELTA_X;
newY += dir.getDeltaY() * DELTA_Y;
}
}
if (newX < 0 || newY < 0) {
return;
}
if (newX + SPRITE_WIDTH > getWidth() || newY + SPRITE_HEIGHT > getHeight()) {
return;
}
spriteX = newX;
spriteY = newY;
repaint();
}
}
private class DirAction extends AbstractAction {
private String pressedOrReleased;
private Dir dir;
public DirAction(Dir dir, String pressedOrReleased) {
this.dir = dir;
this.pressedOrReleased = pressedOrReleased;
}
@Override
public void actionPerformed(ActionEvent evt) {
if (pressedOrReleased.equals(PRESSED)) {
dirMap.put(dir, Boolean.TRUE);
} else if (pressedOrReleased.equals(RELEASED)) {
dirMap.put(dir, Boolean.FALSE);
}
}
}
private static void createAndShowGui() {
KeyBindingEg2 mainPanel = new KeyBindingEg2();
JFrame frame = new JFrame("KeyBindingEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}