OK this is the basic in all page flickering and how to avoid flickering
First a basic concept:
- computer screens are refreshed 60 times a second in North America and 50 times a second in Europe
Java (and Swing) do a good job at it and refresh the screen the best way it can manage but some times it is nto the best way so sometimes, particularly in Games, you will need to do the job and help a little bit the Java GUI for best performance.
How to do it ? I'll teach you right away.
Sorry for my European and Asiatic friend, I'll do it the Obama way
The idea is to draw what you have to draw on 2 buffers, wait for the part of the GUI that repaint the screen, and tell it from which buffer in memory it has to redraw the image
I have 3 classes here... feel free to cut & paste them
OK the first one in to create the main frame... you are use to it
and we will use a Canvas to do our drawing... Canvas are needed because we can apply a
BfferStartegy to them
BuffeStartegy is the bumber of buffers we will use to draw "offscreen".
Here is a first glitch/trap: before applying a buffer strategy to a Component this component has to be visible so here is my first class
For this example we will use the stupid "bouncing ball" program:
CODE
import java.awt.BorderLayout;
import javax.swing.*;
public class GameFrame extends JFrame {
GameFrame() {
// frame description
super("Bouncing ball");
// our Canvas
GameCanvas canvas = new GameCanvas();
add(canvas, BorderLayout.CENTER);
// set it's size and make it visible
setSize(600, 400);
setVisible(true);
// now that is visible we can tell it that we will use 2 buffers to do the repaint
// befor being able to do that, the Canvas as to be visible
canvas.createBufferStrategy(2);
}
// just to start the application
public static void main(String[] args) {
// instance of our stuff
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GameFrame();
}
});
}
}
To this JFrame we will add a Canvas, because Canvas support BufferStrategy
This class that extends Canvas will use a Timer to be recalled 60 times a second... this is everytime the hardware refresh the screen
60 times (in NorthAmerica) a second is every 16/1000 a second
There are no need to refresh the screen more often... if you do so
1) the human eye won't see it
2) you will ask the blitter to show something and then to show something else and only the last one will be displayed
CODE
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.util.Random;
import javax.swing.Timer;
// OK this the class where we will draw
public class GameCanvas extends Canvas{
// the initial position of the ball in the canvas that will be determine randomly
int ballX, ballY;
// the delta we apply to the x and y position at each repaint
// here it is set to 1... would should have a larger value in "real" life
// but for testing purpose that will give you the fastest possible value for a 1 pixel update
int deltaX = +1, deltaY = +1;
// the size of the ball in pixels
final int BALLSIZE = 25;
// a flag if repaint in progress (needed if our computation are to long)
boolean repaintInProgress = false;
// we use 2 pages to do our buffering
// just for demo purpose we will change the background color everytime we repaint a frame
// you will never do that in real life :-)
Color[] back = {Color.YELLOW, Color.MAGENTA};
// just used to determine on which buffer I am writting... will not be used in real life
int page = 0;
// a random object to determine where the initial position of the ball will be
Random ran = new Random();
// this is a Canvas but I wont't let the system when to repaint it I will do it myself
GameCanvas() {
// so ignore System's paint request I will handle them
setIgnoreRepaint(true);
// a random place to start the ball
ballX = ran.nextInt(580);
ballY = ran.nextInt(380);
// build Chrono that will call me
Chrono chrono = new Chrono(this);
// ask the chrono to calll me every 60 times a second so every 16 ms
new Timer(16, chrono).start();
}
// my own paint method that repaint off line and switch the displayed buffer
// according to the VBL
public void myRepaint() {
// wasting too much time doing the repaint... ignore it
if(repaintInProgress)
return;
// so I won't be called 2 times in a row for nothing
repaintInProgress = true;
// get actual Canvas size so I can check if I am out of bounds
Dimension size = getSize();
// test for all debordement possibilities on the X axis
if(deltaX > 0) {
if(ballX > size.width - BALLSIZE)
deltaX = -deltaX;
}
else {
if(ballX < 0)
deltaX = -deltaX;
}
// check on the Y axis
if(deltaY > 0) {
if(ballY > size.height - BALLSIZE)
deltaY = -deltaY;
}
else {
if(ballY < 0)
deltaY = -deltaY;
}
// update ball position
ballX += deltaX;
ballY += deltaY;
// ok doing the repaint on the not showed page
BufferStrategy strategy = getBufferStrategy();
Graphics graphics = strategy.getDrawGraphics();
// this is for testing purpose you would not do that in real life
// we change the background color to that you will see that the page are flipped
page++;
page %= 2;
// again testing purpose we flip backgound color every repaint
graphics.setColor(back[page]);
graphics.fillRect(0, 0, size.width, size.height);
// now we draw the ball
graphics.setColor(Color.BLACK);
graphics.fillOval(ballX, ballY, BALLSIZE, BALLSIZE);
if(graphics != null)
graphics.dispose();
// show next buffer
strategy.show();
// synchronized the blitter page shown
Toolkit.getDefaultToolkit().sync();
// ok I can be called again
repaintInProgress = false;
}
}
finally the Chrono class that recalls me 60 times a second
CODE
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/** Will be called at each blitter page */
public class Chrono implements ActionListener {
GameCanvas gc;
// constructor that receives the GameCanvas that we will repaint every 60 milliseconds
Chrono(GameCanvas gc) {
this.gc = gc;
}
// calls the method to repaint the anim
public void actionPerformed(ActionEvent arg0) {
gc.myRepaint();
}
}
This is the fastest way you can refresh a screen in Java
Change the deltaX and deltaY the ball will go faster
The keypoints:
- you can inform a Canvas that it should not do it's own repaint
- you can overload that repaint()
- if you do it by hand call the toolkit synch() method to synchronize the VBL
Hope this helps