1

JBox2D シミュレーション用のグラフィカル フロントエンドを作成しています。シミュレーションは段階的に実行され、更新の間にシミュレーションの内容が描画されることになっています。入力がないこと以外はゲームに似ています。

JBox2D シミュレーションを描画するには、ジオメトリ プリミティブのみが必要です。この API は最も単純な選択のように思えましたが、その設計は少しわかりにくいものです。

現在、 extends と呼ばれる 1 つのクラスがありWindowJFrameこれには と呼ばれる別のクラスがメンバーとして含まれていますRendererWindowクラスはそれ自体を初期化するだけで、(メイン ループによって呼び出される) メソッドを提供updateDisplay()updateDisplay(objects)Renderer. 私はこれら 2 つのメソッドを自分で作成しましたが、その唯一の目的は を呼び出すことrepaint()ですRenderer

はそのJPanelように使用されることになっていますか?それとも、アニメーションにもっと洗練された方法を使用することになっていますか (バックエンド スレッドでイベントや時間間隔を含むなど)。

4

3 に答える 3

3

設定した間隔で更新をスケジュールする場合javax.swing.Timerは、Swing 統合サービスを提供します。Timer明示的なループなしで、定期的に EDT でタスクを実行します。(明示的なループは、EDT によるイベントの処理をブロックし、UI をフリーズさせます。これについては、こちらで詳しく説明しました。)

最終的に Swing であらゆる種類のペイントを行う場合でも、次の 2 つのことを行う必要があります。

  1. 描画をオーバーライドpaintComponentします。
  2. 必要に応じて呼び出しrepaintて、図面を表示するように要求します。(Swing は通常、必要な場合にのみ再描画します。たとえば、他のプログラムのウィンドウが Swing コンポーネントの上を通過する場合などです。)

この 2 つのことを行っている場合は、おそらく正しく行っていることになります。Swing には、アニメーション用の高レベル API は実際にはありません。主に GUI コンポーネントの描画を念頭に置いて設計されています。それは確かにいくつかの良いことをすることができますが、あなたがやっているように、ほとんどゼロからコンポーネントを書かなければなりません。

AWT と 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 スクリーンショット


ルーチンが長時間実行され、再描画が同時に発生する可能性がある場合は、ダブル バッファリングも使用できます。描画は、表示されているものとは別の画像に対して行われます。次に、描画ルーチンが完了すると、更新がシームレスになるようにイメージ参照が交換されます。

たとえば、通常、ゲームにはダブル バッファリングを使用する必要があります。ダブルバッファリングにより、画像が部分的な状態で表示されるのを防ぎます。これは、たとえば、ゲーム ループに ( の代わりに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 のスクリーンショット

于 2014-01-21T18:27:04.863 に答える