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

Java迷宫游戏源码解析:DFS生成与碰撞检测实战

  • 首页
  • 资讯中心
  • /
  • Java迷宫游戏源码解析:DFS生成与碰撞检测实战

相关资讯

零 API Key 的本地知识库搜索:chubbyskills SQLite 增量索引与 semantic-lite 原理详解 2026/10/14 15:34:03
决策树ID3算法详解:信息增益、Python实现与常见坑 2026/10/14 15:34:03
Pandas数据变形实战:pivot、melt、stack/unstack全解析 2026/10/14 15:34:03

最新资讯

离线语音合成完全指南:从选型到避坑的TTS落地路径
MySQL慢查询诊断实战:从连接检查到索引优化的DBA排查指南
MOS管在无刷电机的作用有哪些?
MySQL存储过程、函数与触发器:数据一致性的最后一道防线
STM32F103C8T6与SCL3300高精度角度传感SPI例程详解
实验 2.10 自动随机变化的颜色

今日推荐

IBM Verify Identity Access 修复 22 个漏洞:两个 9.8 严重 RCE 无需认证
小区物业管理系统
小型超市仓库管理系统

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

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

Java迷宫游戏源码解析:DFS生成与碰撞检测实战

发布时间:2026/10/14 15:34:03
Java迷宫游戏源码解析:DFS生成与碰撞检测实战 简介这是一份基于Java的迷宫游戏毕业设计完整源码包适合计算机相关专业学生用于课程设计、毕业设计以及Java后端与游戏开发入门者研究参考。项目围绕迷宫生成与求解展开涉及深度优先搜索、Prim等典型算法并结合Java面向对象思想设计了Maze、Player、GameLoop等核心类同时使用Swing或JavaFX构建图形界面能够帮助读者完整理解游戏循环、碰撞检测与界面交互的实现链路。压缩包共11个文件以Java源码、GIF演示动图、Markdown/Word说明文档及License为主整体大小约460KB目录结构清晰既包含可直接运行的工程代码也配有课程设计报告和运行说明便于对照学习与二次改造。当前已有265人学习下载对于希望快速掌握Java游戏项目组织方式、迷宫算法落地细节的开发者而言是一份体量精巧、内容完整的参考资料。1. 迷宫游戏 Java 源码拆开之前先解决「毕业设计能不能跑」的顾虑毕业设计选 JAVA 迷宫游戏的多半图它「图形界面 算法 面向对象」三项占齐可真等解开源码包卡住的往往是不知道从哪一行开始读。这套基于 JAVA 的迷宫游戏设计与实现完整源码把迷宫生成、玩家移动、主循环和 GUI 拆成了独立模块主线清楚既能当课程设计的底子也能补 Java 图形编程的短板。它解决的不只是「交一份能跑的作业」答辩被追问生成算法时你能说清 DFS 回溯的栈怎么用被问碰撞检测时你能解释为什么先查目标格再移动。新手照着 README 跑通一遍、改改迷宫尺寸就算入门熟手值得看的是 GameLoop 的刷新节奏和 Maze 的拆墙逻辑这两处是后续加道具、加计时器、加自动寻路的改动入口。读源码的顺序别从头顺到尾先沿 Main → GUI → GameLoop → Maze 把对象关系理清再回头补细节。下面按这个顺序拆。2. 读源码顺序从 Main 到 GameLoop先理清对象关系再动界面2.1 解压后先看 README确认入口和构建方式压缩包解压以后先别急着双击打开 Maze.java。根目录里躺着 README.md、LICENSE、code 目录和一份课程设计报告文档其中 README 最值得先读。它通常写了 JDK 版本要求、编译命令、入口类名甚至游戏规则。这个信息比任何外部教程都准因为课程设计代码几经翻改后入口类不一定和报告里的描述完全一致以 README 为准才不会白折腾。如果 README 里给了 Maven 相关配置说明这份工程是按 Maven 组织的直接按它的命令构建即可。如果没有给也不奇怪很多毕业设计工程就是纯 javac/java 组织不依赖 Maven 之类的构建工具。这种时候常见做法是打开终端先进到 code 目录然后编译再运行# 先把所有源码编译到 out 目录再运行 Main javac -encoding UTF-8 -d out $(find src -name *.java) java -cp out 包名.Main第一句用find把所有.java文件收集起来一次编译-encoding UTF-8是为了避免中文注释在终端环境下变成乱码-d out指定字节码输出目录。第二句的-cp out让类加载器从输出目录读 class 文件包名.Main要替换成 README 里写的实际入口。如果你的终端不支持find这种写法就用 IDE 直接打开工程后从 Main 类运行效果一样。这里想强调一件事源码里没有 Maven 不一定是坏事反而是理解 Java 编译链路的好机会。手动编译一次就能直观看到src下的源码怎么变成out下的字节码后续排查「找不到主类」「类不存在」时也更有方向。2.2 Main → GUI → GameLoop三分钟画出对象关系定位入口之后顺着 main 方法读一遍被实例化的对象整个项目的骨架就出来了。这类迷宫项目最常见的结构是Main 创建迷宫和玩家再把它们交给 GUIGUI 负责窗口和绘制GameLoop 负责反复更新状态和触发重绘。写成代码大致如下public class Main { public static void main(String[] args) { Maze maze new Maze(21, 21); // 先生成地图 Player player new Player(1, 1, maze); // 再创建玩家 GameGUI gui new GameGUI(maze, player); // 最后准备界面 gui.setVisible(true); } }这里要留意创建顺序Maze 先于 PlayerPlayer 先于 GUI。因为 Player 构造时需要拿到 maze 才能做碰撞检测而 GameGUI 构造时需要知道地图尺寸和玩家初始坐标才能初始化面板。如果你改代码时把顺序调反最常见的结果就是构造器里到处空指针。参数上21是迷宫的行列数必须用奇数(1, 1)是玩家起始格子也刻意取奇数坐标这样起点在迷宫的「路」上而不是墙上。把关系理清以后给你一条三分钟阅读路径先打开 Main.java看它 new 了哪几个对象然后打开 GUI.java看 paintComponent 方法里用了 maze 和 player 的哪些数据最后打开 GameLoop.java看 update 方法里更新了谁的状态。这三步做完你已经比大多数照着源码抄一遍交作业的人更清楚这个工程是怎么转起来的。2.3 Maze、Player、GameLoop 各自的边界接下来把每个类该管的事说清楚这也是答辩时老师最爱问的点。Maze 的职责是「地图本身」内部维护一个二维数组负责生成、查询当前格子是不是墙、提供行列边界所有和迷宫结构相关的逻辑都应该待在这个类里。Player 的职责是「玩家状态」当前所在的行列、移动方向、走了多少步。Player 移动时要问 Maze 借一步先查目标位置能不能走再决定是否更新坐标自己不能直接改地图形状。GameLoop 的职责是「不断推进游戏」维护 running 状态定时调 update 和 repaint不参与绘制细节也不直接写 grid。GUI 的职责则是「显示和接收输入」创建窗口、绑定键盘事件、把地图和玩家画出来。这样拆的好处是想换一种迷宫生成算法只需要动 Maze想让玩家换外观只需要动 GUI想让游戏变快变慢只需调整 GameLoop 的刷新间隔。如果代码里出现「一个类既改地图又算步数又画界面」说明它已经偏离合理分工后面扩展任何一个功能都会踩到另一个功能的雷。这里补充一个最容易看晕的约定二维数组的下标顺序。多数实现写成grid[row][col]第一维是行纵坐标方向第二维是列横坐标方向而 Swing 绘制坐标是 x 向右、y 向下。如果在 Maze 里用grid[row][col]到 GUI 里画矩形时就要换算成x col * cellSize, y row * cellSize。这个换算关系一旦搞反画面里的迷宫就会横竖颠倒。注意把行列顺序搞混是这类项目最常见的翻车点。读源码时先确认 Maze 对外提供的是 getCell(row, col) 还是 getCell(x, y)全项目必须统一不然移动逻辑和绘制逻辑会各走各的。2.4 先理解更新与绘制的分离再改界面很多新手拿到工程后第一件事是改窗口标题、换背景色结果发现改完界面没有任何反应。这不是改错了地方而是没理解 Swing 的刷新机制面板不会在数据变化后自动重画必须显式调用 repaint()而且 repaint() 只是向事件分派线程发送一个「尽快重绘」的请求不是同步执行。正常节奏是 GameLoop 负责「更新状态」面板的 paintComponent 负责「把当前状态画出来」两者通过同一个 Maze 和 Player 对象共享数据。你在键盘事件里改了玩家坐标之后记得让面板重绘一次画面才会动起来。这段逻辑看起来简单却是后面改速度、加动画、加特效的地基。3. 迷宫生成算法DFS 回溯与 Prim 随机版参数改哪里最见效3.1 深度优先回溯栈是生成器的主力迷宫生成要解决的核心问题是怎么把一个全是墙的网格拆出通路并保证从入口能到出口。工程实现里最常用的是深度优先回溯也就是 DFS。它的思路很直白从起点出发随机挑一个没访问过的邻居打通中间的墙走过去走到死路就沿栈回退直到所有格子都被访问。对应代码通常会写成下面这样。这里用grid存地图0 表示通路1 表示墙visited记录格子是否被纳入已连通集合public class Maze { private final int rows, cols; private final int[][] grid; private final boolean[][] visited; private final Random random new Random(); public Maze(int rows, int cols) { this.rows rows; this.cols cols; this.grid new int[rows][cols]; this.visited new boolean[rows][cols]; for (int r 0; r rows; r) { for (int c 0; c cols; c) { grid[r][c] 1; // 初始全是墙 } } generate(1, 1); } private void generate(int startRow, int startCol) { Dequeint[] stack new ArrayDeque(); stack.push(new int[]{startRow, startCol}); visited[startRow][startCol] true; grid[startRow][startCol] 0; while (!stack.isEmpty()) { int[] cur stack.peek(); int r cur[0]; int c cur[1]; int[][] neighbors getUnvisitedNeighbors(r, c); if (neighbors.length 0) { stack.pop(); // 没有可扩展的格子回溯 continue; } int[] next neighbors[random.nextInt(neighbors.length)]; int nr next[0]; int nc next[1]; removeWall(r, c, nr, nc); visited[nr][nc] true; grid[nr][nc] 0; stack.push(new int[]{nr, nc}); } } }逻辑说明stack.peek只读栈顶不弹出只有当当前格子四个方向都没有未访问邻居时才pop回退以此实现「走到死路原路返回」。random.nextInt(neighbors.length)负责随机选邻居这是「随机迷宫」随机性的来源。removeWall负责打通两个格子之间的墙。参数rows和cols必须为奇数否则起点坐标和邻居坐标的奇偶约定会被打破。打通墙壁的代码单独看更清楚private void removeWall(int r1, int c1, int r2, int c2) { int wallRow (r1 r2) / 2; // 两格中间那行的墙 int wallCol (c1 c2) / 2; // 两格中间那列的墙 grid[wallRow][wallCol] 0; }这里假设相邻两个「路格子」之间恰好隔一格墙所以取两个坐标的中间值就是墙的位置。比如从 (1,1) 走到 (1,3)中间墙在 (1,2)。removeWall只拆这一格不会误伤其它墙壁。这个细节看着小却是很多翻车现场的来源有人图省事直接把邻居格子改成通路却忘了拆中间的墙结果玩家被一堵「看不见的墙」挡住。3.2 Prim 随机版边界墙列表让结构更均匀DFS 生成的迷宫有一个明显特征通道路径大多比较长、分支不够分散看起来像是从起点一路「甩」出来的。想要分布更均匀的迷宫常见替代方案是 Prim 算法的随机版。它不是按图论最小生成树严格实现而是维护一个「候选墙」列表不断随机取墙判断墙对面的格子是否已经连通如果没连通就拆开并加入新墙。public void generatePrim() { Listint[] walls new ArrayList(); grid[1][1] 0; addWalls(1, 1, walls); while (!walls.isEmpty()) { int idx random.nextInt(walls.size()); int[] wall walls.remove(idx); // 随机取一堵候选墙 int wr wall[0], wc wall[1]; int targetR wall[2], targetC wall[3]; if (visited[targetR][targetC]) { continue; // 对面已经连通这堵墙不能再拆 } grid[wr][wc] 0; grid[targetR][targetC] 0; visited[targetR][targetC] true; addWalls(targetR, targetC, walls); } }逻辑说明候选墙数组里每个元素存四个值前两个是墙坐标后两个是墙对面的目标格子坐标。之所以要存目标坐标是因为随机取到墙之后必须知道它「通向哪里」才能判断拆了它会不会形成环。如果目标格子已经访问过就直接丢弃这面墙。addWalls会把当前格子四周的墙连同对面格子坐标加入列表你需要自己实现注意别把边界外的坐标加进去。如果把它和 DFS 放在同一个 Maze 实例里调用前要重置grid和visited否则上一次生成的状态会污染第二次生成。两种算法放在一起对比答辩时很容易讲出区别维度DFS 回溯Prim 随机版数据结构栈候选墙列表路径特征长通道多偏单向分支更分散整体均匀实现成本低中典型效果更像「探险迷宫」更像「古城迷宫」选哪个没有标准答案。我的建议是资源里默认用哪种就先跑通哪种再花半小时把另一种算法以独立方法写进 MazeGUI 里留一个切换按钮展示效果立刻立体起来。3.3 参数调优尺寸、随机种子与「看起来像迷宫」最后说参数。很多同学调迷宫只改行列数改完发现窗口太小或格子挤成一团就开始怀疑生成算法有 bug。其实生成算法本身很少出问题问题多半在 GUI 绘制尺寸没有跟着变。常见需要同步调整的是三个参数迷宫行列数、单格像素、窗口尺寸。比如行列从 21 改成 31理论上通路变多但绘制时如果格子像素还按原来的 20 像素算窗口尺寸不够就会显示不全。比较稳的做法是把格子像素定义成常量然后让面板的 preferredSize 按cols * cellSize和rows * cellSize计算而不是写死。关于随机种子这里有个「玄学」new Random()会拿当前时间做种子所以每次运行迷宫都不一样new Random(固定值)则会让迷宫序列完全可复现。答辩演示时固定种子有用地图稳定、讲解不被打断但交作业前最好把种子去掉或提供重新生成按钮不然老师一眼看出地图永远不变。更好的做法是给 Maze 增加一个带种子参数的构造器重载public Maze(int rows, int cols) { this(rows, cols, System.currentTimeMillis()); // 默认随机 } public Maze(int rows, int cols, long seed) { this.rows rows; this.cols cols; this.random new Random(seed); // 继续原有 grid、visited 初始化 }参数说明第一个构造器保持原有调用方式不变第二个把随机种子暴露出来方便测试复现特定地图。如果你想让「看起来像迷宫」的程度更高可以把行列数设到 31 以上并把墙和通路的绘制颜色区分开但单纯把行列加到 51 并不会让迷宫变难只会让窗口更挤。4. 玩家移动、碰撞检测与主循环让迷宫真正「能玩」4.1 键盘输入KeyListener 是入门方案Key Bindings 是后悔药玩家控制最常见的方式是给窗口挂 KeyListener。方向键的 keyCode 分别是 VK_UP、VK_DOWN、VK_LEFT、VK_RIGHT代码大致如下frame.addKeyListener(new KeyAdapter() { Override public void keyPressed(KeyEvent e) { int key e.getKeyCode(); if (key KeyEvent.VK_UP) { player.move(-1, 0); } else if (key KeyEvent.VK_DOWN) { player.move(1, 0); } else if (key KeyEvent.VK_LEFT) { player.move(0, -1); } else if (key KeyEvent.VK_RIGHT) { player.move(0, 1); } panel.repaint(); // 坐标变了必须刷新画面 } }); frame.setFocusable(true);逻辑说明move 的第一个参数是行增量第二个是列增量。把「方向键映射成增量」放在监听器里Player 不用关心按键细节只需要按增量移动。最后一定要 repaint否则坐标变了画面不更新。setFocusable(true)很关键不然窗口里的按钮或文本框可能抢走键盘焦点方向键怎么按都没反应。如果想支持 WASD只需要在分支里多加几个条件和 VK_UP 分在同一组即可。这里不建议用 e.getKeyChar() 判断方向键因为方向键在 KeyEvent 里对应的不是普通字符容易写出永远不触发分支的 bug。4.2 碰撞检测先查 Maze 借一步再决定动不动玩家移动最稳妥的姿势是「先问能不能走再更新坐标」。很多初版代码写成先移动、发现撞墙再退回来这在快速连按的情况下会留下残影甚至可能因为边界处理不当直接数组越界。Maze 里先提供一个查询方法public boolean isWalkable(int row, int col) { if (row 0 || row rows) return false; // 行越界 if (col 0 || col cols) return false; // 列越界 return grid[row][col] 0; // 0 表示通路 }逻辑说明前两个判断处理越界第三个判断处理墙体。行列边界判断缺一不可缺了行判断玩家走到最下面一行时继续按向下方向程序就会抛 ArrayIndexOutOfBoundsException。Player 的 move 方法再调用它public boolean move(int dRow, int dCol) { int nextRow row dRow; int nextCol col dCol; if (maze.isWalkable(nextRow, nextCol)) { row nextRow; col nextCol; steps; // 移动成功才计步 return true; } return false; }参数说明dRow 和 dCol 只能是 -1、0、1表示上下左右移动一格。steps只在移动成功时累加这样统计出来才是实际走过的格子数而不是按键次数。返回 boolean 之后GUI 可以用它判断是否走到终点如果玩家坐标等于出口坐标就弹出胜利提示。4.3 主循环Timer 还是 Thread选错就是一场血泪教训迷宫游戏本质上是一个不断「更新状态 → 重绘画面」的循环。Swing 项目里最稳的实现是 javax.swing.TimerTimer timer new Timer(100, e - { gameLoop.update(); // 更新玩家、判断胜负 panel.repaint(); // 请求重绘 }); timer.start();逻辑说明100是触发间隔单位毫秒也就是每 0.1 秒更新一次10 帧对走迷宫完全够用。回调里的两行分别做「逻辑更新」和「请求重绘」既简单又安全因为 Swing Timer 的回调运行在事件分派线程上不会和绘制抢同一个面板。也有人选择独立线程加 Thread.sleep 的写法Thread gameThread new Thread(() - { while (running) { gameLoop.update(); panel.repaint(); Thread.sleep(50); } }); gameThread.start();这里要特别注意panel.repaint()本身线程安全真正执行绘制还是在事件分派线程所以不会立刻崩溃但如果 update 里修改了玩家坐标而 paintComponent 正在画上一帧就可能出现位置跳变或闪烁。我的习惯是迷宫这种低频移动的小项目直接用 Swing Timer把独立线程留给需要高帧率的游戏。你要是在单独线程里越改越卡别急着怀疑电脑先把刷新间隔调大再把耗时操作挪出循环。5. 运行与集成常见报错、踩坑记录与排查清单5.1 五条踩坑记录现象、原因、解决第一条是窗口白屏。现象是窗口能弹出来但内容面板一片空白迷宫和玩家都看不到。原因多半是 Main 里先创建了 GUI之后才生成 MazepaintComponent 第一次执行时拿到的还是未初始化的地图也可能是生成完成后忘了 repaint。解决方法是把 Maze 创建挪到 GUI 之前并在 GUI 构造器末尾主动调用一次 repaint。第二条是方向键完全没反应。现象是鼠标点击窗口后按键无响应或者焦点在某个按钮上时只有按钮响应。原因是 KeyListener 挂到了丢失焦点的组件上或者窗口没有设置 focusable。解决方法是把监听器加在持有焦点的主面板上窗口显示后调用 requestFocusInWindow()如果加了请求焦点还是不行改用 Key Bindings 会更省心。第三条是每次生成的迷宫一模一样。现象是反复运行程序地图布局完全不变。原因是随机种子被写死常见于开发者为了截图或调试临时改的。解决方法是把种子参数改成时间戳或者加一个「新迷宫」按钮每次点击都用新种子重新生成。答辩前记得检查这一条不然演示时按十次都一样场面会很难收场。第四条是资源文件找不到。现象是项目里明明有 Resources 文件夹图片也放在里面运行时报文件不存在。原因是代码用相对路径读文件而 IDE 或终端的工作目录不在项目根目录下。解决方法是把资源放进 classpath用getClass().getResource(/Resources/xxx.png)读取不要把绝对路径写死那样换一台机器就崩。第五条是迷宫有环或者玩家走不到终点。现象是地图上出现一大片 2×2 通路的空洞或者明明有路却到不了出口。原因多半是拆墙逻辑把「已访问格子」和「未访问格子」之间的墙也拆了形成了多余回路或者 removeWall 计算中间墙时行、列顺序反了。解决方法是严格按前文写法只拆当前格子与未访问邻居之间的墙并把中间墙坐标打印出来核对是否符合预期。5.2 快速排查清单从运行到答辩前过一遍与其出问题再查不如每次改动后按固定清单过一遍。我的习惯是四步第一步看入口确认 Main 的包名和 class 文件路径一致避免「找不到主类」的低级错误。第二步用测试用例或临时 main 方法检查迷宫连通性确认起点和终点都是通路。第三步手动走一遍顺便验证步数累加和胜利判定。第四步换一次随机种子重新运行确认地图能变化。如果项目里带了 TestCases.java通常会用断言验证迷宫生成的基本性质。即使没引入 JUnit也可以把测试写成普通 main 方法不满足条件就抛异常public class TestCases { public static void main(String[] args) { Maze maze new Maze(21, 21); if (!maze.isWalkable(1, 1)) { throw new IllegalStateException(起点必须是通路); } if (maze.isWalkable(0, 0)) { throw new IllegalStateException(边界必须是墙); } System.out.println(basic checks passed); } }逻辑说明第一行创建一个小尺寸迷宫第二行验证起点可走第三行验证边界不可走。这两个断言能拦住大部分生成器写错的情况。参数 21 表示行列(0,0) 是左上角边界正常生成后该位置一定是墙。你可以在测试里继续加「从起点用 BFS 能到达终点」的连通性检查那才是迷宫生成器最该测的指标。把生成器当黑匣子用是最危险的跑一次看着像迷宫不等于每个格子都可达。手动验证只能证明你走过的那条路通换一张地图可能就翻车。所以测试用例里一定要有连通性检查哪怕只是用简单的栈或队列从起点扫一遍也能把大部分问题拦在答辩前。5.3 编码与中文乱码报告和控制台各管各的资源里带着课程设计报告文档打开如果出现乱码多数不是文件损坏而是编码和打开方式不匹配。报告是文档格式时直接用文档处理软件打开一般没问题如果解压工具把它转成了文本再打开就容易被编辑器按 UTF-8 解码中文变乱码。控制台输出中文乱码则是另一回事。某些终端默认代码页不是 UTF-8编译时用-encoding UTF-8只能保证源码里的中文被正确解析运行时输出到控制台还是可能乱码。解决方法是把终端代码页切到 UTF-8或者至少保证源码、编译选项和运行终端三者编码一致。这个问题不影响游戏功能但答辩演示时满屏乱码会很减分值得提前处理。6. 进阶给迷宫加自动求解把 DFS 生成器反过来用6.1 为什么完美迷宫不需要上重型算法DFS 回溯生成的迷宫有个很好的副产品它是一个「完美迷宫」任意两点之间有且仅有一条通路。所以求解根本不需要 Dijkstra 或 A* 这类带权图算法用一次 BFS 或 DFS 就够重点是把访问过的路径记录下来。这也是一个答辩加分项你能解释「迷宫无环 → 路径唯一 → 简单搜索即可」。写一个求解方法思路和生成时很像。从玩家当前位置出发用队列做 BFS遇到终点就停同时用两个二维数组记录每个格子的父节点最后从终点反向回溯到起点public Listint[] solveBFS(int startR, int startC, int endR, int endC) { boolean[][] visited new boolean[rows][cols]; int[][] parentRow new int[rows][cols]; int[][] parentCol new int[rows][cols]; ArrayDequeint[] queue new ArrayDeque(); queue.add(new int[]{startR, startC}); visited[startR][startC] true; parentRow[startR][startC] -1; parentCol[startR][startC] -1; while (!queue.isEmpty()) { int[] cur queue.poll(); if (cur[0] endR cur[1] endC) { break; } int[][] dirs {{-1,0},{1,0},{0,-1},{0,1}}; // 上、下、左、右 for (int[] d : dirs) { int nr cur[0] d[0]; int nc cur[1] d[1]; if (isWalkable(nr, nc) !visited[nr][nc]) { visited[nr][nc] true; parentRow[nr][nc] cur[0]; parentCol[nr][nc] cur[1]; queue.add(new int[]{nr, nc}); } } } Listint[] path new ArrayList(); int r endR, c endC; while (r ! -1) { path.add(new int[]{r, c}); int pr parentRow[r][c]; // 回溯父节点 int pc parentCol[r][c]; r pr; c pc; } Collections.reverse(path); return path; }逻辑说明parentRow和parentCol共同记录了每个格子是从哪里走过来的。反向回溯时如果父节点是 -1说明已经回到起点循环结束。最后Collections.reverse把路径从起点到终点排正。参数startR/startC是玩家当前坐标endR/endC是出口坐标实际调用时从 GUI 里获取即可。拿到路径后最简单的展示方式是面板里画一条折线把路径每个格子的行列坐标换算成像素中心点用 Graphics2D 的 drawPolyline 画出来。颜色选高亮的和墙、通路区分开。画的时候记得把抗锯齿打开效果会好很多。这段求解代码千万不要直接丢进 GameLoop 每帧执行路径只在玩家点击「提示」或进入新地图时才需要计算算完缓存下来绘制阶段只读结果。如果你用的是这份带课程设计报告的源码包把上面的求解方法加进 Maze 类报告里补一节「自动寻路模块设计与实现」项目完成度会明显提升。某次演示前我把生成器种子写死想让地图稳定好讲结果导师临时让我换一张地图我按了半天出不了新图场面很尴尬。从那以后我每次演示前都强制走一遍「换种子 跑测试 手动走到出口」的流程再顺手验证一次自动求解确保它不是一个黑匣子。这套流程看着琐碎但能挡住绝大多数临时翻车希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

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

快速链接

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

服务项目

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

联系方式

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

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