Java多线程程序中弹球的图形

Graphics for bouncing ball in multi-threaded program in Java

我正在尝试改编我创建的用于在 Java 中弹跳球的程序。如果解决方案很明显,我很新,所以很抱歉。我使用以下代码创建了一个弹跳球数组,然后创建了两个弹跳球线程。

我试图在没有数组的情况下实现这一点。所以,每个线程只有 1 个球。 我觉得答案就在眼前,但我就是无法解决问题。任何帮助将不胜感激。

 import java.awt.Color;
 import java.awt.Graphics;
 import java.util.ArrayList;
 import java.util.Scanner;

 import javax.swing.JFrame;
 import javax.swing.JPanel;



 public class AClass implements Runnable {  
     private JFrame mainFrame;
     private DrawPanel drawPanel;
    // private java.util.List<Ball> balls;


     private int windowWidth = 640;
     private int windowHeight = 480;
     private String windowLabel = "Multi-threaded ball application";

     public void run() {

         //balls = new ArrayList<>();
         //Scanner sc = new Scanner(System.in);
         //System.out.print("Enter the number of balls you would like to create:");
         //int n = sc.nextInt();
         //sc.close();
    
    
         /* Generate balls */
         //for (int i = 0; i < n; i++) {
            Ball ball = new Ball(
                     /* Random positions from 0 to windowWidth or windowHeight */
                     (int) Math.floor(Math.random() * windowWidth),
                     (int) Math.floor(Math.random() * windowHeight),
                     /* Random size from 10 to 30 */
                     (int) Math.floor(Math.random() * 20) + 10,
                     /* Random RGB colors*/
                     new Color(
                             (int) Math.floor((Math.random() * 256)),
                             (int) Math.floor((Math.random() * 256)),
                             (int) Math.floor((Math.random() * 256))
                     ),
                     /* Random velocities from -5 to 5 */
                     (int) Math.floor((Math.random() * 10) - 5),
                     (int) Math.floor((Math.random() * 10) - 5)
             );

            // balls.add(ball);
        // }

         /* Initialize program */
         mainFrame = new JFrame();
         drawPanel = new DrawPanel();
         mainFrame.getContentPane().add(drawPanel);
         mainFrame.setTitle(windowLabel);
         mainFrame.setSize(windowWidth, windowHeight);
         mainFrame.setVisible(true);
         mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

         while (true) {
             //for (Ball b: balls) {
                 ball.update();
            // }

             /* Give Swing 10 milliseconds to see the update! */
             try {
                 Thread.sleep(10);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }

             mainFrame.repaint();
         }
     }

    public class DrawPanel extends JPanel {
         /**
          * 
          */
         private static final long serialVersionUID = 1L;

         @Override
         public void paintComponent(Graphics graphics) {
             super.paintComponent(graphics);

            // for (Ball b: balls) {
                 ball.draw(graphics);
            // }

         }
     }

     class Ball {//ball class
         private int posX, posY, size;
         private Color color;

         private int vx = 5;
         private int vy = 5;

         public Ball(int posX, int posY, int size, Color color, int vx, int vy) {
             this.posX = posX;
             this.posY = posY;
             this.size = size;
             this.color = color;
             this.vx = vx;
             this.vy = vy;
         }

         void update() {

             if (posX > mainFrame.getWidth() || posX < 0) {
                 vx *= -1;
             }

             if (posY > mainFrame.getHeight() || posY < 0) {
                 vy *= -1;
             }

             if (posX > mainFrame.getWidth()) {
                 posX = mainFrame.getWidth();
             }

             if (posX < 0) {
                 posX = 0;
             }

             if (posY > mainFrame.getHeight()) {
                 posY = mainFrame.getHeight();
             }

             if (posY < 0) {
                 posY = 0;
             }

             this.posX += vx;
             this.posY += vy;

         }

         void draw(Graphics g) {
             g.setColor(color);
             g.fillOval(posX, posY, size, size);
         }
     }


     public static void main(String[] args) {  
         AClass ex = new AClass();  
         Thread t1= new Thread(ex);  
         Thread t2 = new Thread(ex);
         t1.start();  
         t2.start();
         //System.out.println("Hi");  
     }  
 }  

首先,Swing 不是线程安全的。您不应该从事件调度线程的上下文之外更新 UI(或 UI 所依赖的任何状态)。

有关详细信息,请参阅 Concurrency in Swing

我不认为你的意图是正确的方法(试图让每个球都是自己的Thread),你很快就会遇到各种各样的碰撞检测问题,因为每个球的状态总是相互独立地变化,并且不会很好地缩放。数组和 Swing Timer 将是更合适的解决方案。

