跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Java Swing 经典贪吃蛇游戏从零开发

基于 Java Swing 框架实现经典贪吃蛇游戏,涵盖环境配置、项目结构、核心逻辑流程及分步代码实现。重点讲解定时器驱动循环、事件监听、碰撞检测及面向对象设计模式,提供完整可运行源码与调试技巧,适合 Java GUI 入门学习。

神经兮兮发布于 2026/2/25更新于 2026/5/3018 浏览
Java Swing 经典贪吃蛇游戏从零开发

引言

贪吃蛇作为编程入门的经典实战案例,不仅能巩固面向对象编程思想,还能深入理解 Java Swing GUI 开发、事件监听、定时器控制、碰撞检测等核心技术。本文将基于 Java Swing 框架,从零拆解贪吃蛇游戏的开发全流程,提供逐行代码解析及调试技巧,帮助新手快速上手。

本文适用于 Java 8 及以上版本,无需额外依赖(基于 JDK 原生 Swing 库),全程采用面向对象设计,代码结构清晰、注释详尽,可直接复制运行。

一、开发环境准备

1.1 环境要求
  • JDK 版本:Java 8 及以上(兼容性最佳,避免 Swing API 差异问题)
  • 开发工具:IntelliJ IDEA、Eclipse、VS Code 均可(推荐 IDEA,语法提示更完善)
  • 依赖说明:无需额外导入第三方库,直接使用 JDK 自带的 javax.swing 和 java.awt 包实现 GUI 与绘图功能
1.2 项目结构

为保证代码可读性与可维护性,采用模块化设计,核心类分工如下:

SnakeGame/
├─ src/
│  ├─ GameFrame.java // 主窗口类(负责创建游戏窗口、加载面板)
│  ├─ GamePanel.java // 游戏核心面板(绘图、逻辑、事件监听)
│  ├─ Direction.java // 方向枚举类(规范蛇的移动方向)
│  └─ SnakeMain.java // 程序入口类(启动游戏)

二、游戏核心逻辑与流程图

贪吃蛇游戏的核心是'定时器驱动循环 + 事件响应 + 状态更新 + 碰撞检测 + 图形渲染',整体逻辑闭环如下:

2.1 整体流程图
  1. 程序启动
  2. 初始化游戏环境
  3. 创建 GameFrame 主窗口
  4. 初始化 GamePanel 面板(背景、字体、颜色)
  5. 初始化蛇(链表存储身体、初始方向向右)
  6. 随机生成食物(避开蛇身坐标)
  7. 启动 Swing 定时器(控制游戏速度)
  8. 进入游戏主循环(定时器触发)
  9. 事件处理:键盘按键触发?
    • 更新蛇移动方向(禁止 180°反向)
    • 切换游戏暂停/继续状态
    • 重置游戏状态(蛇、食物、分数)
    • 停止定时器,退出程序
  10. 状态更新(非暂停/游戏结束):根据当前方向计算蛇头新坐标
  11. 蛇头是否吃到食物?
    • 是:蛇身增长(链表添加新头部,不删尾部)+ 分数累加 + 重新生成食物
    • 否:蛇正常移动(添加新头部,删除尾部)
  12. 碰撞检测:蛇头撞墙/撞自身?
    • 是:设置游戏结束状态,停止定时器,显示结果
    • 否:游戏继续
  13. 图形渲染:绘制背景、蛇身(区分头尾颜色)、食物,绘制分数、游戏状态提示,刷新面板
