I’ve been messing around with Java animation this weekend, but the constant screen-flicker as the graphics were cleared and repainted kept distracting me from the experiments I actually wanted to do (playing with collision detection and user interaction is a bit laborious when your eyes feel like they’re about to melt out of their sockets). So before I carried on with the project, I decided to get the flickering problem sorted out once and for all.

However, for some reason the advice given in the course text didn’t work for my program. The unit book says:

On some systems there may be a flickering effect from the background frame. This arises because the whole frame is redrawn in the background colour each time the program loops, when repaint calls a method called update (also inherited from Component), which in turn calls paint. If the flickering occurs on your system (and only if it does) then by overriding update rather than paint you can avoid the flicker.

So the first thing I tried was to replace my overridden paint with an overridden update, using the same code for the method body; unfortunately this didn’t work. I checked a few 2D Java animation tutorials to see whether I’d misunderstood the “override update rather than paint” bit, and from what I could tell it seems like this approach only works for certain kinds of animation – the kind where you don’t need the whole screen to be cleared and repainted, I think.

I was starting to think that my little bouncing-square animation was doomed to be perpetually eye-strain-inducing, but then I happened across a tutorial that explained how to use double-buffering to prevent the flickering; it turned out that the key to getting rid of the flicker was to do the redrawing offscreen, using BufferedImage and Graphics objects. I’m not sure if this is technically double-buffering, since I got the impression that there would be two offscreen buffers involved in that, but whatever this approach is called, I’m very glad I found out about it!

So now I’ve got a non-flickering, bouncing square which changes colour pseudo-randomly whenever it hits the edge of the screen. Here’s the code for the class which does the animation:

import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class AniFrame extends JFrame
{
    private JPanel panel;
    private int xPos = 50;
    private int yPos = 50;
    private int height = 20;
    private int width = 20;
    private int frame_height = 240;
    private int frame_width = 320;
    private int yChange = 3;
    private int xChange = 3;
    private int margin = 10;
    private int red, green, blue = 100;
    private Graphics offscreenGraphics;
    private BufferedImage image;
    
    
    /**
     * One-argument constructor, taking frame title as argument
     */
    public AniFrame(String title)
    {
        super(title);
        setSize(frame_width,frame_height);
        setLocation(100,100);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        panel = new JPanel();
        panel.setBackground(Color.BLACK);
        
        Container cp = getContentPane();
        cp.add(panel);
        
        image = new BufferedImage(frame_width, frame_height,
                                BufferedImage.TYPE_INT_RGB);
        offscreenGraphics = image.createGraphics();
    }
    
    
    /**
     * Overrides paint to draw rectangle to offscreen image, then
     * onto onscreen area
     */
    public void paint(Graphics g)
    {
        super.paint(offscreenGraphics);
        panel.paint(offscreenGraphics);
        
        offscreenGraphics.setColor(new Color(red,blue,green));
        offscreenGraphics.fillRect(xPos, yPos, width, height);
        offscreenGraphics.setColor(Color.WHITE);
        offscreenGraphics.drawRect(xPos, yPos, width, height);
        
        g.drawImage(image,0,0,this);
    }
    
    
    
     /**
     * Changes xPos and yPos, repaints the graphics and
     * suspends program for 50 milliseconds
     */
    public void move()
    {        
        while (true)
        {            
            //check for Y-axis collision
            if ((yPos + height >= (frame_height - margin))
                || (yPos = (frame_width - margin))
                || (xPos < margin))
            {
                xChange = -xChange;
                changeColour();
            }
                        
            //increment/decrement xPos and yPos, then repaint
            yPos = yPos + yChange;
            xPos = xPos + xChange;
            repaint();
            
            //suspend for 50 milliseconds
            try
            {
                Thread.sleep(50);
            }
            catch(InterruptedException e)
            {
                System.exit(0);
            }
        }
    }
    
    //set red, green and blue to "random" values in the range 0-255
    public void changeColour()
    {
        red = (int) Math.round(Math.random() * 255);
        green = (int) Math.round(Math.random() * 255);
        blue = (int) Math.round(Math.random() * 255);        
    }
        
}
 

And for completion’s sake, the main method of my main class:

public static void main(String[] args)
{
    AniFrame frame = new AniFrame("Animation Expt");
    frame.setVisible(true);
    frame.move();
}
 

For the next version of this, I’d like to have multiple squares, which not only change colour when they hit the edge of the screen, but also when they hit each other – Unit 7 promises that we’ll cover multiple animated objects that interact with each other in a later unit, so hopefully Unit 8: Threads will give me an idea of how to do it.

Advertisements