Animate a Java Line drawing using a timer - java

I am trying to draw two circle on a panel with a line joining them, all after a button is pressed. So far (apart from tweaking locations of the line) this is ok. However, I would like to animate it using a timer. The first circle should appear, then gradually the line will be revealed, and finally the second circle.
I have looked at many examples of timers, but I can't seem to get it to work for me. I must be misunderstanding something.
here is the ball class (for each circle):
package twoBalls;
import java.awt.Color;
import java.awt.Point;
public class Ball {
private int x;
private int y;
private int r;
private Color color;
private Point location;
private Ball parent;
public Ball(int x, int y, int r) {
this.x = x;
this.y = y;
this.r = r;
Point p = new Point(x, y);
setLocation(p);
}
public void setParent(Ball b) {
parent = b;
}
public Ball getParent() {
return parent;
}
public void setx(int x) {
this.x = x;
}
public void sety(int y) {
this.y = y;
}
public int getx() {
return x;
}
public int gety() {
return y;
}
public int getr() {
return r;
}
public void setPreferedSize() {
}
public void setLocation(Point p) {
setx(p.x);
sety(p.y);
location = p;
}
public Point getLocation() {
return location;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
then the class that will store balls in an arrayList. And I think that this is where the actual drawing should take place, along with the timer.
I am trying to set the start and end point of the line to be the same, and increment the end point until it is where it should be, using the timer. I'm probably way of track, but that was the intention!
I have change this class, the if statements in the while loop can now be entered, as I am now comparing different point. But the line doesn't get drawn at all still.
package twoBalls;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.Timer;
public class BallsArray extends JPanel implements ActionListener {
private ArrayList<Ball> balls;
private Timer timer;
private final int DELAY = 25;
private int xDest;
private int yDest;
private Point dest;
private Point starts;
private int xStart;
private int yStart;
public BallsArray() {
balls = new ArrayList<Ball>();
timer = new Timer(DELAY, this);
yDest = 0;
xDest = 0;
dest = new Point(xDest, yDest);
starts = new Point(xStart, yStart);
}
public void setDestXY(int x, int y) {
xDest = x;
yDest = y;
dest = new Point(xDest, yDest);
setDest(dest);
}
public void setDest(Point p) {
dest = p;
}
public Point getDest() {
return dest;
}
public void setStartsXY(int x, int y) {
xStart = x;
yStart = y;
starts = new Point(xStart, yStart);
setStarts(starts);
}
public void setStarts(Point p) {
starts = p;
}
public Point getStarts() {
return starts;
}
public void addBall(Ball b) {
balls.add(b);
}
public void addBall(int x, int y, int r) {
balls.add(new Ball(x, y, r));
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
for (int i = 0; i < balls.size(); i++) {
if (i == 0) {
paintBall(balls.get(0), g2);
}
if (i != 0) {
int j = i - 1;
Ball bp = balls.get(j);
Ball bc = balls.get(i);
bc.setParent(bp);
paintLine(bc, g2);
paintBall(bc, g2);
}
}
}
public void paintBall(Ball b, Graphics2D g2d) {
Ellipse2D circ = new Ellipse2D.Float(b.getx(), b.gety(), b.getr(),
b.getr());
g2d.draw(circ);
}
public void paintLine(Ball b, Graphics2D g2d) {
timer.start();
if (b != null && b.getLocation() != null) {
Ball parent = b.getParent();
if (parent != null) {
g2d.setColor(Color.GRAY);
if (parent.getLocation() != null && b.getLocation() != null) {
setDest(parent.getLocation());
setStarts(parent.getLocation());
g2d.draw(new Line2D.Float(starts, dest));
}
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
// Not sure what I need to do here
// increment second location somehow
// Point s = getStarts();
Point p = getDest();
Point t = this.getLocation();
while (p != t) {
if (p.x != t.x && p.y != t.y) {
System.out.println("hello");
int x = dest.x;
int y = dest.y;
x++;
y++;
setDestXY(x, y);
p = getDest();
repaint();
} else if (p.x == t.x && p.y != t.y) {
System.out.println("part 2");
int y = dest.y;
y++;
setDestXY(dest.x, y);
p = getDest();
repaint();
} else if (p.x != t.x && p.y == t.y) {
System.out.println("part 3");
int x = dest.x;
x++;
setDestXY(x, dest.y);
p = getDest();
repaint();
}
repaint();
}
}
}
I have had a lot of help online getting this far, I worry I am just beyond my depth now!. I am unsure about the EventQueue/run part below. Here is the class to set it all up:
package twoBalls;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Display implements ActionListener {
private JFrame frame;
private JButton button;
private BallsArray b;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Display ex = new Display();
}
});
}
public Display() {
b = new BallsArray();
frame = new JFrame();
frame.setSize(800, 500);
frame.setTitle("Show balls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
button = new JButton("New Ball");
frame.add(button, BorderLayout.SOUTH);
frame.setVisible(true);
button.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
Ball ball1 = new Ball(100, 100, 50);
b.addBall(ball1);
b.addBall(200, 200, 50);
frame.add(b, BorderLayout.CENTER);
frame.revalidate();
frame.repaint();
}
}
At the moment it draws the two circles, but not the line at all.

When you make an animation, it helps to use the model / view / controller pattern.
Here's the GUI I created from your code.
I simplified your Ball class. This is all you need to define a ball.
package twoBalls;
import java.awt.Color;
import java.awt.Point;
public class Ball {
private final int radius;
private final Color color;
private final Point center;
public Ball(int x, int y, int radius, Color color) {
this(new Point(x, y), radius, color);
}
public Ball(Point center, int radius, Color color) {
this.center = center;
this.radius = radius;
this.color = color;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
I created the GUIModel class to hold all of the information your GUI needs. This separates the model from the view.
package twoBalls;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
public class GUIModel {
private double direction;
private double distance;
private List<Ball> balls;
private Point lineStartPoint;
private Point lineEndPoint;
public GUIModel() {
this.balls = new ArrayList<>();
}
public void addBall(Ball ball) {
this.balls.add(ball);
}
public List<Ball> getBalls() {
return balls;
}
public void calculatePoints() {
this.lineStartPoint = balls.get(0).getCenter();
this.lineEndPoint = balls.get(1).getCenter();
this.distance = Point.distance(lineStartPoint.x, lineStartPoint.y,
lineEndPoint.x, lineEndPoint.y);
this.direction = Math.atan2(lineEndPoint.y - lineStartPoint.y,
lineEndPoint.x - lineStartPoint.x);
}
public Point getCurrentPoint(int pos, int total) {
double increment = distance / total;
double length = increment * pos;
double x = lineStartPoint.x + Math.cos(direction) * length;
double y = lineStartPoint.y - Math.sin(direction) * length;
x = Math.round(x);
y = Math.round(y);
return new Point((int) x, (int) y);
}
public Point getLineStartPoint() {
return lineStartPoint;
}
}
This class holds the two Ball instances, and calculates the length and direction of the line, divided into total increments.
Now that we've defined the model classes, let's look at the view classes. The first is your Display class.
package twoBalls;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Display implements Runnable {
private GUIModel guiModel;
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Display());
}
public Display() {
this.guiModel = new GUIModel();
Ball ball1 = new Ball(150, 200, 50, Color.BLUE);
Ball ball2 = new Ball(450, 200, 50, Color.GREEN);
guiModel.addBall(ball1);
guiModel.addBall(ball2);
guiModel.calculatePoints();
}
#Override
public void run() {
frame = new JFrame();
frame.setTitle("Show Balls Animation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
DrawingPanel drawingPanel = new DrawingPanel(guiModel);
panel.add(drawingPanel, BorderLayout.CENTER);
panel.add(createButtonPanel(drawingPanel), BorderLayout.SOUTH);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
private JPanel createButtonPanel(DrawingPanel drawingPanel) {
JPanel panel = new JPanel();
JButton startButton = new JButton("Start Animation");
startButton.addActionListener(new StartAnimation(drawingPanel));
panel.add(startButton);
return panel;
}
public class StartAnimation implements ActionListener {
private DrawingPanel drawingPanel;
public StartAnimation(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
}
#Override
public void actionPerformed(ActionEvent event) {
LineRunnable runnable = new LineRunnable(drawingPanel);
new Thread(runnable).start();
}
}
}
The constructor of the Display class sets up the GUI model.
The run method of the Display class constructs the GUI, and starts the animation.
See how I've separated the model and view.
The StartAnimation class is your controller. It starts the animation when you left click on the JButton. I'll discuss the LineRunnable class later.
Next, let's take a look at the DrawingPanel class.
package twoBalls;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import javax.swing.JPanel;
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -3709678584255542338L;
private boolean drawLine;
private int pos;
private int total;
private GUIModel guiModel;
public DrawingPanel(GUIModel guiModel) {
this.guiModel = guiModel;
this.drawLine = false;
this.setPreferredSize(new Dimension(600, 400));
}
public boolean isDrawLine() {
return drawLine;
}
public void setDrawLine(boolean drawLine) {
this.drawLine = drawLine;
}
public void setPos(int pos) {
this.pos = pos;
repaint();
}
public void setTotal(int total) {
this.total = total;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Ball ball : guiModel.getBalls()) {
g2d.setColor(ball.getColor());
Point center = ball.getCenter();
int radius = ball.getRadius();
g2d.fillOval(center.x - radius, center.y - radius, radius + radius,
radius + radius);
}
if (isDrawLine()) {
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(5.0F));
Point a = guiModel.getLineStartPoint();
Point b = guiModel.getCurrentPoint(pos, total);
g2d.drawLine(a.x, a.y, b.x, b.y);
}
}
}
The only thing this view class does is draw the balls and the line. The responsibility for calculating the length of the line belongs in the model.
I set the preferred size here, and use the pack method in the Display class to get the size of the JFrame. You usually want to know the dimensions of the drawing area, rather than the entire window.
Finally, let's look at the LineRunnable class. This is the class that controls the animation.
package twoBalls;
import java.awt.EventQueue;
public class LineRunnable implements Runnable {
private int total;
private DrawingPanel drawingPanel;
public LineRunnable(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
this.total = 240;
}
#Override
public void run() {
setDrawLine();
for (int pos = 0; pos <= total; pos++) {
setPos(pos);
sleep(50L);
}
}
private void setDrawLine() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.setDrawLine(true);
drawingPanel.setTotal(total);
}
});
}
private void setPos(final int pos) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.setPos(pos);
}
});
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
}
}
}
In the run method, we divide the line into 240 segments, and draw a segment every 50 milliseconds. It takes the GUI 12 seconds to draw the line. You can play with these numbers if you wish.
The for loop is a classic animation loop. First you update the model, which I'm doing through the drawing panel. Then you sleep.
This animation loop is running on a different thread from the GUI thread. This keeps the GUI responsive. Since the loop is running on a different thread, we have to use the invokeLater method to draw on the Event Dispatch thread.
I hope this was helpful to you. Divide and conquer. Don't let a class do more than one thing.

