Графика для прыгающего мяча в многопоточной программе на 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 не является потокобезопасным - вы должны использовать Swing Timer

MadProgrammer 23.04.2022 11:10

Я не думаю, что ваше намерение является правильным подходом (попытка сделать каждый мяч своим собственным Thread), вы быстро столкнетесь со всевозможными проблемами, пытаясь выполнить обнаружение столкновений, поскольку состояние каждого мяча всегда меняются независимо друг от друга, и это не будет хорошо масштабироваться. Массив и Swing Timer были бы более подходящим решением

MadProgrammer 23.04.2022 14:12
Основы программирования на Java
Основы программирования на Java
Java - это высокоуровневый объектно-ориентированный язык программирования, основанный на классах.
Концепции JavaScript, которые вы должны знать как JS программист!
Концепции JavaScript, которые вы должны знать как JS программист!
JavaScript (Js) - это язык программирования, объединяющий HTML и CSS с одной из основных технологий Всемирной паутины. Более 97% веб-сайтов используют...
0
2
25
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, Swing не является потокобезопасным. Вы не должны обновлять пользовательский интерфейс (или любое состояние, на которое опирается пользовательский интерфейс) вне контекста потока диспетчеризации событий.

Дополнительные сведения см. в статье Параллелизм в 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) в том, что он плохо масштабируется. Например, во время моих экспериментов я набрал всего около 5 000 шаров, прежде чем у меня начались проблемы с реагированием (изменение размера окна сильно отставало), по сравнению с примерно 20 000 шаров с использованием ArrayList (и одного Timer) - я Честно говоря, частота кадров была ужасной, но пользовательский интерфейс оставался относительно отзывчивым — я мог изменять размер окна без задержек.

Одиночный 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);
        }
    }
}

большое спасибо за ваш подробный ответ. Я очень ценю это. И объяснение фантастическое. К сожалению, нам специально сказали, чтобы у каждого шара была своя нить. Но я обязательно прочитаю больше об альтернативном подходе, который вы дали. Еще раз очень признателен, так как я очень новичок в этом и просто стараюсь изо всех сил, как я иду.

Nolan22 23.04.2022 16:00

Другие вопросы по теме