资讯动态

Java Swing+Eclipse开发俄罗斯方块:环境搭建、碰撞检测与消行实现

发布时间:2026/10/27 17:28:42 来源:尧图企业网站定制
简介这份资源是基于Java Swing与Eclipse环境的俄罗斯方块游戏GUI设计与实现项目包含完整源码和配套文档适合Java课程设计、毕业设计及桌面开发入门者参考学习。资源包共60个文件约64.17MB涵盖9个java源文件及编译后的class与jar包便于直接导入运行还有3份答辩PPT、任务书、中期检查表、文档说明等可支撑项目汇报与材料整理内置音效与运行截图让游戏交互效果更直观。包内还附带操作模块与主界面模块的辅导视频能帮助快速理解项目结构。目前已有567人学习下载项目从UI绘制、事件处理到游戏逻辑均有完整实现目录层次清晰可作为同类练手项目或毕设二次开发的基础尤其对桌面GUI编程和Java综合应用训练具有不错的参考价值。1. 基于 Java Swing Eclipse 的俄罗斯方块游戏先解决环境问题再谈实现这类题目的第一道坎不是方块旋转算法而是环境把整包源码拖进 Eclipse按下运行经常只得到一个灰色窗口或是一行 ClassNotFoundException。原因是 Swing 的运行机制和 GUI 状态同步没搞清楚。这个项目本质上是一个零第三方依赖的 Java GUI 课程设计模板JFrame 负责窗口JPanel 负责画布javax.swing.Timer 负责下落节奏二维数组负责网格状态。适合正在做课程设计的学生、刚学完 Java 语法想接触界面编程的入门者以及准备 java 面试前想快速回忆 Swing 事件分发机制的人。接下来按数据模型、Swing 绘制、Eclipse 搭建、避坑和验证顺序展开每一步都给出能直接抄进工程的代码。2. 俄罗斯方块的核心游戏逻辑方块定义、碰撞检测与消行的数据设计把游戏逻辑和界面分开是这个项目能不能跑顺的关键。常见做法是先写一个不依赖任何 Swing 类的 GameBoard它只维护网格、当前方块和得分规则。这样后续用 Eclipse 运行时界面只是这个模型的“显示器”规则问题都能在控制台里单独验证。2.1 七种方块与旋转用 4x4 矩阵比坐标列表更省事一种常见的数据表示是给每种方块存一组相对坐标像 I 型存{ (0,1), (1,1), (2,1), (3,1) }。这种写法直观但旋转时要对每个点做坐标换算以哪个点为中心很容易算晕。自己做这个项目时更推荐把每一种形状放进一个 4x4 矩阵里1 表示有方块0 表示空。旋转就是顺时针方向交换下标代码量小边界判断也集中。public class Tetromino { private int[][] shape; private int row, col; public static final int[][][] SHAPES { // I 型横放时占第 1 行 {{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}, // O 型旋转后形状不变 {{1, 1, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, // T 型 {{0, 1, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}} // S、Z、J、L 型按同样方式继续补全 }; public Tetromino(int type) { shape SHAPES[type]; row 0; col 3; } public void rotateCW() { int n shape.length; int[][] rotated new int[n][n]; for (int r 0; r n; r) { for (int c 0; c n; c) { rotated[c][n - 1 - r] shape[r][c]; } } shape rotated; } public int[][] getShape() { return shape; } }这里 type 从 0 开始对应 I、O、T、S、Z、J、L后续颜色数组的下标也要跟这个顺序保持一致。row、col 是方块的锚点我习惯把它理解为矩阵左上角所在网格坐标移动和碰撞检测都基于这个锚点。rotateCW 是顺时针旋转公式rotated[c][n-1-r] shape[r][c]O 型旋转后视觉不变但每个方块都统一走这个逻辑省去特判。用 4x4 矩阵的代价是旋转后可能产生“空边”比如 I 型横放时实际方块在第二行旋转后到了第一列四周多了一圈空白。这不影响正确性但视觉上会有轻微漂移。课程设计阶段不用做踢墙补偿只要保证每次旋转前后锚点不变大多数情况下手感可接受。2.2 碰撞检测二维网格与边界条件的正确顺序网格用int[rows][cols]0 表示空格非 0 表示已有方块的形状编号。碰撞检测的核心是“能不能把这个方块放到目标位置”。移动、旋转、自然下落都复用同一个方法而不是在按键事件里各写一套。public boolean canPlace(Tetromino t, int targetRow, int targetCol) { int[][] shape t.getShape(); for (int r 0; r 4; r) { for (int c 0; c 4; c) { if (shape[r][c] 0) { continue; } int row targetRow r; int col targetCol c; // 左右边界、底部边界必须拦截 if (col 0 || col cols || row rows) { return false; } // 顶部允许方块先出现在可视区上方 if (row 0 grid[row][col] ! 0) { return false; } } } return true; }注意顺序左右和底部边界要先判断再去访问grid[row][col]否则一旦 row 越界会抛数组下标异常。顶部 row 为负数时不拦截这是为了让新方块可以从可视区上方平滑落入。很多初学者在这里把row 0也判为非法结果方块一出生就卡死游戏没法开始。这个细节是这类项目里最常见的“无声 bug”之一。碰撞检测只关心目标位置是否合法不负责移动。真正的移动逻辑写在 Game 类里比如moveLeft()内部先给 col 减 1再调 canPlace通过才真正更新锚点。这样写的好处是硬降和软降可以复用同一套检查。2.3 消行与计分自底向上扫描删完一行要原地再查消行逻辑如果和界面混在一起后面调分数和动画都会很难受。把它放在 GameBoard 类里只返回消掉的行数加分规则交给上层 Game 类。public int clearLines() { int cleared 0; for (int r rows - 1; r 0; r--) { boolean full true; for (int c 0; c cols; c) { if (grid[r][c] 0) { full false; break; } } if (full) { cleared; for (int k r; k 0; k--) { grid[k] grid[k - 1].clone(); } grid[0] new int[cols]; r; } } return cleared; }这里最容易被忽视的是r。删除第 18 行后原来第 17 行的内容整体下移到第 18 行如果循环继续往 r--就会漏查这一行。r让循环回到当前下标再检查一次。用clone()复制一整行代码比逐格 for 循环简洁性能在这个场景下也没有问题。计分规则建议放在 Game 类里而不是 GameBoardif (cleared 1) { score 100; } else if (cleared 2) { score 300; } else if (cleared 3) { score 500; } else if (cleared 4) { score 800; }一次消四行的分数远大于四倍单行分数这是 Tetris 的经典计分逻辑。把计分和消行分离后以后想改成连续消行加倍只需要改 Game 类不影响网格数据。这个数据层设计是整个项目后续所有功能的地基值得多花半小时理清。3. 用 Swing 画出一个能玩的界面JPanel 重绘、Timer 循环与键盘事件Swing 的界面核心不是 JFrame而是 JPanel 的paintComponent。JFrame 只是窗框真正画网格和方块的是 JPanel。理解这一点很多“窗口打开了但没内容”的问题就能直接定位。3.1 JFrame 与 GamePanel窗口、画布和数据层分离启动入口可以这样写public class GameFrame extends JFrame { public GameFrame() { setTitle(Tetris - Java Swing); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setResizable(false); GameBoard board new GameBoard(20, 10); GamePanel panel new GamePanel(board); add(panel); pack(); setLocationRelativeTo(null); } }GamePanel 的尺寸一般通过格子数量乘单元格大小来算。比如setPreferredSize(new Dimension(cols * cellSize, rows * cellSize))而不是硬编码像素值。pack()会按照这个尺寸自适应窗口。数据层 GameBoard 不引用任何 Swing 类这是第 2 章强调过的关键设计。3.2 paintComponent 与绘制顺序先清背景再画网格和方块绘制顺序决定了画面是否花屏。标准流程是先调用super.paintComponent(g)清掉上一次残留再画网格线再画已经落定的格子最后画当前活动方块。顺序反了活动方块会被背景盖住看起来像卡键。public class GamePanel extends JPanel { private GameBoard board; private Game game; private int cellSize 30; Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 画网格线 g.setColor(Color.LIGHT_GRAY); for (int r 0; r board.getRows(); r) { for (int c 0; c board.getCols(); c) { g.drawRect(c * cellSize, r * cellSize, cellSize, cellSize); } } // 画已落定的格子 for (int r 0; r board.getRows(); r) { for (int c 0; c board.getCols(); c) { int value board.getGrid()[r][c]; if (value ! 0) { g.setColor(COLORS[value]); g.fillRect(c * cellSize, r * cellSize, cellSize, cellSize); } } } // 画当前活动方块 Tetromino current game.getCurrent(); int[][] shape current.getShape(); int row current.getRow(); int col current.getCol(); for (int r 0; r 4; r) { for (int c 0; c 4; c) { if (shape[r][c] ! 0) { g.setColor(COLORS[shape[r][c]]); g.fillRect((col c) * cellSize, (row r) * cellSize, cellSize, cellSize); } } } } }注意画活动方块时坐标是(col c)和(row r)不是直接用 shape 的索引。很多人的方块“跑偏”就是因为漏加了锚点。cellSize 建议做成成员变量后续调格子大小只改一处。COLORS 数组的下标对应第 2 章的 SHAPES 顺序I 型是 1、O 型是 2依此类推颜色可以随意选但最好让同种形状在不同旋转状态下颜色一致。3.3 Timer 驱动下落Swing Timer 与 Thread.sleep 的差别有人喜欢开一个线程写while(true) { Thread.sleep(500); panel.repaint(); }这在控制台程序里没问题但在 Swing 界面里会跟事件分发线程抢资源轻则刷新不匀重则界面卡死。标准做法是用javax.swing.Timer它的回调本身就运行在 EDT 上改完状态后立刻 repaint 即可。Timer timer new Timer(500, e - { if (game.tick()) { panel.repaint(); } else { game.mergeToBoard(); game.clearLines(); game.spawnNext(); panel.repaint(); } }); timer.start();tick()内部尝试把当前方块下移一行成功返回 true失败说明方块落定接下来做 merge 和消行。第一个参数 500 是初始下落间隔单位毫秒。等级提升时不要重新 new Timer直接timer.setDelay(newDelay)旧的 Timer 没停会导致两个 tick 同时在跑方块速度会越来越离谱。实际项目中速度下限可以设成 100ms再快就没有操作空间了。3.4 键盘操作KeyListener 的焦点陷阱与 Key Bindings直接用panel.addKeyListener(...)会踩焦点坑窗口刚打开时 GamePanel 没有焦点方向键按了没反应鼠标点一下窗口后才有响应。原因不是程序逻辑错了而是 Swing 的焦点默认在某个按钮或文本组件上。最稳的办法是用 Key Bindings它支持WHEN_IN_FOCUSED_WINDOW模式只要焦点还在窗口内按键就有效。InputMap inputMap panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap actionMap panel.getActionMap(); inputMap.put(KeyStroke.getKeyStroke(LEFT), moveLeft); actionMap.put(moveLeft, new AbstractAction() { Override public void actionPerformed(ActionEvent e) { game.moveLeft(); } }); inputMap.put(KeyStroke.getKeyStroke(RIGHT), moveRight); actionMap.put(moveRight, new AbstractAction() { Override public void actionPerformed(ActionEvent e) { game.moveRight(); } });getKeyStroke(LEFT)默认匹配按下事件不需要额外区分 pressed 和 released。空格硬降可以用SPACE上方向键旋转用UP。这套写法的好处是彻底绕开requestFocusInWindow()的时序问题也避免界面上有按钮时按钮抢占焦点导致的操作失灵。4. 在 Eclipse 里搭建工程包结构、运行配置和脱离 IDE 的编译验证Eclipse 跑 Swing 项目本身不复杂但很多“找不到或无法加载主类”的问题出在工程结构上。这里按最稳妥的路径走先检查 JDK 环境再新建工程再把代码放进正确目录。4.1 环境自检JDK、JRE 和 Eclipse 的 JRE 配置打开 Eclipse 前先在终端跑一下java -version和javac -version确认 JDK 而不是 JRE 可用。Eclipse 默认用工作空间里的 JRE但有些压缩包项目需要 JDK 编译特性。在 Window → Preferences → Java → Installed JREs 里指定 JDK 安装目录然后到 Project → Properties → Java Build Path → Libraries 里看 JRE System Library 是否指向这个 JDK。版本上 JDK 8 或 11 都能跑 Swing没必要追新。这里有个常见误用项目里如果混入了.class文件Eclipse 会优先加载旧 class导致你改了.java文件后运行结果没变化。这时在 Project → Clean 里做一次全量清理能解决大多数缓存问题。4.2 从零建工程包结构、运行配置与主类推荐包结构如下src/ com/tetris/ Main.java Game.java GameFrame.java GamePanel.java GameBoard.java Tetromino.javaMain.java 是最小启动入口package com.tetris; public class Main { public static void main(String[] args) { GameFrame frame new GameFrame(); frame.setVisible(true); } }在 Eclipse 里右键 Main.java → Run As → Java Application 即可。如果报“找不到或无法加载主类”先检查 main 方法签名是否是public static void main(String[] args)再检查 src 是否被标记为源文件夹。右键项目 → Build Path → Configure Build Path → Source 里应该有 src 目录如果没有手动 Add Folder 加上。4.3 脱离 IDE 验证javac 和 java 命令行编译启动Eclipse 的编译有时候会掩盖命令行问题。拿到源码后先在终端里验证一遍能跑通说明代码本身没问题后续问题都出在 IDE 配置mkdir -p out javac -encoding UTF-8 -d out $(find src -name *.java) java -cp out com.tetris.Main-encoding UTF-8是给中文注释用的Windows 默认 GBK 编码时不加这一行会报乱码或编译错误。$(find src -name *.java)自动收集所有 java 文件省得手动列文件名。-d out指定编译输出目录。运行时用-cp out加全限定类名com.tetris.Main。这条命令能在 Eclipse 外直接启动游戏也可以作为交付前最后的验收手段。4.4 导入既有源码.project 与 .classpath 缺失时的处理拿到的压缩包如果带.project和.classpath用 File → Import → General → Existing Projects into Workspace 直接导入。如果只有 src 目录建议新建一个 Java Project再把.java文件复制进 src不要直接 File → Open File 打开单个文件那会丢失包结构。压缩包里的文档通常是课程设计报告和源码分开存放不影响工程运行。5. 避坑清单黑屏、按键失灵、Timer 失控与主类加载失败的五个现象这个项目最耗时间的往往不是写代码而是排一些看着没逻辑的界面问题。以下五条是按实际踩坑频率排出来的每条都按现象、原因、解决三个步骤说明。5.1 运行后只有一个灰色窗口没有网格也没有方块现象窗口正常弹出但内容区一片灰什么都没画。原因最常见是 JPanel 没有加入 JFrame或者加入了但 paintComponent 第一行没调用super.paintComponent(g)。paintComponent最后一行代码没执行也会导致画面空白比如在绘制方法里抛了数组下标越界异常。另一个隐蔽原因是窗口显示在数据更新之前Timer 启动后忘了repaint()。解决检查 JFrame 构造函数里是否add(panel)paintComponent 第一行写super.paintComponent(g)Timer 回调里每次状态变更后都调用panel.repaint()。如果仍空白在 paintComponent 第一行加一个System.out.println(paint)看控制台是否持续打印就能定位到底重绘有没有被触发。5.2 方向键没反应鼠标点一下窗口后才能操作现象游戏启动后按左右方向键完全无效但用鼠标点一下游戏区域后按键恢复正常。原因焦点不在 GamePanel 上。Swing 窗口刚打开时焦点可能在某个不可见的默认组件上GamePanel 没有收到键盘事件。解决用第 3 章的 Key Bindings 替代 KeyListener设置WHEN_IN_FOCUSED_WINDOW后焦点只要在窗口内就能收到按键。如果坚持用 KeyListener必须在窗口显示后再调用panel.requestFocusInWindow()并且把setFocusable(true)写在构造函数里。5.3 下落速度越来越快最终完全失控现象游戏开始时速度正常消几行后突然变快然后越来越快根本没法操作。原因加速逻辑里重新创建了 Timer但旧的 Timer 没有 stop。两个 Timer 同时运行事件回调被成倍触发。还有一种可能是把等级换算 delay 的公式写反了等级越高 delay 越大速度越来越慢。解决Game 类里只保存一个 Timer 引用加速时调用timer.setDelay(newDelay)不要新建。代码里加一个速度下限保护比如Math.max(100, delay - level * 50)。调参时留意 setDelay 的参数单位是毫秒不是帧率。5.4 Eclipse 报“找不到或无法加载主类”现象右键 Run As → Java Application 没反应控制台报 ClassNotFoundException或者提示“编辑器不含主类型”。原因src 没有被标记为源文件夹Main.java 的 package 声明和在磁盘上的目录不一致Java 文件名和 public 类名不一致。解决Project → Properties → Java Build Path → Source 里确认 src 是源文件夹。Main.java 里如果写了package com.tetris;文件必须在src/com/tetris/目录下。运行配置里 Main class 要写全限定名com.tetris.Main。检查类名和文件名大小写完全一致Java 对大小写敏感。5.5 导出的 Runnable JAR 双击没反应现象Eclipse 里运行正常导出 Runnable JAR 后双击没有任何反应。原因导出时没选择 Launch configuration或者选了错误的配置。如果另一台机器双击没反应也可能因为系统没有安装 Java 运行环境。解决File → Export → Java → Runnable JAR fileLaunch configuration 选择Main - com.tetris.Main。导出后在终端里用java -jar tetris.jar验证能看到异常输出就能定位问题。本项目是纯色块绘制没有外部图片资源所以不太会遇到 jar 内资源读取失败的问题如果以后加了图片必须用getResourceAsStream而不是new File。6. 验证方法一个不依赖 Swing 的规则自检和三个代码走查习惯最后一层是验收。编写一个不依赖 Swing 的纯 Java 自检方法可以放在单独的 RuleCheck.java 里在控制台直接运行。这样能证明核心逻辑成立再进 Eclipse 调界面。public class RuleCheck { public static void main(String[] args) { GameBoard board new GameBoard(20, 10); for (int c 0; c board.getCols(); c) { board.getGrid()[19][c] 1; } if (board.clearLines() ! 1) { throw new AssertionError(clearLines failed); } Tetromino t new Tetromino(0); if (board.canPlace(t, 1, 3) false) { throw new AssertionError(canPlace failed); } System.out.println(logic ok); } }这段代码不创建任何窗口直接验证两个核心规则满行能被消掉方块在空网格顶部可以放置。board.getGrid()[19][c] 1手动填满最后一行clearLines()返回 1 说明消行逻辑正确。运行方式和普通 Java 类一样右键 Run As → Java Application。性能方面如果玩到高层出现掉帧优先看repaint()是否每次重绘整个面板。课程设计阶段不需要引入复杂优化但有一个习惯值得养成paintComponent 里不要做耗时计算比如每次都 new 数组、每次都做文件读取。网格线可以在第一次绘制时生成一张 BufferedImage 缓存之后直接贴图视觉上会流畅很多。代码走查时可以检查三个点。第一GameBoard 是否完全不引用 JComponent如果引用了规则自检就没法脱离 Swing 运行。第二Timer 是否只有一个实例加速是否用 setDelay 而不是重新 new。第三所有修改界面状态的操作是否都发生在 EDT 上Swing 单线程模型的底线是不要在普通线程里直接调用panel.repaint()之外的方法否则会出现间歇性空指针。我做这类项目时吃过一个亏把消行动画拆到子线程里处理结果界面偶发卡死后来发现是 Swing 单线程模型没守好。从那以后我先把规则写成纯 Java 类再用 JPanel 包装最后才调 Timer 参数。这个顺序帮我省了很多排查时间希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