2014-01-21 71 views
1

我正在为JBox2D模拟创建图形前端。模拟将逐步运行,并且在更新之间,应该绘制模拟的内容。除了没有输入外,与游戏类似。这是使用Java 2D图形API的正确方法吗?

我只需要几何图元来绘制一个JBox2D模拟。这个API看起来是最简单的选择,但它的设计有点混乱。

目前我有一个类Window延伸JFrame,其中包含作为成员另一个类叫RendererWindow类只进行初始化,并提供updateDisplay()方法(由主循环调用),该方法在Renderer上调用updateDisplay(objects)方法。我自己做了这两种方法,他们唯一的目的是致电 Renderer

JPanel是否应该这样使用?或者我应该使用一些更复杂的动画方法(例如涉及某些后端线程中的事件和/或时间间隔)?

回答

3

如果您想要以设定的时间间隔安排更新,javax.swing.Timer会为其提供Swing集成服务。 Timer定期在EDT上运行其任务,而没有显式循环。 (明确的循环会阻止EDT处理事件,这会冻结UI,我更深入地解释了here)。

最终在Swing中做任何类型的绘画,你仍然会做两件事:

  1. 覆盖paintComponent做你的绘画。
  2. 根据需要调用repaint以请求使您的图形可见。 (Swing通常只在需要时才重新绘制,例如当某个其他程序的窗口经过Swing组件顶部时)。

如果您正在做这两件事,那么您可能做得很对。 Swing并没有真正具有动画的高级API。它的设计主要是考虑绘制GUI组件。它当然可以做一些好的事情,但是你必须从头开始编写一个组件,就像你正在做的那样。

Painting in AWT and Swing涵盖了一些'幕后'的东西,如果你没有书签。

您可能会查看JavaFX。我个人不太了解它,但它应该更适合动画。

作为一种优化,可以做的一件事就是在单独的图像上绘制图像,然后在paintComponent上将图像绘制到面板上。如果绘画很长时,这是特别有用的:重绘可以由系统调度,以便在发生更多控制时保持重绘。

如果您未绘制图像,则需要用对象构建模型,并在paintComponent内每次绘制所有模型。


这里画上一个形象的例子:

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

/** 
* Holding left-click draws, and 
* right-clicking cycles the color. 
*/ 
class PaintAnyTime { 
    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       new PaintAnyTime(); 
      } 
     }); 
    } 

    Color[] colors = {Color.red, Color.blue, Color.black}; 
    int currentColor = 0; 
    BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB); 
    Graphics2D imgG2 = img.createGraphics(); 

    JFrame frame = new JFrame("Paint Any Time"); 
    JPanel panel = new JPanel() { 
     @Override 
     protected void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      // Creating a copy of the Graphics 
      // so any reconfiguration we do on 
      // it doesn't interfere with what 
      // Swing is doing. 
      Graphics2D g2 = (Graphics2D) g.create(); 
      // Drawing the image. 
      int w = img.getWidth(); 
      int h = img.getHeight(); 
      g2.drawImage(img, 0, 0, w, h, null); 
      // Drawing a swatch. 
      Color color = colors[currentColor]; 
      g2.setColor(color); 
      g2.fillRect(0, 0, 16, 16); 
      g2.setColor(Color.black); 
      g2.drawRect(-1, -1, 17, 17); 
      // At the end, we dispose the 
      // Graphics copy we've created 
      g2.dispose(); 
     } 
     @Override 
     public Dimension getPreferredSize() { 
      return new Dimension(img.getWidth(), img.getHeight()); 
     } 
    }; 

    MouseAdapter drawer = new MouseAdapter() { 
     boolean rButtonDown; 
     Point prev; 

     @Override 
     public void mousePressed(MouseEvent e) { 
      if (SwingUtilities.isLeftMouseButton(e)) { 
       prev = e.getPoint(); 
      } 
      if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) { 
       // (This just behaves a little better 
       // than using the mouseClicked event.) 
       rButtonDown = true; 
       currentColor = (currentColor + 1) % colors.length; 
       panel.repaint(); 
      } 
     } 

     @Override 
     public void mouseDragged(MouseEvent e) { 
      if (prev != null) { 
       Point next = e.getPoint(); 
       Color color = colors[currentColor]; 
       // We can safely paint to the 
       // image any time we want to. 
       imgG2.setColor(color); 
       imgG2.drawLine(prev.x, prev.y, next.x, next.y); 
       // We just need to repaint the 
       // panel to make sure the 
       // changes are visible 
       // immediately. 
       panel.repaint(); 
       prev = next; 
      } 
     } 

     @Override 
     public void mouseReleased(MouseEvent e) { 
      if (SwingUtilities.isLeftMouseButton(e)) { 
       prev = null; 
      } 
      if (SwingUtilities.isRightMouseButton(e)) { 
       rButtonDown = false; 
      } 
     } 
    }; 

    PaintAnyTime() { 
     // RenderingHints let you specify 
     // options such as antialiasing. 
     imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
          RenderingHints.VALUE_ANTIALIAS_ON); 
     imgG2.setStroke(new BasicStroke(3)); 
     // 
     panel.setBackground(Color.white); 
     panel.addMouseListener(drawer); 
     panel.addMouseMotionListener(drawer); 
     Cursor cursor = 
      Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR); 
     panel.setCursor(cursor); 
     frame.setContentPane(panel); 
     frame.pack(); 
     frame.setResizable(false); 
     frame.setLocationRelativeTo(null); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setVisible(true); 
    } 
} 

