【问题标题】:How do I double buffer a Java animation with many threads painting simultaneously?如何在多个线程同时绘制的情况下对 Java 动画进行双重缓冲?
【发布时间】:2014-04-20 12:23:43
【问题描述】:

我正在制作一个简单的“弹跳球”——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();
    }
}

【问题讨论】:

  • 任何通过在Component 上调用getGraphics() 完成的绘画迟早会以一种或另一种形式中断。这根本不是您在 AWT/Swing 中绘制的方式。你应该阅读docs.oracle.com/javase/tutorial/uiswing/painting。除此之外:每个球一个线程完全是矫枉过正。一个线程可以处理 1000 个球。也许你应该解释你想用这些线程实现什么。他们不会在这里带来任何优势。如果你有,也许,100000 个球,并且想用 10 个线程来做 运动,好的。但是绘画仍然是由一个线程完成的。

标签: java multithreading animation double buffering


【解决方案1】:

在使用 Swing JComponents 进行绘制时不必担心双缓冲。默认情况下它们是双缓冲的。

您应该为动画实现一个摇摆计时器,而不是在不同的线程上创建每个球。在How to Use Swing timers 上查看更多信息。您可以看到一个很好的示例here,其中将球对象添加到球列表并以不同的间隔呈现。

其他说明

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

您可以查看更多示例 herehereherehereherehere

【讨论】:

  • 非常感谢!我查看了您的一些建议,并整理了一个新版本的动画。我已经在下面的答案中发布了它。干杯!
【解决方案2】:

感谢 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;
    }
}

【讨论】:

    猜你喜欢
    • 2020-06-20
    • 1970-01-01
    • 1970-01-01
    • 2012-06-25
    • 2011-05-24
    • 2014-04-07
    • 1970-01-01
    • 1970-01-01
    • 2017-03-04
    相关资源
    最近更新 更多