Java 2D平台游戏卡顿优化求助:游戏循环与重绘问题
看起来你已经精准定位到卡顿根源在游戏循环和重绘环节了,用java.util.Timer做游戏循环确实是个常见的坑——它的调度精度不足,而且运行在非事件调度线程(EDT)里,和Swing的UI操作天生不兼容,很容易引发线程冲突和画面卡顿。下面给你几个针对性的优化方案,从根本上解决问题:
1. 替换游戏循环为Swing专用的javax.swing.Timer
java.util.Timer完全不适合Swing应用,它的任务不在EDT执行,而Swing的所有UI操作(包括repaint())都必须在EDT中处理。改用javax.swing.Timer,它天生绑定EDT,既能保证调度精度,又能彻底避免线程安全问题:
// 替换原来的java.util.Timer相关代码 import javax.swing.Timer; // 在Panel类里声明: private Timer gameLoop; // 在构造方法里初始化: public Panel() { initComponents(); p = new Spelare(20, 100, 50); väggar = new ArrayList<>(); skapaVäggar(); // ... 其他初始化代码 ... // 初始化Swing Timer,每11ms触发一次(约90FPS) gameLoop = new Timer(11, e -> gameLoopTick()); gameLoop.start(); } // 把原来TimerTask里的逻辑抽成单独的方法 private void gameLoopTick() { // 添加重力 if (gravatyTimer <= 0) { p.addVsp(gravSpeed); gravatyTimer = 8; } else { gravatyTimer--; } // 检测碰撞 kollaKollision(); // 移动玩家 p.flyttaXLed(); p.flyttaYLed(); // 现在不需要手动控制repaint频率了——Swing Timer会在EDT触发,直接调用repaint即可 repaint(); }
2. 优化渲染性能:缓存静态元素
你的paintComponent每次都遍历所有墙体重新绘制,这是个不小的性能开销。因为墙体是静态的(不会移动),可以提前把它们绘制到一个BufferedImage里,之后每次只需要绘制这个缓存的图像,不用重复绘制每个墙体:
private BufferedImage levelBuffer; // 在skapaVäggar()之后初始化缓存: private void initLevelBuffer() { levelBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = levelBuffer.getGraphics(); for (Vägg vägg : väggar) { vägg.rita(g); } g.dispose(); } // 修改paintComponent: @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 先绘制缓存的墙体 g.drawImage(levelBuffer, 0, 0, this); // 再绘制动态的玩家 p.rita(g); }
记得在Panel的大小变化时(比如窗口 resize)重新生成缓存,不然会显示异常。
3. 优化碰撞检测的效率
当前的kollaKollision()每次都检查所有墙体,当墙体数量变多的时候会很慢。可以只检查玩家附近的墙体,比如根据玩家的位置计算出可能发生碰撞的墙体范围,只遍历这些墙体:
public void kollaKollision() { int playerX = p.getX(); int playerY = p.getY(); int playerSize = p.getSize(); // 假设Spelare有getSize()方法返回尺寸 // 只检查玩家周围一定范围内的墙体 for (Vägg vägg : väggar) { int wallX = vägg.getX(); int wallY = vägg.getY(); int wallSize = vägg.getSize(); // 判断墙体是否在玩家的碰撞检测范围内 if (wallX + wallSize < playerX - playerSize || wallX > playerX + playerSize || wallY + wallSize < playerY - playerSize || wallY > playerY + playerSize) { continue; // 不在范围内,跳过 } // 执行垂直碰撞检测 if (p.placeMeetingY(p.getY() + p.getVsp(), vägg)) { p.setVsp(0); } // 执行水平碰撞检测 if (p.placeMeetingX(p.getX() + p.getHsp(), vägg)) { p.setHsp(0); } } }
这样可以大幅减少碰撞检测的循环次数,提升游戏循环的执行速度。
4. 确保线程安全:所有UI状态修改在EDT执行
之前用java.util.Timer时,玩家的位置修改是在非EDT线程执行的,这可能导致UI渲染时读取到不一致的状态,出现画面跳帧或卡顿。改用javax.swing.Timer后,所有游戏逻辑都在EDT执行,天然避免了这个问题。如果之后需要在其他线程处理逻辑,一定要用SwingUtilities.invokeLater()把状态修改切换到EDT。
额外小优化
- 去掉手动控制
repaintTimer的逻辑——Swing的双缓冲机制会自动处理渲染队列,频繁调用repaint()不会导致过度绘制,反而能保证画面流畅。 - 确保
Spelare和Vägg的rita()方法里没有耗时操作,比如不要在绘制时加载图片,提前把所有游戏资源加载好。
按照这些方案修改后,你的游戏卡顿问题应该会得到明显改善。
内容的提问来源于stack exchange,提问作者NiceRobert

