本文以經典「貪吃蛇」為例,帶領初學者逐步掌握 Java Swing 核心組件:JFrame 視窗框架、JPanel 繪圖面板、KeyListener 鍵盤事件、Runnable/Thread 遊戲迴圈、雙緩衝繪圖技巧,並附完整專案結構與代碼片段。
JFrame,負責視窗初始化 (標題、尺寸、置中、關閉操作、不可縮放)。JPanel,核心繪圖邏輯 (paintComponent)、控制按鈕、分數顯示、鍵盤監聽註冊。KeyListener,處理方向鍵輸入,防止 180° 反向移動。public class SnakeFrame extends JFrame {
private static final long serialVersionUID = 1L;
private final int WIDTH = 530, HEIGHT = 450;
public SnakeFrame() {
setTitle("貪吃蛇 v1.0");
setSize(WIDTH, HEIGHT);
setLocationRelativeTo(null); // 畫面置中
setDefaultCloseOperation(EXIT_ON_CLOSE); // 關閉視窗結束程式
setResizable(false); // 禁止縮放
setLayout(null); // 絕對佈局,由 Panel 內部管理
setVisible(true);
}
}
private JLabel lblScoreText; // "得分:"
private JButton btnScore; // 顯示分數 (不可編輯)
private JButton btnStart; // 開始按鈕
private JButton btnAbout; // 關於按鈕
private void initUI() {
setLayout(null);
lblScoreText = new JLabel("得分:");
lblScoreText.setFont(new Font("Dialog", Font.BOLD, 20));
lblScoreText.setBounds(420, 100, 80, 60);
add(lblScoreText);
btnScore = new JButton("0");
btnScore.setBounds(480, 100, 50, 60);
btnScore.setEnabled(false); // 僅顯示
add(btnScore);
btnStart = new JButton("開始遊戲");
btnStart.setBounds(420, 250, 90, 30);
btnStart.addActionListener(e -> startGame());
add(btnStart);
btnAbout = new JButton("關於遊戲");
btnAbout.setBounds(420, 300, 90, 30);
add(btnAbout);
requestFocusInWindow(); // 獲取鍵盤焦點
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 清屏、重繪背景與子元件
Graphics2D g2d = (Graphics2D) g;
// 開啟抗鋸齒
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawGrid(g2d); // 畫網格
if (isGameOver) drawGameOver(g2d);
if (isStarted) {
drawSnake(g2d); // 畫蛇身 (頭/身/尾區分圖片)
drawFood(g2d); // 畫食物
if (countdown > -1) drawCountdown(g2d); // 倒計時
}
}
3. 鍵盤事件處理 (SnakeControl implements KeyListener)
public class SnakeControl implements KeyListener { private SnakePanel panel; private Snake.Direction nextDir = Snake.Direction.UP; public SnakeControl(SnakePanel panel) { this.panel = panel; } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: if (nextDir != Snake.Direction.DOWN) nextDir = Snake.Direction.UP; break; case KeyEvent.VK_DOWN: if (nextDir != Snake.Direction.UP) nextDir = Snake.Direction.DOWN; break; case KeyEvent.VK_LEFT: if (nextDir != Snake.Direction.RIGHT) nextDir = Snake.Direction.LEFT; break; case KeyEvent.VK_RIGHT: if (nextDir != Snake.Direction.LEFT) nextDir = Snake.Direction.RIGHT; break; } panel.getSnake().setNextDirection(nextDir); } @Override public void keyReleased(KeyEvent e) {} @Override public void keyTyped(KeyEvent e) {} }在
SnakePanel建構子註冊:addKeyListener(new SnakeControl(this));4. 遊戲領域模型 (座標、蛇、食物)
技術點 說明 paintComponentvspaint覆寫 paintComponent只負責繪圖區域,配合super.paintComponent(g)清屏,避免重繪邊框/子元件。雙緩衝 Swing 預設開啟雙緩衝,直接在 paintComponent畫即可;若自行管理BufferedImage需手動g.drawImage(buffer, 0, 0, null)。repaint() 非同步 呼叫 repaint()只排程重繪,不立即執行;遊戲迴圈sleep控制幀率。焦點問題 JPanel需setFocusable(true)+requestFocusInWindow()才能接收鍵盤事件。執行緒安全 遊戲狀態變數 (isStarted, isGameOver, paused) 需 volatile或synchronized保護。常見問題與避坑指南
- 視窗空白不顯示:Windows 下偶發,確保在 EDT 執行 GUI 建構:
SwingUtilities.invokeLater(() -> new SnakeFrame())。- 圖片路徑失效:用
getClass().getResource("/images/head.png")載入 classpath 資源,打包成 JAR 也能正常讀取。- 鍵盤無反應:檢查
requestFocusInWindow()、setFocusable(true)、是否有其他元件搶焦點。- 蛇身重疊/穿牆判斷錯誤:座標系統原點 (0,0) 在左上角,x 向右、y 向下;牆壁邊界為 [0, 39]。
擴充練習建議
sleep 時間、增加障礙物。pauseLock 機制)。Preferences 或 SQLite 本地持久化。貪吃蛇雖小,却涵蓋了 Swing 開發的核心知識點:視窗管理、自定義繪圖、事件驅動、多執行緒協作、遊戲迴圈設計。建議初學者手敲一遍,再嘗試加入新功能,將被動閱讀轉為主動掌握。