I am trying to write a Java application which draws multiple balls on screen which bounce off of the edges of the frame. I can successfully draw one ball. However when I add
package BouncingBallApp.src.copy;
import java.awt.*;
public class Ball {
private Point location;
private int radius;
private Color color;
private int dx, dy;
//private Color[] ballArr;
public Ball(Point l, int r, Color c){
location = l;
radius = r;
color = c;
}
public Ball(Point l, int r){
location = l;
radius = r;
color = Color.RED;
}
public Point getLocation() {
return location;
}
public void setLocation(Point location) {
this.location = location;
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public void setMotion(int dx, int dy){
this.dx = dx;
this.dy = dy;
}
public void move(){
location.translate(dx, dy);
}
public void moveTo(int x, int y){
location.move(x, y);
}
public void paint (Graphics g) {
g.setColor (color);
g.fillOval (location.x-radius, location.y-radius, 2*radius, 2*radius);
}
public void reclectHoriz() {
dy = -dy;
}
public void reclectVert() {
dx = -dx;
}
}
package BouncingBallApp.src.copy;
public class MyApp {
public static void main(String[] args) {
MyFrame frm = new MyFrame(10);
frm.setVisible(true);
for (int i=0; i<1000; i++){
frm.stepTheBall();
}
}
}
package BouncingBallApp.src.copy;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;
import javax.swing.JFrame;
public class MyFrame extends JFrame {
public final int FRAMEWIDTH = 600;
public final int FRAMEHEIGHT = 400;
private Ball[] ballArr;
private Random random =new Random ();
private Color[] colors={Color.RED,Color.blue,Color.yellow};
private int ballCnt;
public MyFrame(int ballCnt){
super();
setSize(FRAMEWIDTH, FRAMEHEIGHT);
setTitle("My Bouncing Ball Application");
ballArr = new Ball[ballCnt];
this.ballCnt = ballCnt;
int c;
for (int i=0; i < ballCnt; i++){
int bcn =random.nextInt(colors.length);
Color ballcolor=colors[bcn];
ballArr[i] = new Ball(new Point(50,50),c=(int) (Math.random()*10+3)%8,ballcolor);
int ddx = (int) (Math.random()*10+2)%8;
int ddy = (int) (Math.random()*10+2)%8;
ballArr[i].setMotion(ddx, ddy);
//c++;
}
}
public void paint(Graphics g){
super.paint(g);
for (int i=0; i < ballCnt; i++){
ballArr[i].paint(g);
}
}
public void stepTheBall(){
for (int i=0; i < ballCnt; i++){
ballArr[i].move();
Point loc = ballArr[i].getLocation();
if (loc.x < ballArr[i].getRadius() ||
loc.x > FRAMEWIDTH-ballArr[i].getRadius()){
ballArr[i].reclectVert();
}
if (loc.y < ballArr[i].getRadius() ||
loc.y > FRAMEHEIGHT-ballArr[i].getRadius()){
ballArr[i].reclectHoriz();
}
}
repaint();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
You need to use two completely distinct classes here -- one for BallContainer which extends JPanel and is the component that draws the Balls, and another for Ball which does not extend anything but rather holds the coordinates and Color of a Ball. BallContainer should hodl a List<Ball>
that it iterates through when it moves them and when it paints them.
null
layout manager, otherwise it will take over and layout your balls as it sees fit.Ball
within the context of the EDT..
public class AnimatedBalls {
public static void main(String[] args) {
new AnimatedBalls();
}
public AnimatedBalls() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new Balls());
frame.setSize(400, 400);
frame.setVisible(true);
}
});
}
public class Balls extends JPanel {
public Balls() {
setLayout(null);
// Randomize the speed and direction...
add(new Ball("red", 10 - (int) Math.round((Math.random() * 20)), 10 - (int) Math.round((Math.random() * 20))));
add(new Ball("blue", 10 - (int) Math.round((Math.random() * 20)), 10 - (int) Math.round((Math.random() * 20))));
}
}
public class Ball extends JPanel implements Runnable {
Color color;
int diameter;
long delay;
private int vx;
private int vy;
public Ball(String ballcolor, int xvelocity, int yvelocity) {
if (ballcolor == "red") {
color = Color.red;
} else if (ballcolor == "blue") {
color = Color.blue;
} else if (ballcolor == "black") {
color = Color.black;
} else if (ballcolor == "cyan") {
color = Color.cyan;
} else if (ballcolor == "darkGray") {
color = Color.darkGray;
} else if (ballcolor == "gray") {
color = Color.gray;
} else if (ballcolor == "green") {
color = Color.green;
} else if (ballcolor == "yellow") {
color = Color.yellow;
} else if (ballcolor == "lightGray") {
color = Color.lightGray;
} else if (ballcolor == "magenta") {
color = Color.magenta;
} else if (ballcolor == "orange") {
color = Color.orange;
} else if (ballcolor == "pink") {
color = Color.pink;
} else if (ballcolor == "white") {
color = Color.white;
}
diameter = 30;
delay = 100;
vx = xvelocity;
vy = yvelocity;
new Thread(this).start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
int x = getX();
int y = getY();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(color);
g.fillOval(0, 0, 30, 30); //adds color to circle
g.setColor(Color.black);
g2.drawOval(0, 0, 30, 30); //draws circle
}
@Override
public Dimension getPreferredSize() {
return new Dimension(30, 30);
}
public void run() {
try {
// Randamize the location...
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
int x = (int) (Math.round(Math.random() * getParent().getWidth()));
int y = (int) (Math.round(Math.random() * getParent().getHeight()));
setLocation(x, y);
}
});
} catch (InterruptedException exp) {
exp.printStackTrace();
} catch (InvocationTargetException exp) {
exp.printStackTrace();
}
while (isVisible()) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.out.println("interrupted");
}
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
move();
repaint();
}
});
} catch (InterruptedException exp) {
exp.printStackTrace();
} catch (InvocationTargetException exp) {
exp.printStackTrace();
}
}
}
public void move() {
int x = getX();
int y = getY();
if (x + vx < 0 || x + diameter + vx > getParent().getWidth()) {
vx *= -1;
}
if (y + vy < 0 || y + diameter + vy > getParent().getHeight()) {
vy *= -1;
}
x += vx;
y += vy;
// Update the size and location...
setSize(getPreferredSize());
setLocation(x, y);
}
}
}
The "major" problem with this approach, is each Ball
has it's own Thread
. This is going to eat into your systems resources real quick as you scale the number of balls up...
As started by Hovercraft, you're better off creating a container for the balls to live in, where the balls are not components but are "virtual" concepts of a ball, containing enough information to make it possible to bounce them off the walls...
public class SimpleBalls {
public static void main(String[] args) {
new SimpleBalls();
}
public SimpleBalls() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Spot");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
Balls balls = new Balls();
frame.add(balls);
frame.setSize(400, 400);
frame.setVisible(true);
new Thread(new BounceEngine(balls)).start();
}
});
}
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
public class Balls extends JPanel {
private List<Ball> ballsUp;
public Balls() {
ballsUp = new ArrayList<Ball>(25);
for (int index = 0; index < 10 + random(90); index++) {
ballsUp.add(new Ball(new Color(random(255), random(255), random(255))));
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Ball ball : ballsUp) {
ball.paint(g2d);
}
g2d.dispose();
}
public List<Ball> getBalls() {
return ballsUp;
}
}
public class BounceEngine implements Runnable {
private Balls parent;
public BounceEngine(Balls parent) {
this.parent = parent;
}
@Override
public void run() {
int width = getParent().getWidth();
int height = getParent().getHeight();
// Randomize the starting position...
for (Ball ball : getParent().getBalls()) {
int x = random(width);
int y = random(height);
Dimension size = ball.getSize();
if (x + size.width > width) {
x = width - size.width;
}
if (y + size.height > height) {
y = height - size.height;
}
ball.setLocation(new Point(x, y));
}
while (getParent().isVisible()) {
// Repaint the balls pen...
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
getParent().repaint();
}
});
// This is a little dangrous, as it's possible
// for a repaint to occur while we're updating...
for (Ball ball : getParent().getBalls()) {
move(ball);
}
// Some small delay...
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
}
}
public Balls getParent() {
return parent;
}
public void move(Ball ball) {
Point p = ball.getLocation();
Point speed = ball.getSpeed();
Dimension size = ball.getSize();
int vx = speed.x;
int vy = speed.y;
int x = p.x;
int y = p.y;
if (x + vx < 0 || x + size.width + vx > getParent().getWidth()) {
vx *= -1;
}
if (y + vy < 0 || y + size.height + vy > getParent().getHeight()) {
vy *= -1;
}
x += vx;
y += vy;
ball.setSpeed(new Point(vx, vy));
ball.setLocation(new Point(x, y));
}
}
public class Ball {
private Color color;
private Point location;
private Dimension size;
private Point speed;
public Ball(Color color) {
setColor(color);
speed = new Point(10 - random(20), 10 - random(20));
size = new Dimension(30, 30);
}
public Dimension getSize() {
return size;
}
public void setColor(Color color) {
this.color = color;
}
public void setLocation(Point location) {
this.location = location;
}
public Color getColor() {
return color;
}
public Point getLocation() {
return location;
}
public Point getSpeed() {
return speed;
}
public void setSpeed(Point speed) {
this.speed = speed;
}
protected void paint(Graphics2D g2d) {
Point p = getLocation();
if (p != null) {
g2d.setColor(getColor());
Dimension size = getSize();
g2d.fillOval(p.x, p.y, size.width, size.height);
}
}
}
}
Because this is driven by a single thread, it is much more scalable.
You can also check out the images are not loading which is a similar question ;)
What you need to do is augment your paintComponent
method.
Instead of just drawing one ball, you need to loop through them all, and draw each one.
Example:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Ball b: balls) {
g.setColor(color);
g.fillOval(x,y,30,30); //adds color to circle
g.setColor(Color.black);
g2.drawOval(x,y,30,30); //draws circle
}
}