Java图形正在闪烁。

10

好的,我理解您需要一个SSCCE,因此我创建了(我的第一个)SSCCE。

我用不到200行代码就成功复制了问题。在我的系统上,这个演示程序编译并运行得很完美(当然只有闪烁问题还在)。我删除了与此无关的一切内容。因此,现在我们基本上有两个源文件:屏幕管理器和游戏管理器。

屏幕管理器: http://pastebin.com/WeKpxEXW

游戏管理器: http://pastebin.com/p3C5m8UN

您可以使用此Makefile编译此代码(我使用了Linux的端口版本来代替Windows): CC = javac BASE = nl/jorikoolstra/jLevel CLASS_FILES = classes/$(BASE)/Game/GameMain.class classes/$(BASE)/Graphics/ScreenManager.class

jLevel: $(CLASS_FILES)
    @echo Done.

classes/%.class : src/%.java
    @echo Compiling src/$*.java to $@ [command: $(CC) src/$*.java ] ...
    @$(CC) -Xlint:unchecked -d classes -classpath src src/$*.java

源文件存放在/src目录中,而类文件则存放在/classes目录中。

编译后,可以使用以下 .bat 文件启动游戏:

@set STARUP_CLASS=nl.jorikoolstra.jLevel.Game.GameMain
@set ARGUMENTS=1280 1024 32
@java -cp classes;resources %STARUP_CLASS% %ARGUMENTS%

请注意,ARGUMENT变量取决于您自己的屏幕设置,并且您必须更改它,以便游戏以适合您屏幕分辨率的正确分辨率显示。


我还没有仔细阅读代码,但你试过重新启动电脑吗?这经常能解决闪烁问题。 - Zach Latta
如果您将其退出全屏模式,问题是否仍存在?在Linux和Windows上是否使用了相同的硬件加速?您可以尝试使用frame.getGraphicsConfiguration().getBufferCapabilities().isPageFlipping()来查看是否进行了硬件加速。在排除此问题之前,请确保两个平台上都是相同的。 - chubbsondubs
您的Windows电脑上是否安装了当前的驱动程序? - BillRobertson42
今天会检查并回复。 - Jori
1
章节18中链接的原始代码在Windows上能否按预期运行? - siegi
显示剩余10条评论
6个回答

8
我了解为什么会闪烁——BufferStrategy 与组件的 paint() 方法执行不同的绘画任务,它们似乎使用不同的 Graphics 对象并以不同的速率刷新。
当在 show() 方法之前调用 paint() 方法时,一切都正常。但是,当在 show() 方法之后调用 paint() 方法时,它将重新绘制组件以显示其初始空白外观,因此就会出现闪烁的情况。
要消除闪烁很容易:重写你的 JFrame (GameMain) 的 paint() 方法,因为你不需要它执行任何操作(BufferStrategy 可以给你更精确的控制绘画)。
@Override
public void paint (Graphics g) {}

这就是全部内容了。(我已经测试过它,运行得很好,希望能对你有所帮助 :))


===== 更新 =====

不要覆盖paint()方法,一个更好的方法是为你的JFrameGameMain)调用setIgnoreRepaint(true) -- 这个方法就是为这样的目的而设计的!使用它

private GameMain(String ... args)
{
    setIgnoreRepaint(true);
    .....
}

@Jori 我认为它会。这是Java的绘图方案,跨平台兼容。你试过了吗? - shuangwhywhy
好的。这正是我发布的代码链接所展示的。你需要调用bufferStrategy.show(),而不是其他任何方法。 - Gene
是的,它完成了任务:)。谢谢。 - Jori
显然这是与平台相关的,否则为什么在Linux上可以运行而不需要 setIgnoreRepaint(true); 这一行呢? - Jori
Ubuntu运行Gnome桌面,是的,默认安装了JDK。 - Jori

1

这是我实现双缓冲的方式,可能有助于您理解该概念。请注意,它是在JPanel中实现的,但我认为它也可以在其他容器中实现:

TheJApplet.java:

import java.awt.*;
import javax.swing.*;

public class TheJApplet extends JApplet
{
    private Image myImage;

    java.net.URL GameURL = CheckerGameJApplet.class.getResource("GameIMG");

    String GamePath = GameURL.getPath();

    @Override
    public void init()
    {
        String GraphPath = GamePath+"/";

        File myImage_File = new File(GraphPath+"myImage.jpg");

        try
        {
            myImage = ImageIO.read(myImage_File);
        }
        catch (IOException ex)
        {
            // Add how you like to catch the IOExeption
        }

        final TheJPanel myJPanel = new TheJPanel(myImage);

        add(myJPanel);
    }
}

