如何将多个线程同时绘制的Java动画双重缓冲?

时间:2014-04-20 12:23:43

标签: java multithreading animation double buffering

我正在研究一个简单的" Bouncing Ball" -Animation in Java。这个想法是它最初产生一个直线移动的球直到击中面板边界,这导致它像你期望的那样反弹。然后,您可以使用鼠标点击在位置x,y处生成其他球。到现在为止还挺好。

我的问题是每个球都会开始自己的线程,并且每个线程都会以自己的间隔单独进入面板,导致面板像疯了一样闪烁。我知道这些问题可以通过实施双缓冲来解决,我已经读到了这个问题,但从来没有完全使用过我自己。

我想知道如何在这里使用双缓冲以及如果同时绘制多个线程可能是一个问题(或者相反,甚至是常态)?

提前多多感谢!

以下是代码:

import java.awt.*;
import java.awt.event.*;
import java.util.*; 
import javax.swing.*;

class MyCanvas extends JPanel 
{
    MyCanvas() 
    {
        setBackground(Color.white);
        setForeground(Color.black);
    }

    public void paintComponent(Graphics g) 
    {
        super.paintComponent(g);      
    }

    public Dimension getMinimumSize()
    {
        return new Dimension(300,300);
    }

    public Dimension getPreferredSize()
    {
        return getMinimumSize();
    } 
}

public class BouncingBalls extends JFrame     // main class
{
    MyCanvas m_gamefield;

    public BouncingBalls() 
    {
        setLayout(new BorderLayout());
        m_gamefield = new MyCanvas();
        add("Center",m_gamefield);

        m_gamefield.addMouseListener(new MeinMausAdapter());

        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    public void letsgo()
    {
        Ball first = new Ball(m_gamefield,200,50);
        first.start();
    }

    class MeinMausAdapter extends MouseAdapter 
    {
        public void mousePressed(MouseEvent e) 
        {
            Ball next = new Ball(m_gamefield,e.getX(),e.getY()); 
            next.start(); 
        }
    }

    public static void main(String[] args)
    {
        BouncingBalls test = new BouncingBalls();
        test.setVisible(true);
        test.pack();
        test.letsgo();
    }
}

class Ball extends Thread 
{
    JPanel m_display;
    int m_xPos,m_yPos;
    int m_dx = 2;         // Steps into direction x or y
    int m_dy = 2; 

    Ball(JPanel c,int x,int y) 
    {
        m_display = c;
        m_xPos = x;
        m_yPos = y;
    }

    public void run() 
    {
        paintBall();         // Paint at starting position

        while(isInterrupted() == false) 
        {
            moveBall();

            try 
            { 
                sleep(20);
            } 

            catch(InterruptedException e) 
            {
                return;
            }
        }
    }

    void paintBall() 
    {
        Graphics g = m_display.getGraphics();
        g.fillOval(m_xPos, m_yPos, 20, 20);
        g.dispose(); 
    }

    void moveBall() 
    {
        int xNew, yNew;
        Dimension m;
        Graphics g;

        g = m_display.getGraphics();
        m = m_display.getSize();
        xNew = m_xPos + m_dx;
        yNew = m_yPos + m_dy;

        // Collision detection with borders, "bouncing off":

        if(xNew < 0)
        {
            xNew = 0;
            m_dx = -m_dx;
        }

        if(xNew + 20 >= m.width) 
        {
            xNew = m.width - 20;
            m_dx = -m_dx;
        }

        if(yNew < 0) 
        {
            yNew = 0;
            m_dy = -m_dy;
        }

        if(yNew + 20 >= m.height)
        {
            yNew = m.height - 20;
            m_dy = -m_dy; 
        }

        g.setColor(m_display.getBackground());                   // Erases last position by 
        g.fillRect(m_xPos-2, m_yPos-2, m_xPos+22, m_yPos+22);    // painting over it in white

        m_xPos = xNew;
        m_yPos = yNew;
        paintBall();           // paint new position of Ball
        g.dispose();
    }
}

2 个答案:

答案 0 :(得分:0)

使用Swing JComponents绘画时不要担心双缓冲。默认情况下,它们是双缓冲的。

您应该为动画实现Swing Timer,而不是在不同的Thread上创建每个Ball。点击How to Use Swing timers了解更多信息。您可以看到一个很好的示例here,其中Ball对象被添加到Ball of List并以不同的间隔呈现。

其他笔记

