-3

I'm creating a bouncing ball project , the balls are generated with random velocity , random size and random positioning each time the mouse is clicked , everything is working perfect except for one thing , the balls when they are hitting let's say the walls from the top and left it's perfectly getting bounced back but when the ball hits the bottom and right portions of the window they are getting bounced too but not perfectly , what I mean is that the balls bounce back from the right of the window before even hitting the wall and from the bottom when almost half the ball exceeds the window. What is the problem, why it's doing that ?

Here is the Code :

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
//To generate random colors
/*new Color((int) Math.floor((Math.random() * 256)),(int) Math.floor((Math.random() * 256)),(int) 
Math.floor((Math.random() * 256)))*/
public class bouncingBalls {
public static void main(String[] args) {
    Program program = new Program();
    program.run();
}
}

class Program {
protected JFrame mainFrame;
protected DrawPanel drawPanel;
protected java.util.List<Ball> balls;
void run() {

    balls = new ArrayList<>();
    mainFrame = new JFrame();
    drawPanel = new DrawPanel();
    mainFrame.getContentPane().add(drawPanel);
    mainFrame.setTitle("Bouncing Balls");
    mainFrame.setSize(640, 480);
    mainFrame.setVisible(true);
    mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    /* Generate balls */
    mainFrame.addMouseListener(new MouseAdapter() {
        public void mouseClicked(MouseEvent me) {
            System.out.println("Mouse clicked");
            Ball ball = new Ball(
                    /* Random positions from 0 to windowWidth or windowHeight */
                    (int) Math.floor(Math.random() * 640),
                    (int) Math.floor(Math.random() * 480),
                    /* Random size from 10 to 30 */
                    (int) Math.floor(Math.random() * 20) + 10,
                    /* Random RGB colors*/
                    new Color(0x851E3E),
                    /* Random velocities from -5 to 5 */
                    (int) Math.floor((Math.random() * 10) - 5),
                    (int) Math.floor((Math.random() * 10) - 5)
            );

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

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

        mainFrame.repaint();
    }
}

class DrawPanel extends JPanel {
    @Override
    public void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);

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

    }
}

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

    private int vx;
    private int vy;

    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()-size*2 || posX < 0) {
            vx *= -1;
        }

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

        if (posX > mainFrame.getWidth()-size*2) {
            posX = mainFrame.getWidth()-size*2;
        }

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

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

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

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

    }

    void draw(Graphics g) {
        g.setColor(color);
        g.fillOval(posX, posY, size, size);
        g.getClipBounds();
        g.getClipBounds();
    }
}
}
  • It sounds like you are not taking the ball thickness into account correctly. Why do you have `size-32` and `size*2` in your update method? What is the `32`? Also, why is the size multiplied by two? That will stop the ball too far from the edge and sounds like the behaviour you are experiencing. – sorifiend May 23 '21 at 22:44
  • Why `-size-32`? Shouldn't it be `posY + size > height`? Also, beware, Swing is not thread safe. Using a `Thread` the way you are could lead to dirty read/writes between the threads. A Swing `Timer` might be a better solution – MadProgrammer May 23 '21 at 22:51
  • See: https://stackoverflow.com/questions/54028090/get-width-and-height-of-jpanel-outside-of-the-class/54028681#54028681 for a ball animation example using a Swing Timer. – camickr May 24 '21 at 00:12

1 Answers1

3

Okay, so you have a number of key issues.

A JFrame contains a contentPane and decorations. Decorations appear within the bounds of the frame, which makes the contentPanes size equal to the frames size minus the decoration insets. This is where you're primarily running into issues, as you're using the frame size and not the DrawPanel size to make determinations about the collision detection.

Instead, you should be using the DrawPanels size (which has been added to the contentPane).

See How can I set in the midst? for more details.

This is a rebuilt example of you code which uses a Swing Timer instead of Thread, so that it's thread safe and places the primary workload into the DrawPanel, instead of having it spread all about the place.

See How to Use Swing Timers for more details

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 java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Program program = new Program();
                program.run();
            }
        });
    }

    class Program {

        protected JFrame mainFrame;
        protected DrawPanel drawPanel;

        void run() {

            mainFrame = new JFrame();
            drawPanel = new DrawPanel();
            mainFrame.getContentPane().add(drawPanel);
            mainFrame.setTitle("Bouncing Balls");
            mainFrame.pack();
            mainFrame.setVisible(true);
            mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }

        class DrawPanel extends JPanel {

            private java.util.List<Ball> balls;
            private Timer timer;

            public DrawPanel() {
                balls = new ArrayList<>(25);
                addMouseListener(new MouseAdapter() {
                    public void mouseClicked(MouseEvent me) {
                        System.out.println("Mouse clicked");
                        Ball ball = new Ball(
                                /* Random positions from 0 to windowWidth or windowHeight */
                                (int) Math.floor(Math.random() * 640),
                                (int) Math.floor(Math.random() * 480),
                                /* Random size from 10 to 30 */
                                (int) Math.floor(Math.random() * 20) + 10,
                                /* Random RGB colors*/
                                Color.RED,
                                /* Random velocities from -5 to 5 */
                                (int) Math.floor((Math.random() * 10) - 5),
                                (int) Math.floor((Math.random() * 10) - 5)
                        );

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

            @Override
            public void addNotify() {
                super.addNotify();
                if (timer != null) {
                    timer.stop();
                }

                timer = new Timer(30, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        for (Ball b : balls) {
                            b.update(getSize());
                        }
                        repaint();
                    }
                });
                timer.start();
            }

            @Override
            public void removeNotify() {
                super.removeNotify();
                if (timer == null) {
                    return;
                }
                timer.stop();
            }

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

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

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

            }
        }

        class Ball {

            private int posX, posY, size;
            private Color color;

            private int vx;
            private int vy;

            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(Dimension bounds) {

                if (posX + size > bounds.width || posX < 0) {
                    vx *= -1;
                }

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

                if (posX + size > bounds.width) {
                    posX = bounds.width - size;
                }

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

                if (posY + size > bounds.height) {
                    posY = bounds.height - size;
                }

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

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

            }

            void draw(Graphics g) {
                g.setColor(color);
                g.fillOval(posX, posY, size, size);
            }
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366