简体   繁体   English

Java面板双缓冲

[英]Java Panel Double Buffering

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. 我在这里尝试了一些帖子尝试使它工作,但我仍然是一个摇摆awt足够的初学者,任何帮助将是惊人的谢谢。

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);
 }
}

There's a really good tutorial here which describes how to use BufferStrategy to produce non-flickering animation. 这里有一个非常好的教程它描述了如何使用BufferStrategy生成非闪烁动画。

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. 在顶层Canvas上调用setIgnoreRepaint(true)以防止AWT重新绘制它,因为您通常会在动画循环中自己执行此操作。
  • Obtain the Graphics2D object from the BufferStrategy (rather than using the instance passed in via paintComponent(Graphics g) . BufferStrategy获取Graphics2D对象(而不是使用通过paintComponent(Graphics g)传递的实例paintComponent(Graphics g)

A must-read about the painting mechanism in AWT and Swing 关于AWT和Swing中绘画机制的必读书

Painting in AWT and Swing 在AWT和Swing中绘画

The basic problem is, you're violating the basic painting system of Swing. 基本问题是,你违反了Swing的基本绘画系统。 Swing uses a "passive rendering" algorithm, where paints are performed only when they need to be. Swing使用“被动渲染”算法,只有在需要时才执行绘制。 You can make suggestions to the API about when something should be update via the repaint call. 您可以向API提出有关何时应通过repaint调用更新内容的建议。

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. 基于你的代码,基本的问题是,你正在使用自己的Graphics上下文调用paintComponents ,但是系统正在使用它的paint paint pass废弃它,你正在与绘制系统作斗争而不是使用它。

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. 如果正确完成,Swing组件已经是双缓冲的,因此除了实际使用API​​ /系统之外,您不需要做任何“额外”操作。

I strongly recommend having a look at: 强烈建议您查看:

to get a better understanding of how painting works in Swing. 更好地了解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 Swing不是线程安全的,您不应该从事件调度线程的上下文之外更新UI
  • You should NEVER call any paint method directly. 你永远不应该直接调用任何paint方法。 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. 已经有人建议,如果您希望完全控制喷涂系统, BufferStrategy是一个可行的解决方案。 It's more complex, but gets you around the oddities of the passive rendering system used by Swing 它更复杂,但可以让你了解Swing使用的被动渲染系统的奇怪之处

I think you can just call super(true); 我想你可以称之为super(true); first thing, and this just tells the JPanel that it is double buffered... because one of JPanel 's constructors is: 第一件事,这只是告诉JPanel它是双缓冲的...因为JPanel的构造函数之一是:

public Jpanel(boolean isDoubleBuffered) {...}

I hope this helps someone even though it is nearly four years later. 我希望这可以帮助某人,即使它已经将近四年了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM