首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从 0 到 1 开发 Java 贪吃蛇:用 JDK17 打造经典游戏,揭秘底层运行机制

从 0 到 1 开发 Java 贪吃蛇:用 JDK17 打造经典游戏,揭秘底层运行机制

作者头像
果酱带你啃java
发布2026-04-14 10:06:53
发布2026-04-14 10:06:53
470
举报

引言:为什么选择贪吃蛇作为 Java 入门实践?

贪吃蛇游戏看似简单,却包含了 Java 编程的核心知识点:GUI 编程、事件处理、数据结构、线程控制等。通过开发这个小游戏,你不仅能掌握 Swing 框架的使用,更能理解面向对象编程的精髓。本文将带你一步步实现一个可运行的贪吃蛇游戏,从需求分析到代码实现,再到功能扩展,全程干货满满。

一、游戏核心原理与架构设计

1.1 贪吃蛇游戏的核心要素

贪吃蛇游戏主要包含以下核心要素:

  • 蛇身:由多个连续的方块组成,有固定的移动方向
  • 食物:随机出现在游戏区域,被蛇吃掉后蛇身增长
  • 游戏区域:限定蛇的活动范围
  • 碰撞检测:检测蛇是否撞到边界或自身
  • 分数系统:根据吃食物的数量计算分数
  • 控制机制:通过方向键控制蛇的移动方向

1.2 游戏架构设计

我们采用 MVC 模式设计这款游戏,将游戏分为三个核心模块:

  • 模型层 (Model):负责存储游戏数据,包括蛇的位置、食物的位置、当前分数等
  • 视图层 (View):负责游戏界面的绘制,包括蛇、食物、分数的显示
  • 控制层 (Controller):负责处理用户输入、游戏逻辑和状态更新

1.3 游戏运行流程图

二、开发环境与项目配置

2.1 开发环境

  • JDK 版本:17.0.10
  • 开发工具:IntelliJ IDEA 2024.1.1
  • 构建工具:Maven 3.9.6

2.2 Maven 依赖配置

创建pom.xml文件,添加必要的依赖:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.game</groupId>
    <artifactId>snake-game</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <lombok.version>1.18.30</lombok.version>
        <spring.version>6.1.8</spring.version>
        <fastjson2.version>2.0.47</fastjson2.version>
        <guava.version>33.2.1-jre</guava.version>
    </properties>

    <dependencies>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring Core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- FastJson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>

        <!-- Guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>com.game.snake.SnakeGame</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
代码语言:javascript
复制

三、核心数据模型设计

3.1 位置坐标类

首先创建一个表示位置坐标的类,用于记录蛇身和食物的位置:

代码语言:javascript
复制
package com.game.snake.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 位置坐标类,用于表示游戏元素在面板上的位置
 *
 * @author ken
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Position {
    /**
     * x坐标
     */
    private int x;
    /**
     * y坐标
     */
    private int y;

    /**
     * 判断两个位置是否相同
     *
     * @param other 另一个位置对象
     * @return 如果坐标相同则返回true,否则返回false
     */
    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (other == null || getClass() != other.getClass()) return false;
        Position position = (Position) other;
        return x == position.x && y == position.y;
    }

    /**
     * 计算哈希值
     *
     * @return 哈希值
     */
    @Override
    public int hashCode() {
        return 31 * x + y;
    }
}
代码语言:javascript
复制

3.2 蛇对象类

蛇是游戏的核心元素,我们需要设计一个类来表示蛇的属性和行为:

代码语言:javascript
复制
package com.game.snake.model;

import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * 蛇对象类,包含蛇的属性和行为
 *
 * @author ken
 */
@Getter
@Setter
public class Snake {
    /**
     * 蛇身由一系列坐标点组成,头部在列表的第一个位置
     */
    private List<Position> body;

    /**
     * 蛇的移动方向:上、下、左、右
     */
    private Direction direction;

    /**
     * 蛇的移动方向枚举
     */
    public enum Direction {
        UP, DOWN, LEFT, RIGHT
    }

