在這篇文章中,我將帶你一步步實現一個經典的貪吃蛇小游戲。我們將使用Java語言和Swing庫來構建這個游戲,它包含了貪吃蛇游戲的基本功能:蛇的移動、吃食物、計分以及游戲結束判定。
游戲設計思路
貪吃蛇游戲的基本原理是:玩家控制一條蛇在屏幕上移動,蛇會吃隨機出現的食物,每吃一次食物蛇就會變長一節,同時玩家得分增加。如果蛇撞到墻壁或自己的身體,游戲就結束。
我們將使用面向對象的方法設計這個游戲,主要包含以下幾個類:
SnakeGame
:游戲主類,負責初始化游戲窗口和游戲循環GamePanel
:游戲面板類,繼承自JPanel,負責繪制游戲界面和處理游戲邏輯Snake
:蛇類,管理蛇的位置、移動和狀態Food
:食物類,管理食物的位置和生成
下面讓我們開始實現這個游戲吧!
第一步:創建游戲主類
首先,我們需要創建一個主類來啟動游戲。這個類將設置游戲窗口并啟動游戲循環。
import javax.swing.JFrame;public class SnakeGame {public static void main(String[] args) {// 創建游戲窗口JFrame frame = new JFrame("貪吃蛇游戲");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setResizable(false);// 創建游戲面板并添加到窗口GamePanel gamePanel = new GamePanel();frame.add(gamePanel);// 調整窗口大小以適應游戲面板frame.pack();// 居中顯示窗口frame.setLocationRelativeTo(null);// 顯示窗口frame.setVisible(true);// 啟動游戲gamePanel.startGame();}
}
這個類很簡單,它創建了一個JFrame窗口,并將游戲面板添加到窗口中。然后設置窗口的基本屬性,最后調用游戲面板的startGame()
方法來啟動游戲。
第二步:創建游戲面板類
接下來,我們創建游戲面板類,它將繼承自JPanel,并實現游戲的主要邏輯和繪制功能。
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;
import javax.swing.Timer;public class GamePanel extends JPanel implements ActionListener, KeyListener {// 游戲區域的尺寸private static final int WIDTH = 600;private static final int HEIGHT = 600;// 蛇和食物的大小private static final int UNIT_SIZE = 20;// 游戲區域的單元數量private static final int GAME_UNITS = (WIDTH * HEIGHT) / (UNIT_SIZE * UNIT_SIZE);// 游戲速度控制private static final int DELAY = 75;// 蛇的身體部分位置數組private final int[] x = new int[GAME_UNITS];private final int[] y = new int[GAME_UNITS];// 蛇的初始長度private int bodyParts = 6;// 吃掉的食物數量private int applesEaten;// 食物的位置private int appleX;private int appleY;// 蛇的移動方向private char direction = 'R'; // 初始向右移動private boolean running = false;private Timer timer;public GamePanel() {// 設置面板屬性setPreferredSize(new Dimension(WIDTH, HEIGHT));setBackground(Color.BLACK);setFocusable(true);// 添加鍵盤監聽器addKeyListener(this);// 初始化游戲initGame();}public void initGame() {// 初始化蛇的位置for (int i = 0; i < bodyParts; i++) {x[i] = 100 - i * UNIT_SIZE;y[i] = 50;}// 生成第一個食物newApple();// 啟動游戲running = true;timer = new Timer(DELAY, this);timer.start();}@Overrideprotected void paintComponent(Graphics g) {super.paintComponent(g);draw(g);}public void draw(Graphics g) {if (running) {// 繪制網格(可選,僅用于輔助查看)for (int i = 0; i < HEIGHT / UNIT_SIZE; i++) {g.drawLine(i * UNIT_SIZE, 0, i * UNIT_SIZE, HEIGHT);g.drawLine(0, i * UNIT_SIZE, WIDTH, i * UNIT_SIZE);}// 繪制食物g.setColor(Color.RED);g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);// 繪制蛇for (int i = 0; i < bodyParts; i++) {if (i == 0) {// 蛇頭g.setColor(Color.GREEN);} else {// 蛇身g.setColor(new Color(45, 180, 0));// 或者使用不同顏色創建漸變效果// g.setColor(new Color((int)(Math.random() * 255), (int)(Math.random() * 255), (int)(Math.random() * 255)));}g.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);}// 繪制分數g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 40));FontMetrics metrics = getFontMetrics(g.getFont());g.drawString("分數: " + applesEaten, (WIDTH - metrics.stringWidth("分數: " + applesEaten)) / 2, g.getFont().getSize());} else {gameOver(g);}}public void newApple() {// 隨機生成食物位置appleX = (int)(Math.random() * (WIDTH / UNIT_SIZE)) * UNIT_SIZE;appleY = (int)(Math.random() * (HEIGHT / UNIT_SIZE)) * UNIT_SIZE;// 確保食物不會出現在蛇身上for (int i = 0; i < bodyParts; i++) {if ((x[i] == appleX) && (y[i] == appleY)) {newApple();}}}public void move() {// 移動蛇的身體部分for (int i = bodyParts; i > 0; i--) {x[i] = x[i - 1];y[i] = y[i - 1];}// 根據方向移動蛇頭switch (direction) {case 'U':y[0] = y[0] - UNIT_SIZE;break;case 'D':y[0] = y[0] + UNIT_SIZE;break;case 'L':x[0] = x[0] - UNIT_SIZE;break;case 'R':x[0] = x[0] + UNIT_SIZE;break;}}public void checkApple() {// 檢查蛇頭是否碰到食物if ((x[0] == appleX) && (y[0] == appleY)) {bodyParts++;applesEaten++;newApple();}}public void checkCollisions() {// 檢查蛇頭是否碰到自己的身體for (int i = bodyParts; i > 0; i--) {if ((x[0] == x[i]) && (y[0] == y[i])) {running = false;}}// 檢查蛇頭是否碰到左邊界if (x[0] < 0) {running = false;}// 檢查蛇頭是否碰到右邊界if (x[0] >= WIDTH) {running = false;}// 檢查蛇頭是否碰到上邊界if (y[0] < 0) {running = false;}// 檢查蛇頭是否碰到下邊界if (y[0] >= HEIGHT) {running = false;}// 如果游戲結束,停止計時器if (!running) {timer.stop();}}public void gameOver(Graphics g) {// 繪制游戲結束文本g.setColor(Color.RED);g.setFont(new Font("Ink Free", Font.BOLD, 75));FontMetrics metrics1 = getFontMetrics(g.getFont());g.drawString("游戲結束", (WIDTH - metrics1.stringWidth("游戲結束")) / 2, HEIGHT / 2 - 50);// 繪制最終分數g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 40));FontMetrics metrics2 = getFontMetrics(g.getFont());g.drawString("分數: " + applesEaten, (WIDTH - metrics2.stringWidth("分數: " + applesEaten)) / 2, HEIGHT / 2 + 50);// 繪制重新開始提示g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 20));FontMetrics metrics3 = getFontMetrics(g.getFont());g.drawString("按空格鍵重新開始", (WIDTH - metrics3.stringWidth("按空格鍵重新開始")) / 2, HEIGHT / 2 + 100);}public void startGame() {initGame();}@Overridepublic void actionPerformed(ActionEvent e) {if (running) {move();checkApple();checkCollisions();}repaint();}@Overridepublic void keyPressed(KeyEvent e) {int key = e.getKeyCode();// 根據按鍵改變蛇的移動方向,但不能直接反向移動switch (key) {case KeyEvent.VK_LEFT:if (direction != 'R') {direction = 'L';}break;case KeyEvent.VK_RIGHT:if (direction != 'L') {direction = 'R';}break;case KeyEvent.VK_UP:if (direction != 'D') {direction = 'U';}break;case KeyEvent.VK_DOWN:if (direction != 'U') {direction = 'D';}break;case KeyEvent.VK_SPACE:if (!running) {startGame();}break;}}@Overridepublic void keyReleased(KeyEvent e) {}@Overridepublic void keyTyped(KeyEvent e) {}
}
這個類是游戲的核心部分,它處理了游戲的主要邏輯:
- 游戲面板的初始化和設置
- 游戲循環的控制(通過Timer實現)
- 游戲元素的繪制(蛇、食物和分數)
- 蛇的移動和方向控制
- 食物的生成和檢測
- 碰撞檢測(包括邊界和自身)
- 游戲結束的處理
第三步:改進游戲結構(可選)
上面的實現已經可以運行一個簡單的貪吃蛇游戲了,但代碼結構還可以進一步優化。我們可以將蛇和食物單獨封裝成類,使代碼更加模塊化。
以下是優化后的代碼結構:
// Snake.java
public class Snake {private int[] x;private int[] y;private int bodyParts;private char direction;public Snake() {x = new int[GamePanel.GAME_UNITS];y = new int[GamePanel.GAME_UNITS];bodyParts = 6;direction = 'R';// 初始化蛇的位置for (int i = 0; i < bodyParts; i++) {x[i] = 100 - i * GamePanel.UNIT_SIZE;y[i] = 50;}}// Getters and setterspublic int[] getX() { return x; }public int[] getY() { return y; }public int getBodyParts() { return bodyParts; }public char getDirection() { return direction; }public void setDirection(char direction) { this.direction = direction; }public void move() {// 移動蛇的身體部分for (int i = bodyParts; i > 0; i--) {x[i] = x[i - 1];y[i] = y[i - 1];}// 根據方向移動蛇頭switch (direction) {case 'U':y[0] = y[0] - GamePanel.UNIT_SIZE;break;case 'D':y[0] = y[0] + GamePanel.UNIT_SIZE;break;case 'L':x[0] = x[0] - GamePanel.UNIT_SIZE;break;case 'R':x[0] = x[0] + GamePanel.UNIT_SIZE;break;}}public void grow() {bodyParts++;}public boolean checkSelfCollision() {// 檢查蛇頭是否碰到自己的身體for (int i = bodyParts; i > 0; i--) {if ((x[0] == x[i]) && (y[0] == y[i])) {return true;}}return false;}public boolean checkBoundaryCollision() {// 檢查蛇頭是否碰到邊界return x[0] < 0 || x[0] >= GamePanel.WIDTH || y[0] < 0 || y[0] >= GamePanel.HEIGHT;}
}// Food.java
import java.util.Random;public class Food {private int x;private int y;private Random random;public Food() {random = new Random();newPosition();}public void newPosition() {// 隨機生成食物位置x = random.nextInt(GamePanel.WIDTH / GamePanel.UNIT_SIZE) * GamePanel.UNIT_SIZE;y = random.nextInt(GamePanel.HEIGHT / GamePanel.UNIT_SIZE) * GamePanel.UNIT_SIZE;}// Getterspublic int getX() { return x; }public int getY() { return y; }public boolean isEaten(Snake snake) {// 檢查食物是否被蛇吃掉return snake.getX()[0] == x && snake.getY()[0] == y;}
}
使用這些類重構后的GamePanel類:
// 優化后的GamePanel.java
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;
import javax.swing.Timer;public class GamePanel extends JPanel implements ActionListener, KeyListener {private static final int WIDTH = 600;private static final int HEIGHT = 600;private static final int UNIT_SIZE = 20;private static final int GAME_UNITS = (WIDTH * HEIGHT) / (UNIT_SIZE * UNIT_SIZE);private static final int DELAY = 75;private Snake snake;private Food food;private int applesEaten;private boolean running = false;private Timer timer;public GamePanel() {setPreferredSize(new Dimension(WIDTH, HEIGHT));setBackground(Color.BLACK);setFocusable(true);addKeyListener(this);initGame();}public void initGame() {snake = new Snake();food = new Food();applesEaten = 0;running = true;timer = new Timer(DELAY, this);timer.start();}@Overrideprotected void paintComponent(Graphics g) {super.paintComponent(g);draw(g);}public void draw(Graphics g) {if (running) {// 繪制網格for (int i = 0; i < HEIGHT / UNIT_SIZE; i++) {g.drawLine(i * UNIT_SIZE, 0, i * UNIT_SIZE, HEIGHT);g.drawLine(0, i * UNIT_SIZE, WIDTH, i * UNIT_SIZE);}// 繪制食物g.setColor(Color.RED);g.fillOval(food.getX(), food.getY(), UNIT_SIZE, UNIT_SIZE);// 繪制蛇int[] snakeX = snake.getX();int[] snakeY = snake.getY();for (int i = 0; i < snake.getBodyParts(); i++) {if (i == 0) {g.setColor(Color.GREEN);} else {g.setColor(new Color(45, 180, 0));}g.fillRect(snakeX[i], snakeY[i], UNIT_SIZE, UNIT_SIZE);}// 繪制分數g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 40));FontMetrics metrics = getFontMetrics(g.getFont());g.drawString("分數: " + applesEaten, (WIDTH - metrics.stringWidth("分數: " + applesEaten)) / 2, g.getFont().getSize());} else {gameOver(g);}}public void gameOver(Graphics g) {// 繪制游戲結束文本g.setColor(Color.RED);g.setFont(new Font("Ink Free", Font.BOLD, 75));FontMetrics metrics1 = getFontMetrics(g.getFont());g.drawString("游戲結束", (WIDTH - metrics1.stringWidth("游戲結束")) / 2, HEIGHT / 2 - 50);// 繪制最終分數g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 40));FontMetrics metrics2 = getFontMetrics(g.getFont());g.drawString("分數: " + applesEaten, (WIDTH - metrics2.stringWidth("分數: " + applesEaten)) / 2, HEIGHT / 2 + 50);// 繪制重新開始提示g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 20));FontMetrics metrics3 = getFontMetrics(g.getFont());g.drawString("按空格鍵重新開始", (WIDTH - metrics3.stringWidth("按空格鍵重新開始")) / 2, HEIGHT / 2 + 100);}@Overridepublic void actionPerformed(ActionEvent e) {if (running) {snake.move();checkFood();checkCollisions();}repaint();}public void checkFood() {if (food.isEaten(snake)) {snake.grow();applesEaten++;food.newPosition();}}public void checkCollisions() {if (snake.checkSelfCollision() || snake.checkBoundaryCollision()) {running = false;}if (!running) {timer.stop();}}@Overridepublic void keyPressed(KeyEvent e) {int key = e.getKeyCode();switch (key) {case KeyEvent.VK_LEFT:if (snake.getDirection() != 'R') {snake.setDirection('L');}break;case KeyEvent.VK_RIGHT:if (snake.getDirection() != 'L') {snake.setDirection('R');}break;case KeyEvent.VK_UP:if (snake.getDirection() != 'D') {snake.setDirection('U');}break;case KeyEvent.VK_DOWN:if (snake.getDirection() != 'U') {snake.setDirection('D');}break;case KeyEvent.VK_SPACE:if (!running) {initGame();}break;}}@Overridepublic void keyReleased(KeyEvent e) {}@Overridepublic void keyTyped(KeyEvent e) {}
}
完整代碼
下面是完整的貪吃蛇游戲代碼,包含了所有類的實現:
// SnakeGame.java
import javax.swing.JFrame;public class SnakeGame {public static void main(String[] args) {JFrame frame = new JFrame("貪吃蛇游戲");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setResizable(false);GamePanel gamePanel = new GamePanel();frame.add(gamePanel);frame.pack();frame.setLocationRelativeTo(null);frame.setVisible(true);gamePanel.startGame();}
}// GamePanel.java
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;
import javax.swing.Timer;public class GamePanel extends JPanel implements ActionListener, KeyListener {private static final int WIDTH = 600;private static final int HEIGHT = 600;private static final int UNIT_SIZE = 20;private static final int GAME_UNITS = (WIDTH * HEIGHT) / (UNIT_SIZE * UNIT_SIZE);private static final int DELAY = 75;private Snake snake;private Food food;private int applesEaten;private boolean running = false;private Timer timer;public GamePanel() {setPreferredSize(new Dimension(WIDTH, HEIGHT));setBackground(Color.BLACK);setFocusable(true);addKeyListener(this);initGame();}public void initGame() {snake = new Snake();food = new Food();applesEaten = 0;running = true;timer = new Timer(DELAY, this);timer.start();}@Overrideprotected void paintComponent(Graphics g) {super.paintComponent(g);draw(g);}public void draw(Graphics g) {if (running) {// 繪制網格for (int i = 0; i < HEIGHT / UNIT_SIZE; i++) {g.drawLine(i * UNIT_SIZE, 0, i * UNIT_SIZE, HEIGHT);g.drawLine(0, i * UNIT_SIZE, WIDTH, i * UNIT_SIZE);}// 繪制食物g.setColor(Color.RED);g.fillOval(food.getX(), food.getY(), UNIT_SIZE, UNIT_SIZE);// 繪制蛇int[] snakeX = snake.getX();int[] snakeY = snake.getY();for (int i = 0; i < snake.getBodyParts(); i++) {if (i == 0) {g.setColor(Color.GREEN);} else {g.setColor(new Color(45, 180, 0));}g.fillRect(snakeX[i], snakeY[i], UNIT_SIZE, UNIT_SIZE);}// 繪制分數g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 40));FontMetrics metrics = getFontMetrics(g.getFont());g.drawString("分數: " + applesEaten, (WIDTH - metrics.stringWidth("分數: " + applesEaten)) / 2, g.getFont().getSize());} else {gameOver(g);}}public void gameOver(Graphics g) {// 繪制游戲結束文本g.setColor(Color.RED);g.setFont(new Font("Ink Free", Font.BOLD, 75));FontMetrics metrics1 = getFontMetrics(g.getFont());g.drawString("游戲結束", (WIDTH - metrics1.stringWidth("游戲結束")) / 2, HEIGHT / 2 - 50);// 繪制最終分數g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 40));FontMetrics metrics2 = getFontMetrics(g.getFont());g.drawString("分數: " + applesEaten, (WIDTH - metrics2.stringWidth("分數: " + applesEaten)) / 2, HEIGHT / 2 + 50);// 繪制重新開始提示g.setColor(Color.WHITE);g.setFont(new Font("Ink Free", Font.BOLD, 20));FontMetrics metrics3 = getFontMetrics(g.getFont());g.drawString("按空格鍵重新開始", (WIDTH - metrics3.stringWidth("按空格鍵重新開始")) / 2, HEIGHT / 2 + 100);}@Overridepublic void actionPerformed(ActionEvent e) {if (running) {snake.move();checkFood();checkCollisions();}repaint();}public void checkFood() {if (food.isEaten(snake)) {snake.grow();applesEaten++;food.newPosition();}}public void checkCollisions() {if (snake.checkSelfCollision() || snake.checkBoundaryCollision()) {running = false;}if (!running) {timer.stop();}}public void startGame() {initGame();}@Overridepublic void keyPressed(KeyEvent e) {int key = e.getKeyCode();switch (key) {case KeyEvent.VK_LEFT:if (snake.getDirection() != 'R') {snake.setDirection('L');}break;case KeyEvent.VK_RIGHT:if (snake.getDirection() != 'L') {snake.setDirection('R');}break;case KeyEvent.VK_UP:if (snake.getDirection() != 'D') {snake.setDirection('U');}break;case KeyEvent.VK_DOWN:if (snake.getDirection() != 'U') {snake.setDirection('D');}break;case KeyEvent.VK_SPACE:if (!running) {initGame();}break;}}@Overridepublic void keyReleased(KeyEvent e) {}@Overridepublic void keyTyped(KeyEvent e) {}
}// Snake.java
public class Snake {private int[] x;private int[] y;private int bodyParts;private char direction;public Snake() {x = new int[GamePanel.GAME_UNITS];y = new int[GamePanel.GAME_UNITS];bodyParts = 6;direction = 'R';// 初始化蛇的位置for (int i = 0; i < bodyParts; i++) {x[i] = 100 - i * GamePanel.UNIT_SIZE;y[i] = 50;}}public int[] getX() { return x; }public int[] getY() { return y; }public int getBodyParts() { return bodyParts; }public char getDirection() { return direction; }public void setDirection(char direction) { this.direction = direction; }public void move() {// 移動蛇的身體部分for (int i = bodyParts; i > 0; i--) {x[i] = x[i - 1];y[i] = y[i - 1];}// 根據方向移動蛇頭switch (direction) {case 'U':y[0] = y[0] - GamePanel.UNIT_SIZE;break;case 'D':y[0] = y[0] + GamePanel.UNIT_SIZE;break;case 'L':x[0] = x[0] - GamePanel.UNIT_SIZE;break;case 'R':x[0] = x[0] + GamePanel.UNIT_SIZE;break;}}public void grow() {bodyParts++;}public boolean checkSelfCollision() {// 檢查蛇頭是否碰到自己的身體for (int i = bodyParts; i > 0; i--) {if ((x[0] == x[i]) && (y[0] == y[i])) {return true;}}return false;}public boolean checkBoundaryCollision() {// 檢查蛇頭是否碰到邊界return x[0] < 0 || x[0] >= GamePanel.WIDTH || y[0] < 0 || y[0] >= GamePanel.HEIGHT;}
}// Food.java
import java.util.Random;public class Food {private int x;private int y;private Random random;public Food() {random = new Random();newPosition();}public void newPosition() {// 隨機生成食物位置x = random.nextInt(GamePanel.WIDTH / GamePanel.UNIT_SIZE) * GamePanel.UNIT_SIZE;y = random.nextInt(GamePanel.HEIGHT / GamePanel.UNIT_SIZE) * GamePanel.UNIT_SIZE;}public int getX() { return x; }public int getY() { return y; }public boolean isEaten(Snake snake) {// 檢查食物是否被蛇吃掉return snake.getX()[0] == x && snake.getY()[0] == y;}
}
如何運行游戲
- 將上面的代碼復制到四個Java文件中:
SnakeGame.java
,GamePanel.java
,Snake.java
, 和Food.java
- 確保所有文件在同一個目錄下
- 使用Java編譯器編譯這些文件:
javac SnakeGame.java
- 運行編譯后的程序:
java SnakeGame
游戲操作說明
- 使用方向鍵(上、下、左、右)控制蛇的移動方向
- 吃到紅色的食物會增加分數并讓蛇變長
- 如果蛇撞到邊界或自己的身體,游戲結束
- 游戲結束后按空格鍵可以重新開始
游戲改進建議
- 添加難度級別:隨著分數增加,蛇的移動速度加快
- 添加不同類型的食物:有些食物提供額外分數,有些食物讓蛇加速或減速
- 添加音效:吃到食物、游戲結束等事件添加音效
- 實現高分榜:記錄最高分并顯示在游戲界面
希望這篇文章能幫助你理解如何使用Java制作一個簡單的貪吃蛇游戲!如果你有任何問題或建議,歡迎留言討論。