You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么我的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);
}
问题分析与修复方案

核心问题点

  1. paint方法中错误清空渲染列表:Swing的paint方法会被系统自动触发多次(窗口缩放、移动、遮挡恢复都会触发重绘),每次绘制完清空objs会导致后续系统重绘时没有可渲染的对象,出现画面空白、内容缺失。
  2. 未调用父类绘制方法:重写paint时没有执行super.paint(g),会导致组件背景、边框等基础内容绘制异常,出现画面残留、花屏。
  3. 窗口显示时机错误:先调用setVisible(true)再添加组件,会导致首次绘制内容缺失。
  4. 未主动触发重绘:更新渲染列表后没有调用组件的repaint()方法,Swing不会主动刷新画面。
  5. 线程安全风险:未保证渲染列表读写的线程安全,绘制时遍历列表如果同时有其他线程修改列表,会抛出并发修改异常,也会导致渲染顺序错乱。

修复后的代码示例

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.13 09:42:03