Java 游戏的键盘输入
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2702203/
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
Keyboard input for a game in Java
提问by hmp
I'm writing a game in Java, right now it's Swing + JOGL - a JFrame with a GLCanvas.
我正在用 Java 编写游戏,现在它是 Swing + JOGL - 带有 GLCanvas 的 JFrame。
I handle input using keyPressed
etc. events (jframe.addKeyListener(...)
) and it doesn't seem to work properly:
我使用keyPressed
etc. events ( jframe.addKeyListener(...)
)处理输入,但它似乎无法正常工作:
when I have 3+ keys down at the same time, they don't register properly- apparently this is keyboard's fault, I have to find an alternate control scheme.- after the window loses, then regains focus, input stops working completely...
当我同时按下 3 个以上的键时,它们无法正确注册- 显然这是键盘的错,我必须找到替代控制方案。- 窗口丢失后,然后重新获得焦点,输入完全停止工作......
What am I doing wrong?
我究竟做错了什么?
Is there a better way of handling keyboard input in Java?
有没有更好的方法来处理 Java 中的键盘输入?
(I'd rather not switch to another library, like LWJGL... unless I have no choice).
(我宁愿不切换到另一个库,比如 LWJGL ......除非我别无选择)。
采纳答案by aioobe
To keep dependencies down, I would go for the "built-in" keyboard handling. It works just fine if you know what your doing. I'll paste some code from my games:
为了降低依赖性,我会选择“内置”键盘处理。如果您知道自己在做什么,它就可以正常工作。我将从我的游戏中粘贴一些代码:
It handles key-repeats with custom repeat-delay / rate and has no issues with in which component keyboard focus lies.
它使用自定义重复延迟/速率处理按键重复,并且在组件键盘焦点所在的位置没有问题。
public class GameKeyController implements KeyEventDispatcher {
private final int MAX_REPEAT_RATE = 100; // Hz
private final LocalGame game;
private final GamingContext context;
private final Account account;
Timer keyRepeatTimer;
Map<Move, TimerTask> repeatingTasks = new EnumMap<Move, TimerTask>(Move.class);
public GameKeyController(LocalGame game, GamingContext context,
Account account) {
this.game = game;
this.context = context;
this.account = account;
}
public boolean dispatchKeyEvent(KeyEvent e) {
assert EventQueue.isDispatchThread();
int kc = e.getKeyCode();
if (e.getID() == KeyEvent.KEY_PRESSED) {
// If repeat is activated, ignore KEY_PRESSED events.
// Should actually not occur, since KEY_RELEASED *should* have been
// intercepted since last KEY_PRESSED.
if (kc == account.getInt(KC_MOVE_LEFT) && !isRepeating(LEFT)) move(LEFT);
if (kc == account.getInt(KC_MOVE_RIGHT) && !isRepeating(RIGHT)) move(RIGHT);
if (kc == account.getInt(KC_SOFT_DROP) && !isRepeating(SOFT_DROP)) move(SOFT_DROP);
// Regular moves
if (kc == account.getInt(KC_ROT_CW)) move(ROT_CW);
if (kc == account.getInt(KC_ROT_CW2)) move(ROT_CW);
if (kc == account.getInt(KC_ROT_CCW)) move(ROT_CCW);
if (kc == account.getInt(KC_ROT_CCW2)) move(ROT_CCW);
if (kc == account.getInt(KC_HARD_DROP)) move(HARD_DROP);
if (kc == account.getInt(KC_SLIDE_DROP)) move(SLIDE_DROP);
if (kc == account.getInt(KC_FULL_LEFT)) move(FULL_LEFT);
if (kc == account.getInt(KC_FULL_RIGHT)) move(FULL_RIGHT);
if (kc == account.getInt(KC_HOLD)) move(HOLD);
if (kc == account.getInt(KC_SEND_TO_ME)) useSpecial(0);
if (kc == account.getInt(KC_SEND_TO_1)) useSpecial(1);
if (kc == account.getInt(KC_SEND_TO_2)) useSpecial(2);
if (kc == account.getInt(KC_SEND_TO_3)) useSpecial(3);
if (kc == account.getInt(KC_SEND_TO_4)) useSpecial(4);
if (kc == account.getInt(KC_SEND_TO_5)) useSpecial(5);
if (kc == account.getInt(KC_SEND_TO_6)) useSpecial(6);
if (kc == account.getInt(KC_SEND_TO_7)) useSpecial(7);
if (kc == account.getInt(KC_SEND_TO_8)) useSpecial(8);
if (kc == account.getInt(KC_SEND_TO_9)) useSpecial(9);
// Reported bug: Key repeat "lags on releases", that is, the key
// continues to repeat a few ms after it has been released.
// The following two lines gives one "upper" approximation of
// when someone really wants to release the key.
if (kc == account.getInt(KC_MOVE_RIGHT)) stopRepeating(LEFT);
if (kc == account.getInt(KC_MOVE_LEFT)) stopRepeating(RIGHT);
}
if (e.getID() == KeyEvent.KEY_RELEASED) {
if (kc == account.getInt(KC_MOVE_LEFT)) stopRepeating(LEFT);
if (kc == account.getInt(KC_MOVE_RIGHT)) stopRepeating(RIGHT);
if (kc == account.getInt(KC_SOFT_DROP)) stopRepeating(SOFT_DROP);
}
return false;
}
private synchronized void stopRepeating(Move m) {
if (!isRepeating(m))
return;
repeatingTasks.get(m).cancel();
repeatingTasks.remove(m);
}
private synchronized boolean isRepeating(Move m) {
return repeatingTasks.get(m) != null;
}
private synchronized void move(Move move) {
assert EventQueue.isDispatchThread();
context.notIdleSinceStart();
PlayfieldEvent pfe = game.move(move);
// Fake wall kicks
if ((move == ROT_CW || move == ROT_CCW) &&
account.getBool(USE_FAKE_WALL_KICKS) && !pfe.pfChanged) {
// Try RIGHT and ROT, then LEFT and ROT.
Playfield pf = game.getPlayfield();
if (pf.isFakeRotPossible(true, move == ROT_CW)) {
game.move(RIGHT);
game.move(move);
} else if (pf.isFakeRotPossible(false, move == ROT_CW)) {
game.move(LEFT);
game.move(move);
}
}
// Initiate key repeats
int delay = account.getInt(KEY_REPEAT_DELAY);
int rate = account.getInt(KEY_REPEAT_RATE);
if (delay > 0 && rate > 0 && isRepeatable(move))
startRepeating(move);
}
private boolean isRepeatable(Move m) {
return m == LEFT || m == RIGHT || m == SOFT_DROP;
}
private synchronized void startRepeating(Move move) {
assert EventQueue.isDispatchThread();
if (isRepeating(move))
return;
long delay = account.getInt(KEY_REPEAT_DELAY);
int rate = account.getInt(KEY_REPEAT_RATE);
Move repeatMove = move;
if (rate >= MAX_REPEAT_RATE) {
rate = MAX_REPEAT_RATE;
repeatMove = move == LEFT ? FULL_LEFT
: move == RIGHT ? FULL_RIGHT
: move == SOFT_DROP ? SLIDE_DROP
: null; // not a repeatable move!
}
long period = (long) (1000.0 / rate);
if (move == SOFT_DROP)
delay = period;
final Move m = repeatMove;
TimerTask tt = new TimerTask() {
// Should only be executed by keyRepeatTimer thread.
public void run() {
// Remove the if-branch below and you get old school GB behavior
// With the if-branch it's more TDS-ish.
// TODO: Make this depend on an account-setting
if (m == SOFT_DROP && game.getPlayfield().isTetOnSurface()) {
stopRepeating(SOFT_DROP);
return;
}
game.move(m);
// Attempt to make it more responsive to key-releases.
// Even if there are multiple this-tasks piled up (due to
// "scheduleAtFixedRate") we don't want this thread to take
// precedence over AWT thread.
Thread.yield();
}
};
repeatingTasks.put(move, tt);
keyRepeatTimer.scheduleAtFixedRate(tt, delay, period);
}
public synchronized void init() {
if (!isInited()) {
keyRepeatTimer = new Timer("Key Repeat Timer");
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
}
}
public synchronized boolean isInited() {
return keyRepeatTimer != null;
}
public synchronized void uninit() {
if (isInited()) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this);
keyRepeatTimer.cancel();
keyRepeatTimer = null;
}
}
private void useSpecial(int target) {
context.notIdleSinceStart();
context.useSpecial(target);
}
}
回答by Ash
Some of the tips in this article on global event listenersinclude catching key events with the KeyboardFocusManager
, and might help with coming back from losing focus.
本文中有关全局事件侦听器的一些技巧包括使用 捕获关键事件KeyboardFocusManager
,并且可能有助于从失去焦点中恢复过来。
Regarding the 3+ keys, that's going to be tricky since the KeyEvent
accounts for modifiers but not for multiple (regular) keys in its API. You might have to manage the press state yourself, in that if you get a KEY_PRESSED you store that key and build up the set of keys that are currently pressed. But if you simply don't get events at all when 3 or more keys are pressed, I'm not sure there's much you can do.
关于 3+ 键,这将是棘手的,因为KeyEvent
它的 API 中包含修饰符而不是多个(常规)键。您可能必须自己管理按下状态,因为如果您获得 KEY_PRESSED,您将存储该键并建立当前按下的一组键。但是,如果您在按下 3 个或更多键时根本没有收到事件,我不确定您能做多少。
Edit: Also, the JGamelibrary has a JOGL target. Looking at how it handles key events might help. I know it can handle at least 2 keys simultaneously.
编辑:此外,JGame库有一个 JOGL 目标。查看它如何处理关键事件可能会有所帮助。我知道它可以同时处理至少 2 个键。