Java游戏的键盘输入
我正在用Java编写一个游戏,现在是Swing+JOGL——一个带有GLCanvas的JFrame 我使用Java游戏的键盘输入,java,keyboard-events,Java,Keyboard Events,我正在用Java编写一个游戏,现在是Swing+JOGL——一个带有GLCanvas的JFrame 我使用keyPressed等事件(jframe.addKeyListener(…))处理输入,但它似乎无法正常工作: 当我同时按下3个以上的键时,它们没有正确注册-显然这是键盘的错误,我必须找到一个替代控制方案 在窗口丢失后,然后恢复焦点,输入完全停止工作 我做错了什么 在Java中有更好的处理键盘输入的方法吗 (除非我别无选择,否则我不想切换到另一个库,比如LWJGL)。本文中的一些技巧包括
keyPressed
等事件(jframe.addKeyListener(…)
)处理输入,但它似乎无法正常工作:
- 当我同时按下3个以上的键时,它们没有正确注册-显然这是键盘的错误,我必须找到一个替代控制方案
- 在窗口丢失后,然后恢复焦点,输入完全停止工作
(除非我别无选择,否则我不想切换到另一个库,比如LWJGL)。本文中的一些技巧包括使用
KeyboardFocusManager捕捉关键事件,这可能有助于从失去焦点的状态中恢复过来
关于3+键,这将是一个棘手的问题,因为KeyEvent
在其API中考虑了修饰符,但不考虑多个(常规)键。您可能需要自己管理按键状态,因为如果您按下了一个键,您将存储该键并建立当前按下的一组键。但如果你只是在按下3个或更多键时根本没有得到事件,我不确定你能做什么
编辑:此外,库还有一个JOGL目标。了解它如何处理关键事件可能会有所帮助。我知道它至少可以同时处理2个键。为了降低依赖性,我会选择“内置”键盘处理。如果你知道自己在做什么,它就可以正常工作。我将从我的游戏中粘贴一些代码:
它以自定义重复延迟/速率处理按键重复,并且不存在组件键盘焦点所在的问题
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);
}
}
公共类GameKeyController实现KeyEventDispatcher{
专用最终整数最大重复率=100;//Hz
私人决赛;
私人最终游戏语境;
私人最终账户;
定时器按键定时器;
Map repeatingTasks=新的EnumMap(Move.class);
public GameKeyController(本地游戏、GamingContext上下文、,
账户(账户){
这个游戏=游戏;
this.context=上下文;
这个账户=账户;
}
公共布尔dispatchKeyEvent(KeyEvent e){
断言EventQueue.isDispatchThread();
int kc=e.getKeyCode();
如果(e.getID()==KeyEvent.KEY_按下){
//如果激活了repeat(重复),则忽略按键事件。
//实际上不应该发生,因为释放的键*应该*已经
//自上次按下按键后被截获。
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);
//常规动作
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\u HARD\u DROP))move(HARD\u DROP);
if(kc==account.getInt(kc\u SLIDE\u DROP))move(SLIDE\u DROP);
if(kc==account.getInt(kc_FULL_LEFT))move(FULL_LEFT);
if(kc==account.getInt(kc_FULL_RIGHT))move(FULL_RIGHT);
如果(kc==account.getInt(kc_HOLD))移动(HOLD);
如果(kc==account.getInt(kc_SEND_TO_ME))使用特殊(0);
如果(kc==account.getInt(kc_SEND_TO_1))使用特殊(1);
如果(kc==account.getInt(kc_SEND_TO_2))使用特殊(2);
如果(kc==account.getInt(kc_SEND_TO_3))使用特殊(3);
如果(kc==account.getInt(kc_SEND_TO_4))使用特殊(4);
如果(kc==account.getInt(kc_SEND_TO_5))使用特殊(5);
如果(kc==account.getInt(kc_SEND_TO_6))使用特殊(6);
如果(kc==account.getInt(kc_SEND_TO_7))使用特殊(7);
如果(kc==account.getInt(kc_SEND_TO_8))使用特殊(8);
如果(kc==account.getInt(kc_SEND_TO_9))使用特殊(9);
//报告错误:键重复“延迟发布”,即
//释放后继续重复几毫秒。
//以下两行给出了一个“上”近似值
//当有人真的想要释放钥匙时。
如果(kc==account.getInt(kc_MOVE_RIGHT))停止重复(LEFT);
如果(kc==account.getInt(kc_MOVE_LEFT))停止重复(RIGHT);
}
如果(e.getID()==KeyEvent.KEY_已释放){
如果(kc==account.getInt(kc_MOVE_LEFT))停止重复(LEFT);
如果(kc==account.getInt(kc\u MOVE\u RIGHT))停止重复(RIGHT);
如果(kc==account.getInt(kc\u SOFT\u DROP))停止重复(SOFT\u DROP);
}
返回false;
}
私有同步void stopRepeating(移动m){
如果(!isRepeating(m))
返回;
repeatingTasks.get(m.cancel();
重复任务。删除(m);
}
私有同步布尔isRepeating(移动m){
return repeatingTasks.get(m)!=null;
}
私有同步作废移动(移动){
断言EventQueue.isDispatchThread();
context.notIdleSinceStart();
PlayfieldEvent pfe=游戏移动(move);
//假踢墙
如果((移动==ROT_CW |移动==ROT_CCW)&&
account.getBool(使用假墙踢)和&!pfe.pfChanged){
//试着向右旋转,然后向左旋转。
Playfield pf=game.getPlayfield();
if(pf.isfakerotable(true,move==ROT_CW)){
游戏。移动(右);
游戏。移动(移动);
}else if(pf.isfakerotable(false,move==ROT_CW)){
游戏,移动(