Java开发Pong游戏:使用KeyListener实现双Paddle同时移动的问题
不用多线程!这其实是Java Swing里处理键盘输入的常见误区,KeyListener的机制本身就不适合处理多按键同时输入的场景,引入多线程反而会带来UI线程同步的麻烦,完全没必要。
问题根源
KeyListener的keyPressed事件是每次按键按下(或重复)时触发一次,当你同时按住多个键时,系统会发送多个事件,但你的代码每次只能处理一个键的移动逻辑,而且没法在同一帧里同时更新两个 paddle 的位置——因为repaint()是在单个按键事件结束后才调用的,所以看起来两个 paddle 无法同时动。
正确的做法是跟踪按键的状态,然后用一个游戏循环来统一更新所有物体的位置,再刷新画面。下面是具体的修改方案:
步骤1:添加按键状态跟踪数组
在PaintPanel类中定义一个布尔数组,用来记录每个按键是否被按下:
private boolean[] keys = new boolean[256]; // 覆盖大部分常用按键的VK码
步骤2:简化KeyListener实现
不再在keyPressed里直接移动 paddle,只负责更新按键状态:
@Override public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key >= 0 && key < keys.length) { keys[key] = true; } } @Override public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key >= 0 && key < keys.length) { keys[key] = false; } }
步骤3:添加游戏循环(用Swing Timer)
Swing Timer是专门为EDT(事件调度线程)设计的定时任务,适合做简单的游戏循环。在PaintPanel的构造函数里初始化Timer:
PaintPanel() { setBackground(Color.BLACK); addKeyListener(this); // 游戏循环:每30毫秒更新一次(约33FPS,可根据需求调整) Timer gameTimer = new Timer(30, e -> { updateGameState(); repaint(); }); gameTimer.start(); }
步骤4:实现统一的状态更新方法
把原来的移动逻辑移到updateGameState()里,根据按键状态同时处理两个 paddle 的移动:
private void updateGameState() { // 处理第一个Paddle(W/S控制) if (keys[KeyEvent.VK_W] && RACKET_ONE_Y > LOWEST_RACKET_POS) { RACKET_ONE_Y -= SPEED; } if (keys[KeyEvent.VK_S] && RACKET_ONE_Y < HIGHEST_RACKET_POS) { RACKET_ONE_Y += SPEED; } // 处理第二个Paddle(O/L控制) if (keys[KeyEvent.VK_O] && RACKET_TWO_Y > LOWEST_RACKET_POS) { RACKET_TWO_Y -= SPEED; } if (keys[KeyEvent.VK_L] && RACKET_TWO_Y < HIGHEST_RACKET_POS) { RACKET_TWO_Y += SPEED; } }
完整修改后的PaintPanel代码
import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; class PaintPanel extends JPanel implements KeyListener { // holds the panel's insets Insets ins; private int RACKET_ONE_Y = 360; private final int RACKET_ONE_X = 160; private int RACKET_TWO_Y = 360; private final int RACKET_TWO_X = 1025; private final int SPEED = 5; // 调整为更合理的速度,原50过快 private final int LOWEST_RACKET_POS = 60; private final int HIGHEST_RACKET_POS = 600; // 按键状态跟踪数组 private boolean[] keys = new boolean[256]; // Construct a panel. PaintPanel() { setBackground(Color.BLACK); addKeyListener(this); // 初始化游戏循环 Timer gameTimer = new Timer(30, e -> { updateGameState(); repaint(); }); gameTimer.start(); } // Override the paintComponent() method. protected void paintComponent(Graphics g) { // Always call the superclass method first. super.paintComponent(g); drawBorders(g); drawMiddleLine(g); drawRacketOne(g); drawRacketTwo(g); } // drawing rackets private void drawRacketOne(Graphics g){ g.setColor(Color.RED); g.fillRect(RACKET_ONE_X, RACKET_ONE_Y, 15, 90); } private void drawRacketTwo(Graphics g){ g.setColor(Color.RED); g.fillRect(RACKET_TWO_X, RACKET_TWO_Y, 15, 90); } // game frame borders private void drawBorders(Graphics g){ g.setColor(Color.WHITE); g.fillRect(100, 50, 1000, 10); g.fillRect(100, 50, 10, 660); g.fillRect(100, 700, 1000, 10); g.fillRect(1090, 50, 10, 660); } // middle line private void drawMiddleLine(Graphics g){ g.setColor(Color.WHITE); for (int i = 70; i < 672; i += 40) { g.fillRect(590, i, 10, 25); } } // 统一更新游戏状态 private void updateGameState() { // 第一个Paddle移动逻辑 if (keys[KeyEvent.VK_W] && RACKET_ONE_Y > LOWEST_RACKET_POS) { RACKET_ONE_Y -= SPEED; } if (keys[KeyEvent.VK_S] && RACKET_ONE_Y < HIGHEST_RACKET_POS) { RACKET_ONE_Y += SPEED; } // 第二个Paddle移动逻辑 if (keys[KeyEvent.VK_O] && RACKET_TWO_Y > LOWEST_RACKET_POS) { RACKET_TWO_Y -= SPEED; } if (keys[KeyEvent.VK_L] && RACKET_TWO_Y < HIGHEST_RACKET_POS) { RACKET_TWO_Y += SPEED; } } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key >= 0 && key < keys.length) { keys[key] = true; } } @Override public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key >= 0 && key < keys.length) { keys[key] = false; } } }
补充说明
- 我把
SPEED从50调整为5,原速度过快几乎无法控制,你可以根据手感自行修改。 - Swing Timer是线程安全的,它的任务在EDT中执行,符合Swing的单线程规则,不会出现UI同步问题。
- 这种按键状态跟踪的方式是2D游戏处理多按键输入的标准方案,比直接用KeyListener处理事件更灵活,还能避免按键重复触发的延迟问题。
内容的提问来源于stack exchange,提问作者pilotUSA
相关产品推荐
相关产品推荐

