1

我打算写一个简单的太空射击游戏。我读过 repaint() 方法只是一个请求,它不会在每次调用时都执行。我相信我注意到了这一点,因为当我移动它时,我的宇宙飞船往往会略微滞后。目前,我只是在 JPanel 的 paintComponent() 方法中绘制我的船,并定期调用 repaint()(我的面板也是可运行的)。看到 repaint() 可能会把我搞砸,我正试图找到一种方法来解决它,但是我已经没有想法了。我到目前为止的代码:

private void renderGraphics() {
    if (MyImage == null) {
        MyImage = new BufferedImage(getPreferredSize().width,
                getPreferredSize().height, BufferedImage.TYPE_INT_RGB);
    }
    MyGraphics = MyImage.getGraphics();
    MyGraphics.setColor(Color.BLACK);
    MyGraphics.fillRect(0, 0, getPreferredSize().width, getPreferredSize().height);
    MyGraphics.drawImage(ship.getImage(), ship.getCurrentX(), ship.getCurrentY(), null);       
}

这个想法是创建我自己的图形,然后让 JPanel 绘制它,并在我的 run() 方法中继续调用它而不是 repaint(),但是我不知道该怎么做。我会就此事提出任何意见。

4

6 回答 6

4

有多种方法可以解决这个问题。

最好的方法可能是使用BufferStrategy并借鉴它,其中我包含了一个适合您的代码片段。

您可以更进一步,完全放弃 Swing,只需使用 Frame/BufferStrategy。在我的问题中有一个完整的工作示例(从中获取并改编了代码片段):

AWT 自定义渲染 - 捕捉平滑调整大小并消除调整大小闪烁

无论如何,这是一个BufferStrategy您应该可以直接加入的实现:

// you should be extending JFrame
public void addNotify() {
    super.addNotify();
    createBufferStrategy(2);
}

private synchronized void render() {
    BufferStrategy strategy = getBufferStrategy();
    if (strategy==null) return;
    sizeChanged = false;
    // Render single frame
    do {
        // The following loop ensures that the contents of the drawing buffer
        // are consistent in case the underlying surface was recreated
        do {
            MyGraphics draw = strategy.getDrawGraphics();
            draw.setColor(Color.BLACK);
            draw.fillRect(0, 0, getPreferredSize().width, getPreferredSize().height);
            draw.drawImage(ship.getImage(), ship.getCurrentX(), ship.getCurrentY(), null);
            draw.dispose();

            // Repeat the rendering if the drawing buffer contents 
            // were restored
        } while (strategy.contentsRestored());

        // Display the buffer
        strategy.show();

        // Repeat the rendering if the drawing buffer was lost
    } while (strategy.contentsLost());
}
于 2011-08-01T10:22:48.897 回答
2

任何绘图仍将在 Swing Thread 中执行,因此无论您尝试解决什么问题,它都无济于事。

确保您没有在摆动线程中进行任何冗长的计算,这可能会在需要执行时立即停止执行重绘

于 2011-08-01T10:07:08.407 回答
2

将所有逻辑分成两部分。静态和动态。(例如海和移动的船。船在海的静态图像上改变形状/位置)

在图像中绘制一次静态内容并在您的paintComponent() 中使用该图像。在静态图像之后调用动态部件绘画。

使用 setClip() 来限制重绘区域。

于 2011-08-01T10:07:19.530 回答
2

仅用于您在此处发布的代码:

1/如果你想显示Image/ImageIcon,那么最好和最简单的方法是使用标签

2/正如你提到的, Runnable{...}.start();Swing 是简单的线程,所有到 GUI 的输出都必须在 EDT 上完成;您必须查看Swing 中的并发性,结果是所有输出都BackGround Task(s)必须包装到invokeLater(),如果性能有问题,则进入invokeAndWait()

3/如果你是切换(之间JComponents)/添加/删除/更改Layout,那么你必须调用revalidate() + repaint()具体代码块中的最后一行

编辑:

肮脏的黑客将是paintImmediately()

于 2011-08-01T10:24:11.813 回答
2

不带任何参数调用 repaint 意味着重新绘制整个面板。

如果您需要重绘屏幕的某些部分(宇宙飞船已移动到不同的位置),您应该确保只重绘屏幕的这些部分。不应该触摸保持不变的区域。

重绘获取应该重绘的矩形的坐标。移动船时,您应该知道船的旧坐标和船应该移动到的坐标。

repaint( oldShipCoordinateX, oldShipCoordinateY, shipWidth, shipLength );
repaint( newShipCoordinateX, newShipCoordinateY, shipWidth, shipLength );

这通常比不带参数调用 repaint() 快得多。但是,您需要付出额外的努力才能记住船的最后位置,并且必须能够计算船的新位置。

另请参阅:http: //download.oracle.com/javase/tutorial/uiswing/painting/index.html - 特别是第 3 步

于 2011-08-01T10:25:24.233 回答
1

我读过 repaint() 方法只是一个请求,它不会在每次调用时都执行

它将多个 repaint() 请求合并为一个以提高效率。

我相信我注意到了这一点,因为当我移动它时,我的宇宙飞船往往会略微滞后。

然后发布演示此问题的 SSCCE。我怀疑问题出在你的代码上。

关于您接受的解决方案,请查看 Charles 上次发布的帖子:Swing/JFrame 与 AWT/Frame 用于在 EDT 之外进行渲染,比较 Swing 与 AWT 解决方案。

于 2011-08-01T15:01:51.573 回答