    /**
     * 初始化蛇对象
     *
     * @param initialX 初始X坐标
     * @param initialY 初始Y坐标
     * @param length 初始长度
     * @param initialDirection 初始方向
     */
    public Snake(int initialX, int initialY, int length, Direction initialDirection) {
        body = Lists.newArrayList();
        // 初始化蛇身
        for (int i = 0; i < length; i++) {
            switch (initialDirection) {
                case RIGHT:
                    body.add(new Position(initialX - i, initialY));
                    break;
                case LEFT:
                    body.add(new Position(initialX + i, initialY));
                    break;
                case DOWN:
                    body.add(new Position(initialX, initialY - i));
                    break;
                case UP:
                    body.add(new Position(initialX, initialY + i));
                    break;
            }
        }
        this.direction = initialDirection;
    }

    /**
     * 移动蛇,根据当前方向计算新的头部位置,并添加到蛇身
     *
     * @return 新的头部位置
     */
    public Position move() {
        if (CollectionUtils.isEmpty(body)) {
            return null;
        }

        // 获取当前头部位置
        Position head = body.get(0);
        Position newHead = new Position(head.getX(), head.getY());

        // 根据方向计算新的头部位置
        switch (direction) {
            case UP:
                newHead.setY(head.getY() - 1);
                break;
            case DOWN:
                newHead.setY(head.getY() + 1);
                break;
            case LEFT:
                newHead.setX(head.getX() - 1);
                break;
            case RIGHT:
                newHead.setX(head.getX() + 1);
                break;
        }

        // 将新头部添加到蛇身的最前面
        body.add(0, newHead);
        return newHead;
    }

    /**
     * 移除蛇的尾部,当蛇没有吃到食物时调用
     */
    public void removeTail() {
        if (!CollectionUtils.isEmpty(body)) {
            body.remove(body.size() - 1);
        }
    }