Related

Creating endless number of objects in JPanel and draw them through PaintComponent in Java

I have one dilemma , how to realize application. I have JPanel with width 288 and height 512, then I created two objects ( images ) and drew them through paintComponent using coordinates
drawImage (Image1,288,128,this) ;
drawImage (Image2, 288, 384, this);
. They are decrementing simultaneously in the X axis and when it reaches x = 144 , new (same) images should be drawn at the coordinates β€˜( x = 288 , y = (int)Math.random()* 512 )’ and begin decrement as well as first ones should still decrements. And this process should be endless. Every new objects reaching x = 144 should build new ones . I tried to create ArrayList with adding coordinates in it
ArrayList arrayX = new ArrayList();
arrayX.add(288)
arrayY.add((int) Math.random()* 512 )
and then extract values through
array.get()
But that was unsuccessfully.
I saw video where man did it using JavaScript through the array
var position = []
position = ({
X : 288
Y : 256
})
And then implemented through the loop like this
function draw() {
for (int i = 0; i < position.length; i++ ){
cvs.drawImage(Image1,position[i].x , position[i].y)
cvs.drawImage(Image2,position[i].x , position[i].y + 50)
position [i] .x - -;
if(position[i].x == 128)
position.push({
X : 288
Y : Math.floor(Math.random()*512 })
})
}
}
I don’t know how to do this in Java.
May be I should use array too to keep variables with coordinates , or arraylist but in different way. Help me please .
Thanks in advance
Conceptually the idea is simple enough, the problem is, Swing is signal thread and NOT thread safe.
See Concurrency in Swing for more details.
This means you can run a long running or blocking operation (like a never ending loop) inside the Event Dispatching Thread, but also, you shouldn't update the UI (or properties the UI depends on) from outside the context of the EDT.
While there are a number of possible solutions to the problem, the simplest is probably to use a Swing Timer, which provides a means to schedule a delay safely (that won't block the EDT) and which will trigger it's updates within the context of the EDT, allowing you to update the UI from within it.
See How to Use Swing Timers for more details.
Now, because you're in a OO language, you should leverage the power it provides, to me, this means encapsulation.
You have a image, you want drawn at a specific location, but whose location change be changed based on some rules, this just screams Plain Old Java Old (POJO)
Normally, I'd start with a interface to describe the basic properties and operations, but for brevity, I've jumped straight for a class...
public class Drawable {
private int x, y;
private Color color;
public Drawable(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public void update() {
x--;
if (x <= 144) {
reset();
}
}
protected void reset() {
x = 288;
y = (int) (Math.random() * (512 - 20));
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
But wait, you say, it's using Color instead of image!? Yes, I didn't have any small images at hand, besides, I need to leave you something to do ;)
Now, the animation is a sequence of updating and painting repeatedly until a desired state is reached.
In this case, you don't care about the end state so much, so you can just keep it running.
The "update" cycle is handled by a Swing Timer, which loops over a List of Drawable objects, calls their update methods and then schedules a repaint, which triggers the JPanels paintComponent where by the Drawable objects are painted, simple 😝...
public class TestPane extends JPanel {
private List<Drawable> drawables;
public TestPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(288, 128, Color.RED));
drawables.add(new Drawable(288, 384, Color.RED));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Drawable drawable : drawables) {
drawable.update();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
Putting it altogether - runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Drawable {
private int x, y;
private Color color;
public Drawable(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public void update() {
x--;
if (x <= 144) {
reset();
}
}
protected void reset() {
x = 288;
y = (int) (Math.random() * (512 - 20));
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
public class TestPane extends JPanel {
private List<Drawable> drawables;
public TestPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(288, 128, Color.RED));
drawables.add(new Drawable(288, 384, Color.RED));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Drawable drawable : drawables) {
drawable.update();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
}
"Is there a simpler solution"? Yes, of course, I always go to the hardest possible way to solve a problem first πŸ€ͺ. First, good animation is hard. Seriously. I've been playing around with this sought of thing more nearly 20 years, making a good animation engine which is flexible to meet all the possible needs it might be put to is near impossible mission, especially in a framework which isn't really designed for it.
If you don't belief me, you could have a look at
How do I change Swing Timer Delay inside actionPerformed()
How can I fade out or fade in by command JPanel, its components and its color
which are just a couple of examples how complicated animation can be
Sorry, you'd be amazed how often I get asked "can it be simpler" when it comes to animation ;)
"Every new objects reaching x = 144 should build new ones
So, apparently I may be confused about this particular point. If this means "adding new objects after reaching 144" then this raises some new issues. The primary issue is one over GC overhead, which cause slow downs in the animation. Sure, we're only dealing with about 4-6 objects, but it's one of those things which can come back to byte you if you're not careful.
So I took the above example and made some modifications to the update cycle. This adds a reusePool where old objects are placed and can be re-used, reducing the GC overhead of repeatedly creating and destroying short lived objects.
The decaying List simply ensures that once an object passes the swanPoint, it won't be consider for re-spawning new objects. Sure you could put a flag on the POJO itself, but I don't think this is part of the POJOs responsibility
public TestPane() {
drawables = new ArrayList<>(2);
reusePool = new ArrayList<>(2);
decaying = new ArrayList<>(2);
timer = new Timer(5, new ActionListener() {
private List<Drawable> spawned = new ArrayList<>(5);
#Override
public void actionPerformed(ActionEvent e) {
spawned.clear();
Iterator<Drawable> it = drawables.iterator();
int swapnPoint = getWidth() / 2;
while (it.hasNext()) {
Drawable drawable = it.next();
drawable.update();
if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
if (!decaying.contains(drawable)) {
decaying.add(drawable);
Drawable newDrawable = null;
if (reusePool.isEmpty()) {
newDrawable = new Drawable(
getWidth() - 20,
randomVerticalPosition(),
randomColor());
} else {
newDrawable = reusePool.remove(0);
newDrawable.reset(getWidth() - 20,
randomVerticalPosition(),
randomColor());
}
spawned.add(newDrawable);
}
} else if (drawable.getX() <= -20) {
System.out.println("Pop");
it.remove();
decaying.remove(drawable);
reusePool.add(drawable);
}
}
drawables.addAll(spawned);
repaint();
}
});
}
This will now allow objects to travel the whole width of the width, spawning new objects as they pass the half way point. Once they pass beyond the visual range of the view, they will be placed into the reuse List so they can be reused again when new objects are required.
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane testPane = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(testPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
testPane.start();
}
});
}
});
}
public class Drawable {
private int x, y;
private Color color;
public Drawable(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public void update() {
x--;
}
protected void reset(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.fillOval(0, 0, 20, 20);
copy.setColor(Color.BLACK);
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
public class TestPane extends JPanel {
private List<Drawable> drawables;
private List<Drawable> decaying;
private List<Drawable> reusePool;
private Color[] colors = new Color[]{Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};
private Random rnd = new Random();
private Timer timer;
public TestPane() {
drawables = new ArrayList<>(2);
reusePool = new ArrayList<>(2);
decaying = new ArrayList<>(2);
timer = new Timer(40, new ActionListener() {
private List<Drawable> spawned = new ArrayList<>(5);
#Override
public void actionPerformed(ActionEvent e) {
spawned.clear();
Iterator<Drawable> it = drawables.iterator();
int swapnPoint = getWidth() / 2;
while (it.hasNext()) {
Drawable drawable = it.next();
drawable.update();
if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
if (!decaying.contains(drawable)) {
decaying.add(drawable);
Drawable newDrawable = null;
if (reusePool.isEmpty()) {
System.out.println("New");
newDrawable = new Drawable(
getWidth() - 20,
randomVerticalPosition(),
randomColor());
} else {
System.out.println("Reuse");
newDrawable = reusePool.remove(0);
newDrawable.reset(getWidth() - 20,
randomVerticalPosition(),
randomColor());
}
spawned.add(newDrawable);
}
} else if (drawable.getX() <= -20) {
System.out.println("Pop");
it.remove();
decaying.remove(drawable);
reusePool.add(drawable);
}
}
drawables.addAll(spawned);
repaint();
}
});
}
public void start() {
drawables.add(new Drawable(getWidth(), 128, randomColor()));
drawables.add(new Drawable(getWidth(), 384, randomColor()));
timer.start();
}
protected int randomVerticalPosition() {
return rnd.nextInt(getHeight() - 20);
}
protected Color randomColor() {
return colors[rnd.nextInt(colors.length - 1)];
}
#Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
}
My answer is completely based on MadProgrammer's answer (A comprehensive tutorial actually).
From what I read in the post : "Every new objects reaching x = 144 should build new ones", I think the desired implementation is slightly different:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageAnimator {
public ImageAnimator() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new AnimationPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static class Drawable {
private int x;
private final int y;
private static final Image image = image();
//construct with a random y value
public Drawable(int x) {
this(x, -1);
}
public Drawable(int x, int y) {
this.x = x;
this.y = y < 0 ? (int) (Math.random() * (512 - 20)) : y;
}
public int getX() { return x; }
public int getY() { return y; }
public void update() { x--; }
public Image getImage(){ return image; }
public static Image image() {
URL url = null;
try {
//5.SEP.2021 replaced dead link
//url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
url = new URL("https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/32x32/Circle_Green.png");
return ImageIO.read(url);
} catch ( IOException ex) {
ex.printStackTrace();
return null;
}
}
}
public class AnimationPane extends JPanel {
private final List<Drawable> drawables;
private static final int W = 288, H = 512, CYCLE_TIME = 5;
public AnimationPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(W, H/4));
drawables.add(new Drawable(W, 3*H/4));
Timer timer = new Timer(CYCLE_TIME, e -> animate());
timer.start();
}
private void animate() {
for (Drawable drawable : new ArrayList<>(drawables)) {
drawable.update();
if(drawable.getX() == W/2) {
drawables.add(new Drawable(W)); //random Y
}
if(drawable.getX() <= 0) {
drawables.remove(drawable);
}
}
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables ) {
g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new ImageAnimator());
}
}
The following solution is based on my previous answer.
I add it in response to MadProgrammer's comment: "A better solution is to pool the objects for re-use".
DrawAblesProducer produces drawable objects on-demand. It also stores surplus object, to prevent producing too many such objects.
I post it as a separate answer because the additional functionality comes with somewhat higher complexity:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageAnimator {
private static final int W = 288, H = 512;
public ImageAnimator() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new AnimationPane(new DrawAblesProducer()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public class AnimationPane extends JPanel {
private final List<Drawable> drawables;
private static final int CYCLE_TIME = 5;
private final DrawAblesProducer producer;
public AnimationPane(DrawAblesProducer producer) {
this.producer = producer;
drawables = new ArrayList<>(2);
drawables.add(producer.issue(W, H/4));
drawables.add(producer.issue(W, 3*H/4));
Timer timer = new Timer(CYCLE_TIME, e -> animate());
timer.start();
}
private void animate() {
for (Drawable drawable : new ArrayList<>(drawables)) {
drawable.update();
if(drawable.getX() == W/2) {
drawables.add(producer.issue(W)); //random Y
}else if(drawable.getX() <= 0) {
drawables.remove(drawable);
producer.retrn(drawable);
}
}
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables ) {
g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
}
}
}
//produces `drawable` objects on-demand. stores surplus object, to prevent producing
//too many such objects
public class DrawAblesProducer {
private final Queue<Drawable> warehouse = new LinkedList<>();
public Drawable issue(int x){
return issue(x, -1);
}
public Drawable issue(int x, int y){
Drawable drawable = warehouse.poll();
if(drawable != null ) {
drawable.setX(x); drawable.setY(y);
return drawable;
}
return new Drawable(x, y);
}
public void retrn(Drawable drawable){
warehouse.add(drawable);
}
}
public static class Drawable {
//made static so image is reused for all instances
private static final Image image = image();
private int x, y;
//construct with a random y value
public Drawable(int x) {
this(x, -1);
}
public Drawable(int x, int y) {
setX(x);
setY(y);
}
public int getX() { return x; }
public void setX(int x) { this.x = x;}
public int getY() { return y; }
public void setY(int y) {
this.y = y < 0 ? randomY() : y ;
}
private int randomY() {
int iHeight = image.getHeight(null);
return iHeight + (int) (Math.random() * (H - iHeight));
}
public void update() { x--; }
public Image getImage(){ return image; }
public static Image image() {
URL url = null;
try {
//5.SEP.2021 replaced dead link
//url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
url = new URL("https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/32x32/Circle_Green.png");
return ImageIO.read(url);
} catch ( IOException ex) { ex.printStackTrace(); }
return null;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new ImageAnimator());
}
}

