恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Java Swing游戏开发实战:大鱼吃小鱼教学骨架解析

  • 首页
  • 资讯中心
  • /
  • Java Swing游戏开发实战:大鱼吃小鱼教学骨架解析

相关资讯

Brave浏览器源码编译实战:从环境搭建到定制拦截规则 2026/10/18 9:36:47
Graphormer官方实现:图结构编码与注意力机制实战 2026/10/18 9:36:47
冠豪猪优化器CPO实现城市三维无人机路径规划:原理与Matlab实战 2026/10/18 9:36:47

最新资讯

用AI Agent聊聊天就能完成数据抓取与报告生成?一文讲透原理与实战
规格管理工具实测与自建方案:从文档混乱到自动化校验
EarlGrey iOS UI自动化测试实战:同步机制、元素匹配与工程接入
智能UI逆向工程:解构ChatGPT交互状态与行为逻辑
MS-HUB-M实战拆解:模块化架构、权限模型与落地避坑
Linux下NVML初始化失败的系统级诊断与修复

今日推荐

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Java Swing游戏开发实战:大鱼吃小鱼教学骨架解析

发布时间:2026/10/18 9:41:47
Java Swing游戏开发实战:大鱼吃小鱼教学骨架解析 简介这是一份基于Java Swing开发的‘大鱼吃小鱼’经典小游戏完整源码工程面向Java初学者与GUI编程入门者帮助理解事件驱动、图形绘制、集合管理与简单游戏逻辑设计。资源包含43个文件涵盖14个核心Java源码含Ui主窗口、Entity实体类、用户登录注册模块、15个编译后class文件、8张PNG/JPG游戏素材如fish*.png、bg.jpg、start.png及Eclipse项目配置文件.project、.classpath、.prefs等整体压缩包仅1.97MB轻量易导入。已有273人学习下载代码结构清晰分层明确src目录组织业务逻辑images集中管理资源User.text模拟简易用户数据存储。读者可直接运行体验完整游戏流程——从用户注册登录到键盘控制己方鱼移动、碰撞检测、吞食/被吞判定再到多鱼AI引导与动态集合渲染是掌握Swing绘图、线程基础与面向对象设计的优质实践案例。1. 这不是怀旧彩蛋而是一套可跑、可调、可嵌入教学系统的 Java Swing 游戏骨架某高校实训课上导师让 A 同学三天内交一个“能体现面向对象设计事件驱动图形渲染”的 Java 小项目。他翻遍 GitHub 和 CSDN最后选中这个「大鱼吃小鱼」——不是因为画风多炫而是它把 Swing 的核心能力全摊开了窗口生命周期管理、双缓冲绘图、键盘事件分发、对象集合动态渲染、碰撞判定与状态切换连用户登录注册都用最朴素的文件存储做了闭环。它不依赖 Spring Boot、不碰数据库连接池、不走 REST API纯 Java SE AWT/Swing 原生 API 实现JDK 8 即可编译运行。新手照着跑通后能立刻看清「一个 Swing 程序从 main() 到 paintComponent() 再到 keyPressed() 的完整脉络」熟手拆开 src 目录三分钟就能定位到鱼群移动逻辑、碰撞检测边界、用户凭证校验入口——所有代码都在眼皮底下没有黑匣子也没有玄学配置。如果你正卡在「Swing 怎么组织 MVC 结构」「如何避免 repaint() 导致画面撕裂」「Swing Timer 和线程安全怎么平衡」这类问题上这个项目就是你该打开的第一个 .jar。2. 项目结构与技术栈解剖从 .classpath 到 images/ 下的 10 张 PNG这个压缩包表面看是「大鱼吃小鱼.rar」但解压后你会看到一个典型的 Eclipse 工程结构没有 Maven pom.xml没有 Gradle 配置靠 .classpath 和 .project 文件维持 IDE 兼容性。它不是玩具 Demo而是一个具备生产级模块划分意识的 Swing 应用UI 层、实体层、资源层、持久化层全部物理隔离。我把它重排成标准目录树后发现其结构比多数教学代码更干净——连图片命名都带语义fish03.png 是第三种敌方小鱼bg.jpg 是唯一背景图这种细节对后续扩展意义重大。2.1 工程根目录Eclipse 元数据与资源路径的隐含约定大鱼吃小鱼/ ├── .classpath ← 指定源码路径为 src/输出目录为 bin/ ├── .project ← Eclipse 项目标识含 buildSpecJava Builder ├── .settings/ │ ├── org.eclipse.jdt.core.prefs ← 编码为 UTF-8源码兼容 JDK 1.8 │ └── org.eclipse.core.resources.prefs ├── myFile/ ← 用户数据目录注意不是系统临时目录 │ └── User.text ← 明文存储用户名密码SHA-256 加密前的原始拼接 ├── images/ ← 所有资源集中地共 10 张 PNG 1 张 JPG │ ├── bg.jpg ← 800×600 背景图paintBackground() 中 drawImage() │ ├── fish.png ← 玩家主鱼蓝色大鱼宽高约 64×48 │ ├── fish03.png ~ fish17.png ← 7 种敌方小鱼红/黄/绿/紫等色系尺寸递减 │ ├── start.png ← 开始界面按钮贴图非 Swing JButton而是 drawImage 绘制 │ └── fish12.png ← 特殊体型鱼头部偏大用于测试碰撞半径容错 ├── src/ ← Java 源码根目录包结构严格按功能分层 │ └── com/ │ └── entity/ ← 核心实体类Fish抽象基类、PlayerFish继承、EnemyFish继承、User登录凭证 ├── bin/ ← 编译输出目录含 class 文件和 resources如 User.text 被复制至此 └── 大鱼吃小鱼.java ← 主类含 main()new Ui().setVisible(true)提示myFile/User.text是程序首次运行时自动创建的内容格式为username:password冒号分隔无空格。它被硬编码在com.entity.User.login()方法中路径拼接为myFile File.separator User.text。这不是最佳实践但对教学场景足够透明——你想改存储方式直接进 User.java 改两行就行。2.2 src/com/entity/面向对象建模的真实落地整个游戏逻辑围绕Fish抽象类展开它不是空接口而是定义了所有鱼共有的行为契约// src/com/entity/Fish.java public abstract class Fish { protected int x, y; // 当前坐标像素 protected int width, height; // 宽高用于碰撞矩形 protected int speedX, speedY; // 水平/垂直速度每帧偏移量 protected boolean isAlive; // 生存状态被吃后设为 false避免重复绘制 public abstract void move(); // 子类必须实现移动逻辑 public abstract Rectangle getBounds(); // 返回碰撞检测矩形AABB public abstract void draw(Graphics g); // 绘制自身传入 Graphics 对象 }PlayerFish和EnemyFish分别继承它但实现天差地别PlayerFish.move()是空方法——因为它的移动由键盘事件实时驱动keyPressed()中直接改x/yEnemyFish.move()则是真·移动随机生成方向向量叠加speedX/speedY并做边界反弹if (x 0 || x 750) speedX -speedXgetBounds()在PlayerFish中返回new Rectangle(x, y, 64, 48)而在EnemyFish中根据fishType构造时传入 0~7返回不同尺寸比如fishType3时返回new Rectangle(x, y, 32, 24)。这种设计让「同一种操作move」在不同角色上有完全不同的语义正是 Swing 游戏里多态价值的直观体现。2.3 images/ 资源规范为什么 fish12.png 不是随便编号你可能觉得fish03.png到fish17.png的编号很乱但实际它们对应EnemyFish构造参数int type。查看Ui.java中鱼群初始化代码// src/Ui.java 片段 for (int i 0; i 15; i) { int type (int)(Math.random() * 8); // 0~7 共 8 种类型 EnemyFish enemy new EnemyFish(100 i*30, 100, type); enemyList.add(enemy); }而EnemyFish构造器中// src/com/entity/EnemyFish.java public EnemyFish(int x, int y, int type) { this.x x; this.y y; this.type type; switch (type) { case 0: this.img loadImage(images/fish.png); break; // 主鱼同款 case 1: this.img loadImage(images/fish03.png); break; // 小一号 case 2: this.img loadImage(images/fish06.png); break; // 更小变色 case 3: this.img loadImage(images/fish08.png); break; // 扁平化体型 case 4: this.img loadImage(images/fish12.png); break; // 头部放大重点用于测试碰撞半径 case 5: this.img loadImage(images/fish13.png); break; case 6: this.img loadImage(images/fish17.png); break; default:this.img loadImage(images/fish.png); } // 根据 type 设置 width/height/speed确保小鱼游得更快、更难抓 setDimensionsByType(); }setDimensionsByType()方法里type4即fish12.png对应的width40, height32, speed3比type0的width64, height48, speed1快 3 倍且碰撞框更紧凑——这说明作者在用资源编号承载业务逻辑不是随手打的标签。你要新增一种鱼只需加一张图、在 switch 里加 case、在setDimensionsByType()里配参不用动任何其他模块。2.4 用户系统文件存储的极简主义实现登录注册没用 JDBC没用 SQLite就靠User.text一行一用户。User.java里两个关键方法// src/com/entity/User.java public static boolean login(String username, String password) { try (BufferedReader reader new BufferedReader( new FileReader(myFile File.separator User.text))) { String line; while ((line reader.readLine()) ! null) { String[] parts line.split(:); if (parts.length 2 parts[0].equals(username)) { // 注意这里明文比对教学用途非生产环境 return parts[1].equals(password); } } } catch (IOException e) { e.printStackTrace(); } return false; } public static boolean register(String username, String password) { try (FileWriter writer new FileWriter( myFile File.separator User.text, true)) { // true 表示追加 writer.write(username : password \n); return true; } catch (IOException e) { e.printStackTrace(); return false; } }注意FileReader和FileWriter默认使用平台默认编码Windows 是 GBK而 Eclipse 项目设置为 UTF-8。如果用户输入中文用户名此处会乱码。这是本项目第一个真实踩坑点我们放在第 4 章统一讲。3. 运行与调试从 JDK 8 到双缓冲防闪烁的实操链路这个项目不需要 Tomcat、不依赖任何外部 JAR但要跑起来你得亲手过一遍从环境准备到画面稳定的全流程。很多教程只说「右键 Run As Java Application」却不说为什么有时画面撕裂、有时键盘失灵、有时图片加载为空——这些恰恰是 Swing 新手最常翻车的地方。3.1 环境准备JDK 8 是底线Eclipse 配置是关键JDK 版本必须 JDK 8u202 或更高低版本SwingUtilities.invokeLater()行为有差异。JDK 11 也能跑但javax.swing.Timer在高 DPI 屏幕下需额外处理缩放建议先用 JDK 8。Eclipse 配置右键项目 → Properties → Java Build Path → Libraries → 确认 JRE System Library 是JavaSE-1.8同页面 → Source → 双击src→ 确认 Output folder 是bin不是默认的bin/子目录最关键一步Properties → Java Compiler → 将 Compiler compliance level 设为1.8并勾选Enable project specific settings。提示如果解压后直接 Import → Existing Projects into WorkspaceEclipse 可能因.project文件缺失或路径错误报红。此时手动新建 Java Project名称随意如FishGame然后将解压后的src/拖入新项目的src文件夹images/拖入项目根目录myFile/同理。.classpath和.project文件可删掉Eclipse 会自动生成。3.2 启动流程main() → Ui 构造 → paintComponent() 的完整调用栈主类大鱼吃小鱼.java只有一行有效代码// 大鱼吃小鱼.java public class 大鱼吃小鱼 { public static void main(String[] args) { SwingUtilities.invokeLater(() - new Ui().setVisible(true)); } }SwingUtilities.invokeLater()是 Swing 线程安全的基石。它确保new Ui()和setVisible(true)都在 Event Dispatch ThreadEDT中执行。Ui类继承JFrame其构造器干了四件事setTitle(大鱼吃小鱼)、setSize(800, 600)、setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)setResizable(false)锁死窗口大小避免 resize 导致paintComponent()重绘异常add(new GamePanel())——GamePanel是核心绘图面板继承JPaneladdKeyListener(new KeyHandler())—— 注册全局键盘监听注意必须在setVisible(true)前添加否则监听无效。GamePanel的paintComponent(Graphics g)是整个游戏的渲染心脏// src/Ui.java 内部类 GamePanel Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 双缓冲开始创建内存图像 if (offscreenImage null) { offscreenImage createImage(getWidth(), getHeight()); } Graphics g2 offscreenImage.getGraphics(); // 1. 绘制背景 g2.drawImage(bgImage, 0, 0, null); // 2. 绘制玩家鱼 playerFish.draw(g2); // 3. 绘制所有敌方鱼 for (EnemyFish enemy : enemyList) { if (enemy.isAlive()) { enemy.draw(g2); } } // 4. 绘制 UI 文字分数、提示 g2.setColor(Color.WHITE); g2.setFont(new Font(微软雅黑, Font.BOLD, 16)); g2.drawString(分数 score, 20, 30); // 双缓冲结束将内存图像一次性刷到屏幕 g.drawImage(offscreenImage, 0, 0, null); }这段代码解释了为什么不用repaint()会闪烁repaint()请求 EDT 异步重绘若paintComponent()中直接drawImage()到屏幕每一帧都是「画一半→清屏→画一半」的撕裂过程。而双缓冲强制先画到内存offscreenImage再整帧drawImage()到屏幕视觉上就是丝滑动画。3.3 键盘事件KeyHandler 如何把 WASD 映射为鱼的位移KeyHandler是Ui的内部类实现KeyListener接口。它不处理keyTyped()字符输入只用keyPressed()和keyReleased()// src/Ui.java 内部类 KeyHandler private class KeyHandler implements KeyListener { Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_W: playerFish.setDirection(0, -1); break; // 上 case KeyEvent.VK_S: playerFish.setDirection(0, 1); break; // 下 case KeyEvent.VK_A: playerFish.setDirection(-1, 0); break; // 左 case KeyEvent.VK_D: playerFish.setDirection(1, 0); break; // 右 case KeyEvent.VK_SPACE: // 空格暂停/继续 isPaused !isPaused; break; } } Override public void keyReleased(KeyEvent e) { // 松开按键时不重置方向方向由最后一次按键决定长按移动 // 这是游戏手感的关键W 按住不放鱼持续上移 } }PlayerFish.setDirection(dx, dy)并不直接改x/y而是更新directionX/directionY字段真正的位移发生在GamePanel的gameLoop中// GamePanel.run() 方法在 Timer 动作中调用 private void gameLoop() { if (!isPaused playerFish.isAlive()) { // 根据方向向量和速度计算新坐标 playerFish.x playerFish.directionX * playerFish.speed; playerFish.y playerFish.directionY * playerFish.speed; // 边界检测不能游出窗口 playerFish.x Math.max(32, Math.min(playerFish.x, 750)); playerFish.y Math.max(32, Math.min(playerFish.y, 550)); } }这里Math.max/min的边界值32 和 750不是随便写的playerFish.width64所以x32保证鱼头不贴左边界x750800-6414留出右侧安全区。这种硬编码数值在教学项目里很常见但也是你二次开发时最先要参数化的点。3.4 游戏循环javax.swing.Timer 的精度与陷阱GamePanel使用javax.swing.Timer驱动游戏逻辑而非Thread.sleep()// GamePanel 构造器中 timer new Timer(16, e - { // 16ms ≈ 60FPS gameLoop(); // 更新逻辑移动、碰撞 checkCollision(); // 检测吃鱼/被吃 repaint(); // 触发 paintComponent() }); timer.start();Timer(16, ...)的 16ms 是理论值实际间隔受 EDT 负载影响。如果paintComponent()耗时超过 16ms比如加载新图片Timer会累积事件导致「一帧执行多次gameLoop()」鱼突然瞬移。这就是为什么gameLoop()里要加isPaused判断——暂停时Timer仍在触发但逻辑被跳过避免状态爆炸。提示想验证帧率在gameLoop()开头加System.out.println(System.nanoTime())观察时间戳间隔。若发现大量 32ms、48ms 输出说明paintComponent()过重需优化图片加载或减少绘制对象。4. 避坑指南5 个真实发生过的崩溃现场与修复方案这个项目在 CSDN 博客评论区被问得最多的问题几乎都集中在以下五个点。它们不是理论缺陷而是开发者在真实环境不同 Windows 版本、不同 JDK 补丁、不同显示器 DPI下亲手踩出的坑。我把现象、根因、修复步骤写成可直接抄作业的清单。4.1 现象启动后窗口空白控制台无报错paintComponent()从未被调用原因GamePanel没有被正确添加到JFrame的内容窗格content pane或JFrame未调用pack()/setVisible(true)。排查在Ui构造器末尾加System.out.println(Ui constructed, panel added: (getContentPane().getComponentCount() 0));解决确认add(new GamePanel())在super()之后、setVisible(true)之前若用BorderLayout需显式add(new GamePanel(), BorderLayout.CENTER)检查GamePanel是否重写了getPreferredSize()返回new Dimension(800,600)否则pack()会压缩为 0×0。4.2 现象键盘按下无反应keyPressed()断点不触发原因JFrame或GamePanel未获取焦点focusSwing 键盘事件只发给当前焦点组件。排查在keyPressed()开头加System.out.println(Focus owner: KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner());解决在Ui构造器末尾加this.requestFocusInWindow();和gamePanel.requestFocusInWindow();或在GamePanel中重写requestFocusInWindow()并返回true终极方案gamePanel.setFocusable(true); gamePanel.requestFocusInWindow();4.3 现象fish.png等图片加载失败img nulldrawImage()报 NPE原因ImageIO.read()路径错误。images/fish.png是相对路径但ImageIO.read()以 classpath 为根而images/在项目根目录不在bin/下。排查打印getClass().getResource(/images/fish.png)若为null则路径不对。解决将images/文件夹拖入src/下成为src/images/或改用new ImageIcon(images/fish.png).getImage()ImageIcon支持文件系统路径更健壮写法ImageIO.read(new File(images/fish.png))。4.4 现象中文用户名注册后登录时始终失败原因FileWriter默认用系统编码GBK而FileReader读取时也用系统编码但 Eclipse 编辑器保存User.text时用 UTF-8造成编码错位。排查用记事本打开myFile/User.text看中文是否显示为乱码。解决统一用InputStreamReader/OutputStreamWriter指定编码// login() 中 try (BufferedReader reader new BufferedReader( new InputStreamReader( new FileInputStream(myFile File.separator User.text), StandardCharsets.UTF_8))) { // register() 中 try (OutputStreamWriter writer new OutputStreamWriter( new FileOutputStream(myFile File.separator User.text, true), StandardCharsets.UTF_8)) {4.5 现象游戏运行几分钟后 CPU 占用飙升至 100%窗口卡死原因Timer事件堆积。当paintComponent()因图片加载慢或repaint()频繁而耗时过长Timer的动作队列不断增长EDT 被占满。排查在Timer动作中加计时long start System.nanoTime(); ... long end System.nanoTime(); System.out.println(Loop time: (end-start)/1000000 ms);解决在Timer动作开头加if (isRunning) return; isRunning true;结尾加isRunning false;或改用SwingWorker异步加载图片最简单将Timer间隔从 16ms 改为 32ms30FPS降低压力。5. 二次开发实战从「加一条血条」到「接入 MySQL 用户库」的渐进改造现在你已经跑通、理解了、避开了坑下一步就是动手改。我不会教你“如何写一个新游戏”而是给你一条清晰的改造路径从 UI 层微调加血条到逻辑层增强吃鱼音效再到架构层升级MySQL 替代文件存储。每一步都附可验证的代码、必改的文件、以及我踩过的血泪经验。5.1 第一步在 UI 上加血条Health Bar理解 JPanel 的 Z-Order 渲染顺序血条要始终显示在玩家鱼上方且随生命值变化。PlayerFish类已有一个health字段初始 100但没用。我们把它可视化。修改文件src/Ui.java中的GamePanel.paintComponent()方法。原逻辑绘制玩家鱼playerFish.draw(g2);替换为先画血条再画鱼// 1. 计算血条位置在玩家鱼头顶 10px宽度随 health 缩放 int barX playerFish.x (playerFish.width - 60) / 2; // 居中对齐鱼身 int barY playerFish.y - 15; int barWidth (int) (60 * (playerFish.health / 100.0)); // 0~60px int barHeight 6; // 2. 绘制血条背景灰色 g2.setColor(Color.GRAY); g2.fillRect(barX, barY, 60, barHeight); // 3. 绘制血条前景红色按比例 g2.setColor(playerFish.health 30 ? Color.GREEN : playerFish.health 10 ? Color.YELLOW : Color.RED); g2.fillRect(barX, barY, barWidth, barHeight); // 4. 绘制玩家鱼覆盖在血条下方但血条已画完所以显示在上层 playerFish.draw(g2);关键点Swing 渲染是「后绘制的组件在上层」。我们先画血条fillRect再画鱼drawImage所以血条永远在玩家鱼上方。如果你把playerFish.draw(g2)放前面血条会被鱼图遮住。验证运行后按 W/A/S/D 移动观察血条颜色变化绿→黄→红。想测试归零效果在checkCollision()中找到被吃逻辑加一行playerFish.health - 10;撞一次小鱼掉 10 血。5.2 第二步接入吃鱼音效用 Clip 实现低延迟播放Swing 自带AudioClip但加载慢、不支持并发。我们用javax.sound.sampled.Clip它可预加载、可循环、可精确控制。新增文件src/com/entity/SoundPlayer.javapackage com.entity; import javax.sound.sampled.*; import java.io.File; public class SoundPlayer { private Clip clip; public SoundPlayer(String filePath) { try { AudioInputStream audioIn AudioSystem.getAudioInputStream(new File(filePath)); clip AudioSystem.getClip(); clip.open(audioIn); } catch (Exception e) { e.printStackTrace(); } } public void play() { if (clip ! null) { if (clip.isRunning()) clip.stop(); // 防止重复播放卡顿 clip.setFramePosition(0); clip.start(); } } }在Ui.java中初始化GamePanel构造器private SoundPlayer eatSound; // ... eatSound new SoundPlayer(sounds/eat.wav); // 你需准备一个 100KB 以内的 WAV 文件在checkCollision()中触发当玩家鱼吃掉敌方鱼时if (playerFish.getBounds().intersects(enemy.getBounds()) enemy.isAlive()) { enemy.setAlive(false); score 10; if (eatSound ! null) eatSound.play(); // 播放音效 }血泪经验WAV 文件必须是 PCM 编码、单声道、16bit、44100Hz。用 Audacity 导出时选「WAV (Microsoft) signed 16-bit PCM」。MP3 不支持Clip会抛UnsupportedAudioFileException。5.3 第三步用 MySQL 替代 User.text重构用户模块这才是真正考验工程能力的一步。目标登录注册走 MySQLUser.text彻底废弃。第一步建表CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 存 SHA-256 加密后字符串 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );第二步引入 MySQL Connector/J下载mysql-connector-java-8.0.33.jar右键项目 → Build Path → Add External Archives选中该 JAR。第三步重写User.java删除所有FileReader/FileWriter逻辑新增数据库连接// src/com/entity/User.java private static final String URL jdbc:mysql://localhost:3306/fishgame?useSSLfalseserverTimezoneUTC; private static final String USER root; private static final String PASS 123456; public static boolean login(String username, String password) { String sql SELECT password FROM users WHERE username ?; try (Connection conn DriverManager.getConnection(URL, USER, PASS); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { String dbPass rs.getString(password); return dbPass.equals(hashPassword(password)); // hashPassword() 用 MessageDigest 实现 } } } catch (SQLException e) { e.printStackTrace(); } return false; } public static boolean register(String username, String password) { String sql INSERT INTO users (username, password) VALUES (?, ?); try (Connection conn DriverManager.getConnection(URL, USER, PASS); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, hashPassword(password)); return ps.executeUpdate() 1; } catch (SQLException e) { e.printStackTrace(); return false; } }第四步修改Ui.java中登录按钮逻辑找到loginButton.addActionListener(...)将原来的User.login(u, p)替换为新方法。注意Swing 不允许在 EDT 中执行耗时 DB 操作必须用SwingWorker包裹new SwingWorkerBoolean, Void() { Override protected Boolean doInBackground() throws Exception { return User.login(usernameField.getText(), passwordField.getText()); } Override protected void done() { try { if (get()) { JOptionPane.showMessageDialog(null, 登录成功); showGamePanel(); } else { JOptionPane.showMessageDialog(null, 用户名或密码错误); } } catch (Exception e) { e.printStackTrace(); } } }.execute();血泪经验MySQL 8 默认启用 caching_sha2_password 插件老版 Connector/J 不兼容。解决方案安装 MySQL 8 时选「Use Legacy Authentication Method」或在连接 URL 加allowPublicKeyRetrievaltrueserverTimezoneUTC。从加血条到播音效再到换数据库——这三步不是孤立功能而是同一套思维定位入口 → 修改最小单元 → 验证副作用 → 封装隔离。我第一次改这个项目时在paintComponent()里加了 5 行血条代码结果发现鱼游太快血条跟不上又去调Timer间隔加音效时没关clip.stop()导致声音堆叠炸耳换 MySQL 时忘了SwingWorker界面直接假死 10 秒。从那以后我每次改 Swing 游戏都强制走一遍「EDT 安全检查 → 资源释放检查 → 线程阻塞检查」三步。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号