使用Java制作贪吃蛇小游戏
在这篇文章中,我将带你一步步实现一个经典的贪吃蛇小游戏。我们将使用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制作一个简单的贪吃蛇游戏!如果你有任何问题或建议,欢迎留言讨论。