资讯动态

Java课程设计泡泡糖游戏:从Swing骨架到BFS消除的完整实现

发布时间:2026/10/26 16:39:36 来源:尧图企业网站定制
简介这是一份面向高校计算机相关专业学生的Java程序设计实践项目资料以经典泡泡糖游戏为选题适合用作课程设计、期末大作业或编程实训参考。项目已通过导师指导并获得97分高分评价代码完整、下载即用无需额外修改即可运行能够帮助学习者快速理解Java游戏开发的整体流程与实现思路。资源包共包含201个文件整体约174.03MB其中35个java源文件构成游戏核心逻辑64个png与8个jpg提供界面与素材资源16个ttf字体、16个css样式及8个fxml界面文件支撑图形化交互另有14个md文档与8个xml配置用于说明和工程管理目录结构清晰便于按模块查阅。目前已有134人学习关注。通过该资源读者可获得完整的游戏源码、配套文档说明以及可运行的工程结构既能直接用于课程作业提交也能作为Java面向对象编程、GUI开发与事件处理等知识点的实战案例帮助梳理从需求分析到代码落地的完整实现路径。1. 泡泡糖游戏到底在练什么从一份 95 分课程设计说起很多人看到「Java程序设计实践泡泡糖游戏代码文档说明」这个标题第一反应是「又一个换皮小游戏」。但如果你真在课程设计或面试里被追问过就会发现它考的根本不是游戏好不好玩而是你能不能把面向对象、集合、事件驱动、文件读写这几块 Java 基础串成一个能跑、能讲、能改的完整工程。泡泡糖游戏的核心机制是「发射—碰撞—消除」听起来简单落到代码里就是坐标计算、状态管理、碰撞判定和渲染刷新四件事的配合。它适合两类人一类是正在做 Java 课程设计、需要一份结构清晰能拿高分的参考实现另一类是工作几年但 Java 基础生疏、想用一个有画面感的项目把面向对象和 Swing 重新捡起来的工程师。下面我按「先跑通、再拆解、后避坑」的顺序把这份东西讲透。2. 泡泡糖游戏的机制拆解与最小可运行骨架2.1 为什么用 Swing 而不是 JavaFX 做课程设计选型这件事课程设计和生产项目是两套逻辑。生产里我一般会推 JavaFX 或者直接上 Web 前端但课程设计场景下Swing 反而是更稳的选择。原因有三条第一JDK 自带不需要额外配 Maven 依赖评阅老师拿到 zip 解压就能跑不会因为环境问题扣分第二Swing 的paintComponent加Timer这套刷新模型足够直观能把「游戏循环」这个概念讲清楚而 JavaFX 的动画时间轴对新手来说抽象层太多第三网上关于 Swing 泡泡糖、消消乐的参考资料多遇到问题好查。常见做法是用JPanel做画布javax.swing.Timer做定时刷新MouseListener或MouseMotionListener处理发射方向。这里有个容易被忽略的点Timer的延迟参数不是帧率它只是「每隔多少毫秒触发一次 actionPerformed」。真正决定画面流畅度的是你在每次回调里做了多少计算。我一般把延迟设在 16 毫秒左右对应大约 60 次每秒的刷新再高对 Swing 没意义反而增加 CPU 占用。2.2 用三个类撑起整个游戏最小骨架代码不要一上来就写几千行。泡泡糖游戏的最小骨架只需要三个类GameFrame负责窗口GamePanel负责画布和逻辑Bubble负责单个泡泡的数据。先让一个泡泡能动、能画出来再往上加碰撞和消除。// GameFrame.java只做窗口容器逻辑全部交给 GamePanel import javax.swing.JFrame; public class GameFrame extends JFrame { public GameFrame() { setTitle(泡泡糖游戏); setSize(480, 640); // 竖屏比例适合泡泡糖的纵向布局 setResizable(false); // 固定窗口避免坐标换算出错 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); add(new GamePanel()); // 画布挂上去 setLocationRelativeTo(null);// 居中显示 } public static void main(String[] args) { new GameFrame().setVisible(true); } }// Bubble.java一个泡泡就是一组坐标加一个颜色 import java.awt.Color; public class Bubble { public int x, y; // 泡泡中心坐标 public int radius; // 半径碰撞判定要用 public Color color; // 颜色消除时按同色分组 public Bubble(int x, int y, int radius, Color color) { this.x x; this.y y; this.radius radius; this.color color; } // 两个泡泡是否相交用圆心距离和半径和比较 public boolean intersects(Bubble other) { int dx this.x - other.x; int dy this.y - other.y; int dist (int) Math.sqrt(dx * dx dy * dy); return dist this.radius other.radius; } }// GamePanel.java画布 定时刷新 一个测试泡泡 import javax.swing.*; import java.awt.*; public class GamePanel extends JPanel { private Bubble testBubble new Bubble(240, 300, 20, Color.PINK); private Timer timer; public GamePanel() { setBackground(Color.WHITE); // 16ms 触发一次约 60 帧每秒 timer new Timer(16, e - { // 这里先留空后面加移动和碰撞逻辑 repaint(); }); timer.start(); } Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 (Graphics2D) g; // 开启抗锯齿泡泡边缘不会锯齿状 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(testBubble.color); g2.fillOval(testBubble.x - testBubble.radius, testBubble.y - testBubble.radius, testBubble.radius * 2, testBubble.radius * 2); } }这三段代码的逻辑说明GameFrame只负责窗口生命周期不掺业务逻辑这是后面能扩展的前提。Bubble把坐标、半径、颜色打包成一个对象intersects方法用圆心距和半径和比较这是最朴素的碰撞判定够用且好懂。GamePanel里Timer每 16 毫秒触发一次repaintpaintComponent里先调super.paintComponent(g)清屏再画泡泡。参数上radius我一般设 18 到 22 之间太小点击判定难太大一屏放不下几个。窗口 480×640 是竖屏比例泡泡糖的纵向发射布局用这个尺寸最顺手。提示paintComponent里第一行必须是super.paintComponent(g)否则上一帧的画面不会清除泡泡会拖出一条残影这是新手最常见的翻车点之一。3. 碰撞判定、消除逻辑与关卡数据的落地写法3.1 网格化坐标把「玄学」碰撞变成可预测的数学如果泡泡可以停在任意像素位置碰撞判定会变成一场灾难浮点误差、边界重叠、消除后悬空全是坑。成熟做法是把泡泡吸附到网格上。假设泡泡直径 40 像素画布宽 480那么横向就是 12 列。每个泡泡的落点由「列号 × 40 20」算出中心坐标纵向同理。这样碰撞判定就退化成「相邻格子是否同色」而不是「两个圆是否相交」。// GridUtil.java像素坐标和网格坐标互转 public class GridUtil { public static final int CELL 40; // 每个格子 40 像素 public static final int COLS 12; // 横向 12 列 public static final int ROWS 15; // 纵向 15 行 // 网格坐标转像素中心坐标 public static int toPixelX(int col) { return col * CELL CELL / 2; } public static int toPixelY(int row) { return row * CELL CELL / 2; } // 像素坐标转最近的网格列用于发射时吸附 public static int toCol(int pixelX) { int col pixelX / CELL; return Math.max(0, Math.min(COLS - 1, col)); } }逻辑说明toPixelX和toPixelY把网格坐标映射到画布中心点toCol把鼠标点击的像素位置吸附到最近的列并做边界钳制防止越界。参数上CELL取 40 是权衡结果太小泡泡密集消除判定频繁太大一屏放不下几行游戏节奏拖沓。COLS和ROWS要和窗口尺寸匹配480 宽对应 12 列640 高对应 16 行留一行给发射区。3.2 同色消除的广度优先搜索实现消除逻辑的本质是「从落点出发找所有相连的同色泡泡数量达到 3 个及以上就消除」。这是一个典型的图连通性问题用广度优先搜索BFS最稳。用队列存待检查的格子用visited数组防重复找到的同色集合超过阈值就清空。import java.util.*; public class MatchFinder { // grid 存每个格子的颜色null 表示空 public static Listint[] findSameColor(Color[][] grid, int startRow, int startCol) { Color target grid[startRow][startCol]; if (target null) return Collections.emptyList(); Listint[] matched new ArrayList(); boolean[][] visited new boolean[grid.length][grid[0].length]; Queueint[] queue new LinkedList(); queue.offer(new int[]{startRow, startCol}); visited[startRow][startCol] true; int[][] dirs {{-1,0},{1,0},{0,-1},{0,1}}; // 上下左右四个方向 while (!queue.isEmpty()) { int[] cur queue.poll(); matched.add(cur); for (int[] d : dirs) { int nr cur[0] d[0]; int nc cur[1] d[1]; // 边界检查 未访问 同色 if (nr 0 nr grid.length nc 0 nc grid[0].length !visited[nr][nc] target.equals(grid[nr][nc])) { visited[nr][nc] true; queue.offer(new int[]{nr, nc}); } } } // 少于 3 个不算消除 return matched.size() 3 ? matched : Collections.emptyList(); } }逻辑说明dirs定义四个方向BFS 从起点扩散只收集同色且未访问的格子。matched.size() 3是消除阈值这个数字直接决定游戏难度设 2 太容易设 4 太难3 是经典值。参数上visited数组必须每次调用重新创建不能复用否则上一轮的访问记录会污染这一轮。返回空列表表示不消除调用方据此决定是否触发下落逻辑。3.3 关卡数据用文本文件存别硬编码在类里课程设计文档里经常要求「关卡可配置」硬编码在 Java 类里会被扣分。常见做法是用一个纯文本文件存每关的初始布局每行一个字符串字符代表颜色。读取时用Files.readAllLines解析成二维数组。import java.nio.file.*; import java.awt.Color; import java.util.*; public class LevelLoader { // 字符到颜色的映射R红 G绿 B蓝 Y黄 private static final MapCharacter, Color COLOR_MAP Map.of( R, Color.RED, G, Color.GREEN, B, Color.BLUE, Y, Color.YELLOW ); public static Color[][] load(String path) throws Exception { ListString lines Files.readAllLines(Paths.get(path)); int rows lines.size(); int cols lines.get(0).length(); Color[][] grid new Color[rows][cols]; for (int r 0; r rows; r) { for (int c 0; c cols; c) { char ch lines.get(r).charAt(c); grid[r][c] ch . ? null : COLOR_MAP.get(ch); } } return grid; } }逻辑说明.表示空格子其他字符查COLOR_MAP得到颜色。Map.of是 Java 9 之后的写法如果课程环境是 Java 8改成HashMap手动 put。参数上关卡文件的行列数必须和GridUtil.COLS、GridUtil.ROWS一致否则读取时数组越界。我一般会在加载后加一句校验行列不匹配直接抛异常比运行到一半崩溃好排查。4. 文档说明怎么写才不被扣分结构、图表与评分点对齐4.1 课程设计文档的六个必备章节代码能跑只是及格线95 分以上的项目文档占的分量不比代码轻。评阅老师看文档的时间通常只有几分钟所以结构必须一眼能看懂。我总结的必备章节是需求分析、总体设计、详细设计、核心算法说明、测试用例、总结与改进。其中「详细设计」和「核心算法说明」是拉开差距的地方前者要配类图后者要配流程图或伪代码。需求分析不要写成散文用表格列功能点每条功能点标注优先级和对应类。总体设计画一张类图标出GameFrame、GamePanel、Bubble、MatchFinder、LevelLoader之间的关系。详细设计逐个类讲职责、关键方法、参数含义。核心算法说明把 BFS 消除和碰撞判定单独拎出来讲配伪代码。测试用例列正常场景和边界场景比如「点击最左列」「消除后上方泡泡下落」「连续消除触发连锁」。4.2 类图与流程图的画法工具和粒度类图用 draw.io 或者 PlantUML 都行课程设计不要求多专业但要求关系正确。类与类之间是关联、依赖还是继承箭头不能画错。GamePanel依赖Bubble和MatchFinder用虚线箭头GameFrame关联GamePanel用实线箭头。粒度上一个类一个框框里写类名、关键属性、关键方法不用把每个 getter 都列上。流程图重点画两个游戏主循环和消除判定。主循环的流程是「定时器触发 → 更新泡泡位置 → 检测碰撞 → 检测消除 → 重绘」。消除判定的流程是「落点入队 → 出队检查四邻 → 同色入队 → 队列空 → 判断数量 → 达标则清除」。画流程图时注意判断框要有两个出口别画成一条线走到底这是文档里最常见的低级错误。4.3 把评分点映射到文档章节不同学校的评分表不一样但核心维度大同小异功能完整性、代码规范性、算法合理性、文档质量、创新点。我的做法是拿到评分表后在文档目录页旁边手写一张映射表比如「功能完整性 → 需求分析 测试用例」「算法合理性 → 核心算法说明」「创新点 → 总结与改进里的连锁消除或关卡编辑器」。这样写文档时不会漏项评阅老师也能快速找到给分点。注意文档里的代码片段要和实际代码一致别文档写一套、代码跑一套。评阅老师真会对着看不一致直接扣「文档与实现不符」。5. 避坑与排查那些让项目从 95 掉到 70 的细节5.1 现象泡泡消除后上方悬空不落原因消除逻辑只做了「清除同色」没做「重力下落」。网格里被清掉的格子变成 null但上方的泡泡还停在原位视觉上就是悬空。解决消除后从下往上遍历每一列把非 null 的格子往下压空位补 null。代码上用一个写指针从底部往上走遇到非空就写入最后把剩余位置置 null。5.2 现象连续快速点击导致泡泡重叠原因Timer的回调是异步的两次点击如果在同一帧内触发会生成两个落点相同的泡泡。解决加一个boolean isShooting标志发射后置 true泡泡落位后置 false期间忽略新的点击。这是最省事的做法比加锁简单效果也够。5.3 现象窗口缩放后坐标全乱原因GamePanel的尺寸随窗口变化但网格坐标是按固定 480×640 算的。解决setResizable(false)锁死窗口尺寸这是课程设计里最稳的选择。如果非要支持缩放就得在paintComponent里按当前尺寸重新计算CELL工作量翻倍不划算。5.4 现象Java 8 环境跑不起来Map.of原因Map.of是 Java 9 引入的很多学校机房还是 Java 8。解决把Map.of换成静态初始化块里的HashMap或者用Collections.unmodifiableMap。写代码前先确认目标 JDK 版本别等答辩现场才发现跑不起来那真是血泪经验。5.5 现象打包成 jar 后图片资源找不到原因用new File(res/bg.png)读资源jar 里没有文件系统路径。解决改用getClass().getResourceAsStream(/res/bg.png)资源放在src目录下打包时会被收进 jar。这个坑在课程设计里出现频率极高因为本地 IDE 跑得好好的一打包就白屏。6. 进阶技巧用状态机管理游戏流程让代码能讲也能改前面几章把「能跑」讲完了这一章讲「能改」。泡泡糖游戏写到后面最容易失控的地方是状态管理什么时候能发射、什么时候在消除动画、什么时候游戏结束如果全靠一堆 boolean 标志代码会变成一团乱麻。我的习惯是引入一个简单的状态机用枚举定义状态用 switch 驱动逻辑。public enum GameState { READY, // 等待发射 SHOOTING, // 泡泡飞行中 MATCHING, // 消除判定中 FALLING, // 下落动画中 GAME_OVER // 游戏结束 } // GamePanel 里维护当前状态 private GameState state GameState.READY; // 定时器回调里按状态分发 private void update() { switch (state) { case READY: // 等待鼠标点击不做位置更新 break; case SHOOTING: // 更新飞行泡泡位置检测碰撞后切 MATCHING break; case MATCHING: // 执行 BFS 消除有消除切 FALLING无消除切 READY break; case FALLING: // 执行下落落完切 READY break; case GAME_OVER: // 停止定时器显示结束画面 break; } }逻辑说明每个状态只做自己该做的事状态之间的切换由具体条件触发。READY只响应输入SHOOTING只更新飞行泡泡MATCHING只跑消除判定FALLING只做下落。这样每个方法都很短改一个状态不影响其他状态。参数上状态切换的时机要卡准碰撞检测到就切MATCHING消除返回非空就切FALLING下落完成切READY网格触顶切GAME_OVER。验证状态机是否写对有个简单办法在每次状态切换时打印一行日志跑一局游戏看日志顺序是否符合预期。如果出现READY直接跳FALLING说明MATCHING被跳过了多半是消除判定没触发。这个日志技巧在答辩时也能用老师问「你怎么保证逻辑正确」把日志一放比空口说强。再进一步可以把状态机画进文档的流程图里和代码里的枚举一一对应。这样文档和实现就绑定了评阅老师看到流程图再翻代码能对上号分数自然高。我自己的习惯是先画状态图再写枚举最后写 switch顺序反了容易漏状态。这套方法不只适用于泡泡糖游戏任何有明确阶段划分的小项目都能套比如贪吃蛇、俄罗斯方块、飞机大战。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