2.2 核心逻辑说明
  • 蛇的存储与移动:用 LinkedList 存储蛇身坐标(每个节点为 Point 对象),移动时通过'添加新头部 + 删除尾部'实现,吃到食物时不删除尾部即实现增长,效率高于数组操作。
  • 方向控制:通过枚举 Direction 规范方向(上/下/左/右),禁止反向移动(如向右时不能直接向左),避免蛇自撞。
  • 碰撞检测:分两类——边界碰撞(蛇头超出面板网格范围)、自碰撞(蛇头坐标与身体任意节点坐标重叠)。
  • 定时器控制:使用 javax.swing.Timer 触发游戏循环,通过调整定时器延迟时间控制蛇的移动速度(延迟越小,速度越快)。
  • 三、分步实现与代码解析

    按'枚举定义→主窗口→游戏面板→程序入口'的顺序实现,每个类都标注详细注释,核心逻辑逐行解析。

    3.1 方向枚举类(Direction.java)

    用枚举规范蛇的移动方向,避免使用魔法值(数字/字符),提升代码可读性与可维护性。

    /**
     * 方向枚举类:规范蛇的移动方向
     */
    public enum Direction {
        UP,    // 上
        DOWN,  // 下
        LEFT,  // 左
        RIGHT  // 右
    }
    
    3.2 主窗口类(GameFrame.java)

    负责创建游戏主窗口,设置窗口属性(大小、标题、关闭行为),并将游戏面板加载到窗口中。

    import javax.swing.JFrame;
    
    /**
     * 游戏主窗口类:承载游戏面板,设置窗口基础属性
     */
    public class GameFrame extends JFrame {
        // 游戏窗口尺寸常量(像素)
        public static final int WIDTH = 600;
        public static final int HEIGHT = 480;
    
        public GameFrame() {
            // 初始化窗口属性
            initFrame();
            // 加载游戏面板
            GamePanel gamePanel = new GamePanel(this);
            this.add(gamePanel);
            // 窗口自适应面板大小(避免内容被截断)
            this.pack();
            // 窗口居中显示
            this.setLocationRelativeTo(null);
            // 窗口大小固定(禁止用户拉伸,保证游戏体验)
            this.setResizable(false);
        }
    
        /**
         * 初始化窗口核心属性
         */
        private void initFrame() {
            // 设置窗口标题
            this.setTitle("Java 贪吃蛇 | 方向键控制 | P 暂停 | R 重启");
            // 设置窗口关闭行为(关闭窗口时终止程序)
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // 设置窗口初始大小
            this.setSize(WIDTH, HEIGHT);
        }
    }
    
    3.3 游戏核心面板类(GamePanel.java)

    游戏的核心类,集成绘图、事件监听、逻辑处理、定时器控制等功能,实现 ActionListener(定时器事件)和 KeyListener(键盘事件)接口。

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.KeyEvent;
    import java.awt.event.KeyListener;
    import java.util.LinkedList;
    import java.util.Random;
    
    /**
     * 游戏核心面板类:负责绘图、事件监听、游戏逻辑处理
     */
    public class GamePanel extends JPanel implements ActionListener, KeyListener {
        // 网格大小(蛇身/食物的基础单位,像素)
        private static final int GRID_SIZE = 20;
        // 定时器延迟(毫秒),值越小速度越快(初始速度适中)
        private static final int TIMER_DELAY = 150;
        // 颜色常量(RGB 值,提升游戏视觉体验)
        private static final Color BG_COLOR = new Color(30, 30, 30); // 深色背景
        private static final Color SNAKE_HEAD_COLOR = new Color(0, 200, 0); // 蛇头深绿
        private static final Color SNAKE_BODY_COLOR = new Color(0, 150, 0); // 蛇身浅绿
        private static final Color FOOD_COLOR = new Color(255, 0, 0); // 食物红色
        private static final Color TEXT_COLOR = new Color(255, 255, 255); // 文字白色
    
        private GameFrame frame; // 关联主窗口
        private LinkedList<Point> snake; // 存储蛇身坐标(LinkedList 便于增删)
        private Point food; // 食物坐标
        private Direction direction; // 蛇当前移动方向
        private Timer timer; // 驱动游戏循环的定时器
        private int score; // 游戏分数
        private boolean isPaused; // 暂停状态标记
        private boolean isGameOver; // 游戏结束状态标记
        private Random random; // 随机生成食物位置
    
        public GamePanel(GameFrame frame) {
            this.frame = frame;
            // 初始化游戏参数
            initGame();
            // 初始化面板属性
            initPanel();
        }
    
        /**
         * 初始化游戏核心参数
         */
        private void initGame() {
            // 初始化蛇:3 节身体,横向排列在面板中间(网格坐标)
            snake = new LinkedList<>();
            int startX = frame.WIDTH / 2 / GRID_SIZE;
            int startY = frame.HEIGHT / 2 / GRID_SIZE;
            snake.add(new Point(startX, startY)); // 蛇头
            snake.add(new Point(startX - 1, startY)); // 第一节身体
            snake.add(new Point(startX - 2, startY)); // 第二节身体
            direction = Direction.RIGHT; // 初始方向向右
            score = 0;
            isPaused = false;
            isGameOver = false;
            random = new Random();
            // 生成初始食物(避开蛇身坐标)
            generateFood();
            // 初始化定时器,绑定事件监听器(每 TIMER_DELAY 毫秒触发一次)
            timer = new Timer(TIMER_DELAY, this);
            timer.start();
        }
    
        /**
         * 初始化面板属性
         */
        private void initPanel() {
            // 设置面板大小(与窗口一致)
            this.setPreferredSize(new Dimension(frame.WIDTH, frame.HEIGHT));
            // 设置面板背景色
            this.setBackground(BG_COLOR);
            // 注册键盘事件监听器(监听方向键、暂停键等)
            this.addKeyListener(this);
            // 设置面板可获取焦点(否则无法监听键盘事件)
            this.setFocusable(true);
            // 开启双缓冲(解决 Swing 绘图闪烁问题)
            this.setDoubleBuffered(true);
        }
    
        /**
         * 随机生成食物,确保不与蛇身重叠
         */
        private void generateFood() {
            while (true) {
                // 生成网格范围内的随机坐标(0 到最大网格数 -1)
                int x = random.nextInt(frame.WIDTH / GRID_SIZE);
                int y = random.nextInt(frame.HEIGHT / GRID_SIZE);
                food = new Point(x, y);
                // 若食物位置不在蛇身上,则生成有效,退出循环
                if (!snake.contains(food)) {
                    break;
                }
            }
        }
    
        /**
         * 绘制游戏画面(重写 JPanel 的 paintComponent 方法)
         * @param g 绘图上下文对象
         */
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            // 清空面板,避免绘图残留
            // 绘制蛇
            drawSnake(g);
            // 绘制食物
            drawFood(g);
            // 绘制 UI(分数、状态提示)
            drawUI(g);
        }
    
        /**
         * 绘制蛇身:区分蛇头与蛇身颜色,绘制圆角矩形提升美观度
         */
        private void drawSnake(Graphics g) {
            for (int i = 0; i < snake.size(); i++) {
                Point point = snake.get(i);
                // 网格坐标转换为像素坐标
                int x = point.x * GRID_SIZE;
                int y = point.y * GRID_SIZE;
                // 设置蛇头/蛇身颜色
                if (i == 0) {
                    g.setColor(SNAKE_HEAD_COLOR);
                } else {
                    g.setColor(SNAKE_BODY_COLOR);
                }
                // 绘制圆角矩形(蛇身),留 1 像素间隙区分节段
                g.fillRoundRect(x, y, GRID_SIZE - 1, GRID_SIZE - 1, 3, 3);
            }
        }
    
        /**
         * 绘制食物:红色圆形,与蛇身尺寸适配
         */
        private void drawFood(Graphics g) {
            int x = food.x * GRID_SIZE;
            int y = food.y * GRID_SIZE;
            g.setColor(FOOD_COLOR);
            g.fillOval(x, y, GRID_SIZE - 1, GRID_SIZE - 1);
        }
    
        /**
         * 绘制 UI 元素:分数、暂停提示、游戏结束提示、操作指引
         */
        private void drawUI(Graphics g) {
            g.setColor(TEXT_COLOR);
            g.setFont(new Font("宋体", Font.BOLD, 18));
            // 绘制分数
            g.drawString("分数:" + score, 20, 30);
            // 绘制操作指引(右上角)
            g.setFont(new Font("宋体", Font.PLAIN, 14));
            g.drawString("方向键:控制移动", frame.WIDTH - 150, 20);
            g.drawString("P:暂停/继续", frame.WIDTH - 150, 40);
            g.drawString("R:重启游戏", frame.WIDTH - 150, 60);
            // 绘制暂停提示
            if (isPaused && !isGameOver) {
                g.setFont(new Font("宋体", Font.BOLD, 24));
                String pauseText = "暂停中 | 按 P 继续";
                // 计算文字位置,居中显示
                int textX = (frame.WIDTH - g.getFontMetrics().stringWidth(pauseText)) / 2;
                int textY = frame.HEIGHT / 2;
                g.drawString(pauseText, textX, textY);
            }
            // 绘制游戏结束提示
            if (isGameOver) {
                g.setFont(new Font("宋体", Font.BOLD, 24));
                String overText1 = "游戏结束!最终得分:" + score;
                String overText2 = "按 R 键重启 | 关闭窗口退出";
                int textX1 = (frame.WIDTH - g.getFontMetrics().stringWidth(overText1)) / 2;
                int textX2 = (frame.WIDTH - g.getFontMetrics().stringWidth(overText2)) / 2;
                int textY1 = frame.HEIGHT / 2 - 30;
                int textY2 = frame.HEIGHT / 2 + 10;
                g.drawString(overText1, textX1, textY1);
                g.drawString(overText2, textX2, textY2);
            }
        }
    
        /**
         * 蛇移动逻辑:根据当前方向更新坐标,处理吃食物、碰撞检测
         */
        private void moveSnake() {
            // 获取当前蛇头坐标
            Point head = snake.getFirst();
            Point newHead = new Point(head);
            // 复制蛇头坐标,作为新头部基础
            // 根据方向更新新蛇头坐标(网格坐标)
            switch (direction) {
                case UP:
                    newHead.y--;
                    break;
                case DOWN:
                    newHead.y++;
                    break;
                case LEFT:
                    newHead.x--;
                    break;
                case RIGHT:
                    newHead.x++;
                    break;
            }
            // 碰撞检测
            if (checkCollision(newHead)) {
                isGameOver = true;
                timer.stop(); // 停止定时器,结束游戏循环
                return;
            }
            // 添加新蛇头到链表头部
            snake.addFirst(newHead);
            // 判断是否吃到食物:吃到则增长(不删尾部),否则正常移动(删尾部)
            if (newHead.equals(food)) {
                score += 10; // 每吃一个食物加 10 分
                generateFood(); // 重新生成食物
            } else {
                snake.removeLast(); // 删除尾部,实现移动
            }
        }
    
        /**
         * 碰撞检测:判断新蛇头是否撞墙或撞自身
         * @param newHead 新蛇头坐标
         * @return true=发生碰撞,false=无碰撞
         */
        private boolean checkCollision(Point newHead) {
            // 1. 边界碰撞:新蛇头超出面板网格范围
            if (newHead.x < 0 || newHead.x >= frame.WIDTH / GRID_SIZE || newHead.y < 0 || newHead.y >= frame.HEIGHT / GRID_SIZE) {
                return true;
            }
            // 2. 自碰撞:新蛇头与蛇身任意节点重叠
            for (int i = 1; i < snake.size(); i++) {
                if (newHead.equals(snake.get(i))) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 定时器事件回调(每帧执行一次,驱动游戏循环)
         */
        @Override
        public void actionPerformed(ActionEvent e) {
            // 非暂停、非游戏结束状态下,更新游戏状态
            if (!isPaused && !isGameOver) {
                moveSnake(); // 移动蛇
            }
            repaint(); // 刷新面板,重绘游戏画面
        }
    
        /**
         * 键盘按键按下事件处理(控制方向、暂停、重启)
         */
        @Override
        public void keyPressed(KeyEvent e) {
            int keyCode = e.getKeyCode();
            // 游戏结束时,仅响应 R 键重启
            if (isGameOver) {
                if (keyCode == KeyEvent.VK_R) {
                    initGame(); // 重置游戏状态
                }
                return;
            }
            // 暂停状态下,仅响应 P 键切换暂停/继续
            if (isPaused) {
                if (keyCode == KeyEvent.VK_P) {
                    isPaused = false;
                }
                return;
            }
            // 方向键控制:禁止 180°反向(如向右时不能直接向左)
            switch (keyCode) {
                case KeyEvent.VK_UP:
                    if (direction != Direction.DOWN) {
                        direction = Direction.UP;
                    }
                    break;
                case KeyEvent.VK_DOWN:
                    if (direction != Direction.UP) {
                        direction = Direction.DOWN;
                    }
                    break;
                case KeyEvent.VK_LEFT:
                    if (direction != Direction.RIGHT) {
                        direction = Direction.LEFT;
                    }
                    break;
                case KeyEvent.VK_RIGHT:
                    if (direction != Direction.LEFT) {
                        direction = Direction.RIGHT;
                    }
                    break;
                case KeyEvent.VK_P:
                    isPaused = true; // 切换暂停状态
                    break;
                case KeyEvent.VK_R:
                    initGame(); // 强制重启游戏
                    break;
            }
        }
    
        // 以下两个方法为 KeyListener 接口必填,无实际逻辑,留空即可
        @Override
        public void keyTyped(KeyEvent e) {}
    
        @Override
        public void keyReleased(KeyEvent e) {}
    }
    
    3.4 程序入口类(SnakeMain.java)

    简洁的入口类,负责启动游戏(创建主窗口并显示),符合 Java 程序设计规范。

    import javax.swing.SwingUtilities;
    
    /**
     * 程序入口类:启动贪吃蛇游戏
     * 用 SwingUtilities.invokeLater 确保 UI 组件在 EDT 线程(事件调度线程)中创建,避免线程安全问题
     */
    public class SnakeMain {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                GameFrame gameFrame = new GameFrame();
                gameFrame.setVisible(true); // 显示主窗口
            });
        }
    }
    

    四、代码运行与调试说明

    4.1 运行步骤
    1. 在 IDE 中创建 Java 项目(如命名为 SnakeGame);
    2. 创建上述 4 个类(Direction、GameFrame、GamePanel、SnakeMain),复制代码并粘贴;
    3. 运行 SnakeMain.java 的 main 方法,即可启动游戏。
    4.2 操作说明
    • 方向键↑↓←→:控制蛇的移动方向,禁止反向移动;
    • P 键:切换游戏暂停/继续状态;
    • R 键:游戏中/游戏结束后均可重启游戏;
    • 关闭窗口:直接终止游戏程序。
    4.3 常见问题调试
    • 键盘事件无响应:检查 GamePanel 是否调用 setFocusable(true),确保面板获取焦点;若仍无效,可在 GameFrame 初始化后调用 gamePanel.requestFocus() 强制获取焦点。
    • 绘图闪烁:已开启双缓冲(setDoubleBuffered(true)),若仍有闪烁,可检查是否在 paintComponent 外执行绘图操作(必须在该方法内绘图)。
    • 蛇移动速度异常:调整 TIMER_DELAY 常量(值越小速度越快,建议范围 100-200ms)。
    • 食物与蛇身重叠:检查 generateFood() 方法中的循环逻辑,确保食物位置不在蛇身链表中才退出循环。

    五、进阶功能拓展建议

    基础版本实现后,可添加以下功能提升游戏体验,适合进一步练手巩固 Java 知识点:

    1. 难度递增:每得 100 分减小定时器延迟(如减 10ms),最低延迟设为 50ms,增强游戏挑战性。
    2. 音效添加:使用 java.applet.AudioClip 类添加吃食物、碰撞、游戏开始的音效,提升沉浸感。
    3. 排行榜功能:用文件 IO(BufferedWriter/BufferedReader)将最高分保存到本地文件,启动时读取并显示历史最高分。
    4. 皮肤切换:提供多种蛇身、食物、背景颜色选择,存储在配置类中,支持用户手动切换。
    5. 边界穿越:修改碰撞检测逻辑,允许蛇从面板一侧穿出,从另一侧进入(如左边界穿出,从右边界进入)。
    6. 障碍物功能:随机生成固定障碍物,蛇撞到障碍物则游戏结束,增加游戏策略性。

    六、总结

    本文基于 Java Swing 框架实现了经典贪吃蛇游戏,核心在于掌握'定时器驱动循环 + 事件监听 + 状态管理'的 GUI 开发模式,同时通过面向对象设计将复杂逻辑拆解为独立模块(窗口、面板、方向枚举),提升代码可读性与可扩展性。

    通过本次实战,不仅能熟练掌握 Swing 的核心用法(窗口创建、绘图、事件处理、定时器),还能深入理解碰撞检测、坐标转换、链表操作等通用编程思想,为后续开发更复杂的 Java GUI 程序(如计算器、记事本、小游戏)打下坚实基础。

    目录

    1. 引言
    2. 一、开发环境准备
    3. 1.1 环境要求
    4. 1.2 项目结构
    5. 二、游戏核心逻辑与流程图
    6. 2.1 整体流程图
    7. 2.2 核心逻辑说明
    8. 三、分步实现与代码解析
    9. 3.1 方向枚举类(Direction.java)
    10. 3.2 主窗口类(GameFrame.java)
    11. 3.3 游戏核心面板类(GamePanel.java)
    12. 3.4 程序入口类(SnakeMain.java)
    13. 四、代码运行与调试说明
    14. 4.1 运行步骤
    15. 4.2 操作说明
    16. 4.3 常见问题调试
    17. 五、进阶功能拓展建议
    18. 六、总结
    • 💰 8折买阿里云服务器限时8折了解详情
    • Magick API 一键接入全球大模型注册送1000万token查看
    • 🤖 一键搭建Deepseek满血版了解详情
    • 一键打造专属AI 智能体了解详情
    极客日志微信公众号二维码

    微信扫一扫,关注极客日志

    微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

    更多推荐文章

    查看全部
    • Linux 基础指令与权限管理详解
    • Spring AI Tool 机制详解:让大模型调用外部工具
    • AstrBot 开源 AI 聊天机器人部署与配置指南
    • GitHub 汉化插件核心技术解析与实现
    • C++ unordered_map 与 unordered_set 认识及模拟实现
    • 二级 Python 考试真题及参考代码解析
    • FastGPT 集成 MCP 协议构建工具增强型 AI Agent
    • llama.cpp 多 GPU 分布式计算优化实践
    • Docker 快速部署 AstrBot+NapCat 打造 QQ 智能客服机器人
    • OpenClaw 实战:AI 摄像头访问与图像分析指南
    • 虚拟机vmx打不开Failed to lock the file的解决方法
    • 深入解析潜在扩散模型(LDMs)技术架构与原理
    • 数据结构:二叉树中的递归实现
    • 2024 信奥赛 C++ 提高组 CSP-S 复赛真题及题解:擂台游戏
    • 人工智能赋能传统医疗设备改造:路径、挑战与展望
    • Java ArrayList 底层方法自我实现
    • Stable Diffusion 4.9 升级:AI 绘画工具效率优化与商业应用
    • 宇树 Unitree 机器人 ROS 2 环境部署指南 (Humble + 真实硬件)
    • Java 继承机制详解:概念、语法与关键字使用
    • VSCode Copilot 接入 DeepSeek 模型配置指南

    相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online