Java Swing中InvokeLater无法立即触发repaint问题求助
问题分析与解决方案
你的核心问题在于Swing重绘机制的特性和不合理的游戏循环设计:
- Swing的
repaint()并非立即执行绘制,而是向EDT(事件调度线程)发送重绘请求。如果短时间内发送大量重复请求,EDT会自动合并这些请求,只执行一次绘制,这就导致paintComponent被调用的次数远低于你设置的120FPS。 - 当鼠标移动或按键时,EDT会优先处理这些输入事件,此时会顺带处理积压的重绘请求,所以你会看到渲染变得流畅。
修复步骤
1. 优化游戏循环,避免忙等并给EDT留处理时间
你的当前循环是无休眠的忙等,占用大量CPU资源的同时,让EDT没有足够时间处理重绘请求。修改循环逻辑,加入休眠:
@Override public void run() { double timePerFrame = 1000000000.0 / FPS_SET; long lastFrame = System.nanoTime(); long now; long sleepTime; while (true) { now = System.nanoTime(); if (now - lastFrame >= timePerFrame) { // 先执行游戏逻辑更新(和渲染分离) updateGameState(); // 发送重绘请求 _reDraw(); lastFrame = now; } else { // 计算剩余时间,休眠减少CPU占用,给EDT处理重绘的机会 sleepTime = (long) ((timePerFrame - (now - lastFrame)) / 1000000); if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } } } } } // 单独的游戏逻辑更新方法 private void updateGameState() { // 这里写角色移动、碰撞检测、动画帧更新等逻辑 }
2. 正确实现paintComponent方法
必须调用super.paintComponent(g),这是Swing组件绘制的基础,负责清理上一帧的绘制内容,否则会出现画面残留或绘制异常:
public class GamePanel extends JPanel { // ... @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 不可省略,清理背景 // 你的绘制逻辑 renderGame(g); } private void renderGame(Graphics g) { // 绘制角色、场景等内容 } }
3. 可选:用Swing Timer简化实现(适合轻量游戏)
如果游戏逻辑不复杂,可以直接使用Swing的Timer来驱动更新和渲染,它本身运行在EDT上,无需手动处理线程同步:
public class Game { private GamePanel gamePanel; private final int FPS_SET = 120; private Timer gameTimer; public Game() { gamePanel = new GamePanel(); gamePanel.requestFocus(); // 计算每帧间隔毫秒数 int frameDelay = 1000 / FPS_SET; gameTimer = new Timer(frameDelay, e -> { updateGameState(); gamePanel.repaint(); }); gameTimer.start(); } private void updateGameState() { // 游戏状态更新逻辑 } }
内容的提问来源于stack exchange,提问作者BananaJoe
相关产品推荐
相关产品推荐

