0

I have a problem. I am a beginner with java, and succeeded up to this point. Add bubbles with random sizes. Now I need to make the bubbles escaping mouse when he gets near them. Can anyone give me a hint how?

Thank you.

public class BounceBall extends JFrame {

    private ShapePanel drawPanel;
    private Vector<NewBall> Balls;
    private JTextField message;

// set up interface
    public BounceBall() {
        super("MultiThreading");
        drawPanel = new ShapePanel(400, 345);
        message = new JTextField();
        message.setEditable(false);

        Balls = new Vector<NewBall>();
        add(drawPanel, BorderLayout.NORTH);
        add(message, BorderLayout.SOUTH);

        setSize(400, 400);
        setVisible(true);
    }

    public static void main(String args[]) {

        BounceBall application = new BounceBall();
        application.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
    }

    private class NewBall extends Thread {

        private Ellipse2D.Double thisBall;
        private boolean ballStarted;
        private int size, speed; // characteristics
        private int deltax, deltay; // of the ball

        public NewBall() {
            ballStarted = true;
            size = 10 + (int) (Math.random() * 60);
            speed = 10 + (int) (Math.random() * 100);
            int startx = (int) (Math.random() * 300);
            int starty = (int) (Math.random() * 300);
            deltax = -10 + (int) (Math.random() * 21);
            deltay = -10 + (int) (Math.random() * 21);
            if ((deltax == 0) && (deltay == 0)) {
                deltax = 1;
            }
            thisBall = new Ellipse2D.Double(startx, starty, size, size);
        }

        public void draw(Graphics2D g2d) {
            if (thisBall != null) {
                g2d.setColor(Color.BLUE);
                g2d.fill(thisBall);
            }
        }

        public void run() {
            while (ballStarted) // Keeps ball moving
            {
                try {
                    Thread.sleep(speed);
                } catch (InterruptedException e) {
                    System.out.println("Woke up prematurely");
                }

                // calculate new position and move ball
                int oldx = (int) thisBall.getX();
                int oldy = (int) thisBall.getY();
                int newx = oldx + deltax;
                if (newx + size > drawPanel.getWidth() || newx < 0) {
                    deltax = -deltax;
                }
                int newy = oldy + deltay;
                if (newy + size > drawPanel.getHeight() || newy < 0) {
                    deltay = -deltay;
                }
                thisBall.setFrame(newx, newy, size, size);
                drawPanel.repaint();
            }
        }
    }

    private class ShapePanel extends JPanel {

        private int prefwid, prefht;

        public ShapePanel(int pwid, int pht) {
            prefwid = pwid;
            prefht = pht;

            // add ball when mouse is clicked
            addMouseListener(
                    new MouseAdapter() {
                        public void mouseClicked(MouseEvent e) {
                            NewBall nextBall = new NewBall();
                            Balls.addElement(nextBall);
                            nextBall.start();
                            message.setText("Number of Balls: " + Balls.size());

                        }
                    });
        }

        public Dimension getPreferredSize() {
            return new Dimension(prefwid, prefht);
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            for (int i = 0; i < Balls.size(); i++) {
                (Balls.elementAt(i)).draw(g2d);
            }
        }
    }
}
afzalex
  • 8,598
  • 2
  • 34
  • 61
Fernando A.W.
  • 273
  • 2
  • 12

2 Answers2

2

I'm going to answer this, but I'm very close to issuing a close vote because it doesn't show what you've done so far to attempt this. I would not be surprised if others are closer to the edge than I am on this. At the same time, you've clearly shown your progress before you reached this point, so I'll give you the benefit of the doubt. In the future, I would strongly advise making an attempt and then posting a question that pertains to the specific problem you're having while making that attempt.

You need two things:

  1. The current location of the mouse
  2. A range check and reversal of direction if too close.

The location of the mouse can be achieved by adding two variables (x and y) and, every time the mouse is moved (so add a mouse event listener to your JPanel or something) update those variables with the new location.