PaintAnyTime screenshot


如果程序是长时间运行并重新绘制可能同时发生,也可使用双缓冲。将图像与所显示的图像分开进行绘制。然后,当绘图程序完成时,图像引用被交换,因此更新是无缝的。例如,您通常应该对游戏使用双缓冲。双缓冲可防止图像以部分状态显示。例如,如果您在游戏循环中使用后台线程(而不是Timer),并且重绘发生在游戏正在进行绘画时,就可能发生这种情况。如果没有双缓冲,这种情况会导致闪烁或撕裂。

默认情况下,Swing组件是双缓冲区,所以如果所有绘图都在EDT上进行,则不需要自己编写双缓冲区逻辑。 Swing已经做到了。

这是一个较为复杂的例子显示了一个长时间运行的任务和缓冲交换:

import java.awt.*; 
import javax.swing.*; 
import java.awt.image.*; 
import java.awt.event.*; 
import java.util.*; 

/** 
* Left-click to spawn a new background 
* painting task. 
*/ 
class DoubleBuffer { 
    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       new DoubleBuffer(); 
      } 
     }); 
    } 

    final int width = 640; 
    final int height = 480; 

    BufferedImage createCompatibleImage() { 
     GraphicsConfiguration gc = 
      GraphicsEnvironment 
       .getLocalGraphicsEnvironment() 
       .getDefaultScreenDevice() 
       .getDefaultConfiguration(); 
     // createCompatibleImage creates an image that is 
     // optimized for the display device. 
     // See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int- 
     return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT); 
    } 

    // The front image is the one which is 
    // displayed in the panel. 
    BufferedImage front = createCompatibleImage(); 
    // The back image is the one that gets 
    // painted to. 
    BufferedImage back = createCompatibleImage(); 
    boolean isPainting = false; 

    final JFrame frame = new JFrame("Double Buffer"); 
    final JPanel panel = new JPanel() { 
     @Override 
     protected void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      // Scaling the image to fit the panel. 
      Dimension actualSize = getSize(); 
      int w = actualSize.width; 
      int h = actualSize.height; 
      g.drawImage(front, 0, 0, w, h, null); 
     } 
    }; 

    final MouseAdapter onClick = new MouseAdapter() { 
     @Override 
     public void mousePressed(MouseEvent e) { 
      if (!isPainting) { 
       isPainting = true; 
       new PaintTask(e.getPoint()).execute(); 
      } 
     } 
    }; 

    DoubleBuffer() { 
     panel.setPreferredSize(new Dimension(width, height)); 
     panel.setBackground(Color.WHITE); 
     panel.addMouseListener(onClick); 
     frame.setContentPane(panel); 
     frame.pack(); 
     frame.setLocationRelativeTo(null); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setVisible(true); 
    } 

    void swap() { 
     BufferedImage temp = front; 
     front = back; 
     back = temp; 
    } 

    class PaintTask extends SwingWorker<Void, Void> { 
     final Point pt; 

     PaintTask(Point pt) { 
      this.pt = pt; 
     } 

     @Override 
     public Void doInBackground() { 
      Random rand = new Random(); 

      synchronized(DoubleBuffer.this) { 
       Graphics2D g2 = back.createGraphics(); 
       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON); 
       g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
            RenderingHints.VALUE_STROKE_PURE); 
       g2.setBackground(new Color(0, true)); 
       g2.clearRect(0, 0, width, height); 
       // (This computes pow(2, rand.nextInt(3) + 7).) 
       int depth = 1 << (rand.nextInt(3) + 7); 
       float hue = rand.nextInt(depth); 
       int radius = 1; 
       int c; 
       // This loop just draws concentric circles, 
       // starting from the inside and extending 
       // outwards until it hits the outside of 
       // the image. 
       do { 
        int rgb = Color.HSBtoRGB(hue/depth, 1, 1); 
        g2.setColor(new Color(rgb)); 

        int x = pt.x - radius; 
        int y = pt.y - radius; 
        int d = radius * 2; 

        g2.drawOval(x, y, d, d); 

        ++radius; 
        ++hue; 
        c = (int) (radius * Math.cos(Math.PI/4)); 
       } while (
         (0 <= pt.x - c) || (pt.x + c < width) 
        || (0 <= pt.y - c) || (pt.y + c < height) 
       ); 

       g2.dispose(); 
       back.flush(); 

       return (Void) null; 
      } 
     } 

     @Override 
     public void done() { 
      // done() is completed on the EDT, 
      // so for this small program, this 
      // is the only place where synchronization 
      // is necessary. 
      // paintComponent will see the swap 
      // happen the next time it is called. 
      synchronized(DoubleBuffer.this) { 
       swap(); 
      } 

      isPainting = false; 
      panel.repaint(); 
     } 
    } 
} 