    /**
     * 检查蛇是否撞到了自己
     *
     * @return 如果撞到自己返回true,否则返回false
     */
    public boolean isCollidedWithSelf() {
        if (CollectionUtils.isEmpty(body) || body.size() <= 1) {
            return false;
        }

        Position head = body.get(0);
        // 检查头部是否与身体其他部分碰撞
        for (int i = 1; i < body.size(); i++) {
            if (head.equals(body.get(i))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 改变蛇的移动方向,但不能直接反向移动
     *
     * @param newDirection 新的方向
     */
    public void changeDirection(Direction newDirection) {
        // 防止蛇直接反向移动(例如从向上直接变为向下)
        if ((direction == Direction.UP && newDirection != Direction.DOWN) ||
            (direction == Direction.DOWN && newDirection != Direction.UP) ||
            (direction == Direction.LEFT && newDirection != Direction.RIGHT) ||
            (direction == Direction.RIGHT && newDirection != Direction.LEFT)) {
            this.direction = newDirection;
        }
    }
}
代码语言:javascript
复制

3.3 食物对象类

食物是蛇的目标,被蛇吃掉后会增加分数并让蛇变长:

代码语言:javascript
复制
package com.game.snake.model;

import lombok.Data;

import java.util.Random;

/**
 * 食物对象类,包含食物的属性和行为
 *
 * @author ken
 */
@Data
public class Food {
    /**
     * 食物的位置
     */
    private Position position;

    /**
     * 食物的分数值
     */
    private int scoreValue;

    /**
     * 随机数生成器
     */
    private static final Random random = new Random();

    /**
     * 初始化食物对象
     *
     * @param maxX X坐标最大值(不包含)
     * @param maxY Y坐标最大值(不包含)
     */
    public Food(int maxX, int maxY) {
        this.scoreValue = 10; // 默认每个食物10分
        generateNewPosition(maxX, maxY);
    }

    /**
     * 生成新的食物位置,确保不在蛇身上
     *
     * @param maxX X坐标最大值(不包含)
     * @param maxY Y坐标最大值(不包含)
     * @param snakeBody 蛇的身体
     */
    public void generateNewPosition(int maxX, int maxY, Snake snake) {
        Position newPosition;
        do {
            newPosition = generateNewPosition(maxX, maxY);
        } while (snake.getBody().contains(newPosition)); // 确保食物不会出现在蛇身上

        this.position = newPosition;
    }

    /**
     * 生成新的食物位置
     *
     * @param maxX X坐标最大值(不包含)
     * @param maxY Y坐标最大值(不包含)
     * @return 新的食物位置
     */
    private Position generateNewPosition(int maxX, int maxY) {
        int x = random.nextInt(maxX);
        int y = random.nextInt(maxY);
        return new Position(x, y);
    }
}
代码语言:javascript
复制

3.4 游戏状态类

游戏状态类用于管理整个游戏的状态,包括分数、游戏是否结束等:

代码语言:javascript
复制
package com.game.snake.model;

import lombok.Getter;
import lombok.Setter;

/**
 * 游戏状态类,管理游戏的整体状态
 *
 * @author ken
 */
@Getter
@Setter
public class GameState {
    /**
     * 当前分数
     */
    private int score;

    /**
     * 游戏是否正在运行
     */
    private boolean isRunning;

    /**
     * 游戏是否结束
     */
    private boolean isGameOver;

    /**
     * 游戏区域的宽度(以格子为单位)
     */
    private final int gridWidth;

    /**
     * 游戏区域的高度(以格子为单位)
     */
    private final int gridHeight;

    /**
     * 格子的大小(像素)
     */
    private final int gridSize;

    /**
     * 初始化游戏状态
     *
     * @param gridWidth 游戏区域宽度(格子)
     * @param gridHeight 游戏区域高度(格子)
     * @param gridSize 格子大小(像素)
     */
    public GameState(int gridWidth, int gridHeight, int gridSize) {
        this.gridWidth = gridWidth;
        this.gridHeight = gridHeight;
        this.gridSize = gridSize;
        this.score = 0;
        this.isRunning = false;
        this.isGameOver = false;
    }

    /**
     * 增加分数
     *
     * @param points 要增加的分数
     */
    public void addScore(int points) {
        this.score += points;
    }

    /**
     * 重置游戏状态
     */
    public void reset() {
        this.score = 0;
        this.isRunning = false;
        this.isGameOver = false;
    }

    /**
     * 检查位置是否超出游戏边界
     *
     * @param position 要检查的位置
     * @return 如果超出边界返回true,否则返回false
     */
    public boolean isOutOfBounds(Position position) {
        return position.getX() < 0 || position.getX() >= gridWidth ||
               position.getY() < 0 || position.getY() >= gridHeight;
    }
}
代码语言:javascript
复制

四、视图层实现

4.1 游戏面板类

游戏面板是显示游戏元素的主要区域,负责绘制蛇、食物等元素:

代码语言:javascript
复制
package com.game.snake.view;

import com.game.snake.model.Food;
import com.game.snake.model.GameState;
import com.game.snake.model.Position;
import com.game.snake.model.Snake;
import lombok.Setter;
import org.springframework.util.ObjectUtils;

import javax.swing.*;
import java.awt.*;

/**
 * 游戏面板类,负责绘制游戏元素
 *
 * @author ken
 */
@Setter
public class GamePanel extends JPanel {
    /**
     * 游戏状态
     */
    private GameState gameState;

    /**
     * 蛇对象
     */
    private Snake snake;

    /**
     * 食物对象
     */
    private Food food;

    /**
     * 初始化游戏面板
     *
     * @param gameState 游戏状态
     */
    public GamePanel(GameState gameState) {
        this.gameState = gameState;
        // 设置面板大小
        int panelWidth = gameState.getGridWidth() * gameState.getGridSize();
        int panelHeight = gameState.getGridHeight() * gameState.getGridSize();
        setPreferredSize(new Dimension(panelWidth, panelHeight));
        // 设置背景色
        setBackground(Color.BLACK);
    }

    /**
     * 重写绘制方法,绘制游戏元素
     *
     * @param g 绘图对象
     */
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        // 绘制网格线(可选)
        drawGrid(g);

        // 如果游戏对象已初始化,则绘制它们
        if (!ObjectUtils.isEmpty(snake) && !ObjectUtils.isEmpty(food) && !ObjectUtils.isEmpty(gameState)) {
            drawSnake(g);
            drawFood(g);
        }

        // 如果游戏结束,显示游戏结束信息
        if (gameState.isGameOver()) {
            drawGameOver(g);
        } else if (!gameState.isRunning()) {
            // 如果游戏未开始,显示开始信息
            drawStartMessage(g);
        }
    }

    /**
     * 绘制蛇
     *
     * @param g 绘图对象
     */
    private void drawSnake(Graphics g) {
        // 绘制蛇身
        for (int i = 1; i < snake.getBody().size(); i++) {
            Position segment = snake.getBody().get(i);
            drawGridCell(g, segment, Color.GREEN);
        }

        // 绘制蛇头(与身体颜色不同)
        if (!snake.getBody().isEmpty()) {
            Position head = snake.getBody().get(0);
            drawGridCell(g, head, Color.RED);
        }
    }

    /**
     * 绘制食物
     *
     * @param g 绘图对象
     */
    private void drawFood(Graphics g) {
        drawGridCell(g, food.getPosition(), Color.YELLOW);
    }

    /**
     * 绘制单个网格单元
     *
     * @param g 绘图对象
     * @param position 位置
     * @param color 颜色
     */
    private void drawGridCell(Graphics g, Position position, Color color) {
        int x = position.getX() * gameState.getGridSize();
        int y = position.getY() * gameState.getGridSize();
        int size = gameState.getGridSize();

        g.setColor(color);
        g.fillRect(x, y, size, size);

        // 绘制边框,使格子更清晰
        g.setColor(Color.BLACK);
        g.drawRect(x, y, size, size);
    }

    /**
     * 绘制网格线
     *
     * @param g 绘图对象
     */
    private void drawGrid(Graphics g) {
        g.setColor(new Color(40, 40, 40)); // 深色网格线,不影响游戏视觉

        int gridSize = gameState.getGridSize();
        int width = getWidth();
        int height = getHeight();

        // 绘制水平线
        for (int y = 0; y <= height; y += gridSize) {
            g.drawLine(0, y, width, y);
        }

        // 绘制垂直线
        for (int x = 0; x <= width; x += gridSize) {
            g.drawLine(x, 0, x, height);
        }
    }

    /**
     * 绘制游戏结束信息
     *
     * @param g 绘图对象
     */
    private void drawGameOver(Graphics g) {
        String message = "游戏结束!得分: " + gameState.getScore() + " 按空格键重新开始";
        drawCenteredMessage(g, message, Color.RED, getHeight() / 2);
    }

    /**
     * 绘制开始游戏信息
     *
     * @param g 绘图对象
     */
    private void drawStartMessage(Graphics g) {
        String message = "按空格键开始游戏";
        drawCenteredMessage(g, message, Color.WHITE, getHeight() / 2);
    }

    /**
     * 在面板中央绘制文本信息
     *
     * @param g 绘图对象
     * @param message 要显示的消息
     * @param color 文本颜色
     * @param y Y坐标
     */
    private void drawCenteredMessage(Graphics g, String message, Color color, int y) {
        g.setColor(color);
        g.setFont(new Font("Arial", Font.BOLD, 20));
        FontMetrics metrics = g.getFontMetrics();
        int x = (getWidth() - metrics.stringWidth(message)) / 2;
        g.drawString(message, x, y);
    }
}
代码语言:javascript
复制

4.2 分数面板类

分数面板用于显示当前游戏的分数:

代码语言:javascript
复制
package com.game.snake.view;

import com.game.snake.model.GameState;
import lombok.Setter;

import javax.swing.*;
import java.awt.*;

/**
 * 分数面板类,显示游戏分数
 *
 * @author ken
 */
@Setter
public class ScorePanel extends JPanel {
    /**
     * 游戏状态
     */
    private GameState gameState;

    /**
     * 初始化分数面板
     */
    public ScorePanel() {
        setBackground(Color.DARK_GRAY);
        setPreferredSize(new Dimension(0, 30)); // 高度固定为30像素,宽度自适应
    }

    /**
     * 重写绘制方法,显示分数
     *
     * @param g 绘图对象
     */
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (gameState != null) {
            g.setColor(Color.WHITE);
            g.setFont(new Font("Arial", Font.PLAIN, 16));
            String scoreText = "分数: " + gameState.getScore();
            g.drawString(scoreText, 10, 20);
        }
    }
}
代码语言:javascript
复制

4.3 主窗口类

主窗口类用于组合各个面板,形成完整的游戏界面:

代码语言:javascript
复制
package com.game.snake.view;

import com.game.snake.controller.GameController;
import com.game.snake.model.GameState;
import lombok.Getter;

import javax.swing.*;
import java.awt.*;

/**
 * 游戏主窗口类
 *
 * @author ken
 */
@Getter
public class GameFrame extends JFrame {
    /**
     * 游戏面板
     */
    private final GamePanel gamePanel;