Then, you can do a range check (think Pythagorean theorem) on each bubble to make sure they're far enough away. If the bubble is too close, you'll want to check where that bubble would end up if it carried on its current course, as well as where it would end up if it changed X direction, Y direction, or both. Pick the one that ends up being furthest away and set the deltax and deltay to those, and let the calculation carry on as normal.

It sounds like a lot, but those are the two basic components you need to achieve this.

corsiKa
  • 81,495
  • 25
  • 153
  • 204
2

You should not have a Thread for each individual ball, this will not scale well, the more balls you add, the more threads you add. At some point, the amount of work it takes to manage the threads will exceed the benefit for using multiple threads...

Also, I doubt if your need 1000fps...something like 25fps should be more than sufficient for your simple purposes. This will give the system some breathing room and allow other threads within the system time to execute.

Lets start with a simple concept of a Ball. The Ball knows where it is and which direction it is moving it, it also knows how to paint itself, for example...

public class Ball {

    private int x;
    private int y;

    private int deltaX;
    private int deltaY;

    private int dimeter;

    private Ellipse2D ball;
    private Color color;

    public Ball(Color color, Dimension bounds) {
        this.color = color;
        Random rnd = new Random();

        dimeter = 5 + rnd.nextInt(15);

        x = rnd.nextInt(bounds.width - dimeter);
        y = rnd.nextInt(bounds.height - dimeter);

        if (x < 0) {
            x = 0;
        }
        if (y < 0) {
            y = 0;
        }

        int maxSpeed = 10;
        do {
            deltaX = rnd.nextInt(maxSpeed) - (maxSpeed / 2);
        } while (deltaX == 0);

        do {
            deltaY = rnd.nextInt(maxSpeed) - (maxSpeed / 2);
        } while (deltaY == 0);

        ball = new Ellipse2D.Float(0, 0, dimeter, dimeter);

    }

    public void update(Dimension bounds) {

        x += deltaX;
        y += deltaY;

        if (x < 0) {
            x = 0;
            deltaX *= -1;
        } else if (x + dimeter > bounds.width) {
            x = bounds.width - dimeter;
            deltaX *= -1;
        }
        if (y < 0) {
            y = 0;
            deltaY *= -1;
        } else if (y + dimeter > bounds.height) {
            y = bounds.height - dimeter;
            deltaY *= -1;
        }

    }

    public void paint(Graphics2D g2d) {

        g2d.translate(x, y);
        g2d.setColor(color);
        g2d.fill(ball);
        g2d.translate(-x, -y);

    }

}

Next, we need somewhere for the balls to move within, some kind of BallPit for example...

public class BallPit extends JPanel {

    private List<Ball> balls;

    public BallPit() {
        balls = new ArrayList<>(25);
        balls.add(new Ball(Color.RED, getPreferredSize()));

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

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        for (Ball ball : balls) {
            ball.paint(g2d);
        }
        g2d.dispose();
    }

}

This maintains a list of balls, tells them when the need to update and when the need to paint. This example uses a simple javax.swing.Timer, which acts as the central timer which updates the balls and schedules the repaints.

The reason for this is takes care of synchronisation between the updates and the paints, meaning that the balls won't be updating while they are been painted. This is achieved because javax.swing.Timer triggers it's callbacks within the context of the EDT.

See Concurrency in Swing and How to use Swing Timers for more details.

Okay, so that fixes the threading issues, but what about the mouse avoidance...

That's a little more complicated...

What we need to is add a MouseMoitionListener to the BillPit and record the last position of the mouse.

public class BallPit extends JPanel {
    //...
    private Point mousePoint;
    //...
    public BallPit() {
        //...
        MouseAdapter handler = new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                mousePoint = e.getPoint();
            }

            @Override
            public void mouseExited(MouseEvent e) {
                mousePoint = null;
            }

        };

        addMouseListener(handler);
        addMouseMotionListener(handler);
        //...

The reason for including mouseExit is to ensure that balls don't try and move away from a phantom mouse cursor...

Next, we need to update Ball to have an "area of effect", this is the area around the ball that will trigger a change in movement if the mouse cursor moves within it's range...

public class Ball {
    //...
    private final Ellipse2D.Float areaOfEffect;

