开发者

Java Panel Double Buffering

开发者 https://www.devze.com 2022-12-16 14:32 出处:网络
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

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 imple开发者_JAVA百科ments 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);
 }
}


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).


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

Painting in AWT and Swing


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:

  • Performing Custom Painting
  • Painting in AWT and Swing

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:

  • Concurrency in Swing
  • How to Use Swing Timers for a possible solution

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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号