Java Panel Double Buffering
Asked Answered
K

4

8

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);
 }
}
Killarney answered 14/1, 2010 at 11:0 Comment(0)
M
7

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).
Miyamoto answered 14/1, 2010 at 11:15 Comment(0)
E
3

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

Painting in AWT and Swing

Elyot answered 14/1, 2010 at 11:21 Comment(1)
Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.Hitherward
L
2

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

Lundt answered 17/5, 2018 at 5:36 Comment(1)
Hi, thank you for the pointers. I'm interested to BufferedStrategy, do you think it could help working around some popup reshaping flickering (on macOs) ? (I never used these before). Context : I found out that on macOs the previous "rendered components" gets repainted on the native window before Swing could render the updated components tree.Oatcake
Y
-1

I think you can just call super(true); first thing, and this just tells the JPanel that it is double buffered... because one of JPanel's constructors is:

public Jpanel(boolean isDoubleBuffered) {...}

I hope this helps someone even though it is nearly four years later.

Ymir answered 24/6, 2014 at 19:25 Comment(1)
JPanel default constructor already calls it: public JPanel() { this(true); }Scever

© 2022 - 2024 — McMap. All rights reserved.