Swing threading vs timers in my program Java

I'm looking for some guidance in finishing my program for my project. It is a simple simulation where multiple graphical objects are drawn at runtime and using a button to add another ball in this case to the jpanel, I had to rewrite my whole code to take in multiple objects at once just 2 days ago and last night I did some research when I got an exception...changed some code then got another exception in thread AWT EventQueue 0, after doing some more research many people suggested that dynamically drawing objects on a jpanel at runtime with a thread is a pain in the.... and I should just use a timer instead so this is where I am at right now. The Grid_Bag_Constraints can be ignored for the time being as its just to lay it out at the moment.
My question is how could i make it so a new ball is added at runtime on the press of a button...sorry forgot to mention
Ps yeh i tried playing round with validating the jpanels already :/
All guidance is appreciated :)
Sim
=============
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class Sim extends JPanel
private static final int UPDATE_RATE = 60; // Number of refresh per second
public static int X = 640;
public static int Y = 480;
public static int numberOfBall = 3;
public static boolean isRunning = true;
public Sim() {
// have some code here all commented out
}
public static void main(String[] args) {
// Run GUI in the Event Dispatcher Thread (EDT) instead of main thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Set up main window (using Swing's Frame)
JFrame frame = new JFrame("Simulation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.gridwidth = 3;
c.gridheight = 3;
balls balls = new balls();
frame.add(balls);
gui GUI = new gui();
frame.add(GUI);
frame.setPreferredSize(new Dimension(1280,720));
frame.setResizable(false);
//frame.setContentPane(new Sim());
frame.pack();
frame.setVisible(true);
new Thread(new bounceEngine(balls)).start();
}
});
}
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
invalidate();
repaint();
}
gui
---------
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class gui extends JPanel {
boolean shouldFill;
public gui(){
setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
if(shouldFill){
c.fill = GridBagConstraints.HORIZONTAL;
}
AbstractButton addBallButton = new JButton("Add Ball");
addBallButton.setMultiClickThreshhold(1500);
addBallButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
balls.listBalls.add(new Ball((new Color(Sim.random(255),Sim.random(255), Sim.random(255))),20));
for(Ball ball : balls.listBalls){
System.out.print("I work ");
}
invalidate();
//repaint();
}
});
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.weighty = 0.5;
add(addBallButton);
bounceEngine
------------------
import java.awt.*;
import javax.swing.*;
public class bounceEngine implements Runnable {
private balls parent;
private int UPDATE_RATE = 144;
public bounceEngine(balls parent) {
this.parent = parent;
}
public void run() {
int width = Sim.X;
int height = Sim.Y;
// Randomize the starting position...
for (Ball ball : getParent().getBalls()) {
int x = Sim.random(width);
int y = Sim.random(height);
int size = ball.getRadius();
if (x + size > width) {
x = width - size;
}
if (y + size > height) {
y = height - size;
}
ball.setLocation(new Point(x, y));
}
while (getParent().isVisible()) {
// Repaint the balls pen...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
getParent().repaint();
}
});
//I know this is dangerous to update it as the repaint method is called
for (Ball ball : getParent().getBalls()) {
move(ball);
}
try{
Thread.sleep(1000/UPDATE_RATE);
}catch (InterruptedException e){
}
}
}
public balls getParent() {
return parent;
}
public void move(Ball ball) {
Point p = ball.getLocation();
Point speed = ball.getSpeed();
int size = ball.getRadius();
int vx = speed.x;
int vy = speed.y;
int x = p.x;
int y = p.y;
if (x + vx < 0 || x + (size*2) + vx > getParent().getWidth()) {
vx *= -1;
}
if (y + vy < 0 || y + (size*2) + vy > getParent().getHeight()) {
vy *= -1;
}
x += vx;
y += vy;
ball.setSpeed(new Point(vx, vy));
ball.setLocation(new Point(x, y));
}
balls(for all the balls)
---------------------------------
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class balls extends JPanel {
public static ArrayList<Ball> listBalls;
public balls() {
setPreferredSize(new Dimension(640,480));
setBackground(Color.white);
listBalls = new ArrayList<Ball>(10);
for(int i = 0; i < Sim.numberOfBall; i++){
listBalls.add(new Ball((new Color(Sim.random(255),Sim.random(255), Sim.random(255))),20));
}
}
public ArrayList<Ball> getBall(){
return listBalls;
}
#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 : listBalls) {
ball.paint(g2d);
}
g2d.dispose();
}
public ArrayList<Ball> getBalls(){
return listBalls;
}
Ball(for 1 ball object blank class with basic constructor)
----------------------------------------------------------------------
import java.awt.*;
public class Ball {
private Color color;
private Point location;
private int radius;
private Point speed;
public Ball(Color color, int radius) {
setColor(color);
speed = new Point(5 - Sim.random(20), 5 - Sim.random(20));
this.radius = radius;
}
public int getRadius() {
return radius;
}
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());
g2d.fillOval(p.x, p.y, radius*2, radius*2);
}
}
}