TheJPanel.java:

import java.awt.*;
import javax.swing.*;

public class TheJPanel extends JPanel
{
    private int screenWidth  = 500;
    private int screenHeight = 500;

    private BufferedImage BuffImg = new BufferedImage
                                         (screenWidth, 
                                          screenHeight,
                                          BufferedImage.TYPE_INT_RGB);

    private Graphics2D Graph = BuffImg.createGraphics();

    private Image myImage;

    public TheJPanel(Image myImage)
    {
        this.myImage = myImage;

        repaint();
    }

    @Override
    public void paintComponent(Graphics G)
    {
        Graphics2D Graph2D = (Graphics2D)G;

        super.paintComponent(Graph2D);

        if(BuffImg == null)
        {
            System.err.println("BuffImg is null");
        }

        Graph.drawImage(myImage, 0, 0, this);

        Graph2D.drawImage(BuffImg, 0, 0, this);
    }
}

希望这有所帮助,祝好运。

在您的JPanel中调用super.paintComponent()可能会触发背景重绘 - 我建议跳过该行。此外,通过setDoubleBuffered(true),JComponent本地支持双缓冲。 - msteiger
我用这种方式制作了一个棋盘游戏,完美运行,你所说的“可能会触发背景重绘”是什么意思,对我来说不是很清楚,你能进一步解释一下吗?我会查看 setDoubleBuffered(true) 并检查它是否可以减少我的代码行数,虽然我有点记得我曾在我的棋盘游戏上尝试过它,但没用,再次核实也无妨,感谢您的提示和评论。 - Shikatsu
1
当您调用super方法时,它会绘制默认的组件外观,如果您自己绘制整个组件的内容,则不需要此外观。这也可能导致闪烁问题。 - msteiger

1

当你将 hwnd.createBufferStrategy(2) 放在自己的方法中时,它可能对你有效。


你是指在 initializeGameEnvironment() 方法中吗? - Jori
应该可以工作,但最好像这样:public void createStrategy() { createBufferStrategy(2); strategy = getBufferStrategy(); }在你的第一个屏幕可见后调用它。 - Eveli
很不幸,它不起作用:(我在screenManager.setFullScreen(displayMode, this);之后粘贴了createBufferStrategy(2),但仍然会非常快速地闪烁。另外,我不知道为什么这会起作用,因为hwnd是对JFrame的引用,对其调用的方法与从内部类调用完全相同,对吧?无论如何,感谢您的帮助,也许您可以找到真正的错误。 - Jori

1
我有一个跨平台的基于Java AWT的动画程序。在严格遵循Java BufferStrategy文档中的示例代码之前,它出现了闪烁问题。但是我使用的是嵌入到Swing层次结构中的AWT Canvas,而不是像你一样全屏。如果感兴趣,可以在这里查看代码
另一个需要注意的事情是,AWT管道使用OpenGL原语以获得良好的性能,而OpenGL支持在许多视频驱动程序中存在错误。尝试安装最新版本的平台驱动程序。

1

Java渲染透明背景GIF图片时出现了问题。可能就是这个问题。


我在精灵的渲染循环中只使用 .png 图像。 - Jori
在新的示例中,也没有使用任何图像,只有文本,但仍会闪烁。 - Jori

0

没有 SCCSE,我觉得很难回答你的问题。我也不知道 RepaintManagerResetter 是做什么的。

你可以将背景颜色设置为一些花哨的颜色,比如 0xFF00FF,以找出是否有人在绘制之前“清除”了背景。如果闪烁的图像是紫色的,那就是这个原因;如果包含垃圾或旧图像,则可能是双缓冲。

无论如何,我会尽量确保只有你自己才能进行绘制。首先,尝试防止本机 Windows 代码绘制窗口背景。请设置以下内容:

/*
 * Set a Windows specific AWT property that prevents heavyweight components 
 * from erasing their background. 
 */
System.setProperty("sun.awt.noerasebackground", "true");

此外,如果您正在使用Swing组件,请确保在JFrame中覆盖此方法。
@Override
public void paintComponent(Graphics G)
{
  // do not call super.pC() here
  ...
}

如果这没有帮助,请提供一个能工作的代码示例,以便其他人可以复现这个问题。

System.setProperty("sun.awt.noerasebackground", "true"); 这个方法没有起作用,而且在新的例子中我也没有使用任何 JComponents。 - Jori

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接