I want to do a bouncing balls application in java. Each ball should take place by mouse clicking and each of them should have random speed, color, radius and starting position. I managed to do everything except the part where mouse listener takes place. Whatever i do in the mousePressed method didn't work. What should i do to make user create a random ball when he presses the mouse?
EDIT: This is the last version of my code. Now the problem is that i can't create more than one ball. When i click on the screen same ball is just keeps speeding.
BouncingBalls Class
public class BouncingBalls extends JPanel implements MouseListener{
private Ball ball;
protected List<Ball> balls = new ArrayList<Ball>(20);
private Container container;
private DrawCanvas canvas;
private int canvasWidth;
private int canvasHeight;
public static final int UPDATE_RATE = 30;
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int count = 0;
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
public BouncingBalls(int width, int height){
canvasWidth = width;
canvasHeight = height;
ball = new Ball(x, y, speedX, speedY, radius, red, green, blue);
container = new Container();
canvas = new DrawCanvas();
this.setLayout(new BorderLayout());
this.add(canvas, BorderLayout.CENTER);
this.addMouseListener(this);
}
public void start(){
Thread t = new Thread(){
public void run(){
while(true){
update();
repaint();
try {
Thread.sleep(1000 / UPDATE_RATE);
} catch (InterruptedException e) {}
}
}
};
t.start();
}
public void update(){
ball.move(container);
}
class DrawCanvas extends JPanel{
public void paintComponent(Graphics g){
super.paintComponent(g);
container.draw(g);
ball.draw(g);
}
public Dimension getPreferredSize(){
return(new Dimension(canvasWidth, canvasHeight));
}
}
public static void main(String[] args){
javax.swing.SwingUtilities.invokeLater(new Runnable(){
public void run(){
JFrame f = new JFrame("Bouncing Balls");
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
f.setContentPane(new BouncingBalls(500, 500));
f.pack();
f.setVisible(true);
}
});
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
balls.add(new Ball(x, y, speedX, speedY, radius, red, green, blue));
start();
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
}
Ball Class
import java.awt.Color;
import java.awt.Graphics;
public class Ball{
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
private BouncingBalls balls;
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int i = 0;
public Ball(int x, int y, int speedX, int speedY, int radius, int red, int green, int blue){
this.x = x;
this.y = y;
this.speedX = speedX;
this.speedY = speedY;
this.radius = radius;
this.red = red;
this.green = green;
this.blue = blue;
}
public void draw(Graphics g){
for(Ball ball : balls){
g.setColor(new Color(red, green, blue));
g.fillOval((int)(x - radius), (int)(y - radius), (int)(2 * radius), (int)(2 * radius));
}
}
public void move(Container container){
x += speedX;
y += speedY;
if(x - radius < 0){
speedX = -speedX;
x = radius;
}
else if(x + radius > 500){
speedX = -speedX;
x = 500 - radius;
}
if(y - radius < 0){
speedY = -speedY;
y = radius;
}
else if(y + radius > 500){
speedY = -speedY;
y = 500 - radius;
}
}
}
Container Class
import java.awt.Color;
import java.awt.Graphics;
public class Container {
private static final int HEIGHT = 500;
private static final int WIDTH = 500;
private static final Color COLOR = Color.WHITE;
public void draw(Graphics g){
g.setColor(COLOR);
g.fillRect(0, 0, WIDTH, HEIGHT);
}
}
ERROR: I get "Can only iterate over an array or an instance of java.lang.Iterable" error in this part of code:
public void draw(Graphics g){
for(Ball ball : balls){
g.setColor(new Color(red, green, blue));
g.fillOval((int)(x - radius), (int)(y - radius), (int)(2 * radius), (int)(2 * radius));
}
}
First, if you want to render more than one ball, you should create another class to contain all the properties needed to draw a ball (maybe even a draw (Graphics g) method to delegate the drawing). Then you would have a list of balls on your BouncingBalls class which should be iterated over and painted on the paintComponent method.
Said that, your mouseClicked handler would just create a new Ball instance and add it to the list.
EDIT:
Example of how the drawing process would be on your DrawCanvas class:
class DrawCanvas {
public void paintComponent(Graphics g){
super.paintComponent(g);
container.draw(g);
for (Ball ball : balls)
//the draw method should only care of the specific ball instance
//you are calling it from
ball.draw(g);
}
...
I think you are having problems separating your problem into classes and making their instances cooperate to do what you want. If you are indeed having doubts about this, I recommend you read some articles/books about the topic to get a better idea of the concepts of a class and an object and how they work; it'll definitely help you do your programming with ease.
You need to add the MouseListener to the component:
public BouncingBalls() {
this.addMouseListener(this); // <-- Add this object as a MouseListener.
this.setPreferredSize(new Dimension(BOX_WIDTH, BOX_HEIGHT));
Try using this code in your main method:
frame.addMouseListener(this);
You need to add the mouse listener to the frame/panel.
(response to this comment by you) Alternatively, if you want to add the listener to the panel, first you must call
setFocusable(true);
requestFocusInWindow();
In your BouncingBalls constructor. Then you can add the mouse listener to the panel with:
addMouseListener(this);
This is because the panel does not initially have focus.
The easiest way to do it, though, is to just add the mouse listener to the frame.
Related
I am trying to rotate a circle around a separate point in a program. right now I can get the circle to rotate but it slowly starts getting closer and closer to the point it's rotating from. I am trying to do this using JPanel and implementing it as a rectangle.
package WoffindenZone;
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.event.*;
import java.lang.Math;
public class Protector extends Rectangle{
double Velocity;
int speed = 3;
Protector(int x, int y, int PROTECTOR_DIAMETER){
super(x,y,PROTECTOR_DIAMETER,PROTECTOR_DIAMETER);
}
public void keyPressed(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(speed);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(speed);
move();
}
}
public void keyReleased(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(0);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(0);
move();
}
}
public void setDirection(int Direction){
Velocity = Direction*Math.PI/180;
}
public void move(){
x = (int)Math.round(500 + Math.cos(Velocity) * (x-500) - Math.sin(Velocity) * (y-((1000*0.5555)/2)));
y = (int)Math.round(((1000*0.5555)/2) + Math.sin(Velocity) * (x-500) + Math.cos(Velocity) * (y-((1000*0.5555)/2)));
System.out.println(x);
System.out.println(y);
}
public void draw(Graphics g){
g.setColor(Color.blue);
g.fillOval(x,y,width,height);
}
Use a rotation instance of an AffineTransform. See getRotateInstance(theta,anchorx,anchory) for details.
Returns a transform that rotates coordinates around an anchor point. This operation is equivalent to translating the coordinates so that the anchor point is at the origin (S1), then rotating them about the new origin (S2), and finally translating so that the intermediate origin is restored to the coordinates of the original anchor point (S3).
How do you rotate a circle about a point in JPanel?
Here's how I rotate a circle about a point in a JPanel.
I don't know how to make an animated GIF. Just imagine the blue circle rotating clockwise around the center of the drawing JPanel.
So, let's start at the beginning. Basically, I have a circle rotating on the circumference of another circle. So, I create a Circle model class from plain Java.
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
The class consists of basic getters and setters. I make the radius and color final because they don't change value in this Java application.
The calculateCircumferencePoint method is the only interesting method. It takes an int angle in degrees, and calculates the point on the circumference represented by that angle, rounded to the nearest X and Y integer points.
Next, we create two Circle instances, an inner circle and an outer circle. Here's the class constructor setting the preferred size of the drawing area, the inner circle, and the outer circle. We start the outer circle at zero degrees (to the right);
private Circle innerCircle;
private Circle outerCircle;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
Now, we can start coding the GUI. First, we start the Java application by calling the SwingUtilities invokeLater method. This method ensures that we create and execute the Swing components on the Event Dispatch Thread.
Next, we define the JFrame. Here's the code we have so far.
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
The JFrame methods must be called in a certain order. This is the order I use for most of my SWwing applications.
I pack the JFrame. I don't set a JFrame size. I let the Swing layout managers set the size of my JFrame. The default layout of a JFrame content pane is a BorderLayout. I put my drawing JPanel in the center of the BorderLayout.
Next, I create the drawing JPanel.
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
All the drawing JPanel does is paint a Circle object. Pretty straightforward.
The fillOval method paints an oval from the upper left hand corner. We calculate the upper left hand point from the center point.
The responsibility for calculating and updating the outer circle center point falls to my controller class, the Animation class. I use a simple loop to update the theta angle, calculate the new outer circle center point, paint the outer circle, and wait a period of time.
Here's that code.
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
The Animation repaint method makes the call to the drawing JPanel repaint method inside another SwingUtilities invokeLater method. This method ensures that the drawing happens on the Event Dispatch Thread.
Finally, here's the complete, runnable, example. I used inner classes so I could post the code as one block, and you can copy and run this code as one block. Generally, classes should be in separate files and for a more complex GUI, separate packages.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RotateCircle implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
}
this is my board class and i try to call ball object in GameBoard class but i didn't and my problem is didn't show ball on screen.
Used to execute code after a given delay
The attribute is corePoolSize - the number of threads to keep in
the pool, even if they are idle
package test2;
public class Board extends JFrame{
public static int boardWidth = 800;
public static int boardHeight = 800;
public static void main(String[] args){
new Board();
}
public Board() {
this.setSize(boardWidth, boardHeight);
this.setTitle("Ball");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GameBoard gb = new GameBoard();
this.add(gb, BorderLayout.CENTER);
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
executor.scheduleAtFixedRate(new RepaintTheBoard(this), 0L, 20L, TimeUnit.MILLISECONDS);
this.setVisible(true);
}
}
class RepaintTheBoard implements Runnable{
Board theBoard;
public RepaintTheBoard(Board theBoard){
this.theBoard = theBoard;
}
#Override
public void run() {
// Redraws the game board
theBoard.repaint();
}
}
#SuppressWarnings("serial")
//GameDrawingPanel is what we are drawing on
class GameBoard extends JComponent {
Random rnd=new Random();
public ArrayList<Ball> balls = new ArrayList<Ball>();
int width = Board.boardWidth;
int height = Board.boardHeight;
public GameBoard(){
for(int i=0; i<50; i++){
int randomStartXPos = (int) (Math.random() * (Board.boardWidth - 40) + 1);
int randomStartYPos = (int) (Math.random() * (Board.boardHeight - 40) + 1);
balls.add(new Ball(randomStartXPos,randomStartYPos,30));
}
}
public void paint(Graphics g) {
// Allows me to make many settings changes in regards to graphics
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(new Color(rnd.nextInt(255),rnd.nextInt(255),rnd.nextInt(255)));
for(Ball ball : balls){
ball.move();
g2d.draw(ball);
}
}
}
this ball class and i think , i have problem in move() class
package test2;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
public class Ball extends Ellipse2D{
int uLeftXPos, uLeftYPos;
int xDirection = 1;
int yDirection = 1;
int diameter;
int width = Board.boardWidth;
int height = Board.boardHeight;
public Ball(int randomStartXPos, int randomStartYPos, int Diam) {
super();
this.xDirection = (int) (Math.random() * 4 + 1);
this.yDirection = (int) (Math.random() * 4 + 1);
// Holds the starting x & y position for the Rock
this.uLeftXPos = randomStartXPos;
this.uLeftYPos = randomStartYPos;
this.diameter = Diam;
}
public void move(){
if (uLeftXPos + xDirection < 0)
xDirection = 1;
if (uLeftXPos + xDirection > width - diameter)
xDirection = -1;
if (uLeftYPos + yDirection < 0)
yDirection = 1;
if (uLeftYPos + yDirection > height - diameter)
yDirection = -1;
uLeftXPos = uLeftXPos + xDirection;
uLeftYPos = uLeftYPos + yDirection;
}
#Override
public Rectangle2D getBounds2D() {
// TODO Auto-generated method stub
return null;
}
#Override
public double getX() {
// TODO Auto-generated method stub
return 0;
}
#Override
public double getY() {
// TODO Auto-generated method stub
return 0;
}
#Override
public double getWidth() {
// TODO Auto-generated method stub
return 0;
}
#Override
public double getHeight() {
// TODO Auto-generated method stub
return 0;
}
#Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return false;
}
#Override
public void setFrame(double x, double y, double w, double h) {
// TODO Auto-generated method stub
}
}
Your main problem is that your Ball class extends a Shape object, Ellipse2D and does so incompletely, preventing full Ellipse2D/Shape behavior. I think that you'd be far better off not using inheritance but rather using composition -- have Ball contain a valid and complete Ellipse2D object, one that it uses to help it draw itself.
Other issues:
Your JComponent should have paintComponent overridden, not paint
You should always call the super's painting method within your override
It's not a good idea to have program logic within a painting method, as you can never fully control this method, nor do you want to. Better to have your move method separate and have the painting method do one thing -- paint the state of the component, and that's it.
Your code is skirting danger with Swing threading. Consider using a Swing Timer and not a scheduled executor service.
Start your GUI on the Swing thread using SwingUtilities.invokeLater(...)
Since your Ball object uses default overrides for most of the Ellipse2D methods, no movement will occur since its these method returns that determine the location of the Shape.
But again, you don't want to really override this object, but instead use composition.
Something like:
class Ball {
private static final double ELLIPSE_W = 20;
private static final double ELLIPSE_H = ELLIPSE_W;
private int x = 0;
private int y = 0;
private Ellipse2D ellipse = new Ellipse2D.Double(x, y, ELLIPSE_W, ELLIPSE_H);
int uLeftXPos, uLeftYPos;
int xDirection = 1;
int yDirection = 1;
int diameter;
int width = Board.boardWidth;
int height = Board.boardHeight;
public Ball(int randomStartXPos, int randomStartYPos, int Diam) {
super();
this.xDirection = (int) (Math.random() * 4 + 1);
this.yDirection = (int) (Math.random() * 4 + 1);
// Holds the starting x & y position for the Rock
this.uLeftXPos = randomStartXPos;
this.uLeftYPos = randomStartYPos;
this.diameter = Diam;
x = uLeftXPos;
y = uLeftYPos;
ellipse = new Ellipse2D.Double(x, y, ELLIPSE_W, ELLIPSE_H);
}
public Ellipse2D getEllipse() {
return ellipse;
}
public void move() {
if (uLeftXPos + xDirection < 0)
xDirection = 1;
if (uLeftXPos + xDirection > width - diameter)
xDirection = -1;
if (uLeftYPos + yDirection < 0)
yDirection = 1;
if (uLeftYPos + yDirection > height - diameter)
yDirection = -1;
uLeftXPos = uLeftXPos + xDirection;
uLeftYPos = uLeftYPos + yDirection;
x = uLeftXPos;
y = uLeftYPos;
ellipse = new Ellipse2D.Double(x, y, ELLIPSE_W, ELLIPSE_H);
}
}
And in the game board:
class GameBoard extends JComponent {
Random rnd = new Random();
public ArrayList<Ball> balls = new ArrayList<Ball>();
int width = Board.boardWidth;
int height = Board.boardHeight;
public GameBoard() {
for (int i = 0; i < 50; i++) {
int randomStartXPos = (int) (Math.random() * (Board.boardWidth - 40) + 1);
int randomStartYPos = (int) (Math.random() * (Board.boardHeight - 40) + 1);
balls.add(new Ball(randomStartXPos, randomStartYPos, 30));
}
}
public void move() {
for (Ball ball : balls) {
ball.move();
}
}
#Override
protected void paintComponent(java.awt.Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(new Color(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255)));
for (Ball ball : balls) {
// ball.move();
g2d.draw(ball.getEllipse());
}
}
}
I created a program that makes multiple bouncing balls with random color, speed and radius. When user clicks on the screen a new random ball should appear and move around screen. But i have a multi-thread issue. When i click on the screen a ball appears and doesn't moving at all. When another click comes nothing happens.
BouncingBalls Class
public class BouncingBalls extends JPanel implements MouseListener{
private Ball ball;
protected List<Ball> balls = new ArrayList<Ball>(20);
private Container container;
private DrawCanvas canvas;
private int canvasWidth;
private int canvasHeight;
public static final int UPDATE_RATE = 30;
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int count = 0;
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
public BouncingBalls(int width, int height){
canvasWidth = width;
canvasHeight = height;
ball = new Ball(x, y, speedX, speedY, radius, red, green, blue);
container = new Container();
canvas = new DrawCanvas();
this.setLayout(new BorderLayout());
this.add(canvas, BorderLayout.CENTER);
this.addMouseListener(this);
}
public void start(){
Thread t = new Thread(){
public void run(){
while(true){
update();
repaint();
try {
Thread.sleep(1000 / UPDATE_RATE);
} catch (InterruptedException e) {}
}
}
};
t.start();
}
public void update(){
ball.move(container);
}
class DrawCanvas extends JPanel{
public void paintComponent(Graphics g){
super.paintComponent(g);
container.draw(g);
ball.draw(g);
}
public Dimension getPreferredSize(){
return(new Dimension(canvasWidth, canvasHeight));
}
}
public static void main(String[] args){
javax.swing.SwingUtilities.invokeLater(new Runnable(){
public void run(){
JFrame f = new JFrame("Bouncing Balls");
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
f.setContentPane(new BouncingBalls(500, 500));
f.pack();
f.setVisible(true);
}
});
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
count++;
balls.add(new Ball(x, y, speedX, speedY, radius, red, green, blue));
balls.get(count-1).start();
start();
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
}
Ball Class
import java.awt.Color;
import java.awt.Graphics;
public class Ball{
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
private BouncingBalls balls;
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int i = 0;
public Ball(int x, int y, int speedX, int speedY, int radius, int red, int green, int blue){
this.x = x;
this.y = y;
this.speedX = speedX;
this.speedY = speedY;
this.radius = radius;
this.red = red;
this.green = green;
this.blue = blue;
}
public void draw(Graphics g){
for(Ball ball : balls){
g.setColor(new Color(red, green, blue));
g.fillOval((int)(x - radius), (int)(y - radius), (int)(2 * radius), (int)(2 * radius));
}
}
public void move(Container container){
x += speedX;
y += speedY;
if(x - radius < 0){
speedX = -speedX;
x = radius;
}
else if(x + radius > 500){
speedX = -speedX;
x = 500 - radius;
}
if(y - radius < 0){
speedY = -speedY;
y = radius;
}
else if(y + radius > 500){
speedY = -speedY;
y = 500 - radius;
}
}
}
Container Class
import java.awt.Color;
import java.awt.Graphics;
public class Container {
private static final int HEIGHT = 500;
private static final int WIDTH = 500;
private static final Color COLOR = Color.WHITE;
public void draw(Graphics g){
g.setColor(COLOR);
g.fillRect(0, 0, WIDTH, HEIGHT);
}
}
You're maintaing two different references to your ball.
You have a reference to a single Ball called ball and a List of balls. Your update and paint methods only reference the single ball
Ball doesn't seem to have a start method (that I can see) so this balls.get(count-1).start(); doesn't make sense...
Updated
You don't need the reference to ball
While not a bad idea, while testing, you should probably call start in the constructor
Your update method in BouncingBalls should looping through the balls list, calling move on each ball in the list...
The paintComponent method of DrawCanvas needs access to and should make use of the balls list. This might be better achievable through a model interface
Do not construct a new Ball with parameters, as it's giving each ball the same properties, especially when you assign random values to it when you construct it any way...
Ball doesn't have (or need) a start method
public class BouncingBalls extends JPanel implements MouseListener {
// private Ball ball;
protected List<Ball> balls = new ArrayList<Ball>(20);
private Container container;
private DrawCanvas canvas;
private int canvasWidth;
private int canvasHeight;
public static final int UPDATE_RATE = 30;
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int count = 0;
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
public BouncingBalls(int width, int height) {
canvasWidth = width;
canvasHeight = height;
// ball = new Ball(x, y, speedX, speedY, radius, red, green, blue);
container = new Container();
canvas = new DrawCanvas();
this.setLayout(new BorderLayout());
this.add(canvas, BorderLayout.CENTER);
this.addMouseListener(this);
start();
}
public void start() {
Thread t = new Thread() {
public void run() {
while (true) {
update();
repaint();
try {
Thread.sleep(1000 / UPDATE_RATE);
} catch (InterruptedException e) {
}
}
}
};
t.start();
}
public void update() {
for (Ball ball : balls) {
ball.move(container);
}
}
class DrawCanvas extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
container.draw(g);
for (Ball ball : balls) {
ball.draw(g);
}
// ball.draw(g);
}
public Dimension getPreferredSize() {
return (new Dimension(canvasWidth, canvasHeight));
}
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame("Bouncing Balls");
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
f.setContentPane(new BouncingBalls(500, 500));
f.pack();
f.setVisible(true);
}
});
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
count++;
balls.add(new Ball());
// balls.add(new Ball(x, y, speedX, speedY, radius, red, green, blue));
// balls.get(count - 1).start();
// start();
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
public static class Ball {
public int random(int maxRange) {
return (int) Math.round(Math.random() * maxRange);
}
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int i = 0;
public Ball() { //int x, int y, int speedX, int speedY, int radius, int red, int green, int blue) {
// this.x = x;
// this.y = y;
// this.speedX = speedX;
// this.speedY = speedY;
// this.radius = radius;
// this.red = red;
// this.green = green;
// this.blue = blue;
}
public void draw(Graphics g) {
g.setColor(new Color(red, green, blue));
g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));
}
public void move(Container container) {
x += speedX;
y += speedY;
if (x - radius < 0) {
speedX = -speedX;
x = radius;
} else if (x + radius > 500) {
speedX = -speedX;
x = 500 - radius;
}
if (y - radius < 0) {
speedY = -speedY;
y = radius;
} else if (y + radius > 500) {
speedY = -speedY;
y = 500 - radius;
}
}
}
public static class Container {
private static final int HEIGHT = 500;
private static final int WIDTH = 500;
private static final Color COLOR = Color.WHITE;
public void draw(Graphics g) {
g.setColor(COLOR);
g.fillRect(0, 0, WIDTH, HEIGHT);
}
}
}
Updated
As pointed out by the commentators, ArrayList is not thread safe, it's not a good idea to have multiple threads trying to access it simultaneously. While adding is slightly safer then removing, it is still bad practice.
You can either replace ArrayList with Vector, which would be the simpler solution, or synchronize the access to the list around a common monitor lock. Given your example, I'd use a java.util.Vector
You Can try this alternate Java Programm for bouncing 10 multi-colored balls on a single "START" button.....
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javaimage.io.*;
class Thr extends Thread
{
boolean up=false;
Ballbounce parent;
int top,left;
Color c;
Thr(int t,int l,Color cr,ex5 p)
{
top=l;
if(top > 170)
top=170-t/8;
left=t;
c=cr;
parent=p;
}
public void run()
{
try
{
while(true)
{
Thread.sleep(37);
if(top >= 188)
up=true;
if(top <= 0)
up=false;
if(!up)
top=top+2;
else
top=top-2;
parent.p.repaint();
}
}catch(Exception e){}
}
}
class Ballbounce extends JFrame implements ActionListener
{
int top=0,left=0,n=0,radius=50;
Color C[]={Color.black,Color.cyan,Color.orange,Color.red,Color.yellow,Color.pink,Color.gray,Color.blue,Color.green,Color.magenta};
Thr t[]=new Thr[10];
GPanel p;
JButton b;
Panel p1;
Ballbounce()
{
setSize(700,300);
setVisible(true);
setLayout( new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(p=new GPanel(this),BorderLayout.CENTER);
b= new JButton("Start");
b.addActionListener(this);
add(p1=new Panel(),BorderLayout.SOUTH);
p1.setBackground(Color.lightGray);
p1.add(b);
}
public static void main(String args[])
{
new Ballbounce();
}
public void actionPerformed(ActionEvent e)
{
t[n]=new Thr(left+(radius+13)*n+29,top+n*25,C[n],this);
t[n].start();
n++;
p.repaint();
if(n >9)
b.setEnabled(false);
}
}
class GPanel extends JPanel
{
Ballbounce parent;
GPanel(Ballbounce p)
{
parent=p;
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
setBackground(Color.white);
for(int i=0;i< parent.n;i++)
{
g.setColor(parent.t[i].c);
g.fillOval(parent.t[i].left,parent.t[i].top,parent.radius,parent.radius);
}
}
}
I hope you will like it....
If u are unable to understand the code... You can question it anytime...... :)
Enjoy the code... :)
Here i have a code which draws a rectangle on the mouseClicked position using the paintComponent.I can get the output message but anything related to graphics and .draw() wont work.
Code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public final class testclass extends JFrame {
static JPanel p;
Timer t;
int x = 1;
int y = 1;
int xspeed = 1;
int yspeed = 1;
public testclass() {
initComponents();
this.setBounds(100, 300, 500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
t.start();
this.add(p);
}
public void initComponents() {
final ActionListener action = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Hello!");
p.repaint();
}
};
t = new Timer(50, action);
p = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D gD = (Graphics2D) g;
moveBALL();
gD.drawOval(x, y, 25, 25);
p.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {
System.out.println("a");
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println("b");
}
#Override
public void mouseExited(MouseEvent e) {
System.out.println("c");
}
#Override
public void mouseEntered(MouseEvent e) {
System.out.println("d");
}
#Override
public void mouseClicked(MouseEvent e) {
gD.drawRect(e.getX(), e.getY(), 10, 60);
gD.setColor(Color.green);
System.out.println("clicked");
}
});
}
void moveBALL() {
x = x + xspeed;
y = y + yspeed;
if (x < 0) {
x = 0;
xspeed = -xspeed;
} else if (x > p.getWidth() - 20) {
x = p.getWidth() - 20;
xspeed = -xspeed;
}
if (y < 0) {
y = 0;
yspeed = -yspeed;
} else if (y > p.getHeight() - 20) {
y = p.getHeight() - 20;
yspeed = -yspeed;
}
}
};
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new testclass().setVisible(true);
p.setBackground(Color.WHITE);
}
});
}
}
What is the proper way to implement a mouseListener() in this program?
Thanks.
Some suggestions on current code:
Watch class naming scheme i.e testclass should be TestClass or even better Test (but thats nit picking). All class names begin with capital letter and each new word thereafter is capitalized.
Dont extend JFrame unnecessarily.
Dont call setBounds on JFrame rather use appropriate LayoutManager and/or override getPreferredSize() of JPanel and return dimensions which fits its content.
Always call pack() on JFrame before setting it visible (taking above into consideration).
Use MouseAdapter vs MouseListener
Dont call moveBall() in paintComponent rather call it in your Timer which repaints the screen, not only slightly better design but we also should not do possibly long running tasks in paint methods.
As for your problem I think your logic is a bit skewed.
One approach would see the Rectangle (or Rectangle2D) get replaced by its own custom class (which will allow us to store attributes like color etc). Your ball would also have its own class which has the method moveBall() and its attributes like x and y position etc. On every repaint() your JPanel would call the method to move the ball, the JPanel itself could wrap the moveBall() in its own public method which we could than call from the timer which repaints the screen.
Here is an example of your code with above fixes implemented (please analyze it and if you have any questions let me know):
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.*;
public class Test {
private MyPanel p;
private Timer t;
public Test() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents();
frame.add(p);
frame.pack();
frame.setVisible(true);
t.start();
}
private void initComponents() {
final ActionListener action = new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
p.moveEntities();//moves ball etc
p.repaint();
}
};
t = new Timer(50, action);
p = new MyPanel();
p.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
p.addEntity(e.getX(), e.getY(), 10, 50, Color.GREEN);
System.out.println("clicked");
}
});
p.setBackground(Color.WHITE);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
class MyPanel extends JPanel {
int width = 300, height = 300;
ArrayList<MyRectangle> entities = new ArrayList<>();
MyBall ball = new MyBall(10, 10, 25, 25, Color.RED, width, height);
void addEntity(int x, int y, int w, int h, Color c) {
entities.add(new MyRectangle(x, y, w, h, c));
}
void moveEntities() {
ball.moveBALL();
}
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(ball.getColor());
g2d.fillOval((int) ball.x, (int) ball.y, (int) ball.width, (int) ball.height);
for (MyRectangle entity : entities) {
g2d.setColor(entity.getColor());
g2d.fillRect((int) entity.x, (int) entity.y, (int) entity.width, (int) entity.height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
}
class MyRectangle extends Rectangle2D.Double {
Color color;
public MyRectangle(double x, double y, double w, double h, Color c) {
super(x, y, w, h);
color = c;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
class MyBall extends Ellipse2D.Double {
int xspeed = 1;
int yspeed = 1;
Color color;
private final int maxWidth;
private final int maxHeight;
public MyBall(double x, double y, double w, double h, Color c, int maxWidth, int maxHeight) {
super(x, y, w, h);
color = c;
this.width = w;//set width and height of Rectangle2D
this.height = h;
//set max width and height ball can move
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
void moveBALL() {
x = x + xspeed;
y = y + yspeed;
if (x < 0) {
x = 0;
xspeed = -xspeed;
} else if (x > maxWidth - ((int) getWidth() / 2)) {// i dont like hard coding values its not good oractice and resuaibilty is diminshed
x = maxWidth - ((int) getWidth() / 2);
xspeed = -xspeed;
}
if (y < 0) {
y = 0;
yspeed = -yspeed;
} else if (y > maxHeight - ((int) getHeight() / 2)) {
y = maxHeight - ((int) getHeight() / 2);
yspeed = -yspeed;
}
}
}
First of all the paint component is called every time swing needs to redraw the component.
And you are adding a new instance of mouse listener to the panel every time the paint is called.
Just move the line
p.addMouseListener(new MouseListener() {...}
out of the paint component, preferably after the initialization of the panel.
default template is
JPanel p = new JPanel(){
#Override
public void paintComponent(Graphics g) {
}
};
p.addMouseListener(new MouseListener() or new MouseAdapter()
//Your overridden methods
});
Hope this helps.
I want to draw a circle which has the follow properties:
Center is the point where the user first clicks the mouse on the window
Radius should be the length of the distance between when the mouse is first clicked and when it's released (i.e. mouse dragging).
Here's what I have so far but it's not doing what I need it to do:
package assignment;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
public class DrawCircle extends JFrame implements MouseListener
{
private int centerX;
private int centerY;
private int endPtX;
private int endPtY;
private double radius;
private double w;
private double h;
private CirclePanel circPanel;
/** constructor **/
public DrawCircle()
{
this.setTitle("Click to Draw Circle");
this.setSize(500, 500);
this.setPreferredSize(new Dimension(500, 500));
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
this.setResizable(false);
this.addMouseListener(this);
this.circPanel = new CirclePanel();
this.circPanel.setPreferredSize(new Dimension(500, 500));
this.add(this.circPanel);
pack();
}
public void mousePressed(MouseEvent e)
{
centerX = e.getX();
centerY = e.getY();
circPanel.set(centerX, centerY, radius, radius);
repaint();
pack();
}
public void mouseReleased(MouseEvent e)
{
endPtX = e.getX();
endPtY = e.getY();
radius = Math.sqrt(Math.pow(endPtX - centerX, 2) + Math.pow(endPtY - centerY, 2));
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e){}
//circle panel
private class CirclePanel extends JComponent
{
private int x;
private int y;
private int w;
private int h;
public void set(int x, int y, double width, double height)
{
this.x = x;
this.y = y;
w = (int) width;
h = (int) height;
}
public void paintComponent(Graphics g)
{
g.drawOval(x, y, w, h);
}
}
//main method
public static void main (String [] args)
{
new DrawCircle();
}
}
It looks like you are doing the
circPanel.set(centerX, centerY, radius, radius);
repaint();
pack();
in the wrong place you shouldn't draw the circle until the user lets go of the mouse because that's when the radius is set and before that the radius is 0 so there is nothing drawn. Try moving that to the mouseReleased method.