Repaint() and PaintComponent() not updating properly

I have a Jframe with two buttons: 'A' and 'B'. Clicking the button 'A' should display the capital letter A in the JPanel. The problem here is that on repeated mouse click the previous letters drawn are not show. I want to make sure that whatever is drawn stays in the drawing. The following is the code for my program.
Code for JFrame:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class DrawFrame extends JFrame{
private final int WIDTH = 500;
private final int HEIGHT = 300;
private GUIModel model;
private JButton number1;
private JButton number2;
private JPanel numberPanel;
private DrawPanel graphicsPanel;
public DrawFrame()
{
this.model = new GUIModel(" ");
createSelectionPanel();
createGraphicsPanel();
this.setSize(WIDTH, HEIGHT);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
private void createSelectionPanel()
{
numberPanel = new JPanel();
ButtonListener listener = new ButtonListener();
number1 = new JButton("A");
number1.addActionListener(listener);
number2 = new JButton("B");
number2.addActionListener(listener);
numberPanel.setLayout(new GridLayout(2,2));
numberPanel.add(number1);
numberPanel.add(number2);
this.add(numberPanel, BorderLayout.WEST);
}
private void createGraphicsPanel()
{
//instantiate drawing panel
graphicsPanel = new DrawPanel(WIDTH, HEIGHT, model);
//add drawing panel to right
add(graphicsPanel);
}
private class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
model.setDisplayString(event.getActionCommand());
}
}
//creates a drawing frame
public static void main(String[] args)
{
DrawFrame draw = new DrawFrame();
}
}
Code for JPanel:
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
public class DrawPanel extends JPanel{
private static final long serialVersionUID = 3443814601865936618L;
private Font font = new Font("Default", Font.BOLD, 30);
private static final Color DEFAULT_TEXT_COLOR = Color.BLACK;
private static final Color HOVER_TEXT_COLOR = Color.RED;
private Color color = DEFAULT_TEXT_COLOR;
private List<GUIModel> numberList = new ArrayList<GUIModel>();
private GUIModel model;
boolean mouseHover = false;
public DrawPanel(int width, int height, GUIModel model){
this.setPreferredSize(new Dimension(width, height));
this.model = model;
//set white background for drawing panel
setBackground(Color.WHITE);
//add mouse listeners
MouseHandler mouseHandler = new MouseHandler();
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
// void checkForHover(MouseEvent event) {
// FontMetrics metrics = getFontMetrics(font);
//
// Graphics g = getGraphics();
// Rectangle textBounds = metrics.getStringBounds("C", g).getBounds();
// g.dispose();
//
// int index = 0;
// while (index < numberList.size()) {
// Double x = numberList.get(index).getCoordinate().getX();
// Double y = numberList.get(index).getCoordinate().getY();
//
// textBounds.translate(x.intValue(), y.intValue());
//
// if (textBounds.contains(event.getPoint())) {
// color = HOVER_TEXT_COLOR;
// }
// else {
// color = DEFAULT_TEXT_COLOR;
// }
// index++;
// }
// }
this is my PaintComponent method where I'm going through the list of letters I've drawn at different coordinates and drawing them on the canvas
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setFont(font);
g.setColor(color);
int index = 0;
while (index < numberList.size()) {
Double x = numberList.get(index).getCoordinate().getX();
Double y = numberList.get(index).getCoordinate().getY();
String display = numberList.get(index).getDisplayString();
g.drawString(display, x.intValue(), y.intValue());
index++;
}
if (model.getCoordinate() != null) {
Point p = model.getCoordinate();
g.drawString(model.getDisplayString(), p.x, p.y);
numberList.add(model);
}
}
//class to handle all mouse events
private class MouseHandler extends MouseAdapter implements MouseMotionListener
{
#Override
public void mousePressed(MouseEvent event)
{
model.setCoordinate(event.getPoint());
}
#Override
public void mouseReleased(MouseEvent event)
{
DrawPanel.this.repaint();
}
// #Override
// public void mouseEntered(MouseEvent event) {
// checkForHover(event);
// }
//
// #Override
// public void mouseMoved(MouseEvent event) {
// checkForHover(event);
// }
}
}
Code for GUIModel:
import java.awt.Point;
public class GUIModel {
private String displayString;
private Point coordinate;
public GUIModel(String displayString) {
this.displayString = displayString;
}
public void setDisplayString(String displayString) {
this.displayString = displayString;
}
public String getDisplayString() {
return displayString;
}
public Point getCoordinate() {
return coordinate;
}
public void setCoordinate(int x, int y) {
this.coordinate = new Point(x, y);
}
public void setCoordinate(Point coordinate) {
this.coordinate = coordinate;
}
}
Any help or suggestions would be much appreciated. Thanks.
#MadProgrammer Your comment helped me fix my code. Like you said I needed to create a new GUIModel and add it to my numberList.
I modified my paintComponent method to reflect that and it now works. So, Thanks!
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setFont(font);
g.setColor(color);
int index = 0;
while (index < numberList.size()) {
Double x = numberList.get(index).getCoordinate().getX();
Double y = numberList.get(index).getCoordinate().getY();
String display = numberList.get(index).getDisplayString();
g.drawString(display, x.intValue(), y.intValue());
index++;
}
if (model.getCoordinate() != null) {
Point p = model.getCoordinate();
g.drawString(model.getDisplayString(), p.x, p.y);
GUIModel number = new GUIModel();
number.setCoordinate(p);
number.setDisplayString(model.getDisplayString());
numberList.add(number);
}
}

