I am trying to draw a vine within a Jframe however the graphics are not appearing when I run the program. The idea is that a "Vine" is randomly generated and can randomly branch out into other "Vines". If anyone can help that would be much appreciated.
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Vine extends JPanel {
public void paint(Graphics g, int a, int b, int c)
{
super.paint(g);
g.setColor(Color.BLACK);
g.drawLine(10, 10, 100, 100);
grow(g, a, b, c);
}
public Boolean TF(){
Boolean A;
int B = ((int)Math.random()+1);
if (B==1){
A = true;
} else {
A = false;
}
return A;
}
public void grow(Graphics g, int a, int b, int c){
int x = a;
int y = b;
int age = c;
for (int i=0; i<= age; i++) {
if (TF()) {
if (TF()) {
grow(g, x, y, ((age-i)/2));
}
}
if (TF()){
if (TF()){
g.drawLine(x, y, (x+1), y);
x++;
} else {
g.drawLine(x, y, (x-1), y);
x++;
}
} else {
g.drawLine(x, y, x, (y+1));
y++;
}
}
}
public static void main(String[] args)
{
JFrame f = new JFrame("Vine");
f.setBounds(300, 300, 200, 120);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Vine panel = new Vine();
Container c = f.getContentPane();
panel.setBackground(Color.WHITE);
c.add(panel);
f.setResizable(true);
f.setVisible(true);
}
}
There are several issues with this code:
Nobody is calling the paint(Graphics g, int a, int b, int c) method. When you inherit from a Swing component like this, there are several methods that are invoked "automatically" (among them a paint(Graphics g) method). But in order to perform custom painting, you should usually override the paintComponent(Graphics g) method.
You are generating random numbers while you are painting. This will have some odd effects. The most obvious one: When you resize the frame (or something else happens that causes the frame to be repainted), a new random vine will appear. It will have nothing in common with the previous one, causing a flickering mess, in the best case.
In order to create reporoducible results that are still "random", I generally recommend to not use Math.random(). (In fact, I never use this, at all). The java.util.Random class is usually preferable. It allows creating reproducible random number sequences (which is helpful for debugging), and it offers convenient methods like Random#nextBoolean(), which has exactly the effect that you probably wanted to achieve with the (somewhat oddly implemented) TF() method. This leads to the next point...:
Use better variable- and method names. Naming variables like the c in your example, or methods TF(), will make the code unreadable. You may call them x and y if they refer to screen coordinates, but the c should probably be called age, and the TF() method ... heck, I don't know how this should be called, maybe something like shouldBranch()?
If you have to perform "extensive" computations (like the random branching, in your example), it is usually better to pull this computation out of the painting process. You can assemble the desired lines and paintings in various ways. For the given example, a simple Path2D should be sufficient.
So far, the technical things. Apart from that: The algorithm itself will not lead to "nice" results. The random branching for each pixel will cause the lines to clob together to a black, fuzzy spot.
In fact, it can be pretty hard to tweak this to create "nice" results. It is hard to exactly say how the "randomness" should influence the overall appearance. The branching should be random, the angles should be random, the branch lengths should be random. Additionally, it will always look a bit odd when all the lines are drawn with the same thickness. The thickness of the lines should probably decrease at each branch, and along the branches in general.
In some practical applications, this generation of random plant-like structures is done with Lindenmayer systems - this may be a starting point for further research.
However, here is a very simple example of how simple lines can be assembled, somewhat randomly, to create something that resembles a plant:
Of course, this looks like cr*p compared to what one could do, given enough time and incentive. But it consists only of a few lines of code that randomly assemble a few branching lines, so this is as good as it gets without considering all the possible improvements.
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class VinePainting
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
private void createAndShowGUI()
{
JFrame f = new JFrame("Vine");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
VinePanel panel = new VinePanel();
Container c = f.getContentPane();
panel.setBackground(Color.WHITE);
c.add(panel);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}
class VinePanel extends JPanel
{
private static final Random RANDOM = new Random(0);
private Path2D vine;
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
if (vine == null)
{
vine = createVine(getWidth()/2, getHeight());
}
g.setColor(Color.BLACK);
g.draw(vine);
}
private Path2D createVine(int x, int y)
{
Path2D path = new Path2D.Double();
double angleRad = Math.toRadians(-90);
grow(path, x, y, angleRad, 10.0, 0, 30);
return path;
}
private static void grow(Path2D path,
double x, double y, double angleRad,
double stepSize, int step, int steps)
{
if (step == steps)
{
return;
}
path.moveTo(x, y);
double dirX = Math.cos(angleRad);
double dirY = Math.sin(angleRad);
double distance = random(stepSize, stepSize + stepSize);
double newX = x + dirX * distance;
double newY = y + dirY * distance;
path.lineTo(newX, newY);
final double angleRadDeltaMin = -Math.PI * 0.2;
final double angleRadDeltaMax = Math.PI * 0.2;
double progress = (double)step / steps;
double branchProbability = 0.3;
double branchValue = RANDOM.nextDouble();
if (branchValue + 0.1 < branchProbability)
{
double angleRadDelta = random(angleRadDeltaMin, angleRadDeltaMax);
double newAngleRad = angleRad + angleRadDelta;
double newStepSize = (1.0 - progress * 0.1) * stepSize;
grow(path, newX, newY, newAngleRad, newStepSize, step+1, steps);
}
double angleRadDelta = random(angleRadDeltaMin, angleRadDeltaMax);
double newAngleRad = angleRad + angleRadDelta;
double newStepSize = (1.0 - progress * 0.1) * stepSize;
grow(path, newX, newY, newAngleRad, newStepSize, step+1, steps);
}
private static double random(double min, double max)
{
return min + RANDOM.nextDouble() * (max - min);
}
}
A side note: This is somewhat similiar to this question. But there, randomness did not play a role, so the recursion is done while painting the tree. (Therefore, it allows playing around with some sliders, to modify the parameters and observe the effects).
Here's a GUI I created using your code. I didn't understand how your grow method worked, so I pretty much left it alone.
Here's what I did.
Created a GrowVine method to hold the line segments to draw. I also created a LineSegment class to hold one line segment. I did this so that I could keep the model data separate from the view. By separating concerns, I could focus on one part of the problem at a time.
I started the Swing application with a call to the SwingUtilities invoke later method. This ensures that the Swing components are created and used on the Event Dispatch thread.
I created a DrawingPanel from a JPanel, and overrode the paintComponent method. Notice that my override code does nothing but draw. All of the calculations are done in the GrowVine class.
I greatly simplified your TF method and renamed it to coinFlip. coinFlip better indicates to future readers of the code (including yourself) that the boolean should be true half the time and false half the time.
I left your grow method alone. I removed the drawLine methods and had the grow method write line segments to the List.
Once you get your grow method working, you can run the GrowVine class in a separate thread to animate the drawing of the vine.
Here's the code. I hope this helps you.
package com.ggl.testing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Vine implements Runnable {
#Override
public void run() {
JFrame frame = new JFrame("Vine");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawingPanel panel = new DrawingPanel(400, 400);
frame.add(panel);
frame.pack();
frame.setVisible(true);
new GrowVine(panel, 400, 400).run();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Vine());
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -8460577623396871909L;
private List<LineSegment> lineSegments;
public DrawingPanel(int width, int height) {
this.setPreferredSize(new Dimension(width, height));
this.lineSegments = new ArrayList<>();
}
public void setLineSegments(List<LineSegment> lineSegments) {
this.lineSegments = lineSegments;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
for (int i = 0; i < lineSegments.size(); i++) {
LineSegment ls = lineSegments.get(i);
Point s = ls.getStartPoint();
Point e = ls.getEndPoint();
g.drawLine(s.x, s.y, e.x, e.y);
}
}
}
public class GrowVine implements Runnable {
private int width;
private int height;
private DrawingPanel drawingPanel;
private List<LineSegment> lineSegments;
public GrowVine(DrawingPanel drawingPanel, int width, int height) {
this.drawingPanel = drawingPanel;
this.lineSegments = new ArrayList<>();
lineSegments.add(new LineSegment(10, 10, width - 10, height - 10));
this.width = width;
this.height = height;
}
#Override
public void run() {
grow(width / 2, height / 2, 200);
drawingPanel.setLineSegments(lineSegments);
}
public void grow(int a, int b, int c) {
int x = a;
int y = b;
int age = c;
for (int i = 0; i <= age; i++) {
if (coinFlip()) {
if (coinFlip()) {
grow(x, y, ((age - i) / 2));
}
}
if (coinFlip()) {
if (coinFlip()) {
lineSegments.add(new LineSegment(x, y, (x + 1), y));
x++;
} else {
lineSegments.add(new LineSegment(x, y, (x - 1), y));
x++;
}
} else {
lineSegments.add(new LineSegment(x, y, x, (y + 1)));
y++;
}
}
}
private boolean coinFlip() {
return Math.random() < 0.50D;
}
}
public class LineSegment {
private final Point startPoint;
private final Point endPoint;
public LineSegment(int x1, int y1, int x2, int y2) {
this.startPoint = new Point(x1, y1);
this.endPoint = new Point(x2, y2);
}
public Point getStartPoint() {
return startPoint;
}
public Point getEndPoint() {
return endPoint;
}
}
}
There are lots of implementation issues here. You can override the paintComponent of the JPanel to paint in it. Here is a quick fix to demonstrate how this is done. I changed your implementation a lot to fix the issues so you can get an idea.
Note: This was a quick fix. So the code quality is low and some OOP concepts were ignored. Just go through this and understand how this work and implement your own code.
Explanation:
You have to call repaint method of JPanel class to make it repaint itself. It will paint all Lines in the LinkedList to the panel too(refer the implementation). After repainting, create a new random line and add it to the LinkedList. It will be painted next time.
Then we have to animate this. So I implemented runnable interface in the Vine class. run method will be called when we add this vine object(panel) to a Thread and start() the thread. We need to run it forever. So add a loop in the run method. Then repaint the panel every time run method is called. This animation is too fast, so add a Thread.sleep() to slow down the animation.
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.util.LinkedList;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Line {//Line class to store details of lines
int x1, y1, x2, y2;
public Line(int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
}
class Vine extends JPanel implements Runnable {//implements runnable to animate it
LinkedList<Line> lines = new LinkedList<>();//LinkedList to store lines
int x = 10;
int y = 10;
Line line;
public Boolean TF() {
Boolean A;
int B = (int) (Math.random() * 2 + 1);//logical error fixed
if (B == 1) {
A = true;
} else {
A = false;
}
return A;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
for (Line line : lines) {//paint all lines in the LinkedList
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
//Create and add a next line
if (TF()) {
if (TF()) {
line = new Line(x, y, (x + 1), y);
lines.add(line);
x++;
} else {
line = new Line(x, y, (x - 1), y);
lines.add(line);
x++;
}
} else {
line = new Line(x, y, x, (y + 1));
lines.add(line);
y++;
}
}
private static Vine panel;
public static void main(String[] args) {
JFrame f = new JFrame("Vine");
f.setSize(300, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new Vine();
Container c = f.getContentPane();
panel.setBackground(Color.WHITE);
c.add(panel);
f.setResizable(true);
f.setVisible(true);
panel.start();//start the animation(thread)
}
private void start() {
Thread thread = new Thread(this);
thread.start();
}
#Override
public void run() {
while (true) {
try {
Thread.sleep(100);//slow down the animation
panel.repaint();//then repaint the panel
} catch (InterruptedException ex) {
}
}
}
}
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
Step 1: Task
Okay so I'm trying to replicate this in a jframe.
https://codepen.io/allanpope/pen/LVWYYd
Problem:
I have a loading screen and over the course of 5 seconds I'm moving dots from a circle to an image. Just like the codepen. Except the problem is I'm not sure how to animate it correctly. JAVA
My Idea was to make an animate function like Move.to(Dot,Duration)
One problem I'm having is that when using decimals the dots wont be in the exact place, and some wont move at all. I'm just not sure how to make this animate function and how to end it. If anyone could help me that would be epic. My code is down below. And if someone could tell me how I could do a Bezier curve animation would also be cool
If anyone wantes to test my code and tell me whats wrong that would be epic.
So first I have a function to make the circle positions
package loadingScreen;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class LoadTree {
static BufferedImage tree;
static int radius =300;
public static List<Dot> dots = new ArrayList<Dot>();
public static void make() {
File e = new File("assets/tree.png");
try {
tree = ImageIO.read(e);
} catch (IOException e1) {
e1.printStackTrace();
}
for(int x = 0; x<tree.getWidth();x+=1) {
for(int y = 0; y<tree.getWidth();y+=1) {
int clr = tree.getRGB(x, y);
if(clr==0) {
}else {
int i =(y)*300+x;
int a = (i / 4) % 300;
int b = (int) Math.floor(Math.floor(i / 300) / 4);
if (( a % ((1)) == 0) && (b % ((1)) == 0)) {
double p = (double) Math.random();
int circleX = (int) (MyFrame.width/2+ radius * Math.cos(2 * Math.PI * p));
int circleY = (int) (MyFrame.height/2 + radius * Math.sin(2 * Math.PI * p));
Dot dot = new Dot(circleX, circleY, clr,
MyFrame.width/2+x-150, MyFrame.height/2+y-150,circleX,circleY);
dots.add(dot);
}
}
}
}
}
}
Here is the dot class
package loadingScreen;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
public class Dot {
public int imageY;
public int imageX;
public int color;
public double y;
public double x;
public int circleX;
public int circleY;
public Dot(int x, int y, int color, int imageX, int imageY, int circleX, int circleY){
this.x = x;
this.y = y;
this.color = color;
this.imageX = imageX;
this.imageY = imageY;
this.circleX = circleX;
this.circleY = circleY;
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Color c = new Color(color);
g2d.setColor(c);
g2d.fillRect((int)x,(int) y, 1, 1);
}
}
Here is the Jpanel
package loadingScreen;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
import loadingScreen.animate.Move;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MyPanel extends JPanel implements ActionListener{
Image background;
Timer timer;
MyPanel(){
timer = new Timer(0,this);
timer.start();
File e = new File("assets/background.png");
try {
background = ImageIO.read(e);
} catch (IOException e1) {
e1.printStackTrace();
}
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
//g2d.drawImage(background, 0, 0, MyFrame.width,MyFrame.height,null);
LoadTree.dots.forEach(dot ->{
dot.paint(g);
});
}
#Override
public void actionPerformed(ActionEvent e) {
LoadTree.dots.forEach(dot ->{
Move.to(dot, 1000, 0);
//get slope
});
repaint();
//System.out.println(LoadTree.dots.size());
}
}
And here is the Jframe
package loadingScreen;
import javax.swing.JFrame;
public class MyFrame extends JFrame{
MyPanel panel;
public static int width = 1080;
public static int height = 720;
MyFrame(){
panel = new MyPanel();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(panel);
this.setSize(width, height);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
You are in for a massive deep dive. Animation, good animation, animation you don't notice, is really, really hard to achieve and is a very complex subject.
You've kind of started in the right direction. You need some kind of "ticker" to tell you when the animation should update, but it kind of falls apart after that.
The first thing you want to do is move away from the concept of "linear progression". That is, on each "tick", the object is moved by a specific delta value. This doesn't produce good animation and can fall apart really quickly when you want to change the speed or duration of the animation.
A better solution is to start with a "duration based progress". This is, basically, the animation will run over a fixed period of time and on each tick of the animation, you calculate the new "state" of the object based on the amount of time which has passed and the amount of time remaining.
This has the benefit of "normalising" the timeline. That is, the animation occurs between 0-1. From this it becomes incredibly easy to calculate where a object should be along that time line. Want to make it faster or slower? Change the duration, the rest is taken care for you!
To start with, figure out how to move one dot from one point to another, if you can move one, you can move a thousand.
Duration base animation engine...
Play close attention to:
The Utilities class
The DurationAnimationEngine
The engine is backed by a Swing Timer, so it's safe to use within Swing. It's whole purpose to run (as fast as it safely can) for a specified period of time and produce "ticks" with the amount of progression has occurred (remember, normalised time)
The following is basic implementation of the animation. A lot of the "work" happens in the mouseClicked event, as it starts the engine. When the engine ticks the dots are updated. Each dot is wrapped in AnimatableDot which has a "from" and "to" point, it then, based on the normalised time, calculates it's new position and then a paint pass is executed
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class DurationTest {
public static void main(String[] args) {
new DurationTest();
}
public DurationTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Utilities {
public static Point2D pointOnCircle(double degress, double radius) {
double rads = Math.toRadians(degress - 90); // 0 becomes the top
double xPosy = Math.round((Math.cos(rads) * radius));
double yPosy = Math.round((Math.sin(rads) * radius));
return new Point2D.Double(radius + xPosy, radius + yPosy);
}
public static Point2D pointOnCircle(double xOffset, double yOffset, double degress, double radius) {
Point2D poc = pointOnCircle(degress, radius);
return new Point2D.Double(xOffset + poc.getX(), yOffset + poc.getY());
}
}
public class TestPane extends JPanel {
private List<AnimatedDot> dots = new ArrayList<>(128);
private Duration duration = Duration.ofSeconds(5);
private DurationAnimationEngine engine;
private List<Color> colors = Arrays.asList(new Color[]{
Color.RED,
Color.BLUE,
Color.CYAN,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.PINK,
Color.WHITE,
Color.YELLOW
});
public TestPane() {
Random rnd = new Random();
setBackground(Color.BLACK);
for (int index = 0; index < 100; index++) {
double fromAngle = 360.0 * rnd.nextDouble();
double toAngle = fromAngle + 180.0;
Collections.shuffle(colors);
Color color = colors.get(0);
dots.add(new AnimatedDot(
Utilities.pointOnCircle(fromAngle, 150),
Utilities.pointOnCircle(toAngle, 150),
color, 2));
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (engine != null) {
engine.stop();
engine = null;
// Reset poisitions
for (AnimatedDot dot : dots) {
dot.move(0);
}
repaint();
return;
}
engine = new DurationAnimationEngine(duration, new DurationAnimationEngine.Tickable() {
#Override
public void animationDidTick(double progress) {
for (AnimatedDot dot : dots) {
dot.move(progress);
}
repaint();
}
});
engine.start();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
int xOffset = (getWidth() - 300) / 2;
int yOffset = (getWidth() - 300) / 2;
g2d.translate(xOffset, yOffset);
g2d.setColor(Color.DARK_GRAY);
g2d.drawOval(0, 0, 300, 300);
for (AnimatedDot dot : dots) {
dot.paint(g2d);
}
g2d.dispose();
}
}
public class DurationAnimationEngine {
public interface Tickable {
public void animationDidTick(double progress);
}
private Duration duration;
private Instant timeStarted;
private Timer timer;
private Tickable tickable;
public DurationAnimationEngine(Duration duration, Tickable tickable) {
this.duration = duration;
this.tickable = tickable;
}
public void start() {
// You could create the timer lazierly and restarted it as needed
if (timer != null) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timeStarted == null) {
timeStarted = Instant.now();
}
Duration runtime = Duration.between(timeStarted, Instant.now());
double progress = Math.min(1.0, runtime.toMillis() / (double) duration.toMillis());
tickable.animationDidTick(progress);
if (progress >= 1.0) {
stop();
}
}
});
timer.start();
}
public void stop() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
}
public class AnimatedDot {
private Dot dot;
private Point2D from;
private Point2D to;
public AnimatedDot(Point2D from, Point2D to, Color color, int radius) {
dot = new Dot(from.getX(), from.getY(), color, radius);
this.from = from;
this.to = to;
}
public void paint(Graphics2D g) {
dot.paint(g);
}
public void move(double progress) {
Point2D pointAt = pointAt(progress);
dot.setLocation(pointAt);
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
protected double getFromX() {
return getFrom().getX();
}
protected double getFromY() {
return getFrom().getY();
}
public Double getXDistance() {
return getTo().getX() - getFrom().getX();
}
public Double getYDistance() {
return getTo().getY() - getFrom().getY();
}
protected Point2D pointAt(double progress) {
double xDistance = getXDistance();
double yDistance = getYDistance();
double xValue = Math.round(xDistance * progress);
double yValue = Math.round(yDistance * progress);
xValue += getFromX();
yValue += getFromY();
return new Point2D.Double(xValue, yValue);
}
}
public class Dot {
private Color color;
private double y;
private double x;
private int radius;
private Ellipse2D dot;
public Dot(double x, double y, Color color, int radius) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius;
dot = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
}
public void setLocation(Point2D point) {
setLocation(point.getX(), point.getY());
}
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(color);
g2d.translate(x - radius, y - radius);
g2d.fill(dot);
g2d.dispose();
}
}
}
Okay, so when we run it, we get...
😮 ... Hmmm, I'd like to say that that was expected, but once I saw it, it was obvious what had gone wrong.
All the dots are moving at the same speed over the same time range!
So, what's the answer. Well, actually a few...
We could change the duration of each dot so that they have an individual duration. This would "randomise" the movement, but I'm not sure it would generate the exact same effect, as they'd be moving at different speeds
We could randomise the start time of the dots, so they started at different times, allowing them all to have the same duration (or even a randomised duration)
We could move only a small subset of the dots, but this would mean that you'd probably end up waiting for the current subset to finish before the next one started
A combination of 2 & 3
Individualised, randomised duration...
Okay, for simplicity (and my sanity), I'm actually going to start with 1. Each dot will have it's own, randomised duration. This means that each dot will be moving at a different speed though.
Pay close attention to LinearAnimationEngine and the AnimatedDot#move method.
This should look familiar, it's basically the same animation logic as before, just isolated for the dot itself
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class RandomIndividualDuration {
public static void main(String[] args) {
new RandomIndividualDuration();
}
public RandomIndividualDuration() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Utilities {
public static Point2D pointOnCircle(double degress, double radius) {
double rads = Math.toRadians(degress - 90); // 0 becomes the top
double xPosy = Math.round((Math.cos(rads) * radius));
double yPosy = Math.round((Math.sin(rads) * radius));
return new Point2D.Double(radius + xPosy, radius + yPosy);
}
public static Point2D pointOnCircle(double xOffset, double yOffset, double degress, double radius) {
Point2D poc = pointOnCircle(degress, radius);
return new Point2D.Double(xOffset + poc.getX(), yOffset + poc.getY());
}
}
public class DurationRange {
private Duration from;
private Duration to;
public DurationRange(Duration from, Duration to) {
this.from = from;
this.to = to;
}
public Duration getFrom() {
return from;
}
public Duration getTo() {
return to;
}
public Duration getDistance() {
return Duration.ofNanos(getTo().toNanos() - getFrom().toNanos());
}
public Duration valueAt(double progress) {
Duration distance = getDistance();
long value = (long) Math.round((double) distance.toNanos() * progress);
value += getFrom().getNano();
return Duration.ofNanos(value);
}
}
public class TestPane extends JPanel {
private List<AnimatedDot> dots = new ArrayList<>(128);
private Duration duration = Duration.ofSeconds(5);
private LinearAnimationEngine engine;
private List<Color> colors = Arrays.asList(new Color[]{
Color.RED,
Color.BLUE,
Color.CYAN,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.PINK,
Color.WHITE,
Color.YELLOW
});
public TestPane() {
Random rnd = new Random();
setBackground(Color.BLACK);
DurationRange range = new DurationRange(Duration.ofSeconds(1), Duration.ofSeconds(5));
for (int index = 0; index < 100; index++) {
double fromAngle = 360.0 * rnd.nextDouble();
double toAngle = fromAngle + 180.0;
Collections.shuffle(colors);
Color color = colors.get(0);
Duration duration = range.valueAt(rnd.nextDouble());
dots.add(new AnimatedDot(
Utilities.pointOnCircle(fromAngle, 150),
Utilities.pointOnCircle(toAngle, 150),
color, 2, duration));
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (engine != null) {
engine.stop();
engine = null;
reset();
return;
}
System.out.println("Go");
List<AnimatedDot> avaliableDots = new ArrayList<>(120);
avaliableDots.addAll(dots);
engine = new LinearAnimationEngine(new LinearAnimationEngine.Tickable() {
#Override
public void animationDidTick() {
List<AnimatedDot> completed = new ArrayList<>(128);
// Reset poisitions
for (AnimatedDot dot : avaliableDots) {
if (!dot.move()) {
completed.add(dot);
}
}
avaliableDots.removeAll(completed);
repaint();
if (avaliableDots.isEmpty()) {
engine.stop();
engine = null;
reset();
}
}
});
engine.start();
}
});
}
protected void reset() {
for (AnimatedDot dot : dots) {
dot.reset();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
int xOffset = (getWidth() - 300) / 2;
int yOffset = (getWidth() - 300) / 2;
g2d.translate(xOffset, yOffset);
g2d.setColor(Color.DARK_GRAY);
g2d.drawOval(0, 0, 300, 300);
for (AnimatedDot dot : dots) {
dot.paint(g2d);
}
g2d.dispose();
}
}
public class LinearAnimationEngine {
public interface Tickable {
public void animationDidTick();
}
private Tickable tickable;
private Timer timer;
public LinearAnimationEngine(Tickable tickable) {
this.tickable = tickable;
}
public void start() {
// You could create the timer lazierly and restarted it as needed
if (timer != null) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tickable.animationDidTick();
}
});
timer.start();
}
public void stop() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
}
public class AnimatedDot {
private Dot dot;
private Point2D from;
private Point2D to;
private Duration duration;
private Instant timeStarted;
public AnimatedDot(Point2D from, Point2D to, Color color, int radius, Duration duration) {
dot = new Dot(from.getX(), from.getY(), color, radius);
this.from = from;
this.to = to;
this.duration = duration;
}
public void paint(Graphics2D g) {
dot.paint(g);
}
public void reset() {
Point2D futureFrom = to;
to = from;
from = futureFrom;
timeStarted = null;
}
public boolean move() {
if (timeStarted == null) {
timeStarted = Instant.now();
}
Duration runtime = Duration.between(timeStarted, Instant.now());
double progress = Math.min(1.0, runtime.toMillis() / (double) duration.toMillis());
Point2D pointAt = pointAt(progress);
dot.setLocation(pointAt);
return progress < 1.0;
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
protected double getFromX() {
return getFrom().getX();
}
protected double getFromY() {
return getFrom().getY();
}
public Double getXDistance() {
return getTo().getX() - getFrom().getX();
}
public Double getYDistance() {
return getTo().getY() - getFrom().getY();
}
protected Point2D pointAt(double progress) {
double xDistance = getXDistance();
double yDistance = getYDistance();
double xValue = Math.round(xDistance * progress);
double yValue = Math.round(yDistance * progress);
xValue += getFromX();
yValue += getFromY();
return new Point2D.Double(xValue, yValue);
}
}
public class Dot {
private Color color;
private double y;
private double x;
private int radius;
private Ellipse2D dot;
public Dot(double x, double y, Color color, int radius) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius;
dot = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
}
public void setLocation(Point2D point) {
setLocation(point.getX(), point.getY());
}
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(color);
g2d.translate(x - radius, y - radius);
g2d.fill(dot);
g2d.dispose();
}
}
}
Now, when we run it we get...
Well, at least it's now more "randomised", and this is where I think points 2 & 3 might be a better mix.
But they're not rebounding?!
Ah, well, actually, click the second example again! The dots will move from the current position (the original to point) and back to their original from point. Soooo, conceptually, it's doable.
But they don't from a nice picture when I click it!
😐 ... So the above examples demonstrate how to animate a object from point A to point B over a specified duration, forming the picture is just changing the target destination (assuming you know what it was to start with). Based on my observations, a moving dot is first allowed to move to its current "end" position before moving to the final picture position, as trying to calculate a curving path would make me 🤯.
What's missing...
Yes, there's something missing, you probably can't see it, but it really stands out for me.
Each dot starts out slowly, speeds up and then decelerates into position. This is known as "easement" (or, in this case, ease-in/ease-out) and it's not the simplest thing in the world to implement. If you're really interested, take a look at How can I implement easing functions with a thread
Now, what is the actual answer to your question? Unless you're completely crazy (and I am), don't try to roll this kind of thing yourself, unless you have a very specific reason for doing so. Instead, make use one of the other ready made engines, for example:
universal-tween-engine
timingframework
Much of the concepts used above are taken from my animation playground source, Super Simple Swing Animation Framework. This is where I do a lot of my playing around and experimentation
This is one of those questions that has you digging deeper and trying to figure out what you could actually achieve, to that end BounceImagePixel is an accumulation of much tinkering to see "where this could go"
package Game;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
import Maps.*;
public class Window extends JFrame implements KeyListener
{
private Insets insets;
private int currentMapX;
private int currentMapY;
public Window()
{
super();
setSize(new Dimension(1920, 1080));
setLayout(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setUndecorated(true);
setFocusable(true);
setContentPane(new Container());
setBackground(Color.BLACK);
addKeyListener(this);
}
public void startGame()
{
insets = getInsets();
currentMapX = 960 - (Game.level_01.getWidth() / 2);
currentMapY = 540 - (Game.level_01.getHeight() / 2);
Game.level_01.setBounds(currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
add(Game.level_01);
}
private void moveMapRight()
{
Game.level_01.setBounds(++currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
}
private void moveMapLeft()
{
Game.level_01.setBounds(--currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
}
public void paint(Graphics g)
{
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Game.player.paint(g2d);
}
public void keyPressed(KeyEvent k)
{
if(k.getKeyCode() == KeyEvent.VK_RIGHT) moveMapRight();
if(k.getKeyCode() == KeyEvent.VK_LEFT) moveMapLeft();
}
public void keyReleased(KeyEvent k){}
public void keyTyped(KeyEvent k){}
}
I've got the first class that extends the JFrame and contains the following class.
package Maps;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.io.*;
import java.util.*;
import javax.swing.*;
import Game.*;
import Tiles.*;
public class Level extends JPanel
{
protected final File txt_MAP;
protected final ArrayList<Tile> jLabel_MAP;
protected final ArrayList<Integer> linesLength;
protected Insets insets = Game.window.getInsets();
protected int arrayIndex;
protected int leftIndex;
protected int topIndex;
protected int width;
protected int height;
public Level(File f)
{
super();
setLayout(null);
setBackground(Color.BLACK);
leftIndex = 0;
topIndex = 0;
txt_MAP = f;
jLabel_MAP = new ArrayList<>();
linesLength = new ArrayList<>();
arrayIndex = 0;
readTxt();
width = linesLength.get(0) * Game.tileSize;
height = linesLength.size() * Game.tileSize;
addTiles();
}
private void readTxt()
{
BufferedReader br = null;
try
{
br = new BufferedReader(new FileReader(txt_MAP));
}
catch(FileNotFoundException e){}
try
{
String line = br.readLine();
while(line != null)
{
String[] words = line.split(" ");
for(int a = 0; a < words.length; a++)
{
for(int b = 0; b < Game.tilesIcons.length; b++)
{
if(Game.tilesList[b].getName().equals(words[a] + ".gif"))
{
if(Game.tilesList[b].getName().contains(Game.grass_TYPE)) jLabel_MAP.add(arrayIndex, new Grass(Game.tilesIcons[b]));
if(Game.tilesList[b].getName().contains(Game.soil_TYPE)) jLabel_MAP.add(arrayIndex, new Soil(Game.tilesIcons[b]));
if(Game.tilesList[b].getName().contains(Game.sky_TYPE)) jLabel_MAP.add(arrayIndex, new Sky(Game.tilesIcons[b]));
arrayIndex++;
}
}
}
linesLength.add(words.length);
line = br.readLine();
}
}
catch(IOException e){}
}
private void addTiles()
{
for(int i = 0; i < jLabel_MAP.size(); i++)
{
jLabel_MAP.get(i).setBorder(null);
jLabel_MAP.get(i).setBounds(insets.left + leftIndex, insets.top + topIndex, 64, 64);
add(jLabel_MAP.get(i));
if(leftIndex == width - Game.tileSize)
{
leftIndex = 0;
topIndex += 64;
}
else leftIndex += 64;
}
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
}
This class extends JPanel and contains an arrayList of Jlabels.
package Player;
import java.awt.*;
import Game.*;
public class Player
{
private int x;
private int y;
private int xa;
private int ya;
private Graphics2D g2d;
public Player(double x, double y)
{
super();
this.x = (int)x;
this.y = (int)y;
xa = 0;
ya = 1;
}
public void movePlayer()
{
x += xa;
y += ya;
}
public void setDirection(int xa, int ya)
{
this.xa = xa;
this.ya = ya;
}
public void paint(Graphics g)
{
g2d = (Graphics2D)g;
g2d.drawImage(Game.playerImages[6], x , y, Game.tileSize, Game.tileSize, null);
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public int getXA()
{
return xa;
}
public int getYA()
{
return ya;
}
}
Finally this class is the class for the player, that is a BufferedImage. My problem is that when I start the program, the player image starts flickering, because when I call the paint() method in the JFrame class, this paints the first the jpanel and then the player image. How can I solve the Image flickering?
Thanks in advance.
As others have said, you shouldn't override paint(Graphics g) in top-level components, so that's part of the problem, but you also need to be careful to make sure you only paint to the screen once per repaint:
My problem is that when I start the program, the player image starts
flickering, because when I call the paint() method in the JFrame
class, this paints the first the jpanel and then the player image.
What's happening now is, every time you call a method that modifies the Graphics object in your paint(Graphics screen) method, it's directly modifying the screen contents, forcing the screen to refresh before you've finished drawing what you really wanted to - the Player. By first painting to super, then again with your custom rendering, you're actually painting to screen at least twice, causing the flicker. You can fix this by double buffering.
Double Buffering involves first rendering to an image, then painting that image to screen. Using built-in methods provided by the Component class (remember that JPanel extends Component), you can get the size of the viewable area of your component. Create a BufferedImage of the same size, then call bufferedImage.createGraphics() - this will give you a Graphics2D object that you can use to draw onto your bufferedImage with. Once you're done rendering to bufferedImage, call screen.drawImage(bufferedImage,0,0,null). This allows you to modify the bufferedImage as many times as you want without actually doing a screen refresh, then draw the contents of the buffer to screen in a single call.
ADDITIONAL INFO
I should have pointed out earlier that I know nothing about how things are actually laid out on screen for you, so you may need to do some additional checks on the bounds of your Player's screen area and where the upper left should be placed in the call to drawImage(Image,x,y,ImageObserver). Also be aware of transparency or the lack thereof in your BufferedImage - you can easily get lost if you paint opaque pixels over other important stuff on screen.
TRY THIS (but don't keep it)
Again, I don't recommend doing all of your painting in top-level components, but you could try doing something like this in the interim:
//This is in the Window class:
public void paint(Graphics screen)
{
//Render everything first to a BufferedImage:
BufferedImage bufferedImage = ... //Initialize based on Window's
//size and bounds.
Graphics2D buf = bufferedImage.createGraphics();
super.paint(buf);
buf.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Game.player.paint(buf);
//Now paint the buffer to screen:
int x = ... //set to top-left x-coordinate, probably 0
int y = ... //set to top-left y-coordinate, probably 0
screen.drawImage(buf,x,y,null);
}
Don't override paint(Graphics) in a top level container like JFrame (which is not double buffered). Instead do custom painting in a JPanel (which is double buffered).
Also, when overriding any of the paint methods, immediately call the super method thereby painting the background and effectively erasing the earlier drawing(s).
I was working on a simple "Bouncing Ball"-Animation in Java. The idea is that it initally spawns a single ball moving in a straight line until hitting the panel border, which causes it to bounce off as you would expect. You can then spawn additional balls at position x,y with mouseclicks. So far so good.
My problem is that each ball starts its own thread, and each thread individually draws into the panel at their own intervals, causing the panel to flicker like crazy. I know that such problems can be solved by implementing double buffering, which I've read about, but never quite used myself.
I was wondering about how one would go about using double buffering here and if having many threads painting at the same time can be an issue (or conversely, even the norm)?
Thanks a lot in advance!
Here's the code:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
class MyCanvas extends JPanel
{
MyCanvas()
{
setBackground(Color.white);
setForeground(Color.black);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
}
public Dimension getMinimumSize()
{
return new Dimension(300,300);
}
public Dimension getPreferredSize()
{
return getMinimumSize();
}
}
public class BouncingBalls extends JFrame // main class
{
MyCanvas m_gamefield;
public BouncingBalls()
{
setLayout(new BorderLayout());
m_gamefield = new MyCanvas();
add("Center",m_gamefield);
m_gamefield.addMouseListener(new MeinMausAdapter());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public void letsgo()
{
Ball first = new Ball(m_gamefield,200,50);
first.start();
}
class MeinMausAdapter extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
Ball next = new Ball(m_gamefield,e.getX(),e.getY());
next.start();
}
}
public static void main(String[] args)
{
BouncingBalls test = new BouncingBalls();
test.setVisible(true);
test.pack();
test.letsgo();
}
}
class Ball extends Thread
{
JPanel m_display;
int m_xPos,m_yPos;
int m_dx = 2; // Steps into direction x or y
int m_dy = 2;
Ball(JPanel c,int x,int y)
{
m_display = c;
m_xPos = x;
m_yPos = y;
}
public void run()
{
paintBall(); // Paint at starting position
while(isInterrupted() == false)
{
moveBall();
try
{
sleep(20);
}
catch(InterruptedException e)
{
return;
}
}
}
void paintBall()
{
Graphics g = m_display.getGraphics();
g.fillOval(m_xPos, m_yPos, 20, 20);
g.dispose();
}
void moveBall()
{
int xNew, yNew;
Dimension m;
Graphics g;
g = m_display.getGraphics();
m = m_display.getSize();
xNew = m_xPos + m_dx;
yNew = m_yPos + m_dy;
// Collision detection with borders, "bouncing off":
if(xNew < 0)
{
xNew = 0;
m_dx = -m_dx;
}
if(xNew + 20 >= m.width)
{
xNew = m.width - 20;
m_dx = -m_dx;
}
if(yNew < 0)
{
yNew = 0;
m_dy = -m_dy;
}
if(yNew + 20 >= m.height)
{
yNew = m.height - 20;
m_dy = -m_dy;
}
g.setColor(m_display.getBackground()); // Erases last position by
g.fillRect(m_xPos-2, m_yPos-2, m_xPos+22, m_yPos+22); // painting over it in white
m_xPos = xNew;
m_yPos = yNew;
paintBall(); // paint new position of Ball
g.dispose();
}
}
Don't worry about double buffering when painting with Swing JComponents. They're double buffered by default.
You should, instead of creating each Ball on a different Thread, implement a Swing Timer for the animation. See more at How to Use Swing timers. You can see a good example here where Ball objects are added to a List of Balls and presents at different intervals.
Other Notes
Never use getGraphics of your components. All painting should be done within the Graphics context passed to the paintComponent method. I see you have the method in place. Use it. You can have a draw method in your Ball class that take a Graphics argument, and call that method from within the paintComponent method, passing to it the Graphics context. Example can also be seen in the link above.
You can see more examples here and here and here and here and here and here.
Thanks to peeskillet's excellent references, I've changed the code around a bit by using Swing timers. It's a lot shorter now and forfeits the use of multithreading completely. Also, due to calculating all of the ball positions before actually drawing them (in a single sweeping repaint() as opposed to many smaller ones), the flickering has stopped.
I'm still a bit curious why it is considered bad form to use getGraphics(), though. Does it always lead to flickering (which I had imagined could be removed with an additional layer of of double buffering)? And doesn't paintComponent() become rather bloated in more complex animations if it directs every single act of painting? I'm still fairly new to this, if anybody is wondering.
Here's the new code for those interested:
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class BouncingBalls extends JFrame // main class
{
MyCanvas m_gamefield;
public ArrayList<Ball> balls;
public Timer timer = null;
public BouncingBalls()
{
setLayout(new BorderLayout());
m_gamefield = new MyCanvas();
add("Center",m_gamefield);
balls = new ArrayList<Ball>();
timer = new Timer(30, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
for (Ball b : balls)
{
b.move();
}
repaint();
}
});
m_gamefield.addMouseListener(new MeinMausAdapter());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
class MeinMausAdapter extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
balls.add(new Ball(m_gamefield,e.getX(),e.getY()));
}
}
class MyCanvas extends JPanel
{
MyCanvas()
{
setBackground(Color.white);
setForeground(Color.black);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Ball b : balls)
{
b.draw(g);
}
}
public Dimension getMinimumSize()
{
return new Dimension(300,300);
}
public Dimension getPreferredSize()
{
return getMinimumSize();
}
}
public void letsgo()
{
balls.add(new Ball(m_gamefield,200,50));
timer.start();
}
public static void main(String[] args)
{
BouncingBalls test = new BouncingBalls();
test.setVisible(true);
test.pack();
test.letsgo();
}
}
class Ball
{
JPanel m_display;
int m_xPos,m_yPos;
int m_dx = 2; // Steps into direction x or y
int m_dy = 2;
Ball(JPanel c,int x,int y)
{
m_display = c;
m_xPos = x;
m_yPos = y;
}
void draw(Graphics g)
{
g.fillOval(m_xPos, m_yPos, 20, 20);
}
void move()
{
int xNeu, yNeu;
Dimension m;
m = m_display.getSize();
xNeu = m_xPos + m_dx;
yNeu = m_yPos + m_dy;
// Collision detection with borders, "bouncing off":
if(xNeu < 0)
{
xNeu = 0;
m_dx = -m_dx;
}
if(xNeu + 20 >= m.width)
{
xNeu = m.width - 20;
m_dx = -m_dx;
}
if(yNeu < 0)
{
yNeu = 0;
m_dy = -m_dy;
}
if(yNeu + 20 >= m.height)
{
yNeu = m.height - 20;
m_dy = -m_dy;
}
m_xPos = xNeu;
m_yPos = yNeu;
}
}
I am making a game in java and I want to create a simulation of a cloud that is pouring rain. The cloud is supposed to move to the right while raining. Moving the cloud is no problem. It's the rain that I am struggling with.
What I was thinking of doing was with a timer to draw a rectangle, thats supposed to look like falling rain at a random x value inside of the cloud. And then add 1 to the y value of the drop each 100 millisecond. But I don't want to create 100 different rectangles, x variables and y variables for each rain drop.
Any idea how I can accomplish this? Suggestions appreciated!
It is a 2d game.. Sorry.
One approach would be to consider a marquee on a theater. You take a series of bulbs and, by lighting and extinguishing them in sequence, you can simulate linear motion.
In the same way, rather than creating raindrops and animating their movement, why not creating multiple raindrops that are invisible and show and hide them in sequence to simulate downward motion. Then, you would have a series of arrays representing a raindrop track and you simply need to cycle through then, hiding the current one, incrementing the array pointer and displaying that one.
Is it a requirement that the rain drops be programmed? Traditionally, this would be done with a few rain sprites that you place under the cloud and animate so that it looks like the rain is falling.
I would recommend just storing the values as an ArrayList of objects.
class Raindrop {
private int x;
private int y;
public void fall() {
y--;
}
}
Then make an ArrayList with a generic type.
ArrayList<Raindrop> drops = new ArrayList<Raindrop>();
To make each drop fall,
for (int i=0; i<drops.length(); i++) {
drops.get(i).fall();
}
Here is my java (swing) implementation of 2d rain with drops, splash, wind and gravity
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main( String [] args ) {
JFrame frame = new JFrame();
frame.setSize(800, 300);
final RPanel rPanel=new RPanel();
frame.add(rPanel);
frame.setVisible( true );
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
rPanel.stop();
System.exit(0);
}
});
}
}
class RPanel extends JPanel {
//*********SETTINGS****************************
private float mWind = 2.05f;
private float mGravity = 9.8f;
private double mRainChance = 0.99; // from 0 to 1
private int mRepaintTimeMS = 16;
private float mRainWidth=1;
private double mDdropInitialVelocity = 20;
private double mDropDiam = 2;
private Color mColor=new Color(0, 0, 255);
//*********************************************
private ArrayList<Rain> rainV;
private ArrayList<Drop> dropV;
private UpdateThread mUpdateThread;
public RPanel() {
rainV = new ArrayList<>();
dropV = new ArrayList<>();
mUpdateThread=new UpdateThread();
mUpdateThread.start();
}
public void stop() {
mUpdateThread.stopped=true;
}
public int getHeight() {
return this.getSize().height;
}
public int getWidth() {
return this.getSize().width;
}
private class UpdateThread extends Thread {
public volatile boolean stopped=false;
#Override
public void run() {
while (!stopped) {
RPanel.this.repaint();
try {
Thread.sleep(mRepaintTimeMS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(mRainWidth));
g2.setColor(mColor);
//DRAW DROPS
Iterator<Drop> iterator2 = dropV.iterator();
while (iterator2.hasNext()) {
Drop drop = iterator2.next();
drop.update();
drop.draw(g2);
if (drop.y >= getHeight()) {
iterator2.remove();
}
}
//DRAW RAIN
Iterator<Rain> iterator = rainV.iterator();
while (iterator.hasNext()) {
Rain rain = iterator.next();
rain.update();
rain.draw(g2);
if (rain.y >= getHeight()) {
//create new drops (2-8)
long dropCount = 1 + Math.round(Math.random() * 4);
for (int i = 0; i < dropCount; i++) {
dropV.add(new Drop(rain.x, getHeight()));
}
iterator.remove();
}
}
//CREATE NEW RAIN
if (Math.random() < mRainChance) {
rainV.add(new Rain());
}
}
//*****************************************
class Rain {
float x;
float y;
float prevX;
float prevY;
public Rain() {
Random r = new Random();
x = r.nextInt(getWidth());
y = 0;
}
public void update() {
prevX = x;
prevY = y;
x += mWind;
y += mGravity;
}
public void draw(Graphics2D g2) {
Line2D line = new Line2D.Double(x, y, prevX, prevY);
g2.draw(line);
}
}
//*****************************************
private class Drop {
double x0;
double y0;
double v0; //initial velocity
double t; //time
double angle;
double x;
double y;
public Drop(double x0, double y0) {
super();
this.x0 = x0;
this.y0 = y0;
v0 = mDdropInitialVelocity;
angle = Math.toRadians(Math.round(Math.random() * 180)); //from 0 - 180 degrees
}
private void update() {
// double g=10;
t += mRepaintTimeMS / 100f;
x = x0 + v0 * t * Math.cos(angle);
y = y0 - (v0 * t * Math.sin(angle) - mGravity * t * t / 2);
}
public void draw(Graphics2D g2) {
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, mDropDiam, mDropDiam);
g2.fill(circle);
}
}
}
You can use a particle system or use a vector of raindrops and animate them every X milliseconds. A link to a particle system library: http://code.google.com/p/jops/
example code for vector:
import java.util.Vector;
// In your class
Vector raindrops;
void animate()
{
ListIterator iter = raindrops.listIterator;
while (iter.hasNext()) {
((Raindrop)iter.next()).moveDown();
}
}
I have a thread which drops a circle in the y direction. I want to now create several circles on screen dropping at the same time with random x positions.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Goo
{
protected GooPanel gooPanel;
private boolean loop = true;
protected int width , height;
private int frameTimeInMillis = 50;
private RenderingHints renderingHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING , RenderingHints.
VALUE_ANTIALIAS_ON);
#SuppressWarnings("serial")
class GooPanel extends JPanel
{
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHints(renderingHints);
draw(g2d);
}
}
public Goo()
{
this (800, 500);
}
public Goo(int w, int h)
{
width = w;
height = h;
JFrame frame = new JFrame ();
frame.setSize(width , height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gooPanel = new GooPanel ();
gooPanel.setPreferredSize(new Dimension(w, h));
frame.getContentPane ().add(gooPanel);
frame.pack();
frame.setVisible(true);
}
public void go()
{
while (loop)
{
gooPanel.repaint();
try
{
Thread.sleep(frameTimeInMillis);
} catch (InterruptedException e) {}
}
}
public void draw(Graphics2D g) {}
public void setFrameTime(int millis)
{
frameTimeInMillis = millis;
}
public Component getGooPanel ()
{
return gooPanel;
}
}
My FallingDrop class:
import java.awt.*;
public class FallingDrops extends Goo
{
double x, y, r;
int red, green, blue = 0;
Color a;
FallingDrops()
{
x = width / 2;
r = 10;
y = -r;
}
FallingDrops(double x)
{
this.x = x;
r = 10;
y = -r;
}
public void draw(Graphics2D g)
{
g.setColor(Color.GRAY);
g.fillRect(0, 0, width , height);
g.setColor(Color.WHITE);
g.fillOval ((int) (x - r), (int) (y - r), (int) (2 * r),
(int) (2 * r));
y++;
if (y - r > height)
y = -r;
}
public static void main(String [] args)
{
int num = 10;
Goo gooDrop [] = new FallingDrops[num];
for(int i = 0; i < gooDrop.length; i++)
{
double x = Math.random()*800;
gooDrop[i] = new FallingDrops(x);
System.out.println(x);
gooDrop[i].go();
}
}
}
At current, the loop fails to complete when the go() method is executed; thus only painting ONE object on screen, and not several as indicated in my loop. This is a simple fix I am sure. Any ideas what I am doing wrong?
The method go() never returns. when it is called on the first object in the array, it continues working infinitely. you should either make the repainting in a separate thread that is constantly repainting. or if you want repainting only when drops are added, then remove the while in your go method
public void go()
{
gooPanel.repaint();
try
{
Thread.sleep(frameTimeInMillis);
} catch (InterruptedException e) {}
}
this way it will returns after it had made a repaining and a pause.
while (loop) .. gooPanel.repaint();
Not the way to do custom painting. Establish a Swing Timer and call repaint() in the actionPerformed() method of the listener.
See the Custom Painting lesson in the tutorial for details and working examples.