Java Swing Pong游戏:无法同时移动两个Raqueta问题求助
Java Swing Pong双球拍同时移动问题
我用Java Swing开发Pong游戏,需求是两名玩家通过同一键盘的W-S键和上下方向键分别控制左右两个球拍移动,但存在无法同时移动两个球拍的问题:按下W键移动左侧球拍时,必须松开W键才能操作右侧球拍,反之亦然。已将两个球拍放入不同线程,但问题依旧,推测是KeyListener导致的。
核心代码
KeyListener相关代码(Ventana类)
public Ventana() { setSize(600, 400); setResizable(false); setLocationRelativeTo(null); setTitle("Pong"); setDefaultCloseOperation(EXIT_ON_CLOSE); jugador1 = new Raqueta(this, Raqueta.JUGADOR1); jugador2 = new Raqueta(this, Raqueta.JUGADOR2); jugador1.start(); jugador2.start(); addKeyListener(this); } @Override public void paint(Graphics g) { g.setColor(Color.BLACK); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.WHITE); jugador1.dibujar(g); jugador2.dibujar(g); } @Override public void keyTyped(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_W: jugador1.moverY(Raqueta.SUBIR); break; case KeyEvent.VK_S: jugador1.moverY(Raqueta.BAJAR); break; case KeyEvent.VK_UP: jugador2.moverY(Raqueta.SUBIR); break; case KeyEvent.VK_DOWN: jugador2.moverY(Raqueta.BAJAR); break; } repaint(); } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_W: case KeyEvent.VK_S: jugador1.moverY(Raqueta.PARAR); case KeyEvent.VK_UP: case KeyEvent.VK_DOWN: jugador2.moverY(Raqueta.PARAR); } }
Raqueta类代码
public class Raqueta extends Thread{ private int x, y, anchura, altura; private byte direccion; public final static byte BAJAR = 0; public final static byte SUBIR = 1; public final static byte PARAR = -1; public final static byte JUGADOR1 = 1; public final static byte JUGADOR2 = 2; public void setDireccion(byte direccion) { this.direccion = direccion; } @Override public void run() { try { while (true) { moverY(direccion); Thread.sleep(5); } } catch (InterruptedException ex) { System.exit(0); } } public void moverY(byte x) { if (x == BAJAR){ this.y = y + 10; } else if (x == SUBIR){ this.y = y - 10; } } public void dibujar(Graphics g){ g.fillRect(getX(), getY(), getAnchura(), getAltura()); } }
问题根源
- KeyListener多键处理缺陷:Swing的KeyListener无法可靠处理多键同时按下的场景,操作系统可能不会持续发送所有按键的重复事件,导致仅第一个按住的键能触发移动。
- keyReleased逻辑错误:switch分支未添加
break,松开W/S键时会同时触发右侧球拍停止,直接打断另一玩家操作。 - 线程与事件同步混乱:keyPressed直接单次调用移动方法,而线程循环又依赖
direccion状态,但direccion未被正确关联到按键状态,导致移动逻辑冲突。
修复方案
1. 修复keyReleased的break问题
给switch分支添加break,避免松开一个玩家的按键影响另一个:
@Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_W: case KeyEvent.VK_S: jugador1.setDireccion(Raqueta.PARAR); break; case KeyEvent.VK_UP: case KeyEvent.VK_DOWN: jugador2.setDireccion(Raqueta.PARAR); break; } }
2. 改用按键状态跟踪
放弃KeyListener的单次事件触发,改用集合跟踪所有按下的按键,在游戏循环中根据状态更新球拍方向:
import java.util.HashSet; import java.util.Set; public class Ventana extends JFrame implements KeyListener { private Set<Integer> pressedKeys = new HashSet<>(); private Raqueta jugador1, jugador2; @Override public void keyPressed(KeyEvent e) { pressedKeys.add(e.getKeyCode()); } @Override public void keyReleased(KeyEvent e) { pressedKeys.remove(e.getKeyCode()); } @Override public void keyTyped(KeyEvent e) {} // 定时更新球拍方向 public void updateGameState() { // 更新左侧球拍方向 if (pressedKeys.contains(KeyEvent.VK_W)) { jugador1.setDireccion(Raqueta.SUBIR); } else if (pressedKeys.contains(KeyEvent.VK_S)) { jugador1.setDireccion(Raqueta.BAJAR); } else { jugador1.setDireccion(Raqueta.PARAR); } // 更新右侧球拍方向 if (pressedKeys.contains(KeyEvent.VK_UP)) { jugador2.setDireccion(Raqueta.SUBIR); } else if (pressedKeys.contains(KeyEvent.VK_DOWN)) { jugador2.setDireccion(Raqueta.BAJAR); } else { jugador2.setDireccion(Raqueta.PARAR); } } }
3. 优化Raqueta线程逻辑
给direccion添加volatile保证线程可见性,修复移动逻辑并添加边界检测:
public class Raqueta extends Thread{ private int x, y, anchura, altura; private volatile byte direccion; // 确保线程间可见性 public final static byte BAJAR = 0; public final static byte SUBIR = 1; public final static byte PARAR = -1; public final static byte JUGADOR1 = 1; public final static byte JUGADOR2 = 2; // 补充构造方法初始化位置 public Raqueta(Ventana ventana, byte jugador) { anchura = 20; altura = 80; if (jugador == JUGADOR1) { x = 30; y = ventana.getHeight() / 2 - altura / 2; } else { x = ventana.getWidth() - 30 - anchura; y = ventana.getHeight() / 2 - altura / 2; } direccion = PARAR; } public void setDireccion(byte direccion) { this.direccion = direccion; } @Override public void run() { try { while (true) { // 根据方向移动 switch (direccion) { case BAJAR: y += 2; break; case SUBIR: y -= 2; break; case PARAR: break; } // 边界检测,防止球拍移出窗口 if (y < 0) y = 0; if (y + altura > 400) y = 400 - altura; Thread.sleep(16); // 约60帧,更流畅 } } catch (InterruptedException ex) { ex.printStackTrace(); } } // 实现必要的getter方法 public int getX() { return x; } public int getY() { return y; } public int getAnchura() { return anchura; } public int getAltura() { return altura; } public void dibujar(Graphics g){ g.fillRect(getX(), getY(), getAnchura(), getAltura()); } }
4. 添加游戏循环
在Ventana构造方法中启动游戏循环线程,定时更新状态并重绘:
public Ventana() { // 原构造代码... jugador1 = new Raqueta(this, Raqueta.JUGADOR1); jugador2 = new Raqueta(this, Raqueta.JUGADOR2); jugador1.start(); jugador2.start(); // 启动游戏循环 new Thread(() -> { try { while (true) { updateGameState(); repaint(); Thread.sleep(16); } } catch (InterruptedException ex) { ex.printStackTrace(); } }).start(); addKeyListener(this); setFocusable(true); // 确保窗口获取焦点 }
内容的提问来源于stack exchange,提问作者MrFull12
相关产品推荐
相关产品推荐

