2014-01-21 3 views
1

Я создаю графический интерфейс для моделирования JBox2D. Моделирование выполняется постепенно, и между обновлениями предполагается, что содержимое моделируется. Подобно игре, кроме без ввода.Является ли это правильным способом использования Java 2D Graphics API?

Мне нужны только геометрические примитивы для моделирования JBox2D. Этот API казался самым простым выбором, но его дизайн немного запутан.

В настоящее время у меня есть один класс под названием Window, расширяющий JFrame, который содержит в качестве члена другой класс под названием Renderer. Класс Window инициализирует себя и предоставляет метод updateDisplay() (который вызывается основным контуром), который вызывает метод updateDisplay(objects) на Renderer. Я сделал эти два метода самостоятельно, и их единственная цель - позвонить repaint() на Renderer.

Предполагается, что используется JPanel таким образом? Или я должен использовать какой-то более сложный метод для анимации (такой, который включает в себя события и/или временные интервалы в каком-то back-end потоке)?

ответ

3

Если вы хотите запланировать обновления с заданным интервалом, javax.swing.Timer предоставляет интегрированную услугу Swing. Timer периодически выполняет свою задачу на EDT, не имея явного цикла. (Явный цикл будет блокировать EDT от обработки событий, которые бы заморозить UI я объяснил это более глубокий here.).

В конечном счете делает любой вид живописи в свинг вы все равно будете делать две вещи:

  1. Переопределение paintComponent, чтобы сделать ваш рисунок.
  2. Вызов repaint как необходимо для того, чтобы ваш чертеж стал видимым. (Качели обычно перекраиваются только тогда, когда это необходимо, например, когда окно какой-либо другой программы проходит над верхней частью компонента 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, вам не нужно писать логику двойной буферизации самостоятельно. Свинг уже делает это.

Вот несколько более сложный пример, который показывает затянувшуюся задачу и буфер подкачку:

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. Invoke publish(), когда по поводу симуляции.

Выбор продиктован частично характером моделирования и рабочего цикла модели.

+0

Отличные фрагменты кода. – Radiodef

1

Почему бы не просто использовать материал на стенде? Он уже делает все. Просто возьмите JPanel, контроллер и отлаживайте ничью. Он использует 2D-рисунок 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

Смежные вопросы