【问题标题】:Why does my draw method only work some of the time?为什么我的绘图方法只在某些时候有效?
【发布时间】:2015-04-28 23:35:36
【问题描述】:

我知道在挥杆中你打算使用重绘来重绘面板,但我开始制作游戏并且读到重绘在时间方面是不可靠的。所以我正在尝试制定一个策略,创建一个 BufferedImage,将我需要的所有东西绘制到该对象的图形对象上,然后使用 drawImage 方法将图像绘制到我的 jpanel。

我正在调用 dispose 让面板重新渲染,但它只在某些时候工作。下面只是一个例子。如果您继续运行此程序,则 BufferedImage 只会在您运行它的大约 3/10 次时被绘制。有人可以向我解释一下吗?

public class TestMain {

public static void main(String[] args) throws InterruptedException {

    JFrame jf = new JFrame();
    MyPanel mp = new MyPanel();
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jf.setLocation(50, 500);
    jf.add(mp);
    jf.pack();
    jf.setVisible(true);

    mp.draw();
}
}

public class MyPanel extends JPanel {

private static final long serialVersionUID = 1L;

BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d;

public MyPanel() {
    super();
    setPreferredSize(new Dimension(300, 300));
}

public void draw() {
    System.out.println("In draw");
    g2d = (Graphics2D) img.getGraphics();
    g2d.drawString("Test", 10, 10);

    Graphics g = getGraphics();

    g.drawImage(img, 0, 0, this);
    g.dispose();
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    System.out.println("In paintComponent");
}

}

我在幕后与经常阻碍平局的事情作斗争。我意识到调整窗口大小等会调用重绘并覆盖我的绘图,但是当我不这样做时,它应该每次都显示出来,不是吗?

编辑:有趣...如果我将 draw 方法包装在一个连续的 while 循环中,它“有效”。但这并不能回答我的问题。我不应该需要不断地回忆这个方法来给人一种画得很好的错觉。

【问题讨论】:

  • jf.setVisible(true); 之前尝试添加jf.revalidate();jf.repaint();
  • @MaVRoSCy 不。结果相同。每隔几次运行绘制一次。
  • 在显示帧后调用 draw() 方法。 (在 WindowOpened 操作中,我建议..)
  • 我推荐阅读Killer Game Programming in Java的第2章(实际上推荐整本书):fivedots.coe.psu.ac.th/~ad/jg/ch1/index.html

标签: java swing graphics


【解决方案1】:

您不应该使用 getGraphics() 方法进行绘画。您使用 getGraphics 执行的任何代码都只是暂时的,只要 Swing 确定组件需要重新绘制(这可能发生在此处),它们就会丢失。

覆盖类的 getPreferredSize() 方法并调用 super.paintComponent(),然后添加 System.out.println(...) 语句。另外,在 draw() 方法中添加 System.out.println(...) 语句,以查看代码的执行顺序。

我正在调用 dispose 让面板重新渲染,

这不会导致面板呈现,它只是释放分配给 Graphics 对象的资源。您需要调用 draw() 方法来不断地重绘。

这本质上与使用 Timer 不断地 repaint() 组件相同。不同之处在于 draw() 可能会立即发生,但重绘可能需要几毫秒。但是由于 repaint() 每次间隔保持不变都需要几毫秒,所以它不像 repaint 会绘制一次然后等待下一次。

【讨论】:

  • 嗨,我引用了《Java 中的杀手级游戏编程》一书,该书鼓励使用 getGraphics 进行绘制,而不是依赖于重绘和覆盖 paintComponent()....“重绘的调用( ) 没了,paintComponent( ) 的重写也没有了,它的功能被并入到了paintScreen( ) 中。主动渲染将缓冲图像渲染到屏幕的任务交到了我的手中。这意味着渲染时间可以准确衡量,并且对重绘请求被 JVM 延迟或跳过的担忧消失了”
  • 另外,你是对的,dispose 只释放资源。
  • 你要求解释你的问题,我提供了。您是否通过添加 System.out.println(...) 语句来验证结果?
  • 是的,我可以看到每次添加打印语句时都会调用 draw 。对不起,我不认为它回答了我的问题。如果是这样,那我就不明白了。为什么它会在某些时间(并停留在那里)而不是所有时间绘制?
  • 也是时间问题。 Swing 组件应在 EDT 上创建。您可以获得随机结果。阅读Concurrency 上的 Swing 教程以获取更多信息。作为一种解决方案,使用您的绘画代码覆盖 paintComponent() 方法,然后直接调用该方法。然后,无论您调用该方法还是 Swing 调用您绘制的图像的方法。也就是说,paintComponent() 方法中的唯一语句应该是 drawImage() 语句。您的 draw() 代码只会更新图像。
【解决方案2】:

我修复了您的代码并获得了以下 GUI:

当您在缓冲图像上绘画时,您必须做所有事情。您必须绘制背景并绘制前景。

当您在 JPanel 的 paintComponent 方法中绘制缓冲图像时,您所要做的就是绘制图像。

Swing paintComponent 方法在 Java 版本 6、7 和 8 中足够快,可以在不闪烁的情况下进行绘制。我做过几万行没有闪烁的动画。

您必须通过调用 SwingUtilities invokeLater 方法在 Event Dispatch 线程上启动 Swing 应用程序。

这是您修改后的代码。

package com.ggl.testing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class TestMain implements Runnable {

    @Override
    public void run() {
        JFrame jf = new JFrame();
        MyPanel mp = new MyPanel();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setLocation(50, 50);
        jf.add(mp);
        jf.pack();
        jf.setVisible(true);
        mp.draw();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new TestMain());
    }

    public class MyPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        BufferedImage img = new BufferedImage(100, 100,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d;

        public MyPanel() {
            super();
            setPreferredSize(new Dimension(300, 300));
        }

        public void draw() {
            System.out.println("In draw");
            g2d = (Graphics2D) img.getGraphics();
            g2d.setColor(Color.WHITE);
            g2d.fillRect(0, 0, img.getWidth(), img.getHeight());
            g2d.setColor(Color.BLACK);
            g2d.drawString("Test", 30, 50);
            g2d.dispose();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            System.out.println("In paintComponent");
            g.drawImage(img, 0, 0, this);
        }

    }

}

【讨论】:

  • 嗨吉尔伯特。该死,我完全忽略了在 EDT 上运行程序!谢谢。 “Swing paintComponent 方法在 Java 版本 6、7 和 8 中足够快,可以在不闪烁的情况下进行绘制。我已经完成了数万行不闪烁的动画。” - 这很高兴知道,因为我已经读到它还不够好。
  • @AlanSmith, I completely overlooked running the program on the EDT! 我在我的 cmets 中提到了这一点,并为您提供了 Swing 教程的链接。这个答案仍然没有解释随机行为,它只演示了正常的 Swing 绘画。由于 RepaintManager 合并了 repaint() 请求并将请求添加回 EDT,因此您仍然可能会遇到时间问题。因此,例如,如果您的游戏处理大量 KeyEvent,理论上这可能会减慢重绘周期。我认为这就是本书所讨论的时间问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-28
  • 2011-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
  • 2015-10-04
相关资源
最近更新 更多