为什么我的Java渲染引擎JPanel不更新且偶现渲染顺序错乱问题
问题描述
正在为游戏开发一款渲染器,其余功能均运行正常,但JPanel无法更新,即便偶尔更新也常出现渲染顺序错乱的问题。
效果对比
- 正常显示效果:

- 最大化窗口后的异常效果:

现有代码
static Container c; static JFrame f; static ArrayList<renderable_object> objs = new ArrayList<>(); public void main () { Image iconI = Toolkit.getDefaultToolkit().getImage(icon); f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); f.getContentPane().add(new canvas()); c = f.getContentPane(); } class canvas extends JComponent { public void paint(Graphics g) { Dimension size = getSize(); g.drawRect(0, 0, size.width, size.height); for (renderable_object part : objs) { g.setColor(part.c); if (part.type == 1) { g.drawLine(part.i,part.j,part.k,part.l); } else if (part.type == 2) { g.drawOval(part.i,part.j,part.k,part.l); } else if (part.type == 3) { g.drawRect(part.i,part.j,part.k,part.l); } else if (part.type == 4) { g.drawString(part.name,part.k,part.l); } } objs.clear(); } } class renderable_object { int i = 0; int j = 0; int k = 0; int l = 0; int type = 0; String name = ""; Color c = new Color(0,0,0); }
问题分析与修复方案
核心问题点
paint方法中错误清空渲染列表:Swing的paint方法会被系统自动触发多次(窗口缩放、移动、遮挡恢复都会触发重绘),每次绘制完清空objs会导致后续系统重绘时没有可渲染的对象,出现画面空白、内容缺失。- 未调用父类绘制方法:重写
paint时没有执行super.paint(g),会导致组件背景、边框等基础内容绘制异常,出现画面残留、花屏。 - 窗口显示时机错误:先调用
setVisible(true)再添加组件,会导致首次绘制内容缺失。 - 未主动触发重绘:更新渲染列表后没有调用组件的
repaint()方法,Swing不会主动刷新画面。 - 线程安全风险:未保证渲染列表读写的线程安全,绘制时遍历列表如果同时有其他线程修改列表,会抛出并发修改异常,也会导致渲染顺序错乱。
修复后的代码示例
static Container c; static JFrame f; // 用线程安全的列表,或者读写时加锁保证线程安全 static List<renderable_object> objs = Collections.synchronizedList(new ArrayList<>()); static Canvas canvas; public void main () { // Swing初始化必须放在事件调度线程执行 SwingUtilities.invokeLater(() -> { Image iconI = Toolkit.getDefaultToolkit().getImage(icon); f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); canvas = new Canvas(); f.getContentPane().add(canvas); c = f.getContentPane(); // 所有组件配置完成后再显示窗口 f.pack(); f.setVisible(true); }); } class Canvas extends JComponent { // 自定义绘制优先重写paintComponent,而非paint @Override protected void paintComponent(Graphics g) { // 先调用父类方法清空原有内容,避免画面残留 super.paintComponent(g); Dimension size = getSize(); g.drawRect(0, 0, size.width - 1, size.height - 1); // 遍历列表时加锁,避免并发修改 synchronized (objs) { for (renderable_object part : objs) { g.setColor(part.c); if (part.type == 1) { g.drawLine(part.i,part.j,part.k,part.l); } else if (part.type == 2) { g.drawOval(part.i,part.j,part.k,part.l); } else if (part.type == 3) { g.drawRect(part.i,part.j,part.k,part.l); } else if (part.type == 4) { g.drawString(part.name,part.k,part.l); } } } } } class renderable_object { int i = 0; int j = 0; int k = 0; int l = 0; int type = 0; String name = ""; Color c = new Color(0,0,0); } // 新增渲染对象、触发刷新的逻辑参考: public void addRenderObj(renderable_object obj) { objs.add(obj); // 主动触发重绘 canvas.repaint(); }
额外注意事项
- 不要再在绘制方法里清空
objs列表,如果你需要每帧刷新渲染内容,应该在你的游戏逻辑线程里更新完所有objs内容后,清空旧列表再新增新的渲染对象,最后调用repaint()触发刷新。 - 游戏逻辑更新不要放在Swing的事件调度线程执行,避免阻塞界面刷新。
内容的提问来源于stack exchange,提问作者sam oakes
相关产品推荐
相关产品推荐

