简介这是基于Java实现的QQ连连看单机版V1.0高仿设计源码面向Java游戏开发学习者、课程设计与毕业设计学生帮助理解经典消除类游戏的完整实现思路。项目参考QQ连连看的界面与玩法涵盖图形界面设计、游戏核心逻辑、资源加载、音效播放与地图管理代码结构清晰按src与resource目录划分包含游戏主窗口、地图编辑器、音效处理、背景面板、图片工具等核心模块可配合使用说明文档逐个模块分析。资源包约3.98MB共98个文件以64个PNG图片、13个地图文件、7个WAV音效、5个Java源码为主另有BMP图片、MID音乐、Git忽略、Markdown文档及IDE配置等辅助文件素材与代码相互配套。地图覆盖光翼、太极、迷宫、方块阵列等主题音效包含消除、胜利、开始等游戏场景音音乐则提供背景配乐压缩包内还附带编译输出目录便于了解构建过程与文件组织方式。目前已有402人学习下载适合希望深入分析项目结构、研究Java游戏开发全流程的初中级开发者也可作为Java课程设计或毕业设计的参考案例。1. Java高仿QQ连连看单机版V1.0源码先判断值不值得下再动手拿到这套基于Java实现的QQ连连看单机版V1.0源码时我第一反应是翻文件清单——98个文件5个Java源码64张PNG图标12个.map地图文件7个WAV音效外加2个MID背景音乐。这个体量不是随手写个控制台小游戏糊弄人的而是把资源、逻辑、编辑器、音效动画整个闭环都做完了。对正在找Java课程设计案例、或者想研究连连看连通算法和地图编辑器实现的开发者来说这套Java游戏源码可以直接在IntelliJ IDEA里编译运行边跑边改。我先说结论它解决的是“从零写一个完整Java小游戏”的问题适合做课设、练手和二次开发的读者。2. 源码结构与游戏主循环五个Java文件的分工与连通算法2.1 五个Java文件的分工从窗口到消块的调用链src目录下共五个Java源文件GameFrame.java、MapEditer.java、Sound.java、BackgroundPanel.java、ImageUtil.java。它们正好对应一个完整游戏项目的五个横切面——窗口与主循环、关卡编辑、音频播放、背景绘制、图片资源处理。我翻源码时最先看的是GameFrame.java因为它是项目入口也是唯一能把其它四个文件串起来的类。GameFrame承担了JFrame窗口创建和游戏面板初始化的双重职责。常见做法是让它继承JFrame在构造方法里依次执行三个动作初始化窗口参数尺寸、标题、关闭策略、加载地图文件到二维数组、把背景面板和网格画布置入内容面板。鼠标点击方块的事件监听也在这里注册监听器拿到点击坐标后换算成网格行列再调用连通判定方法。// GameFrame构造方法中的初始化脉络 public GameFrame() { setTitle(QQ连连看 - 单机版V1.0); setSize(800, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); grid MapEditer.loadMap(resource/map/NO.1.map); BackgroundPanel bg new BackgroundPanel(resource/image/bg.bmp); setContentPane(bg); addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { handleClick(e.getX(), e.getY()); } }); setVisible(true); }这段代码把窗口、地图、背景、鼠标事件四件事一次性接上。grid变量是核心数据源所有渲染和判定都基于它handleClick内部会把像素坐标除以单元格边长得到行列数然后进入选中、判定、消除的流程。注意setContentPane传入的是BackgroundPanel而不是默认的JPanel这样背景图才能覆盖整个窗口后续所有绘制都叠加在这层背景之上。MapEditer.java是独立运行的编辑器入口它有自己的一套窗口和监听逻辑不依赖GameFrame。两者共享的是ImageUtil的工具方法和.map文件的格式约定。Sound.java是纯工具类方法基本是静态的GameFrame在消除成功、开局、胜利、失败时分别调用不同的播放入口。BackgroundPanel继承JPanel并重写paintComponent负责背景图和半透明遮罩的绘制。ImageUtil.java里全是静态方法负责图片缩放、图集切割、资源字节流读取这是整套源码底层被反复调用的工具类。这里有一条值得注意的调用链用户点击 - handleClick换算坐标 - 判定连通性 - Sound播音效 - ImageUtil取图标 - BackgroundPanel重绘。五个类各管一段耦合度控制得很清楚。不少课设项目喜欢把图片加载、音效播放、绘图逻辑全部堆在GameFrame一个类里改一处崩三处。这套源码的拆分方式反而更接近真实项目的组织习惯。2.2 游戏主循环与消块判定连连看的连通算法怎么写的连连看的核心不是渲染是连通判定。这个游戏的判定算法走的是“最多两个拐点”的经典路子从A方块往四个方向扩展能直通B方块则直接消除否则在A的直线可达点上做二次扩展检查有没有路径能到B。实现上一般用BFS或递归回溯避免写死四层循环因为网格尺寸变化后硬编码的嵌套循环就没法用了。// 伪代码连连看两点之间最多允许两个拐角 public boolean canLink(int x1, int y1, int x2, int y2) { if (grid[x1][y1] ! grid[x2][y2]) return false; // 0拐角两点同一直线且中间无障碍 if (isLineEmpty(x1, y1, x2, y2)) return true; // 1拐角折线经过一个拐点拆成两段直线依次判断 if (isLineEmpty(x1, y1, x1, y2) isLineEmpty(x1, y2, x2, y2)) return true; if (isLineEmpty(x1, y1, x2, y1) isLineEmpty(x2, y1, x2, y2)) return true; // 2拐角遍历起始点同行同列的空位做中转中转点与两点各自直线可达 for (int i 0; i rows; i) { if (grid[i][y1] 0) { if (isLineEmpty(x1, y1, i, y1) isLineEmpty(i, y1, i, y2) isLineEmpty(i, y2, x2, y2)) return true; } } for (int j 0; j cols; j) { if (grid[x1][j] 0) { if (isLineEmpty(x1, y1, x1, j) isLineEmpty(x1, j, x2, j) isLineEmpty(x2, j, x2, y2)) return true; } } return false; }这段伪代码把0、1、2拐角分成三组判断。第一段isLineEmpty检查两点间所有格子是否为空属于最直接的命中第二段通过计算拐点坐标把一条折线拆成两条直线分别验证第三段遍历与起始点同行或同列的所有空位每个空位充当一次中转再验证三段路径的通畅性。注意grid数组四周需要预留一圈空位经典做法是初始化时多分配两行两列否则靠边方块永远连不通很多人第一次写连连看就是栽在这里。主循环方面GameFrame没有用死循环而是用javax.swing.Timer驱动重绘每帧约16毫秒对应60FPS。每次tick做的事很轻量检查倒计时是否归零、调用repaint刷新画面、更新剩余可消除对数。这里有一个很重要但容易被忽略的手感细节当前选中方块要用高亮边框标出来候选路径上的拐点用半透明圆点画出。这套视觉反馈在高仿版本里刻意保留玩家能清楚看到自己的点击被系统正确识别。// 主循环的tick逻辑驱动重绘与状态刷新 Timer timer new Timer(16, e - { if (timeLeft 0) { gameOver(); return; } repaint(); }); timer.start();Timer的16毫秒间隔对应60FPS刷新率。timeLeft是剩余秒数每个tick里不直接递减而是在独立的秒级计时器里处理避免帧率和倒计时互相干扰。gameOver之后要停掉Timer否则窗口关闭后后台线程还在跑这是Swing项目里很常见的隐形bug。2.3 资源文件全清单图片、地图、音效各自喂给谁resource目录下分image、map、sound三个子目录对应三种资源类型。image目录里最核心的是block.png和block_gray.png前者是彩色图标图集按网格切割后作为游戏方块后者是同样的图形但做了灰度处理用于方块被锁定或消除后的过渡表现。start.png、select.png是主菜单的按钮与选中框edit.png是编辑器入口图标rearrangement.png对应重排功能progress_bar.jpg是顶部的倒计时进度条。map目录下是12个.map文件文件名即关卡名。光翼、太极、菱形、迷宫这四张偏视觉化布局NO.1、方块阵列、纵横交错、圈圈圆圆偏规则化布局love、男女、hi则是对称装饰图。每张图的行列尺寸和图标密度都不一样加载时GameFrame只按文件名读取不校验内部结构所以手工改坏一个文件就会直接导致关卡进不去。资源类型代表文件消费方实际用途PNG图标图集block.pngImageUtil切成网格方块图PNG置灰图集block_gray.pngGameFrame消除时灰度过渡PNG按钮/UIstart.png, select.pngGameFrame主菜单与选中态JPG进度条progress_bar.jpgBackgroundPanel顶部倒计时条BMP背景bg.bmpBackgroundPanel游戏主背景WAV音效itemboom.wav, elec.wavSound.java消除与闪电音效WAV事件音start.wav, end.wav, sel.wavSound.java开始、结束、选中WAV附加音flystar.wav, zhangsheng.wavSound.java飞星与掌声音效MIDI音乐bg.mid, edit.midSound.java游戏与编辑器BGM地图文件*.mapMapEditer / GameFrame关卡布局数据zhangsheng.wav从命名判断是掌声音效通常在单关通关时播放flystar.wav对应消除时飞出的星星特效和animation目录里的帧序列配合使用。音效文件全是WAV格式MID格式的两个文件分别是游戏背景音乐和编辑器背景音乐播放通道在Sound.java里分成两路避免互相抢占音频系统资源。3. 地图编辑器实战把一张自绘布局存成.map关卡文件3.1 .map文件的格式解析地图文件里存的是什么用文本编辑器直接打开resource/map下的任意.map文件会看到第一行是两个整数表示列数和行数后面每行是等长的数字序列用空格分隔。数字0表示空位非0数字表示对应索引的图标类型。这个格式没有做任何二进制压缩或加密好处是一目了然坏处是手工编辑时容易把行列数写错导致加载时数组越界。# 查看NO.1.map的前6行 head -n 6 resource/map/NO.1.map 10 9 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 1 1 2 2 3 3 4 4 5 5第一行“10 9”定义网格尺寸之后的数字串就是网格内容。数字范围取决于图集切割出的图标总数超出范围的值在绘制时取不到对应子图会画出空白块。这里的隐性约束是每种形状的方块数量必须是偶数否则最终会残留无法配对的孤块地图四周边界要留空否则角上的方块无法连通。MapEditer在保存时不会自动修正这些问题只负责把当前网格写到文件校验逻辑得在游戏加载时做。3.2 用MapEditer造一张图从摆块到存盘的完整步骤MapEditer启动后是独立的编辑窗口左侧是图标面板中间是网格画布右侧是保存与取消按钮。图标面板里的每个小图标都来自block.png按固定行列切割后的子图。左键点击图标选中再左键点击网格放置右键点击网格删除该格。顶部有网格尺寸输入框新建时会要求填行列数我一般控制在10×8到14×10之间太大反而失去可玩性。// MapEditer中保存地图的核心逻辑 public void saveMap(File target) throws IOException { try (BufferedWriter writer Files.newBufferedWriter( target.toPath(), StandardCharsets.UTF_8)) { writer.write(cols rows); writer.newLine(); for (int i 0; i rows; i) { for (int j 0; j cols; j) { writer.write(String.valueOf(grid[i][j])); if (j ! cols - 1) writer.write( ); } writer.newLine(); } } }saveMap先写列数和行数再逐行把二维数组写成空格分隔的数字串最后用try-with-resources保证文件流关闭。编码统一用UTF-8这是为了防止在不同操作系统间拷贝项目时出现乱码。网格数据grid[i][j]存放的是图标索引0代表空位。存盘路径指向resource/map目录文件名带.map后缀保存后即可在游戏主界面看到新关卡。实际操作里有个效率细节先布局再微调比直接画更高效。先把整个网格随机铺满图标再右键删掉部分格子制造空隙最后用“替换”功能把同一类型的图标挪到设计位置。这个流程能显著减少重复点击。摆位时要注意拉开同类图标之间的距离连续两格的同类型图标虽然可以消但会让游戏变得毫无挑战。3.3 地图难度设计12张内置地图的布局规律内置的12张地图覆盖了三种典型难度曲线。第一种是视觉对称图比如love、男女、hi图标按固定图案排列一眼能发现大量配对适合前几关第二种是规则阵列图比如方块阵列、纵横交错采用周期性重复排列玩家需要观察行列交叉点才能快速消除第三种是迷宫类比如迷宫、光翼空位形成隔断墙把图块分割成多个区域跨区域消除必须绕行两个拐角以上。这三种难度的本质差异是“平均连通路径长度”。对称图里的同类型图标大多在相邻格路径长度为0或1拐角阵列图中同类型块均匀分散但至少有直接相邻的配对迷宫图则大量出现需要2拐角的配对玩家计算路径的时间随之增加。MapEditer里可以通过控制空位比例和同类分布来调整难度空位比例在15%到20%时游戏偏休闲超过30%时路径变长卡顿感明显。这里有个新手上路容易踩的坑光顾着排版好看把某一批图标全部放在不连通区域。比如在迷宫图的角落画一个小隔间塞进去四个图标虽然数量成对但隔间外全是空格路径长度被拉得很长。我的习惯是编辑完先用游戏主程序加载一遍实际点击几对验证连通性发现无解立刻回编辑器调整布局而不是等到玩到一半卡死才回头改。4. 音效与动效接入Sound、ImageUtil和消除特效的联动4.1 Sound.java的音频加载方式WAV与MID的播放差异Sound.java在项目里承担所有音频播放任务分为WAV效果音和MIDI背景音乐两条通道。WAV效果音的特点是短促、触发频率高适合用Clip来播放MIDI是序列音乐适合用Sequencer循环播放。我第一次跑这个项目时在播放背景音乐上犯了个错误——直接把MIDI也塞进Clip里结果每点一次消除就卡顿一下后来看了Sound.java的实现才发现两路通道是分开的。// Sound.java中WAV与MID两套播放通道的示意 public class Sound { private static Clip clip; private static Sequencer sequencer; public static void playWav(String path) { try { AudioInputStream stream AudioSystem.getAudioInputStream( new File(path)); clip AudioSystem.getClip(); clip.open(stream); clip.start(); } catch (Exception e) { e.printStackTrace(); } } public static void playMidi(String path, boolean loop) { try { sequencer MidiSystem.getSequencer(); sequencer.open(); sequencer.setSequence(MidiSystem.getSequence(new File(path))); if (loop) { sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY); } sequencer.start(); } catch (Exception e) { e.printStackTrace(); } } }playWav走Clip通道每次调用都会闭合并重新打开新的AudioInputStream适合播放消除、选中这类高频率短音。playMidi走Sequencer通道open之后设置Sequenceloop参数控制是否循环播放。背景音乐在进入游戏时调用一次并循环编辑器里的edit.mid则是打开MapEditer时播放。两个通道相互独立WAV播放不会打断MIDI这是高仿版本能保留完整听觉体验的关键。4.2 ImageUtil的图片切割技巧block_gray与block的对应关系block.png是一张包含多个图标的图集ImageUtil负责把它按行列切割成独立小图。切割原理是用BufferedImage.getSubimage按图集的宽高除以行列数得到每个小图的尺寸再按偏移量取子图。这里有个容易翻车的细节图集尺寸如果不能被行列数整除靠边的那一行一列小图尺寸会错位直接抛出RasterFormatException。// ImageUtil切割图集的核心方法 public static ListBufferedImage splitAtlas(BufferedImage atlas, int cols, int rows) { int cellWidth atlas.getWidth() / cols; int cellHeight atlas.getHeight() / rows; ListBufferedImage images new ArrayList(cols * rows); for (int row 0; row rows; row) { for (int col 0; col cols; col) { images.add(atlas.getSubimage( col * cellWidth, row * cellHeight, cellWidth, cellHeight)); } } return images; }splitAtlas的入参atlas是完整图集cols和rows是图集的行列分割数。计算cellWidth和cellHeight时用的是整数除法如果图集尺寸不能被行列数整除余数会导致最后一行一列的子图尺寸错位getSubimage会抛RasterFormatException。这个异常是切图最常见的踩坑点出现时优先检查图集的原始像素尺寸与行列数是否匹配。block_gray.png与block.png的图集网格完全一致只是每个子图都做了灰度化处理。GameFrame在消除动画的初始阶段把被选中的方块切换成灰度图再让透明度逐渐降低最后消失。这种两帧过渡的手法在视觉上比直接消失柔和很多代码实现也不复杂——在paintComponent中根据动画进度选择画彩色图还是灰度图同时调整AlphaComposite的透明度。4.3 消除特效的触发逻辑闪电、爆炸与飞星特效触发顺序绑定在消除判定成功之后、方块移除之前。animation目录下有两个子目录lightning_x和lightning_y分别存放横向闪电和纵向闪电的帧序列explode是爆炸帧序列。触发逻辑根据两个被消除方块的位置关系选择动画类型——同行用lightning_y同列用lightning_x斜向或跨行跨列则用explode。// 消除成功后的特效触发与延时消除 public void playMatchEffect(int x1, int y1, int x2, int y2) { Sound.playWav(resource/sound/elec.wav); String effectDir; if (y1 y2) { effectDir resource/animation/lightning_y; } else if (x1 x2) { effectDir resource/animation/lightning_x; } else { effectDir resource/animation/explode; } playFrames(effectDir, x1, y1, x2, y2); Timer removeTimer new Timer(150, e - { removePair(x1, y1, x2, y2); repaint(); }); removeTimer.setRepeats(false); removeTimer.start(); }playMatchEffect先播音效再根据坐标关系选定动画序列。effectDir的判定顺序先处理同行同列这两种简单情况剩下的全部走explode这样写比逐个枚举坐标组合要简洁。Timer延时150毫秒后执行回合移除和重绘让玩家先看到动画再看到方块消失。帧序列的播放是逐帧切换图片索引播放完后再触发真正的数据移除。特效和音效的节奏配合直接决定游戏手感。延时太长会让玩家觉得卡顿太短则眼睛跟不上。150毫秒是我调过几版之后觉得最合适的值如果想要更紧凑的反馈可以改到80毫秒但爆炸类多帧动画会来不及播放完整。想调这个节奏的在playMatchEffect里改Timer的构造参数即可。5. 避坑与常见问题导入IDEA、编码乱码、无响应排查5.1 现象IDEA导入后src目录标红或依赖找不到现象用IntelliJ IDEA直接以Open方式导入项目文件夹左侧Project树里src显示为红色GameFrame.java等文件顶部全是红波浪线提示cannot resolve symbol。原因项目里虽然有linkgame.iml这个IDEA模块文件但直接打开文件夹时IDEA可能没有把src和resource正确标记为源码目录和资源目录导致源码不在编译路径上。解决打开File - Project Structure - Modules选中项目模块后在Sources标签页把src目录标记为Sources把resource目录标记为Resources。如果仍然报错确认Project SDK下拉框里选择了正确的JDK版本一般JDK 8或JDK 11都可以。这是个纯JavaSE项目不需要额外引入Maven或Gradle依赖不用担心第三方库缺失。5.2 现象地图加载乱码或按钮不响应现象游戏能启动但地图上某些区域显示空白或乱码关卡列表进不去主菜单按钮点了没反应。原因乱码多半是.map文件的读写编码不一致。MapEditer在Windows上默认用GBK保存但GameFrame在启动时如果没有指定解码字符集JVM默认按操作系统编码读取换到Linux或macOS后就会解析出错行列数错位导致网格越界。按钮不响应则通常是鼠标监听器绑定错了组件——事件处理器挂在JFrame上而实际的可点击区域是内部的自定义面板。解决读文件时显式指定UTF-8用new InputStreamReader结合StandardCharsets.UTF_8不要用默认的FileReader。监听器要绑定到真正接收鼠标事件的组件上比如游戏网格所在的JPanel同时用SwingUtilities.convertPointToScreen之类的方法把面板坐标换算成网格坐标。这段血泪经验是我在项目里排查了好几轮才总结出来的。// 读取map文件时显式指定UTF-8编码 try (BufferedReader reader Files.newBufferedReader( Paths.get(path), StandardCharsets.UTF_8)) { String firstLine reader.readLine(); String[] size firstLine.split( ); cols Integer.parseInt(size[0]); rows Integer.parseInt(size[1]); }这段读取逻辑先读第一行拿到行列数再继续读网格数据。显式指定UTF-8解决了跨平台编码漂移的问题能避免九成以上的乱码故障。如果你接手这套源码后第一件事就是改这里后面会省很多事。5.3 现象WAV音效播放异常或MIDI无声现象游戏运行正常但点击方块时没有消除音效或者背景音乐在某些电脑上静音另一些电脑上正常。原因Java的AudioSystem.getClip对WAV编码有严格限制必须是PCM编码的16位采样。如果WAV文件是ADPCM压缩格式open时会直接抛IllegalArgumentException若是采样率过高播放会失真。MIDI无声的问题不同——Sequencer能正常打开但系统没有可用的Soundbank音色库音符事件没有乐器发声。解决用格式转换工具把WAV统一转成16bit PCM 44100Hz再替换进resource/sound目录。MIDI这边不需要改代码只需要目标系统有可用的Soundbank。Windows自带Microsoft GS波表macOS在系统设置里安装软波表后即可Linux通常需要额外安装fluid-soundfont包。如果不想折腾直接换MP3背景音乐会省心得多。5.4 现象图片模糊或边角发白现象在同一台高分屏上游戏窗口的图标看起来发虚切出来的方块边缘有白色杂边尤其深色背景上特别明显。原因高DPI下JVM没有主动适配Windows缩放比例默认按物理像素渲染再被系统拉伸模糊就产生了。白边则是因为PNG图集在缩放时半透明边缘像素被插值算法算成了不透明值叠加到深色背景上就成了白边。解决在main方法入口设置uiScale参数例如System.setProperty(sun.java2d.uiScale, 1.5)或者根据屏幕DPI动态适配。图像处理上用带alpha通道的BufferedImage.TYPE_INT_ARGB保留原始PNG的透明信息再用Graphics2D.drawImage配合RenderingHints.VALUE_INTERPOLATION_BILINEAR做缩放边缘过渡就能保住。5.5 现象编译通过但点击方块没有任何反应现象程序编译成功窗口正常显示但点击任何方块都没有选中高亮也没有消除动作。原因这类问题九成出在坐标换算上。鼠标监听到的是窗口坐标而网格画布在窗口内可能有偏移——上面是工具栏左侧是图标面板直接拿e.getX()和e.getY()除以单元格宽高得到的结果和真实网格行列对应不上。解决先做一个坐标换算核对把点击点减去面板的起始偏移量再除以单元格尺寸。具体来说记录网格面板在窗口内的x、y偏移用(e.getX() - offsetX) / cellSize计算列(e.getY() - offsetY) / cellSize计算行然后判断结果是否在网格范围内。若点在网格外直接忽略避免数组越界。还有个容易漏的点选中第一个方块后第二个方块如果类型不同或连通失败需要把高亮状态重置回“只有一个待选”否则下一次点击会带着上一轮的状态继续算出现“点了半天消不掉”的假死现象。我一般会在handleClick里维护一个int selectedX -1来表示当前选中状态每当第二次点击判定失败后清空这个习惯帮我躲过了好几次黑匣子式的隐蔽bug。6. 进阶玩法改节奏参数、换皮肤、把MIDI换成MP36.1 调整游戏节奏的关键参数游戏节奏由三处参数控制总倒计时秒数、消除动画延时、重排次数上限。最好在GameFrame顶部划出一块常量区集中管理后续调整难度只改一处。下面这张表是我调参时用的基准值参数位置建议基准值说明倒计时总长GameFrame常量区60~120秒高级关卡建议90秒消除动画延时playMatchEffect100~250ms闪电动画保持150ms重排次数上限GameFrame成员变量3~5次超过会降低容错率6.2 自己设计一套图标皮肤替换皮肤最简单的做法是直接替换block.png。ImageUtil在启动时按固定行列切割这张图集新图保持同样的网格结构同名文件覆盖即可。如果新图集尺寸变了记得同步修改splitAtlas的cols和rows参数这一步漏掉会导致整张棋盘只显示一格图标。我试过用一套像素风图标替换默认图标替换完整个游戏的气质变化很明显。注意透明背景不要带白底否则切出的方块在深色棋盘上会出现白色边缘。block_gray.png和block.png要同步替换否则消除动画里的灰度过渡帧会和新图标风格不搭。6.3 把MIDI背景音乐换成MP3MIDI依赖系统Soundbank换个机器音色可能完全不一样。想让背景音乐在所有平台上听感一致一个直接的做法是引入JavaFX的MediaPlayer播放MP3替换Sound.java里的MIDI通道。// 用JavaFX MediaPlayer播放MP3背景音乐 Media media new Media(new File(resource/sound/bg.mp3).toURI().toString()); MediaPlayer mediaPlayer new MediaPlayer(media); mediaPlayer.setCycleCount(MediaPlayer.INDEFINITE); mediaPlayer.play();MediaPlayer以URI方式读取文件setCycleCount设为无限循环。代价是引入了JavaFX模块依赖运行时需要加--module-path参数但换来的是跨平台一致的音色。从那以后我每次跑这个项目都会先看一眼src目录是不是已经被IDEA正确标记再改任何资源文件前都会先备份原图。换皮、调难度、改音效每个环节动完都要完整跑一遍游戏流程这个习惯帮我避开了好几次“改完一处、坏掉三处”的翻车现场。希望帮到你。本文还有配套的精品资源点击获取