这可能是我能得到你想要的东西的最接近点,问题是,为了绘制它,你需要引用 Ball,所以我扩展了 Ball 来自 JPanel 而不是。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new SurfacePane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public interface Surface {
        public Dimension getSize();

        public void repaint();
    }

    public class SurfacePane extends JPanel implements Surface {
        public SurfacePane() {
            setLayout(null);
            for (int index = 0; index < 10; index++) {
                Ball ball = new Ball(
                        /* Random positions from 0 to windowWidth or windowHeight */
                        (int) Math.floor(Math.random() * 400),
                        (int) Math.floor(Math.random() * 400),
                        /* Random size from 10 to 30 */
                        (int) Math.floor(Math.random() * 20) + 10,
                        /* Random RGB colors*/
                        new Color(
                                (int) Math.floor((Math.random() * 256)),
                                (int) Math.floor((Math.random() * 256)),
                                (int) Math.floor((Math.random() * 256))
                        ),
                        /* Random velocities from -5 to 5 */
                        (int) Math.floor((Math.random() * 10) - 5),
                        (int) Math.floor((Math.random() * 10) - 5),
                        this
                );
                add(ball);
                ball.start();
            }
        }

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

    public class Ball extends JPanel {
        private int posX, posY, size;
        private Color color;

        private int vx = 5;
        private int vy = 5;

        private Surface surface;
        private Timer timer;

        public Ball(int posX, int posY, int size, Color color, int vx, int vy, Surface surface) {
            this.posX = posX;
            this.posY = posY;
            this.size = size;
            this.color = color;
            this.vx = vx;
            this.vy = vy;
            this.surface = surface;
            setBackground(color);
            setSize(size, size);
            setOpaque(false);
        }

        public void start() {
            if (timer != null) {
                timer.stop();
            }
            timer = new Timer(10, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    update();
                    surface.repaint();
                }
            });
            timer.start();
        }

        public void stop() {
            if (timer == null) {
                return;
            }
            timer.stop();
        }

        protected void update() {
            int width = surface.getSize().width;
            int height = surface.getSize().height;
            if (posX > width || posX < 0) {
                vx *= -1;
            }

            if (posY > height || posY < 0) {
                vy *= -1;
            }

            if (posX > width) {
                posX = width;
            }

            if (posX < 0) {
                posX = 0;
            }

            if (posY > height) {
                posY = height;
            }

            if (posY < 0) {
                posY = 0;
            }

            this.posX += vx;
            this.posY += vy;

            setLocation(posX, posY);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
            g.setColor(color);
            g.fillOval(0, 0, size, size);
        }
    }
}

这种方法(甚至 Threaded 方法)的问题是,它无法很好地扩展。例如,在我的实验过程中,在开始出现响应性问题(调整 window 的大小滞后,很多)之前,我只得到了大约 5, 000 个球,而使用 ArrayList 的大约 20, 000 个球(和一个 Timer) - 老实说,帧速率很糟糕,但 UI 仍然相对敏感 - 我可以调整 window 的大小而不会出现延迟

单个 Timer,基于数组的示例...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new SurfacePane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public interface Surface {
        public Dimension getSize();

        public void repaint();
    }

    public class SurfacePane extends JPanel implements Surface {
        private List<Ball> balls = new ArrayList<>(32);

        public SurfacePane() {
            for (int index = 0; index < 20_000; index++) {
                Ball ball = new Ball(
                        /* Random positions from 0 to windowWidth or windowHeight */
                        (int) Math.floor(Math.random() * 400),
                        (int) Math.floor(Math.random() * 400),
                        /* Random size from 10 to 30 */
                        (int) Math.floor(Math.random() * 20) + 10,
                        /* Random RGB colors*/
                        new Color(
                                (int) Math.floor((Math.random() * 256)),
                                (int) Math.floor((Math.random() * 256)),
                                (int) Math.floor((Math.random() * 256))
                        ),
                        /* Random velocities from -5 to 5 */
                        (int) Math.floor((Math.random() * 10) - 5),
                        (int) Math.floor((Math.random() * 10) - 5),
                        this
                );
                balls.add(ball);
            }

            Timer timer = new Timer(10, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Ball ball : balls) {
                        ball.update();
                    }
                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (Ball ball : balls) {
                ball.paint(g2d);
            }
            g2d.dispose();
        }

    }

    public class Ball {
        private int posX, posY, size;
        private Color color;

        private int vx = 5;
        private int vy = 5;

        private Surface surface;
        private Timer timer;

        public Ball(int posX, int posY, int size, Color color, int vx, int vy, Surface surface) {
            this.posX = posX;
            this.posY = posY;
            this.size = size;
            this.color = color;
            this.vx = vx;
            this.vy = vy;
            this.surface = surface;
        }

        protected void update() {
            int width = surface.getSize().width;
            int height = surface.getSize().height;
            if (posX > width || posX < 0) {
                vx *= -1;
            }

            if (posY > height || posY < 0) {
                vy *= -1;
            }

            if (posX > width) {
                posX = width;
            }

            if (posX < 0) {
                posX = 0;
            }

            if (posY > height) {
                posY = height;
            }

            if (posY < 0) {
                posY = 0;
            }

            this.posX += vx;
            this.posY += vy;
        }

        public void paint(Graphics2D g2d) {
            g2d.setColor(color);
            g2d.fillOval(posX, posY, size, size);
        }
    }
}