    /**
     * 分数面板
     */
    private final ScorePanel scorePanel;

    /**
     * 初始化游戏窗口
     *
     * @param title 窗口标题
     * @param gameState 游戏状态
     * @param controller 游戏控制器
     */
    public GameFrame(String title, GameState gameState, GameController controller) {
        super(title);

        // 初始化面板
        this.gamePanel = new GamePanel(gameState);
        this.scorePanel = new ScorePanel();

        // 设置面板的游戏状态引用
        this.gamePanel.setGameState(gameState);
        this.scorePanel.setGameState(gameState);

        // 配置窗口
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);

        // 添加键盘监听器
        addKeyListener(controller);

        // 布局管理
        setLayout(new BorderLayout());
        add(scorePanel, BorderLayout.NORTH);
        add(gamePanel, BorderLayout.CENTER);

        // 调整窗口大小以适应内容
        pack();

        // 窗口居中显示
        setLocationRelativeTo(null);
    }
}
代码语言:javascript
复制

五、控制层实现

5.1 游戏控制器类

游戏控制器是连接模型和视图的桥梁,负责处理用户输入和游戏逻辑:

代码语言:javascript
复制
package com.game.snake.controller;

import com.game.snake.model.Food;
import com.game.snake.model.GameState;
import com.game.snake.model.Position;
import com.game.snake.model.Snake;
import com.game.snake.view.GameFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
 * 游戏控制器类,处理用户输入和游戏逻辑
 *
 * @author ken
 */