    public Ball(Color color, Dimension bounds) {
        //...
        areaOfEffect = new Ellipse2D.Float(-10, -10, dimeter + 20, dimeter + 20);
    }

Now, I also add some additional painting for debug reasons...

public void paint(Graphics2D g2d) {

    g2d.translate(x, y);
    g2d.setColor(new Color(0, 0, 192, 32));
    g2d.fill(areaOfEffect);
    g2d.setColor(color);
    g2d.fill(ball);
    g2d.translate(-x, -y);

}

Next, we need to modify the Ball's update method to accept the mousePoint value...

public void update(Dimension bounds, Point mousePoint) {

    PathIterator pathIterator = areaOfEffect.getPathIterator(AffineTransform.getTranslateInstance(x, y));
    GeneralPath path = new GeneralPath();
    path.append(pathIterator, true);
    if (mousePoint != null && path.contains(mousePoint)) {

        // Determine which axis is closes to the cursor...
        int xDistance = Math.abs(x + (dimeter / 2) - mousePoint.x);
        int yDistance = Math.abs(y + (dimeter / 2) - mousePoint.y);

        if (xDistance < yDistance) {
            // If x is closer, the change the delatX
            if (x + (dimeter / 2) < mousePoint.x) {
                if (deltaX > 0) {
                    deltaX *= -1;
                }
            } else {
                if (deltaX > 0) {
                    deltaX *= -1;
                }
            }
        } else {
            // If y is closer, the change the deltaY
            if (y + (dimeter / 2) < mousePoint.y) {
                if (deltaY > 0) {
                    deltaY *= -1;
                }
            } else {
                if (deltaY > 0) {
                    deltaY *= -1;
                }
            }
        }

    }
    //...Rest of previous method code...
}

Basically, what this is trying to do is determine which axis is closer to the mouse point and in which direction the ball should try and move...it's a little "basic", but gives the basic premise...

Lastly, we need to update the "update" loop in the javax.swing.Timer to supply the additional parameter

for (Ball ball : balls) {
    ball.update(getSize(), mousePoint);
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Seeking to understand a little more, I found this other example you gave. In this configuration, it is possible to make them move away from the mouse? http://stackoverflow.com/a/13022788/1898543 In second example named "A Different Approach". – Fernando A.W. Aug 29 '14 at 14:00
  • The speed appears to be set to `10 + Math.random() * 100` so anywhere from 10 to 109. This isn't 1000 frames per second, it's 9 to 100 fps with an average of 17. Not sure where you get 1000 fps from. – corsiKa Aug 29 '14 at 14:30
  • Thread.sleep(1) is, approx, 1000 fps...the speed of the ball only deals with how much the ball will change between frames – MadProgrammer Aug 29 '14 at 20:03
  • And yes, the "other" bouncy ball example could do exactly what this bouncy ball example does...basically, they're the sme example, this one is just simpler – MadProgrammer Aug 29 '14 at 20:20
  • @MadProgrammer Ok, I'll try to implement this escape of bubbles from the mouse. Given the two examples.... – Fernando A.W. Aug 29 '14 at 20:25
  • If you simply add more balls to the both example, they should already be trying to avoid the mouse... – MadProgrammer Aug 29 '14 at 20:27
  • @MadProgrammer In "Move" method, I got the X and Y coordinates the mouse. But bubbles are not changing direction as the mouse gets close to them. – Fernando A.W. Aug 29 '14 at 21:03
  • @MadProgrammer So already lost me whole. I'm using the source link... This is hard. – Fernando A.W. Aug 29 '14 at 21:16
  • So start with the current example. In the `BallPit` class, start by adding new `Ball`s to the `balls` list see how it goes from there – MadProgrammer Aug 29 '14 at 23:56
  • @MadProgrammer I managed to make it work. Now I'm hitting me with the example of Threads. How could I do to make the ball deviate before "hitting" the mouse pointer. Literally run from him. Thanks.. – Fernando A.W. Sep 10 '14 at 20:14
  • @MadProgrammer You can help me with this?? http://stackoverflow.com/questions/25849166/java-bounceball-ball-move Thanks. – Fernando A.W. Sep 16 '14 at 14:21