Java Panel Double Buffering

15,233

Solution 1

There's a really good tutorial here which describes how to use BufferStrategy to produce non-flickering animation.

The important points are:

  • Call setIgnoreRepaint(true) on the top-level Canvas to prevent AWT from repainting it, as you'll typically be doing this yourself within the animation loop.
  • Obtain the Graphics2D object from the BufferStrategy (rather than using the instance passed in via paintComponent(Graphics g).

Solution 2

A must-read about the painting mechanism in AWT and Swing

Painting in AWT and Swing

Solution 3

The basic problem is, you're violating the basic painting system of Swing. Swing uses a "passive rendering" algorithm, where paints are performed only when they need to be. You can make suggestions to the API about when something should be update via the repaint call.

Based on your code, the basic problem is, you're calling paintComponents with your own Graphics context, but the system is is then trashing it with it's paint paint pass, you are fighting the paint system rather then working with it.

If done correctly, Swing components are already double buffered, so you don't need to do anything "extra", other then actual work with the API/system.

I strongly recommend having a look at:

to get a better understanding of how painting works in Swing.

So, let's start with...

@Override
public void run() {
    while (!isGameOver) {
        dbImage = createImage(screenWidth, screenHeight);
        dbg = this.getGraphics();
        if (!isPaused) {
            if (!gameOverCheck()) {
                updateGame();
                paintComponents(dbg);
            }
        } else if (isPaused) {
            dbg.setColor(Color.ORANGE);
            dbg.setFont(new Font("serif", Font.BOLD, 50));
            dbg.drawString("Paused", screenWidth / 2 - 82, screenHeight / 2);
        }
        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • Swing is NOT thread safe, you should NOT be updating the UI from outside the context of the Event Dispatching Thread
  • You should NEVER call any paint method directly. The system will perform this operation when it wants to update your component.

I would strongly recommend having a read of:

For example...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.text.Position;

public class PongPanel extends JPanel implements Runnable {

    private int screenWidth = 500;
    private int screenHeight = 300;

    private boolean isPaused = false;
    private boolean isGameOver = false;

    private int playToPoints = 10;

    private Padel player1, player2;
    private Ball ball;

    private Timer gameThread;

    public PongPanel() {
        setPreferredSize(new Dimension(screenWidth, screenHeight));
        setBackground(Color.BLACK);
        setDoubleBuffered(true);
        setFocusable(true);
        requestFocus();

        player1 = new Padel(Position.LEFT, screenWidth, screenHeight);
        player2 = new Padel(Position.RIGHT, screenWidth, screenHeight);
        ball = new Ball(10, screenWidth / 2, screenHeight / 2, Color.WHITE);
    }

    public void addNotify() {
        super.addNotify();
        startGame();
    }

    private void startGame() {
        gameThread = new Timer(30, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateGame();
                gameOverCheck();
                if (isGameOver) {
                    repaint();
                    return;
                }
                if (!isPaused) {
                    if (!gameOverCheck()) {
                        updateGame();
                    }
                }
                repaint();
            }
        });
        gameThread.start();
    }

    private boolean gameOverCheck() {
        if (player1.getScore() == playToPoints) {
            setGameOver(true);
            return true;
        } else if (player2.getScore() == playToPoints) {
            setGameOver(true);
            return true;
        }

        return false;
    }

    private void updateGame() {
        ball.move(screenWidth, screenHeight, player1, player2);
        player1.aiForPadel(screenWidth, screenHeight, ball.getX(), ball.getY());
        player2.aiForPadel(screenWidth, screenHeight, ball.getX(), ball.getY());

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponents(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, screenWidth + 20, screenHeight + 20);
        g2d.setColor(Color.WHITE);
        g2d.drawLine(screenWidth / 2, 0, screenWidth / 2, screenHeight);
        g2d.setFont(new Font("serif", Font.BOLD, 32));
        g2d.drawString(player1.getScore() + "", screenWidth / 2 - 40, screenHeight - 20);
        g2d.drawString(player2.getScore() + "", screenWidth / 2 + 20, screenHeight - 20);
        ball.drawBall(g2d);
        player1.drawPadel(g2d);
        player2.drawPadel(g2d);

        if (isGameOver) {
            if (player1.getScore() == playToPoints) {
                g2d.setColor(player1.getColour());
                g2d.setFont(new Font("serif", Font.BOLD, 50));
                g2d.drawString("Player 1 Wins!", screenWidth / 2 - 161, screenHeight / 2);
            } else if (player2.getScore() == playToPoints) {
                g2d.setColor(player2.getColour());
                g2d.setFont(new Font("serif", Font.BOLD, 50));
                g2d.drawString("Player 2 Wins!", screenWidth / 2 - 161, screenHeight / 2);
            }
        } else if (isPaused) {
            g2d.setColor(Color.ORANGE);
            g2d.setFont(new Font("serif", Font.BOLD, 50));
            g2d.drawString("Paused", screenWidth / 2 - 82, screenHeight / 2);
        }

        g2d.dispose();
    }
}

BufferedStrategy

Has already been suggested, BufferStrategy is a viable solution in cases where you want to take complete control off the painting system. It's more complex, but gets you around the oddities of the passive rendering system used by Swing

Share:
15,233
user250643
Author by

user250643

Updated on August 09, 2022

Comments

  • user250643
    user250643 over 1 year

    wondered if anyone could point me in the right directon, i have developed a pong game and it needs double buffering due to flickering. Iv tryed some of the post on here to try and make it work, but im still a beginner with the swing awt suff, any help would be amazing thanks.

    public class PongPanel extends JPanel implements Runnable {
    
    private int screenWidth = 500;
    private int screenHeight = 300;
    
    private boolean isPaused = false;
    private boolean isGameOver = false;
    
    private int playToPoints = 10;
    
    private Padel player1,player2;
    private Ball ball;
    
    private Thread gameThread;
    private Image dbImage;
    private Graphics dbg; 
    
    public PongPanel() {
       setPreferredSize(new Dimension(screenWidth,screenHeight));
       setBackground(Color.BLACK);
       setDoubleBuffered(true);
       setFocusable(true);
       requestFocus();
    
       player1 = new Padel(Position.LEFT,screenWidth,screenHeight);
       player2 = new Padel(Position.RIGHT,screenWidth,screenHeight);
       ball = new Ball(10,screenWidth/2,screenHeight/2,Color.WHITE);
    }
    
    public void addNotify(){
     super.addNotify();
       startGame();
    }
    
    private void startGame(){
      gameThread = new Thread(this);
      gameThread.start();
    }
    
    @Override
    public void run() {
     while (!isGameOver) {   
     dbImage = createImage(screenWidth,screenHeight);
     dbg = this.getGraphics();
     if(!isPaused){
       if(!gameOverCheck()){
         updateGame();
         paintComponents(dbg);   
        }
     }else if(isPaused){
        dbg.setColor(Color.ORANGE);
        dbg.setFont(new Font("serif",Font.BOLD,50));
        dbg.drawString("Paused", screenWidth/2-82, screenHeight/2);
     } 
     try {
         Thread.sleep(30);
        } catch (InterruptedException e) {e.printStackTrace();}
       }
       }
    
      private boolean gameOverCheck(){
      if(player1.getScore() == playToPoints){
       dbg.setColor(player1.getColour());
       dbg.setFont(new Font("serif",Font.BOLD,50));
       dbg.drawString("Player 1 Wins!", screenWidth/2 - 161, screenHeight/2);
       setGameOver(true);
       return true;
      }else if(player2.getScore() == playToPoints){
       dbg.setColor(player2.getColour());   
       dbg.setFont(new Font("serif",Font.BOLD,50));
       dbg.drawString("Player 2 Wins!", screenWidth/2 - 161, screenHeight/2);
       setGameOver(true);
       return true;
      }
    
      return false;
     }
    
     private void updateGame(){
      ball.move(screenWidth,screenHeight,player1,player2);
      player1.aiForPadel(screenWidth, screenHeight, ball.getX(), ball.getY());
      player2.aiForPadel(screenWidth, screenHeight, ball.getX(), ball.getY());
    
     }
    
      @Override
      public void paintComponents(Graphics g) {
      super.paintComponents(g);
      dbg.setColor(Color.BLACK);
      dbg.fillRect(0, 0, screenWidth+20, screenHeight+20);
      dbg.setColor(Color.WHITE);
      dbg.drawLine(screenWidth/2, 0, screenWidth/2, screenHeight);
      dbg.setFont(new Font("serif",Font.BOLD,32));
      dbg.drawString(player1.getScore()+"", screenWidth/2-40, screenHeight - 20);
      dbg.drawString(player2.getScore()+"", screenWidth/2+20, screenHeight - 20);
      ball.drawBall(dbg);
        player1.drawPadel(dbg);
        player2.drawPadel(dbg);
     }
    }