Double buffering

Forum for J2ME mobile games related topics including programming doubts, books and other resources for J2ME game development

Double buffering

Postby DevelopmentTeam » Thu Sep 28, 2006 8:56 am

To avoid flickering when drawing to the screen we will need to use well known double buffering techniques, where everything is rendered to an off screen buffer then later drawn to the visible screen. Some implementations will actually do the double buffering for us! Whether a device does so or not can be queried at runtime via the isDoubleBuffered() method on the Canvas. The advantage of not having to do the double buffering yourself is that it saves us the runtime memory needed to store the off screen buffer. We can easily write our code to automatically check and cater for devices that need us to implement double buffering ourselves.

At the same time as we load and create all our needed Images we can create our off screen buffer if needed.

Code: Select all
/*
* Creates all the needed images, called upon creation
* of our GameScreen class
*/   
public void createImages()
{
   try
   {
      //if device doesnt do automatic double buffering
      if( !isDoubleBuffered() )
      {
         //create offscreen Image
         bufferImage = Image.createImage( getWidth(), getHeight() );
         //get a Graphics context to we can render onto the bufferImage
         buffer = bufferImage.getGraphics();
      }
      myImage = Image.createImage("/sprite.png");
   }
   catch( Exception e )
   {
      e.printStackTrace();
   }
}


We create a new empty Image by calling Image.createImage( width, height ). The Image should be the exact same size as the Canvas's viewable area. In MIDP there are Mutable and Immutable Images. The difference being that Immutable Images, the ones created from image files/data, cannot be modified once created. A mutable Image, normally created through Image.createImage( width, height) can be modified by obtaining a Graphics context that will render to the Image itself. This is done by calling getGraphics() on the Image. This is what we have done for our back buffer!

With a small modification to our paint() method we can accommodate for those devices that do not do the buffering for us.

Code: Select all
/*
* called when the Canvas is to be painted
*/
protected void paint( Graphics g )
{
   //cache a reference to the original Graphics context
   Graphics original = g;
   //if device doesn't do automatic double buffering
   if( !isDoubleBuffered() )
   {
      //change the g object reference to the back buffer Graphics context
      g = buffer;
   }
   //set the current color of the Graphics context to the specified RRGGBB colour
   g.setColor( colour );
   //draw a filled rectangle at x,y coordinates 0, 0 with a width
   // and height equal to that of the Canvas itself
   g.fillRect( 0, 0, this.getWidth(), this.getHeight() );
   
   //draw an image to the centre of the screen
   g.drawImage( myImage, this.getWidth()/2, this.getHeight()/2, Graphics.VCENTER | Graphics.HCENTER );
   
   if( !isDoubleBuffered() )
   {
      //draw the off screen Image to the original graphics context
      original.drawImage( bufferImage, 0, 0, Graphics.TOP | Graphics.LEFT );
   }     
}

This might be a little confusing at first, at the top of the paint() method we keep a reference to the original Graphics context that was passed as a parameter to the method. We then check whether we need to perform the double buffering, if so we change the Graphics context that the g variable references to the Graphics context obtained from the buffer Image. At the end of the paint method we again check if we needed to perform the double buffering, and draw the buffer Image to the original Graphics context we kept earlier.

To wrap up, lets change our input handling so we can move our image around the screen by pressing the keys.

Code: Select all
/*
* called when a key is pressed and this Canvas is the
* current Displayable
*/
protected void keyPressed( int keyCode )
{
   //get the game action from the passed keyCode
   int gameAction = getGameAction( keyCode );
   switch( gameAction )
   {
      case LEFT:
      //move image left
      imageDirection = LEFT;
      break;
     
      case RIGHT:
      //move image right
      imageDirection = RIGHT;
      break;
     
      case UP:
      //move image up
      imageDirection = UP;
      break;
     
      case DOWN:
      //move image down
      imageDirection = DOWN;
      break;
     
      case FIRE:
      //set current to a random colour
      colour = generator.nextInt()&0xFFFFFF;
      break;
   }
}

/*
* Our games main loop, called at a fixed rate by our game Thread
*/
public void tick()
{
   int myImageSpeed = 4;
   switch( imageDirection )
   {
      case LEFT:
      myImageX-=myImageSpeed;
      break;
     
      case RIGHT:
      myImageX+=myImageSpeed;
      break;
     
      case UP:
      myImageY-=myImageSpeed;
      break;
     
      case DOWN:
      myImageY+=myImageSpeed;
      break;
   }
   //schedule a repaint of the Canvas
   repaint();
   //forces any pending repaints to be serviced, and blocks until
   //paint() has returned
   serviceRepaints();
}
User avatar
DevelopmentTeam
Site Admin
 
Posts: 661
Joined: Tue Aug 15, 2006 8:39 am
Location: India

Return to J2ME Games

Who is online

Users browsing this forum: No registered users and 1 guest

cron