2048游戏GUI开发问题:actionPerformed函数无法触发
问题分析与解决方案
核心问题
- EDT线程被阻塞:
actionPerformed方法中调用queue.take()是阻塞式操作,会卡住Swing的事件派发线程(EDT),导致Timer后续事件无法触发,UI也无法更新,这是actionPerformed从未执行或执行后无响应的根本原因。 - 焦点未正确获取:即使设置了
setFocusable(true),面板可能没有实际获得键盘焦点,导致KeyListener无法接收按键事件。 - Timer使用逻辑错误:用Timer配合阻塞队列等待用户输入是不合理的,Timer的作用应该是定时更新UI,而非阻塞等待操作。
修复步骤
- 移除Timer中的阻塞逻辑,直接在按键事件处理后执行方块移动、生成新方块和重绘操作
- 在
startGame方法中添加requestFocusInWindow(),确保面板获得键盘焦点 - 简化逻辑,不需要用BlockingQueue来传递操作信号,直接在按键事件中触发游戏逻辑
修改后的完整代码
GamePanel.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.Random; public class GamePanel extends JPanel { static final int SCREEN_WIDTH = 600; static final int SCREEN_HEIGHT = 600; Random random; Board grid; boolean running = false; GamePanel() { random = new Random(); this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); this.setBackground(Color.white); this.setFocusable(true); this.addKeyListener(new MyKeyAdapter()); startGame(); } public void startGame() { grid = new Board(); grid.newTile(); grid.newTile(); running = true; requestFocusInWindow(); // 确保面板获得焦点 System.out.println("game started"); repaint(); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; int tileSize = SCREEN_WIDTH / this.grid.SIZE; // 绘制网格线 for (int row = 0; row < this.grid.SIZE; row++) { for (int col = 0; col < this.grid.SIZE; col++) { int x = col * tileSize; int y = row * tileSize; g2d.drawRect(x, y, tileSize, tileSize); } } // 绘制方块 for (int row = 0; row < this.grid.SIZE; row++) { for (int col = 0; col < this.grid.SIZE; col++) { int x = col * tileSize; int y = row * tileSize; Tile tile = grid.board[row][col]; if (tile != null) { g2d.setColor(tile.getColor()); g2d.fillRect(x, y, tileSize, tileSize); g2d.setColor(Color.BLACK); // 调整文字位置,使其居中 FontMetrics fm = g2d.getFontMetrics(); int textX = x + (tileSize - fm.stringWidth(String.valueOf(tile.getValue()))) / 2; int textY = y + (tileSize + fm.getAscent()) / 2; g2d.drawString(String.valueOf(tile.getValue()), textX, textY); } } } } public class MyKeyAdapter extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { if (!running) return; boolean moved = false; switch (e.getKeyCode()) { case KeyEvent.VK_LEFT: grid.setDirection('L'); moved = grid.move(); System.out.println("Left key pressed"); break; case KeyEvent.VK_RIGHT: grid.setDirection('R'); moved = grid.move(); break; case KeyEvent.VK_UP: grid.setDirection('U'); moved = grid.move(); break; case KeyEvent.VK_DOWN: grid.setDirection('D'); moved = grid.move(); break; } // 只有方块实际移动了才生成新方块 if (moved) { grid.newTile(); // 检查游戏是否结束 if (!grid.gridHasSpace() && !grid.canMerge()) { running = false; JOptionPane.showMessageDialog(null, "Game Over!"); } repaint(); } } } }
GameFrame.java
import javax.swing.JFrame; public class GameFrame extends JFrame { GameFrame() { this.add(new GamePanel()); this.setTitle("Game2048"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setResizable(false); this.pack(); this.setVisible(true); this.setLocationRelativeTo(null); } }
Game2048.java
import javax.swing.SwingUtilities; public class Game2048 { public static void main(String[] args) { SwingUtilities.invokeLater(() -> new GameFrame()); } }
额外说明
- 假设你的
Board类的move()方法返回布尔值,表示方块是否实际发生了移动,这样可以避免在没有移动时生成新方块,符合2048的游戏规则 - 需要在
Board类中实现canMerge()方法,用于判断游戏是否真的结束(当没有空格且无法合并时才结束) - 调整了文字绘制的位置,使其在方块中居中显示
内容的提问来源于stack exchange,提问作者Maria
相关产品推荐
相关产品推荐

