我创建一个图形前端的JBox2D模拟。 仿真逐步运行,在更新之间,模拟的内容都应该被绘制。 类似的游戏,只是没有输入。
我只需要几何图元绘制JBox2D模拟。 这个API似乎是最简单的选择,但它的设计是有点混乱。
目前,我有叫一类Window
延伸JFrame
,包含作为成员另一个叫做类Renderer
。 的Window
类只初始化自身,并提供一个updateDisplay()
方法(由主循环调用),调用updateDisplay(objects)
的方法Renderer
。 我做了这两种方法我自己和他们的唯一目的是调用repaint()
的Renderer
。
是JPanel
应该使用呀? 或者,我应该使用动画一些更复杂的方法(例如,涉及事件和/或时间间隔在一些后端线程)?
如果你想要在设定的时间间隔来安排更新, javax.swing.Timer
为它提供一个Swing的综合服务。 Timer
运行其上的EDT定期任务,而无需显式的循环。 (显式循环会从处理事件阻止EDT,这将冻结UI。我解释这更深入的在这里 。)
最终做任何种类的挥杆,你仍然会做两件事情画的:
- 重写
paintComponent
做你的图纸。 - 调用
repaint
按需来请求您的图纸是可见的。 (当它的需要,例如,当其他一些程序的窗口经过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);
}
}
如果程序长时间运行并重新绘制可能同时发生,也可用于双缓冲。 附图做是为了其是单独被示出的一个的图像。 然后,当绘图程序完成后,图像的引用进行交换,从而更新是无缝的。
通常,您应该使用双缓冲的比赛为例。 双缓冲可以防止图像中的局部状态被示出。 这可能如果,例如,您使用的游戏循环(而不是在后台线程发生Timer
)和重绘发生的游戏是做画。 如果没有双缓冲,这种情况将导致闪烁或撕裂。
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();
}
}
}
这幅画程序只是打算画垃圾这需要很长的时间:
对于紧密耦合仿真, javax.swing.Timer
是很好的选择。 让计时器的监听调用您的实现paintComponent()
如在这里和在引用的例子在这里 。
松散耦合的模拟,让模型中的后台线程进化SwingWorker
,如图所示这里 。 调用publish()
当中肯给你模拟。
选择部分由模拟的性质和模式的占空比决定。
为什么不直接使用的东西从测试平台? 它已经做了一切。 就拿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