How to fix bad Double-Buffering [closed]

2019-08-26 03:28发布

问题:

I tried following a double buffering tutorial, and I really don't know what I did wrong. It works before then before I did the tutorial, but there is still and occasional flicker here and there. I have two files Game and gameLoop

Game:

import java.awt.Graphics;
public class Game extends gameLoop
{
    public void init()
    {
        setSize(854,480);
        Thread th = new Thread(this);
        th.start();
        offscreen = createImage(854,480);
        d = offscreen.getGraphics();
    }
    public void paint(Graphics g)
    {
        d.clearRect(0, 0, 854, 480);
        d.drawImage(disk, x, y, this);
        g.drawImage(offscreen , 0, 0, this);
    }
    public void Update(Graphics gfx)
    {
        paint(gfx);
    }
}

gameLoop

import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class gameLoop extends Applet implements Runnable, MouseListener, MouseMotionListener 
{
    public int x, y, counter, mouseX, mouseY;
    public Image offscreen;
    public Graphics d;
    public boolean up, down, left, right, pressed;
    public BufferedImage disk1, disk2, disk3, disk4, disk;
    public int ballSpeedX = -6;
    public int ballSpeedY = -3;

    public void run() 
    {
        x = 400;
        y = 200;
        try {
            disk1 = ImageIO.read(new File("disk1.png"));
            disk2 = ImageIO.read(new File("disk2.png"));
            disk3 = ImageIO.read(new File("disk3.png"));
            disk4 = ImageIO.read(new File("disk4.png"));
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        while(true)
        {
            if(x >= (854 - 150))
            {
                    ballSpeedX = ballSpeedX * -1;
            }
            if(y >= (480 - 140))
            {
                    ballSpeedY = ballSpeedY * -1;
            }
            if(y < (0 - 10))
            {
                    ballSpeedY = ballSpeedY * -1;
            }
            if(x < (0- 10))
            {
                    ballSpeedX = ballSpeedX * -1;
            }

            x = x + ballSpeedX;
            y = y + ballSpeedY;

            counter ++;
            if(counter >= 4)
                counter = 0;

            if(counter == 0)
                disk = disk1;
            if(counter == 1)
                disk = disk2;
            if(counter == 2)
                disk = disk3;
            if(counter == 3)
                disk = disk4;

            System.out.println(counter);

            repaint();

            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }



    public void mouseClicked(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
    public void mouseMoved(MouseEvent m) {}

    public void mousePressed(MouseEvent m) 
    {

    }

    public void mouseReleased(MouseEvent m) 
    {
        pressed = false;
    }

    public void mouseDragged(MouseEvent e) {
        PointerInfo a = MouseInfo.getPointerInfo();
        Point b  = a.getLocation();
        mouseX = (int)b.getX();
        mouseY = (int)b.getY();
        ballSpeedX = mouseX;
        ballSpeedY = mouseY;
    }
}

回答1:

public void Update(Graphics gfx)
{
    paint(gfx);
}

Should be lower case, as you're trying to override the update(Graphics g) method in the Applet.

So it should be

@Override
public void update(Graphics gfx)
{
    paint(gfx);
}

As for changing the background, a background is simply a big rectangle that covers the screen and makes it a certain color. In your paint, you're doing clearRect, which clears the screen. Instead just switch that to fillRect after setting the color.

It might look something like

public void paint(Graphics g)
{
    //setColor to whatever you want
    //fillRect to cover the screen
}

When you're doing this though, one thing you have to remember is to not confuse your two graphics objects. As a concept, double buffering works by drawing to memory first (drawing it on an offscreen image) and then to the screen. You want to always draw onto this offscreen image, as it is much faster (and we lose the flicker).

So make sure you're doing imageGraphicsObject.setColor not screenGraphicsObject.setColor or imageGraphicsObject.fillRectnotscreenGraphicsObject.fillRect`. Otherwise you're no longer double-buffering.



回答2:

There are a number of different types "double buffers". The basic is a simple off screen image that you update and this gets painted to the screen instead. If done right, painting the image is often faster then drawing graphics to the screen.

Another type is page flipping. That is, you have an active buffer which is always rendered to the screen and a off screen/scratch buffer which is what you actually render to. You then flip these buffers when you are ready to render the updates to the screen (this is closer to how film animation works).

The following example is a REALLY basic example of page flipping.

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Panel;
import java.awt.image.BufferedImage;
import static testdoublebuffer.TestDoubleBuffer.UPDATE;

public class AppletDoubleBuffer extends Applet {

    private BufferedPane pane;

    @Override
    public void init() {
        pane = new BufferedPane();
        setLayout(new BorderLayout());
        add(pane);
    }

    @Override
    public void start() {
        pane.start();
    }

    @Override
    public void stop() {
        pane.stop();
    }

    public class BufferedPane extends Panel {

        private BufferedImage activeBuffer;
        private BufferedImage scratch;
        private boolean running = false;

        public BufferedPane() {
        }

        public void start() {
            if (!running) {
                running = true;
                Thread thread = new Thread(new MainLoop());
                thread.setDaemon(true);
                thread.start();
            }
        }

        public void stop() {
            running = false;
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        public void invalidate() {
            synchronized (UPDATE) {
                activeBuffer = null;
                scratch = null;
            }
            super.invalidate();
        }

        @Override
        public void update(Graphics g) {
            if (activeBuffer != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.drawImage(activeBuffer, 0, 0, this);
                g2d.dispose();
            }
        }

        public class MainLoop implements Runnable {

            private int delay = 1000 / 25;
            private int x = 0;
            private int velocity = 5;
            private int size = 10;

            public void update() {

                x += velocity;
                if (x + size >= getWidth()) {
                    x = getWidth() - size;
                    velocity *= -1;
                } else if (x <= 0) {
                    x = 0;
                    velocity *= -1;
                }

                if (getWidth() > 0 && getHeight() > 0) {
                    synchronized (UPDATE) {
                        if (scratch == null) {
                            scratch = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
                        }
                        Graphics2D g2d = scratch.createGraphics();
                        int y = (getHeight() - size) / 2;
                        g2d.setBackground(Color.BLUE);
                        g2d.clearRect(0, 0, getWidth(), getHeight());
                        g2d.setColor(Color.RED);
                        g2d.fillOval(x, y, size, size);
                        g2d.dispose();

                        // Flip the buffers...
                        BufferedImage tmp = activeBuffer;
                        activeBuffer = scratch;
                        scratch = tmp;
                    }
                }

            }

            @Override
            public void run() {
                while (running) {

                    long startTime = System.currentTimeMillis();

                    update();
                    repaint();

                    long duration = System.currentTimeMillis() - startTime;
                    if (duration < delay) {
                        try {
                            Thread.sleep(delay);
                        } catch (InterruptedException ex) {
                        }
                    }

                }
            }
        }
    }
}

I personally, would just skip using AWT and move to Swing which provides better/built in functionality for this type of thing.