@Slf4j
public class GameController extends KeyAdapter {
    /**
     * 游戏状态
     */
    private final GameState gameState;

    /**
     * 蛇对象
     */
    private Snake snake;

    /**
     * 食物对象
     */
    private Food food;

    /**
     * 游戏窗口
     */
    private final GameFrame gameFrame;

    /**
     * 游戏线程
     */
    private Thread gameThread;

    /**
     * 游戏速度(毫秒),值越小速度越快
     */
    private int gameSpeed = 150;

    /**
     * 初始化游戏控制器
     *
     * @param gameState 游戏状态
     * @param gameFrame 游戏窗口
     */
    public GameController(GameState gameState, GameFrame gameFrame) {
        this.gameState = gameState;
        this.gameFrame = gameFrame;

        // 初始化游戏对象
        initGameObjects();

        // 将游戏对象设置到视图
        gameFrame.getGamePanel().setSnake(snake);
        gameFrame.getGamePanel().setFood(food);
    }

    /**
     * 初始化游戏对象
     */
    private void initGameObjects() {
        // 初始化蛇,从中间位置开始,初始长度为3,初始方向向右
        int startX = gameState.getGridWidth() / 2;
        int startY = gameState.getGridHeight() / 2;
        this.snake = new Snake(startX, startY, 3, Snake.Direction.RIGHT);

        // 初始化食物
        this.food = new Food(gameState.getGridWidth(), gameState.getGridHeight());
        // 确保食物不会出现在蛇身上
        this.food.generateNewPosition(gameState.getGridWidth(), gameState.getGridHeight(), snake);
    }

    /**
     * 处理键盘事件
     *
     * @param e 键盘事件
     */
    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();

        // 空格键控制游戏开始/重新开始
        if (keyCode == KeyEvent.VK_SPACE) {
            if (gameState.isGameOver()) {
                // 游戏结束时,重新开始
                restartGame();
            } else if (!gameState.isRunning()) {
                // 游戏未运行时,开始游戏
                startGame();
            }
            return;
        }

        // 仅在游戏运行时处理方向键
        if (!gameState.isRunning() || gameState.isGameOver()) {
            return;
        }

