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

Java Applet每帧调用方法咨询:GLFW窗口持续更新方案

解决Java Applet中LWJGL 3每帧更新的问题

首先,你遇到的核心问题是Applet的paint()方法是被动触发的——它只会在组件需要重绘时被调用(比如首次加载、窗口尺寸变化、被遮挡后恢复显示),并非主动的循环调用,所以仅靠它来触发window.update()肯定无法满足每帧更新的需求。

接下来给你几个可行的解决方案,结合LWJGL 3的上下文规则来设计:

方案1:后台线程实现游戏循环(推荐)

LWJGL 3的GLFW要求所有窗口操作(创建、事件处理、渲染)必须在同一个线程中执行,所以我们可以把窗口初始化、游戏循环都放在一个后台线程里,然后将渲染结果(BufferedImage)同步到AWT的事件调度线程(EDT)中绘制。

修改你的Main类如下:

package net.rabbit;
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.SwingUtilities;
import xyz.rabbit.lib.framework.Window;

public class Main extends Applet {
    private static final long serialVersionUID = 6092259155082054438L;
    private Window window;
    private BufferedImage currentFrame;

    @Override
    public void init() {
        super.init();
        // 启动后台线程处理GLFW和游戏循环
        new Thread(() -> {
            // 在该线程中初始化窗口(确保GLFW上下文归属此线程)
            window = new Window();
            window.setParent(this);
            window.open();

            // 游戏主循环
            while (!window.shouldClose()) {
                // 处理GLFW事件、执行OpenGL渲染
                window.update();
                
                // 通过glReadPixels获取帧缓冲区并转换为BufferedImage
                currentFrame = window.captureFrame(); // 你需要在Window类中实现这个方法
                
                // 切换到EDT线程触发重绘(AWT组件必须在EDT中绘制)
                SwingUtilities.invokeLater(this::repaint);

                // 控制帧率(示例为60帧/秒)
                try {
                    Thread.sleep(1000 / 60);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }

            // 清理资源
            window.close();
        }).start();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        // 绘制最新的帧图像
        if (currentFrame != null) {
            g.drawImage(currentFrame, 0, 0, getWidth(), getHeight(), this);
        }
    }
}

关键注意点:

  • Window类的captureFrame()方法需要实现:用glReadPixels读取当前帧缓冲区的像素数据,然后转换为BufferedImage(注意像素格式的匹配,比如GL_RGB对应BufferedImage.TYPE_INT_RGB)。
  • 所有GLFW/OpenGL操作必须在初始化窗口的后台线程中执行,不能跨线程调用,否则会触发上下文错误。
  • 必须通过SwingUtilities.invokeLater()在EDT中执行AWT的绘制操作,避免线程安全问题。

方案2:使用Swing Timer触发更新

如果不想手动管理线程,可以用javax.swing.Timer来定时触发更新,但要注意GLFW操作的线程归属:

@Override
public void init() {
    super.init();
    window = new Window();
    window.setParent(this);
    window.open();

    // 每16ms触发一次(约60帧/秒)
    new javax.swing.Timer(16, e -> {
        // 注意:Timer的回调在EDT中执行,如果window.update()包含GLFW操作,可能会阻塞EDT
        // 所以更推荐把GLFW操作放在后台线程,这里只负责触发重绘
        window.update();
        currentFrame = window.captureFrame();
        repaint();
    }).start();
}

不过这种方案的缺点是:EDT是负责UI交互的线程,如果window.update()耗时较长,会导致UI卡顿,所以方案1是更稳定的选择。

额外提示

Java Applet已经被官方废弃,现代浏览器大多不再支持,如果你的项目有迁移空间,建议考虑改用Java Web Start或者独立的Swing/AWT应用程序,这样会有更灵活的线程控制和更好的兼容性。

内容的提问来源于stack exchange,提问作者scewps

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:19:04