绘画程序只是打算画垃圾这需要很长的时间:

DoubleBuffer screenshot

+0

我不会看JavaFX,因为我时间不多,但谢谢。我正在做这两件事。此外,每次模拟更新时窗口都应该重新绘制,因此现在我可以不使用定时器。 – corazza

+0

这是非常缓慢的,除非你手工加倍缓冲,FYI –

+0

@DanielMurphy当然可以做出优化,但这不是问题。请注意,所有Swing组件默认都是双缓冲的,因此更优雅的优化方法是对图像执行自定义图像绘制,并在重新绘制的面板上绘制图像。没有必要加倍缓冲。 – Radiodef

2

对于紧密耦合的模拟,javax.swing.Timer是一个不错的选择。让定时器的听众调用你的实现paintComponent(),如here和引用的例子here

对于松散耦合的模拟,让模型在SwingWorker的后台线程中进化,如here所示。当模拟到apropos时调用publish()

该选择部分取决于模拟的性质和模型的占空比。

+0

这些是很棒的代码片段。 – Radiodef

1

为什么不只是使用测试平台上的东西呢?它已经完成了一切。只需要使用JPanel,控制器和调试绘图。它使用Java 2D绘图。

看到这里,做缓冲的渲染的JPanel: https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/TestPanelJ2D.java

这里为调试抽奖: https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/DebugDrawJ2D.java

见TestbedMain.java文件,看到正常的测试平台是如何启动,以及撤掉你不需要:)

编辑: 声明:我保持jbox2d

这里测试床框架的包装:https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework

TestbedMain。java是在j2d文件夹,在这里: https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d

+0

通常,当你宣传你所附属的礼物时,指出你的联系。 – Radiodef

+1

新增免责声明:) –

+0

哇,这将是恒星,如果它真的有用,谢谢!你能提供一些关于我应该如何使用这些代码的更多信息吗?也许一个简单的例子?我找不到'TestbedMain.java'。 – corazza

相关问题