        // 处理方向键
        switch (keyCode) {
            case KeyEvent.VK_UP:
                snake.changeDirection(Snake.Direction.UP);
                break;
            case KeyEvent.VK_DOWN:
                snake.changeDirection(Snake.Direction.DOWN);
                break;
            case KeyEvent.VK_LEFT:
                snake.changeDirection(Snake.Direction.LEFT);
                break;
            case KeyEvent.VK_RIGHT:
                snake.changeDirection(Snake.Direction.RIGHT);
                break;
            case KeyEvent.VK_P:
                // 暂停/继续游戏
                togglePause();
                break;
        }
    }

    /**
     * 开始游戏
     */
    public void startGame() {
        if (gameState.isRunning()) {
            return;
        }

        log.info("游戏开始");
        gameState.setRunning(true);
        gameState.setGameOver(false);

        // 创建并启动游戏线程
        gameThread = new Thread(this::gameLoop);
        gameThread.start();
    }

    /**
     * 重新开始游戏
     */
    public void restartGame() {
        log.info("游戏重新开始");

        // 停止当前游戏线程
        if (gameThread != null && gameThread.isAlive()) {
            gameState.setRunning(false);
            try {
                gameThread.join();
            } catch (InterruptedException e) {
                log.error("游戏线程中断异常", e);
                Thread.currentThread().interrupt();
            }
        }

        // 重置游戏状态和对象
        gameState.reset();
        initGameObjects();

        // 更新视图
        gameFrame.getGamePanel().setSnake(snake);
        gameFrame.getGamePanel().setFood(food);

        // 开始新游戏
        startGame();
    }

    /**
     * 暂停/继续游戏
     */
    public void togglePause() {
        if (gameState.isGameOver()) {
            return;
        }

        gameState.setRunning(!gameState.isRunning());

        if (gameState.isRunning()) {
            log.info("游戏继续");
            gameThread = new Thread(this::gameLoop);
            gameThread.start();
        } else {
            log.info("游戏暂停");
            if (gameThread != null && gameThread.isAlive()) {
                gameThread.interrupt();
            }
        }

        gameFrame.getGamePanel().repaint();
    }

    /**
     * 游戏主循环
     */
    private void gameLoop() {
        while (gameState.isRunning() && !gameState.isGameOver()) {
            try {
                // 控制游戏速度
                Thread.sleep(gameSpeed);

                // 移动蛇
                Position newHead = snake.move();

                // 检查是否撞到边界
                if (ObjectUtils.isEmpty(newHead) || gameState.isOutOfBounds(newHead)) {
                    gameOver();
                    break;
                }

                // 检查是否撞到自己
                if (snake.isCollidedWithSelf()) {
                    gameOver();
                    break;
                }

                // 检查是否吃到食物
                if (newHead.equals(food.getPosition())) {
                    // 增加分数
                    gameState.addScore(food.getScoreValue());
                    log.info("吃到食物,当前分数: {}", gameState.getScore());

                    // 生成新食物
                    food.generateNewPosition(gameState.getGridWidth(), gameState.getGridHeight(), snake);

                    // 随着分数增加,提高游戏难度(加快速度)
                    increaseDifficulty();
                } else {
                    // 没有吃到食物,移除尾部
                    snake.removeTail();
                }

                // 重绘游戏界面
                gameFrame.getGamePanel().repaint();
                gameFrame.getScorePanel().repaint();

            } catch (InterruptedException e) {
                // 捕获中断异常,通常是暂停游戏时
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    /**
     * 增加游戏难度(加快速度)
     */
    private void increaseDifficulty() {
        // 每得100分,加快一点速度,但有最低限制
        if (gameSpeed > 50) {
            int newSpeed = gameSpeed - (gameState.getScore() / 100) * 5;
            gameSpeed = Math.max(newSpeed, 50);
        }
    }

    /**
     * 游戏结束处理
     */
    private void gameOver() {
        log.info("游戏结束,最终分数: {}", gameState.getScore());
        gameState.setRunning(false);
        gameState.setGameOver(true);
        gameFrame.getGamePanel().repaint();
    }
}
代码语言:javascript
复制

六、主程序入口

主程序入口类用于初始化并启动游戏:

代码语言:javascript
复制
package com.game.snake;

import com.game.snake.controller.GameController;
import com.game.snake.model.GameState;
import com.game.snake.view.GameFrame;
import lombok.extern.slf4j.Slf4j;

import javax.swing.*;

/**
 * 贪吃蛇游戏主类
 *
 * @author ken
 */
@Slf4j
public class SnakeGame {
    /**
     * 游戏网格宽度(格子数量)
     */
    private static final int GRID_WIDTH = 30;

    /**
     * 游戏网格高度(格子数量)
     */
    private static final int GRID_HEIGHT = 20;

    /**
     * 每个格子的大小(像素)
     */
    private static final int GRID_SIZE = 25;

    /**
     * 主方法,程序入口
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        log.info("贪吃蛇游戏启动");

        // 在EDT(事件调度线程)中创建和显示GUI组件
        SwingUtilities.invokeLater(() -> {
            try {
                // 初始化游戏状态
                GameState gameState = new GameState(GRID_WIDTH, GRID_HEIGHT, GRID_SIZE);

                // 创建游戏控制器(临时为null,后面会设置)
                GameController controller = new GameController(gameState, null);

                // 创建游戏窗口
                GameFrame gameFrame = new GameFrame("Java贪吃蛇游戏", gameState, controller);

                // 完成控制器的初始化
                controller.setGameFrame(gameFrame);

                // 显示窗口
                gameFrame.setVisible(true);

                log.info("游戏窗口创建成功");
            } catch (Exception e) {
                log.error("游戏初始化失败", e);
                JOptionPane.showMessageDialog(null, "游戏初始化失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            }
        });
    }
}
代码语言:javascript
复制

注意:需要在GameController类中添加gameFrame的 setter 方法:

代码语言:javascript
复制
/**
 * 设置游戏窗口
 *
 * @param gameFrame 游戏窗口
 */
public void setGameFrame(GameFrame gameFrame) {
    this.gameFrame = gameFrame;
}
代码语言:javascript
复制

七、游戏功能测试与运行

7.1 编译

使用 Maven 命令编译打包,启动main方法启动游戏:

代码语言:javascript
复制
mvn clean package
代码语言:javascript
复制

7.2 游戏操作说明

  • 按空格键开始或重新开始游戏
  • 使用方向键(上、下、左、右)控制蛇的移动方向
  • 按 P 键暂停或继续游戏
  • 蛇吃到黄色食物后会变长,并增加分数
  • 撞到边界或自身时游戏结束

八、功能扩展与优化建议

8.1 可以添加的功能

  1. 不同难度级别:设置不同的初始速度和加速策略
  2. 特殊食物:随机出现有特殊效果的食物(如减速、加分加倍等)
  3. 排行榜:使用文件或数据库记录最高分
  4. 音效:添加游戏音效(吃食物、碰撞等)
  5. 皮肤选择:允许玩家选择不同的蛇和食物外观
  6. 关卡系统:随着分数增加,增加游戏难度或更换地图

8.2 代码优化建议

  1. 使用配置文件:将游戏参数(如网格大小、速度等)放入配置文件
  2. 增加单元测试:为核心逻辑添加单元测试,确保代码质量
  3. 使用更现代的 GUI 框架:考虑使用 JavaFX 替代 Swing,提供更好的视觉效果
  4. 添加日志系统:更详细的日志记录,便于调试和分析
  5. 代码重构:将游戏逻辑进一步拆分,提高代码复用性和可维护性

九、总结

通过本文的学习,你已经掌握了使用 Java 开发贪吃蛇游戏的全过程。这个项目虽然简单,但涵盖了 Java 编程的多个重要知识点:

  • 面向对象编程(封装、继承、多态)
  • Swing GUI 编程
  • 事件处理机制
  • 多线程编程
  • 游戏开发的基本原理(碰撞检测、游戏循环等)

希望这个项目能帮助你巩固 Java 基础知识,并激发你对游戏开发的兴趣。你可以基于这个基础版本,继续扩展功能,打造属于自己的贪吃蛇游戏。

示例中藏了一个小bug, 需要可运行完整代码的联系我哈。

以下是游戏截图:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言:为什么选择贪吃蛇作为 Java 入门实践?
  • 一、游戏核心原理与架构设计
    • 1.1 贪吃蛇游戏的核心要素
    • 1.2 游戏架构设计
    • 1.3 游戏运行流程图
  • 二、开发环境与项目配置
    • 2.1 开发环境
    • 2.2 Maven 依赖配置
  • 三、核心数据模型设计
    • 3.1 位置坐标类
    • 3.2 蛇对象类
    • 3.3 食物对象类
    • 3.4 游戏状态类
  • 四、视图层实现
    • 4.1 游戏面板类
    • 4.2 分数面板类
    • 4.3 主窗口类
  • 五、控制层实现
    • 5.1 游戏控制器类
  • 六、主程序入口
  • 七、游戏功能测试与运行
    • 7.1 编译
    • 7.2 游戏操作说明
  • 八、功能扩展与优化建议
    • 8.1 可以添加的功能
    • 8.2 代码优化建议
  • 九、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档