Fernando A.W.
Fernando A.W.

Reputation: 273

java bounceBall mouse escape

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);
            }
        }
    }
}

Upvotes: 0

Views: 326

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347214

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);
}

Upvotes: 2

corsiKa
corsiKa

Reputation: 82579

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.

Upvotes: 2

Related Questions