animated background java game

I was programming a game similar to asteroid, but I do not understand how to spawn the asteroids in the background.
now i spawn an asteroid in the main class but i want create a class for the asteroid ho i do it?
MAIN CLASS
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
if(flag_img)
{
background(g2d);
logo(g2d);
menu(g2d);
spaceship(g2d);
crediti(g2d);
}
}
background function(now)
private void background(Graphics2D g2d)
{
asteroidi_g_x+=r.nextInt(4);
asteroidi_g_y+=r.nextInt(1);
g2d.drawImage(asteroidi_g[0], asteroidi_g_x,asteroidi_g_y,this);
}
background function(what i want)
private void background(Graphics2D g2d)
{
asteroid asteroid = new asteroid[10];
}
and class asteroid
public class asteroid extends JPanel implements ActionListener
{
private BufferedImage images_asteroid;
private boolean flag_img;
private JPanel jp;
private int x,y;
public asteroide_grande(JPanel jp)
{
flag_img = true;
x = (jp.getWidth()/2);
y = (jp.getHeight()/2);
this.jp = jp;
try {
images_asterod = ImageIO.read(this.getClass().getResource("images/asteroid/a1.png"));
} catch(IOException e){flag = false;}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(flag_img)
{
g.drawImage(images_asteroid, 100, 100,this);
}
}
#Override
public void actionPerformed(ActionEvent e)
{
x=x-1;
y=y+1;
repaint();
}
method paintcomponent in class doesn't work
Don't have your Asteroid class extends JPanel. Instead have it as a class that model's asteroid data and has data manipulation methods. You'll also want to have a draw method that take a Graphic context. Something like
public class Asteroid {
Image asteroidImage;
JPanel panel;
int x, y;
public Asteroid(JPanel panel, Image image, int x, int y) {
this.panel = panel;
this.asteroidImage = image;
this.x = x;
this.y = y;
}
public void drawAsteroid(Graphics g) {
g.drawImage(asteroidImage, x, y, panel);
}
public void move() {
x += 5;
}
}
Now you have a model of an asteroid, you can create a List of Asteriod objects and iterate through them and use it's drawAsteroid method to paint them. Something like
public class GamePanel extends JPanel {
List<Asteroid> asteroids;
Image asteroidImage;
public GamePanel(){
asteroidImage = ...
asteroids = new ArrayList<>();
asteroids.add(new Asteroid(GamePanel.this, asteroidImage, 100, 100));
// add more asteriods
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Asteriod asteroid: asteriods) {
asteriod.drawAsteroid(g);
}
}
}
To animate them, you'll want to use a javax.swing.Timer. See more at How to Use Swing Timers. You'll want to manipulate the Asteriod data in the Timer. With the code provided above, you can just call it's move method, then call repaint(). Something like
public GamePanel(){
...
Timer timer = new Timer(30, new ActionListener(){
public void actionPerformed(ActionEvent e) {
Iterator it = asteroids.iterator();
while (it.hasNaext()) {
Asteroid asteriod = (Asteroid)it.next();
asteroid.move();
}
}
});
}
You can see a bunch more complete example of animating multiple objects here and here and here and here and here
Here's a full example. You'll see I included a Rectangle2D object in the Astreroid class. That's just if you want to check for collision detection. You should move the Rectangle2D x and/or y with every Asreroid movement of x and y. Then you can check if asteroid.rectangle.intersects(someOtherObject)
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class AsteroidBackground extends JPanel {
private static final int D_W = 400;
private static final int D_H = 600;
BufferedImage asteroidImage;
BufferedImage background;
List<Asteroid> asteroids;
Random random = new Random();
int countToAddAsteroid = 0;
int y;
public AsteroidBackground() {
try {
asteroidImage = ImageIO.read(getClass().getResource("/resources/small-asteroid.png"));
background = ImageIO.read(getClass().getResource("/resources/space.png"));
} catch (IOException ex) {
Logger.getLogger(AsteroidBackground.class.getName()).log(Level.SEVERE, null, ex);
}
asteroids = new ArrayList<>();
y = 0 - asteroidImage.getHeight();
Timer timer = new Timer(40, new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (countToAddAsteroid >= 25) {
int randX = random.nextInt(D_W);
asteroids.add(new Asteroid(AsteroidBackground.this, asteroidImage, randX, y));
countToAddAsteroid = 0;
}
countToAddAsteroid++;
Iterator it = asteroids.iterator();
while (it.hasNext()) {
Asteroid asteroid = (Asteroid)it.next();
if (asteroid.y >= D_H) {
it.remove();
} else {
asteroid.move();
}
}
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, this);
for (Asteroid asteroid : asteroids) {
asteroid.drawAsteroid(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public class Asteroid {
Rectangle2D rectangle;
Image asteroidImage;
JPanel panel;
int x, y;
public Asteroid(JPanel panel, Image image, int x, int y) {
this.panel = panel;
this.asteroidImage = image;
this.x = x;
this.y = y;
rectangle = new Rectangle2D.Double(
x, y, image.getWidth(panel), image.getHeight(panel));
}
public void drawAsteroid(Graphics g) {
g.drawImage(asteroidImage, x, y, panel);
}
public void move() {
y += 5;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new AsteroidBackground());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Help with adding a paddle to a JFrame

This is an excerise i have to complete for a uni course, its not a marked assignment and i could do with a bit of help. I can get the ball to appear on the screen and bounce of the sides, it doesnt matter at the moment if it falls through the bottom of the screen and i can get the paddle to appear on the screen at different times but i cant get them both to appear at the same time. Help please
Here are my classes
MainClass
package movingball;
public class Main
{
public static void main (String []args)
{
MovingBall world = new MovingBall("Moving Ball");
world.setVisible(true);
world.move();
}
}
BallClass
package movingball;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
public class Ball
{
private final int RADIUS = 10;
private Point pos;
private Color ballColour;
private int yChange = 2;
private int xChange = 1;
private int height, width;
private int change;
public Ball (int frameWidth, int frameHeight)
{
change = 3;
ballColour = Color.RED;
width = frameWidth;
height = frameHeight;
pos = new Point();
pos.x = (int)(Math.random() * (width - RADIUS)) + RADIUS;
pos.y = (int)(Math.random() * (height/2 - RADIUS)) + RADIUS;
}
//There are lots of ways you can updateBallState
//Note that the menu bar occupies some of the visible space
public void move()
{
if(pos.y < RADIUS)
{
yChange = - yChange;
}
if(pos.x < RADIUS)
{
xChange = -xChange;
}
if(pos.x > width - RADIUS)
{
xChange = -xChange;
}
if(pos.y < height - RADIUS)
{
pos.translate(xChange, yChange);
}
if(pos.x < width - RADIUS)
{
pos.translate(xChange, yChange);
}
}
public void updateBallState()
{
if (pos.y + change < height - 3*RADIUS)
{
pos.translate(0, change);
// change++; //accelerate
}
}
//This method can be called with a provided graphics context
//to draw the ball in its current state
public void draw(Graphics g)
{
g.setColor(ballColour);
g.fillOval(pos.x - RADIUS, pos.y - RADIUS, 2*RADIUS, 2*RADIUS);
}
public void bounce()
{
yChange = -yChange;
pos.translate(xChange, yChange);
}
public Point getPosition()
{
return pos;
}
}
BallGame
package movingball;
import java.awt.Graphics;
import java.awt.event.*;
public class BallGame extends MovingBall
{
private Paddle myPaddle = new Paddle(FRAME_WIDTH, FRAME_HEIGHT);
public BallGame(String title)
{
super(title);
addKeyListener(new KeyList());
}
public void paint(Graphics g)
{
super.paint(g);
myPaddle.paint(g);
if(isContact())
{
myBall.bounce();
}
else
{
myPaddle.paint(g);
}
}
public boolean isContact()
{
if (myPaddle.area().contains(myBall.getPosition()))
{
return true;
}
else
{
return false;
}
}
public class KeyList extends KeyAdapter
{
public void keyPressed(KeyEvent k)
{
if(k.getKeyCode() == KeyEvent.VK_LEFT)
{
myPaddle.moveLeft();
}
if(k.getKeyCode() == KeyEvent.VK_RIGHT)
{
myPaddle.moveRight();
}
}
}
}
MovingBall class
package movingball;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MovingBall extends JFrame
{
protected final int FRAME_WIDTH = 240;
protected final int FRAME_HEIGHT = 320;
protected Ball myBall = new Ball(FRAME_WIDTH, FRAME_HEIGHT);
public MovingBall (String title)
{
super(title);
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void paint(Graphics g)
{
super.paint(g);
myBall.draw(g);
}
public void move()
{
while(true)
{
myBall.move();
repaint();
try
{
Thread.sleep(50);
}
catch(InterruptedException e)
{
System.exit(0);
}
}
}
}
Paddle class
package movingball;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Paddle
{
private Color paddleColour = Color.GREEN;
private int x,y;
private int paddleWidth = 20;
private int paddleHeight = 10;
private int move = 5;
/** Creates a new instance of Paddle */
public Paddle(int frameWidth, int frameHeight)
{
x =(int)(Math.random()*(frameWidth - paddleWidth));
y = frameHeight - paddleHeight;
}
public void moveRight()
{
x = x + move;
}
public void moveLeft()
{
x = x - move;
}
public Rectangle area()
{
return new Rectangle(x,y, paddleWidth, paddleHeight);
}
public void paint(Graphics g)
{
g.setColor(paddleColour);
g.fillRect(x,y,paddleWidth, paddleHeight);
}
}
Here are a couple of pointers to get you started. I have a lot of things to suggest but I'll just say this to get you further than you are now. You want your JFrame to be double-buffered. That's the first step. To do this, create a new buffer strategy for your JFrame after making it visible:
world.setVisible(true);
world.createBufferStrategy(2);
As for why your Paddle isn't painting? You're going to kick yourself. Look at this line:
MovingBall world = new MovingBall("Moving Ball");
You're not actually creating a BallGame, which is where the logic for painting the paddle is contained! (BallGame.paint()) Try:
BallGame world = new BallGame("Moving Ball");

Categories

Resources