Even after using Java Swing for over a year, it still seems like magic to me. How do I correctly use a BufferStrategy, in particular, the method createBufferSrategy()<
The way you create the BufferStrategy
is "okay", you could have a look at the JavaDocs for BufferStrategy which has a neat little example.
The way you're using it, is questionable. The main reason for using a BufferStrategy
is because you want to take control of the painting process (active painting) away from Swing's painting algorithm (which is passive)
BUT, you seem to trying to do both, which is why it's causing your issues. Instead, you should have a "main" loop which is responsible for deciding what and when the buffer should paint, for example...
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane testPane = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(testPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// The component needs to be attached to displayed window before
// the buffer can be created
testPane.startPainting();
}
});
}
public class TestPane extends Canvas {
private AtomicBoolean painting = new AtomicBoolean(true);
private PaintCycle paintCycle;
private Rectangle clickBounds;
public TestPane() {
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (clickBounds != null && clickBounds.contains(e.getPoint())) {
painting.set(false);
}
}
});
}
public void startPainting() {
if (paintCycle == null) {
createBufferStrategy(2);
painting.set(true);
paintCycle = new PaintCycle();
Thread t = new Thread(paintCycle);
t.setDaemon(true);
t.start();
}
}
public void stopPainting() {
if (paintCycle != null) {
painting.set(false);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
public class PaintCycle implements Runnable {
private BufferStrategy strategy;
private int xDelta = 2;
private int yDelta = 2;
@Override
public void run() {
System.out.println("Painting has started");
int x = (int) (Math.random() * (getWidth() - 40));
int y = (int) (Math.random() * (getHeight() - 40));
do {
xDelta = (int) (Math.random() * 8) - 4;
} while (xDelta == 0);
do {
yDelta = (int) (Math.random() * 8) - 4;
} while (yDelta == 0);
clickBounds = new Rectangle(x, y, 40, 40);
strategy = getBufferStrategy();
while (painting.get()) {
// Update the state of the model...
update();
// Paint the state of the model...
paint();
try {
// What ever calculations you want to use to maintain the framerate...
Thread.sleep(40);
} catch (InterruptedException ex) {
}
}
System.out.println("Painting has stopped");
}
protected void update() {
int x = clickBounds.x + xDelta;
int y = clickBounds.y + yDelta;
if (x + 40 > getWidth()) {
x = getWidth() - 40;
xDelta *= -1;
} else if (x < 0) {
x = 0;
xDelta *= -1;
}
if (y + 40 > getHeight()) {
y = getHeight() - 40;
yDelta *= -1;
} else if (y < 0) {
y = 0;
yDelta *= -1;
}
clickBounds.setLocation(x, y);
}
protected void paint() {
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();
// Render to graphics
// ...
graphics.setColor(Color.BLUE);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.setColor(Color.RED);
graphics.fill(clickBounds);
// Dispose the graphics
graphics.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
}
}
}
You should also remember, Swing's been using either DirectX or OpenGL pipelines since about 1.4 (or maybe 1.5). The main reasons for using BufferStrategy
are more direct access to the hardware (which Swing is pretty close to anyway) AND direct control over the painting process (which is now really the only reason to use it)