  • 切勿使用组件的getGraphics。所有绘制都应该在传递给paintComponent方法的Graphics上下文中完成。我看到你有方法。用它。您可以在Ball类中使用带有Graphics参数的draw方法,并在paintComponent方法中调用该方法,并将Graphics方法传递给它。示例也可以在上面的链接中看到。

您可以看到更多示例herehere以及herehere以及herehere

答案 1 :(得分:0)

感谢peeskillet的优秀参考资料,我通过使用Swing计时器改变了代码。它现在缩短了很多,完全没有使用多线程。此外,由于在实际绘制它们之前计算所有球位置(在单个扫描repaint()而不是许多较小的球位置),闪烁已经停止。

我仍然有点好奇为什么使用getGraphics()被认为是不好的形式。它是否总是导致闪烁(我想象的可以通过额外的双缓冲层去除)?如果它指导每一个绘画行为,那么paintComponent()在更复杂的动画中变得相当臃肿吗?如果有人想知道的话,我还是比较新的。

以下是感兴趣的人的新代码:

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;

public class BouncingBalls extends JFrame     // main class
{
    MyCanvas m_gamefield;
    public ArrayList<Ball> balls;
    public Timer timer = null;

    public BouncingBalls() 
    {
            setLayout(new BorderLayout());
        m_gamefield = new MyCanvas();
            add("Center",m_gamefield);

            balls = new ArrayList<Ball>();

            timer = new Timer(30, new ActionListener() 
        {
                    public void actionPerformed(ActionEvent e) 
                    {
                        for (Ball b : balls) 
                        {
                                b.move();
                            }
                            repaint();
                        }
                });

            m_gamefield.addMouseListener(new MeinMausAdapter());

            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    class MeinMausAdapter extends MouseAdapter 
    {
            public void mousePressed(MouseEvent e) 
            {
                balls.add(new Ball(m_gamefield,e.getX(),e.getY()));
            }
    }

    class MyCanvas extends JPanel 
    {
        MyCanvas() 
        {
            setBackground(Color.white);
                setForeground(Color.black);
        }

        public void paintComponent(Graphics g) 
        {
                super.paintComponent(g);

                for (Ball b : balls)
                {
                    b.draw(g);
                }                     
        }

        public Dimension getMinimumSize()
        {
            return new Dimension(300,300);
        }

        public Dimension getPreferredSize()
        {
            return getMinimumSize();
        } 
    }

    public void letsgo()
    {
        balls.add(new Ball(m_gamefield,200,50));
        timer.start();  
        }

        public static void main(String[] args)
    {
        BouncingBalls test = new BouncingBalls();
        test.setVisible(true);
        test.pack();
        test.letsgo();
    }         
}

class Ball
{
    JPanel m_display;
    int m_xPos,m_yPos;
    int m_dx = 2;         // Steps into direction x or y 
    int m_dy = 2; 

    Ball(JPanel c,int x,int y) 
    {
            m_display = c;
            m_xPos = x;
            m_yPos = y;
    }

    void draw(Graphics g) 
    {
            g.fillOval(m_xPos, m_yPos, 20, 20);
    }

    void move() 
    {
            int xNeu, yNeu;
            Dimension m;

            m = m_display.getSize();
        xNeu = m_xPos + m_dx;
            yNeu = m_yPos + m_dy;


        // Collision detection with borders, "bouncing off":

            if(xNeu < 0)
            {
                xNeu = 0;
                m_dx = -m_dx;
            }

            if(xNeu + 20 >= m.width) 
            {
                xNeu = m.width - 20;
                m_dx = -m_dx; 
            }

            if(yNeu < 0) 
            {
                yNeu = 0;
                m_dy = -m_dy;       
            }

            if(yNeu + 20 >= m.height) 
            {
                yNeu = m.height - 20;
                m_dy = -m_dy; 
            }

            m_xPos = xNeu;
            m_yPos = yNeu;
    }
}