I currently have a working code that draws a fractal tree using recursion. However, when I try to draw it iteratively, it is not working.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
/*
-Used built in Math trig methods to accomodate angling...looked this up online
https://stackoverflow.com/questions/30032635/java-swing-draw-a-line-at-a-specific-angle
*/
public class Test extends JFrame {
public Test() {
setBounds(100, 100, 800, 600); //sets the boundary for drawing
}
public void drawTree(Graphics g, int x1, int y1, double angle, int depth) {
System.out.println("x");
if (depth == 6){
return; //base case here to prevent infinite recursion..
}
else {
System.out.println("y1");
//embedded portion '(Math.toRadians(angle) * depth * 10.0)'represents angle...
int x2 = x1 + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0); //vertical shift calculated using the Sine...PARSED to int because method drawLine() accepts only ints as params
int y2 = y1 + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0); //hor. shift calculated using the Cos..PARSED to int
// System.out.println("x2: " + x2);//will reflect the change in vertical shift
//System.out.println("y2: " + y2);//will reflect change in hor. shift
g.drawLine(x1, y1, x2, y2);//value x1 equals previous line...in other word, start at where previously left off
// notice that the end point (x2 and y2) becomes starting point for each successive call
drawTree(g, x2, y2, angle - 20, depth - 1); //DRAWS LEFT SIDE?
// drawTree(g, x2, y2, angle + 20, depth - 1); //DRAWS RIGHT SIDE?
}
if (depth == 6){
return; //base case here to prevent infinite recursion..
}
else {
System.out.println("y2");
int x2 = x1 + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0); //vertical shift calculated using the Sine...PARSED to int because method drawLine() accepts only ints as params
int y2 = y1 + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0); //hor. shift calculated using the Cos..PARSED to int
// System.out.println("x2: " + x2);//will reflect the change in vertical shift
//System.out.println("y2: " + y2);//will reflect change in hor. shift
g.drawLine(x1, y1, x2, y2);//value x1 equals previous line...in other word, start at where previously left off
// notice that the end point (x2 and y2) becomes starting point for each successive call
// drawTree(g, x2, y2, angle - 20, depth - 1); //DRAWS LEFT SIDE?
drawTree(g, x2, y2, angle + 20, depth - 1); //DRAWS RIGHT SIDE?
}
}
public void drawIteratively(Graphics g, int x1A, int y1A, int x1B, int y1B, double angleA, double angleB, int depthA, int depthB){
while (depthA != 4) {
int x2A = x1A + (int) (Math.cos(Math.toRadians(angleA)) * depthA * 10.0);
int y2A = y1A + (int) (Math.sin(Math.toRadians(angleA)) * depthA * 10.0);
g.drawLine(x1A, y1A, x2A, y2A); //remember it must continue drawing from where it left off
angleA = angleA - 20;
depthA = depthA - 1;
x1A = x2A;
y1A = y2A;
}
while (depthA != 4) {
int x2A = x1A + (int) (Math.cos(Math.toRadians(angleA)) * depthA * 10.0);
int y2A = y1A + (int) (Math.sin(Math.toRadians(angleA)) * depthA * 10.0);
g.drawLine(x1A, y1A, x2A, y2A); //remember it must continue drawing from where it left off
angleA = angleA - 20;
depthA = depthA - 1;
x1A = x2A;
y1A = y2A;
}
/*
while(depthB != 4){
int x2B = x1B + (int) (Math.cos(Math.toRadians(angleB)) * depthB * 10.0);
int y2B = y1B + (int) (Math.sin(Math.toRadians(angleB)) * depthB * 10.0);
g.drawLine(x1B, y1B, x2B, y2B);
angleB = angleB + 20;
depthB = depthB - 1;
x1B = x2B;
y1B = y2B;
}
*/
}
#Override
public void paint(Graphics g) {
g.setColor(Color.BLUE);
//drawTree(g, 400, 400, -90, 9); //these values corresponding to original line aka trunk during initial method call
drawIteratively(g, 400, 500, 400,500 ,-90 , -90, 9,9);
}
public static void main(String[] args) {
new Test().setVisible(true);
}
}
Ignoring the fractals math: if you want to keep the recursive drawing, warp the long process (recursive calculation) with a SwingWorker, and let it update the GUI. Here is an example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class RecursiveDraw extends JFrame {
private int x1A, y1A, x2A, y2A;
private final int W = 700, H = 500;
private Random random = new Random();
private Color randomColor = Color.BLUE;
private JPanel panel;
public RecursiveDraw() {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
panel = new MyPanel();
add(panel, BorderLayout.CENTER);
pack();
setVisible(true);
new Task().run();
}
public void recursiveDraw(int x1A, int y1A, int depth){
if(depth > 15) { return;}
this.x1A = x1A; this.y1A = y1A;
x2A = random.nextInt(W);
y2A = random.nextInt(H);
randomColor = new Color(random.nextInt(0xFFFFFF));
panel.repaint();
try {
Thread.sleep(1000); //delay
} catch (InterruptedException ex) { ex.printStackTrace();}
recursiveDraw(x2A, y2A, ++depth );
}
class MyPanel extends JPanel{
public MyPanel() {
setPreferredSize(new Dimension(W,H));
}
#Override
public void paintComponent(Graphics g) {
//super.paintComponent(g); //requires storing all points calculated
//so they can be redrawn. recommended
g.setColor(randomColor);
g.drawLine(x1A, y1A, x2A, y2A);
}
}
class Task extends SwingWorker<Void,Void> {
#Override
public Void doInBackground() {
recursiveDraw(W/2, H/2, 0);
return null;
}
}
public static void main(String[] args) {
new RecursiveDraw();
}
}
The basic structure of iterative drawing, using Timer, as proposed by #MadProgrammer could look like this :
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class TimerIterativeDraw extends JFrame {
private final static int W = 700, H = 500;
private final static int DELAY= 1000;
private final static int NUMBER_OF_DRAWS_LIMIT = 50;
private int x2A = W/2, y2A = H/2, x1A, y1A, numberOfDraws;
private Random random = new Random();
private Color randomColor = Color.BLUE;
private JPanel panel;
private Timer timer;
public TimerIterativeDraw() {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
panel = new MyPanel();
add(panel, BorderLayout.CENTER);
pack();
setVisible(true);
timer = new Timer(DELAY,new Task());
timer.start();
}
public void upDateGui(){
if(numberOfDraws++ >= NUMBER_OF_DRAWS_LIMIT){
timer.stop();
}
x1A = x2A; y1A = y2A;
x2A = random.nextInt(W);
y2A = random.nextInt(H);
randomColor = new Color(random.nextInt(0xFFFFFF));
//for better implementation store all points in an array list
//so they can be redrawn
panel.repaint();
}
class MyPanel extends JPanel{
public MyPanel() {
setPreferredSize(new Dimension(W,H));
}
#Override
public void paintComponent(Graphics g) {
//super.paintComponent(g); //requires storing all points calculated
//so they can be redrawn. recommended
g.setColor(randomColor);
g.drawLine(x1A, y1A, x2A, y2A);
}
}
class Task implements ActionListener {
#Override
public void actionPerformed(ActionEvent arg0) {
upDateGui();
}
}
public static void main(String[] args) {
new TimerIterativeDraw();
}
}
There's probably a few ways you can do this, but...
You need some kind of class which you can call which will calculate the next step and record it
You need to make sure that you're only updating the state from within the context of the Event Dispatching Thread. This is important, as you don't want to update the UI or anything the UI might rely on out side the EDT, otherwise you run the risk of race conditions and dirty paints
So, first, we need some way to create the branches in some kind of stepped manner. The idea is to only generate a new branch each time the class is told to update.
The class will contain it's only state and management, but will provide access to a List of points which it has created, maybe something like...
public class Generator {
private List<Point> points;
private double angle;
private double delta;
private int depth = 9;
private Timer timer;
public Generator(Point startPoint, double startAngle, double delta) {
points = new ArrayList<>(25);
points.add(startPoint);
angle = startAngle;
this.delta = delta;
}
public List<Point> getPoints() {
return new ArrayList<Point>(points);
}
public boolean tick() {
Point next = updateTree(points.get(points.size() - 1), angle);
angle += delta;
depth--;
if (next != null) {
points.add(next);
}
return next != null;
}
public Point updateTree(Point p, double angle) {
if (depth == 6) {
return null;
}
System.out.println("depth = " + depth + "; angle = " + angle);
//embedded portion '(Math.toRadians(angle) * depth * 10.0)'represents angle...
int x2 = p.x + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0); //vertical shift calculated using the Sine...PARSED to int because method drawLine() accepts only ints as params
int y2 = p.y + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0); //hor. shift calculated using the Cos..PARSED to int
return new Point(x2, y2);
}
}
Now, this class only generates a single branch, in order to make a tree, you will need two instances of this class, with different deltas
Next, we need someway to ask this generator to generate the next step on a regular bases. For me, this typically invokes using a Swing Timer.
The reason been:
It's simple. Seriously, it's really simple
It won't block the EDT, thus not freezing the UI
It updates within the context of the EDT, making it safe to update the state of the UI from within.
Putting these two things together into a simple JPanel which controls the Timer and paints the points...
public class TestPane extends JPanel {
private Generator left;
private Generator right;
public TestPane() {
Point startPoint = new Point(200, 400);
left = new Generator(startPoint, -90, -20);
right = new Generator(startPoint, -90, 20);
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
boolean shouldContinue = left.tick() && right.tick();
if (!shouldContinue) {
((Timer)(e.getSource())).stop();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
render(g2d, left.getPoints());
g2d.setColor(Color.BLUE);
render(g2d, right.getPoints());
g2d.dispose();
}
protected void render(Graphics2D g2d, List<Point> points) {
Point start = points.remove(0);
while (points.size() > 0) {
Point end = points.remove(0);
g2d.draw(new Line2D.Double(start, end));
start = end;
}
}
}
Related
I tried to plot a circle using the java awt, what I get in output is just few small which are separated by much distance apart and doesn't look like a circle as a whole. Code is given below :
class DrawFrame extends JFrame {
int хс, yc, r, x, y;
float p;
DrawFrame(int rr, int c1, int c2) {
setSize(1000, 1000);
setTitle("circle drawing algo");
r = rr;
xc = c1;
yc = c2;
}
public void paint(Graphics g) {
Circl(g);
}
public void Circl(Graphics g) {
x = xc - r;
while (x <= (xc + r)) {
for (y = yc - r; y <= (yc + r); y++) {
p = x * x + y * y - r * r;
if (p == 0.0)
g.drawOval(x, y, 2, 2);
}
x++;
}
}
You should start by having a read of Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how the painting system works and how you should work with it.
You shouldn't override the paint method of top level components, apart from not been double buffered, they are actually compound components. This means that they have a number of additional components laid out on top of them which provide the overall functionality of the window.
This means that it's possible for what you've painted to the surface of the frame to be ignored, as the content on top of it paints over it.
You're also ignoring the complexity of the painting process, unless you're prepared to take over the responsibility of the paint method yourself, you should always call its super method.
A better place to start is with a JPanel (which is simpler) and override its paintComponent method
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
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(100, 100, 100));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
int xc, yc, r;
public TestPane(int rr, int c1, int c2) {
r = rr;
xc = c1;
yc = c2;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(r * 2, r * 2);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
circle(g);
}
public void circle(Graphics g) {
// Credit to LAD for the algorithm updates
int x = xc - r;
while (x <= (xc + r)) {
for (int y = yc - r; y <= (yc + r); y++) {
float p = (x - xc) * (x - xc) + (y - yc) * (y - yc) - (r * r);
if (p <= 0.0f)
{
g.drawOval(x, y, 2, 2);
}
}
x++;
}
}
}
}
Credit to LAD for the algorithm updates
The first thing to change is to make JFrame visible by adding
setVisible(true); to its constructor.
It is recommended to use names that have a clear meaning to make the code more readable. Make the fields scope as limited as possible, in this case make them private:
private int сenterX, centerY, radius;
(x,y and p are method variables and do not need to be fields )
Avoid using magic numbers. Use constants instead:
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
Putting it together, using correct Java naming conventions and fixing the algorithm:
import java.awt.Graphics; //add imports to make tour code mcve
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
class DrawFrame extends JFrame {
private final int сenterX, centerY, radius;
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
DrawFrame(int radius, int centerX, int centerY) {
setSize(W, H);
setTitle("circle drawing algo");
this.radius = radius;
сenterX = centerX;
this.centerY = centerY;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//make program stop when closing frame
setVisible(true); //make frame visible
}
#Override
public void paint(Graphics g) {
super.paint(g);
circl(g);
}
public void circl(Graphics g) {
int x, y;
x = сenterX - radius;
while (x <= сenterX + radius) {
//calculate x
y = (int)Math.sqrt(radius*radius - (сenterX -x)*(сenterX -x ));
g.drawOval(x, centerY-y, 2, 2); // 2 y values for every x
g.drawOval(x, centerY+y, 2, 2);
x++;
}
}
// add main to make your code mcve
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new DrawFrame(100, 400, 400));
}
}
The next improvement could be to refactor so the painting is done on a JPanel rather than on the JFrame itself, using Graphics.drawOval:
class DrawFrame extends JFrame {
DrawFrame(int radius, int centerX, int centerY) {
setTitle("circle drawing algo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//make program stop when closing frame
add(new DrawingPane(radius, centerX, centerY));
pack();
setVisible(true); //make frame visible
}
// add main to make your code mcve
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new DrawFrame(100, 400, 400));
}
}
class DrawingPane extends JPanel{
private final int сenterX, centerY, radius;
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
DrawingPane(int radius, int centerX, int centerY) {
setPreferredSize(new Dimension(W, H));
this.radius = radius;
сenterX = centerX;
this.centerY = centerY;
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval(сenterX, centerY, radius, radius);
}
}
I edited your code a bit and changed the draw algorithm a little in order to completely draw the circle. Here is the refactored code:
class DrawFrame extends JFrame {
int xc, yc, r, x, y;
float p;
DrawFrame(int rr, int c1, int c2) {
setSize(1000, 1000);
setTitle("circle drawing algo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Handles the window being closed
setVisible(true); // Makes the window visible
r = rr;
xc = c1;
yc = c2;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
GradientPaint gp = new GradientPaint(0f,0f,Color.blue,0f,30f,Color.green); // Just sets a color for the paint
g2.setPaint(gp);
Circl(g2);
}
public void Circl(Graphics g) {
x = xc-r;
while (x <= (xc+r)) {
for (y = yc-r; y <= (yc+r); y++) {
p = (x-xc)*(x-xc)+(y-yc)*(y-yc)-(r*r); // Edited this line so that it’s now the correct circle formula
if (p <= 0.0f) // If the point is 0 or less, then the point is within the circle bounds
g.drawOval(x, y, 2, 2);
}
x++;
}
}
public static void main(String[] args) {
new DrawFrame(100, 500, 500);
}
}
The code is a bit slow, though, as the drawOval method seems to be quite slow when called a bunch of times. So, this code is mainly suited for the algorithm aspect, as you could easily fill out an oval by calling the following once:
g.drawOval(x, y, 200, 200);
g.fillOval(x, y, 200, 200);
You set initial value of x as x = xc - r, y as y = yc - r. And
converting Cartesian to Polar coordinate like p = x * x + y * y - r *
r . Assume that if xc=500, and yc=500, r=50, "p" will never be 0. So
I think you forgot to calculate xc, yc when you draw. I modified your
code little bit and attached the result.
package testProject;
import java.awt.*;
import javax.swing.*;
public class DrawFrame extends JPanel {
int xc=500, yc=500, r=150, x, y;
int real_x, real_y;
float p;
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("circle drawing algo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBackground(Color.white);
frame.setSize(1000, 1000);
DrawFrame panel = new DrawFrame();
frame.add(panel);
frame.setVisible(true);
}
public void paint(Graphics g) {
Circl(g);
}
public void Circl(Graphics g) {
x = -r;
while (x <= r) {
y = -r;
while (y <= r) {
p = x * x + y * y - r * r;
// here is the change
if (p>=0 && p<= xc) {
g.drawOval(x+xc, y+yc, 3, 3);
}
y++;
}
x++;
}
}
}
I'm new enough to Java and I'm learning Game Design at the minute. I'm kind of at the beginning so its not a Game really.
I'm dealing with a single thread and an array of numbers.I have to get multiple square shapes moving around a screen at the same time. I have the code running fine for one square but run into trouble when I try implement it to multiple squares. I'm working with two Interfaces and I don't know how to use the Array to make multiple cases of a single random square. Any help would really be appreciated.
Cheers guys.
import java.awt.*;
import javax.swing.*;
public class MovingSquares extends JFrame implements Runnable
{
private static final Dimension WindowSize = new Dimension(600, 600);
private static final int NUMGAMEOBJECTS = 30;
private GameObject[] GameObjectsArray = new GameObject[NUMGAMEOBJECTS];
static int strtCoX = (int) (Math.random() * 600);
static int strtCoY = (int) (Math.random() * 600);
public MovingSquares() {
this.setTitle("Crazy squares");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dimension screensize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
int x = screensize.width/2 - WindowSize.width/2;
int y = screensize.height/2 - WindowSize.height/2;
setBounds(x, y, WindowSize.width, WindowSize.height);
setVisible(true);
Thread t = new Thread(this);
t.start();
}
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) { }
GameObject.move();
this.repaint();
}
}
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, WindowSize.width, WindowSize.height);
int size = 50;
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
g.setColor(c);
g.fillRect(strtCoX, strtCoY, size, size);
}
public static void main(String[] args) {
MovingSquares M = new MovingSquares();
}
}
Second Interface - GameObject class.
import java.awt.*;
public class GameObject
{
private static double x;
private static double y;
private Color c;
public static int ranCoX = (int) (Math.random() * 20);
public static int ranCoY = (int) (Math.random() * 20);
public GameObject() {
int strtX = (int) x;
int strtY = (int) y;
int speedX = (int) (Math.random() * 10);
int speedY = (int) (Math.random() * 10);
}
public static void move() {
int velX = (int) (Math.random() * ranCoX);
int velY = (int) (Math.random() * ranCoY);
if (MovingSquares.strtCoY > 600)
MovingSquares.strtCoY = MovingSquares.strtCoY - 550;
else if (MovingSquares.strtCoX > 600)
MovingSquares.strtCoX = MovingSquares.strtCoX - 550;
MovingSquares.strtCoX += velX;
MovingSquares.strtCoY += velY;
}
public static void paint(Graphics g) {
int size = 50;
x = (Math.random() * 600);
y = (Math.random() * 600);
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
g.setColor(c);
g.fillRect((int)x, (int) y, size, size);
}
}
Here's a version of Moving Squares that hopefully will help you understand how to do an animation.
One of the first things I did was to separate the classes into either a model class, a view class, or a controller class. The model / view / controller pattern helps to separate concerns.
Let's look at the revised version of your model class, the GameObject class.
package com.ggl.moving.squares;
import java.awt.Color;
import java.awt.Graphics;
public class GameObject {
private static final int size = 50;
private double x;
private double y;
private double dx;
private double dy;
private int drawingWidth;
public GameObject(int drawingWidth) {
x = Math.random() * drawingWidth;
y = Math.random() * drawingWidth;
dx = Math.random() * 30D - 15D;
dy = Math.random() * 30D - 15D;
this.drawingWidth = drawingWidth;
}
public void move() {
int lowerLimit = size;
int upperLimit = drawingWidth - size;
x += dx;
if (x < lowerLimit) {
x += upperLimit;
} else if (x > upperLimit) {
x -= upperLimit;
}
y += dy;
if (y < lowerLimit) {
y += upperLimit;
} else if (y > upperLimit) {
y -= upperLimit;
}
}
public void draw(Graphics g) {
g.setColor(generateRandomColor());
g.fillRect((int) x, (int) y, size, size);
}
private Color generateRandomColor() {
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
return c;
}
}
There's only one static field left, the size of the square. Everything else is a dynamic variable or method. This allows us to create more than one instance of the GameObject.
I changed the name of the drawing method from paint to draw. This is so we don't get confused about which methods are Swing methods, and which methods are our methods.
We pass the width of the drawing panel into the constructor. That way, we only have to define the width in one place. You can see in the move method that we allow a margin the size of the square in the drawing area.
The constructor defines a random initial position and initial velocity. Th move method merely keeps the square moving in a straight line.
Next, let's look at the revised version of your main class, the MovingSquares class.
package com.ggl.moving.squares;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class MovingSquares implements Runnable {
private static final int DRAWING_WIDTH = 600;
private static final int NUMGAMEOBJECTS = 30;
private GameObject[] gameObjectsArray = new GameObject[NUMGAMEOBJECTS];
private JFrame frame;
private MovingPanel movingPanel;
private ObjectsRunnable objectsRunnable;
public MovingSquares() {
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i] = new GameObject(DRAWING_WIDTH);
}
}
#Override
public void run() {
frame = new JFrame();
frame.setTitle("Crazy squares");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
movingPanel = new MovingPanel(gameObjectsArray, DRAWING_WIDTH);
frame.add(movingPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
objectsRunnable = new ObjectsRunnable(this, gameObjectsArray);
new Thread(objectsRunnable).start();
}
private void exitProcedure() {
objectsRunnable.setRunning(false);
frame.dispose();
System.exit(0);
}
public void repaintMovingPanel() {
movingPanel.repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingSquares());
}
}
We define the width of the drawing panel here, as well as an array to hold the game objects.
We start the Swing application on the Event Dispatch thread (EDT) by invoking the SwingUtilities invokeLater method. A Swing application must always start on the EDT.
We create the game objects in the constructor and create the Swing components in the run method. I moved the drawing panel into its own class, which we'll talk about later.
We use a window listener so we can stop the thread when we're done with the application.
We pack the JFrame. The only place we're specifying a size is when we create the drawing panel. We use a JFrame. The only time you extend a Swing component, or any Java class, is when you want to override one of the methods.
Let's look at the drawing panel class, the MovingPanel class.
package com.ggl.moving.squares;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class MovingPanel extends JPanel {
private static final long serialVersionUID = -6291233936414618049L;
private GameObject[] gameObjectsArray;
public MovingPanel(GameObject[] gameObjectsArray, int width) {
this.gameObjectsArray = gameObjectsArray;
setPreferredSize(new Dimension(width, width));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i].draw(g);
}
}
}
We override the paintComponent method to draw on the JPanel. Since the game objects draw themselves, we call the draw method on each game object.
Finally, let's look at the animation runnable, the ObjectsRunnable class.
package com.ggl.moving.squares;
import javax.swing.SwingUtilities;
public class ObjectsRunnable implements Runnable {
private volatile boolean running;
private GameObject[] gameObjectsArray;
private MovingSquares movingSquares;
public ObjectsRunnable(MovingSquares movingSquares,
GameObject[] gameObjectsArray) {
this.movingSquares = movingSquares;
this.gameObjectsArray = gameObjectsArray;
this.running = true;
}
#Override
public void run() {
while (running) {
updateObjects();
xxx();
sleep();
}
}
private void updateObjects() {
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i].move();
}
}
private void xxx() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
movingSquares.repaintMovingPanel();
}
});
}
private void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
This is a straightforward animation or game loop. I put the repaint call in another SwingUtilities invokeLater method to ensure that the Swing components are updated on the EDT.
I hope this was helpful to you.
Static variables will only be available once per Runtime. To create several GameObjects you have to avoid the static keywords.
Then call new GameObject() several times to create serveral instances of GameObject, each with its own set of variables.
edit:
move the variables strtCoX and strtCoY into GameObject (as suggested by #andrew-thomson )
fix all references to strtCoX and strtCoY inside of GameObject
change g.fillRect(strtCoX, strtCoY, size, size); to for (GameObject currentObject : GameObjectsArray) g.fillRect(currentObject.strtCoX, currentObject.strtCoY, size, size);
Explanation: The coordinate are attributes of GameObject. Each GameObject should have its own coordinates.
=> Your code should work just as before
remove all static keywords in GameObject
change GameObject.move(); to for (GameObject currentObject : GameObjectsArray) currentObject.move();
initialize you GameObjectArray in public MovingSquares() constructor like this: for (int i = 0; i < GameObjectsArray.length; i++) GameObjectsArray[i] = new GameObject();
Explanation: with new GameObject() we are creating 30 instances (in that loop inside the constructor). Therefore we also have to call move() 30 times (and that is why that is also nested in a loop)
=> Wear sunglasses!
is it possible to simply make 360 degree movement in java(swing) without any game engine? all I have is this attempt:
public class Game extends JPanel implements Runnable {
int x = 300;
int y = 500;
float angle = 30;
Game game;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.add(new Game());
frame.setSize(600, 600);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public Game() {
setSize(600, 600);
Thread thread = new Thread(this);
thread.start();
}
#Override
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.drawRect(0, 0, 600, 600);
g.setColor(Color.CYAN);
g.fillOval(x, y, 10, 10);
g.dispose();
}
#Override
public void run() {
while(true) {
angle += -0.1;
x += Math.sin(angle);
y--;
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException ex) {}
}
}
}
as you can see in following picture, I don't know how to handle movement rotating, this is the output:
image http://screenshot.cz/GOXE3/mvm.jpg
Actually, this is quite possible.
My preferred way is to actually take advantage of the Graphics transform so that you don't have to do any computation, it's all left to the Graphics
By the way:
since you did not create the Graphics object, don't ever dispose it.
override paintComponent() rather than paint()
It's always a good pattern to call super.paintComponent()
Small demo example:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestRotate {
public static class ShapeAndColor {
private final Shape shape;
private final Color color;
public ShapeAndColor(Shape shape, Color color) {
super();
this.shape = shape;
this.color = color;
}
public Shape getShape() {
return shape;
}
public Color getColor() {
return color;
}
}
public static class RotatingShapesPanel extends JComponent {
private List<ShapeAndColor> shapes;
private double rotation = 0.0;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
AffineTransform translate = AffineTransform.getTranslateInstance(-getWidth() / 2, -getHeight() / 2);
AffineTransform rotate = AffineTransform.getRotateInstance(rotation);
AffineTransform t = AffineTransform.getTranslateInstance(getWidth() / 2, getHeight() / 2);
t.concatenate(rotate);
t.concatenate(translate);
g2d.setTransform(t);
AffineTransform scale = AffineTransform.getScaleInstance(getWidth(), getHeight());
for (ShapeAndColor shape : shapes) {
Area area = new Area(shape.getShape());
g2d.setColor(shape.getColor());
area.transform(scale);
g2d.fill(area);
}
}
public void setShapes(List<ShapeAndColor> shapes) {
this.shapes = shapes;
repaint();
}
public double getRotation() {
return rotation;
}
public void setRotation(double rotation) {
this.rotation = rotation;
repaint();
}
}
protected void initUI(final boolean useBorderLayout) {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
List<ShapeAndColor> shapes = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 10; i++) {
double x = r.nextDouble();
double y = r.nextDouble();
double w = r.nextDouble();
double h = r.nextDouble();
w = Math.min(w, 1 - x) / 2;
h = Math.min(h, 1 - y) / 2;
double a = Math.min(w, h) / 10.0;
RoundRectangle2D.Double shape = new RoundRectangle2D.Double(x, y, w, h, a, a);
Color color = new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256));
shapes.add(new ShapeAndColor(shape, color));
}
final RotatingShapesPanel panel = new RotatingShapesPanel();
panel.setShapes(shapes);
frame.add(panel);
frame.setSize(600, 600);
frame.setVisible(true);
Timer t = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
double rotation = panel.getRotation() + 0.02;
if (rotation > Math.PI * 2) {
rotation -= Math.PI * 2;
}
panel.setRotation(rotation);
}
});
t.setRepeats(true);
t.setDelay(10);
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestRotate().initUI(true);
}
});
}
}
Change a few lines...
int basex = 300; // midpoint of the circle
int basey = 400;
int radius = 100; // radius
int x;
int y;
float angle = 0; // Angles in radians, NOT degrees!
public void run() {
while(true) {
angle += 0.01;
x = (int)(basex + radius*Math.cos(angle));
y = (int)(basey - radius*Math.sin(angle));
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException ex) {}
}
}
Not sure what you were trying to code there, but this is the correct formula for a circular movement.
To calculate the rotation around a point, you need a center point for the rotation (cx, cy), the radius or distance of the point from the center, you need the angle (in radians, not degrees), and you need to use sine and cosine to calculate the offset of the point from the center as it rotates around it.
int cx, cy, radius; // I'll let you determine these
double theta = Math.toRadians(30);
double dtheta = Math.toRadians(-0.1);
double dx = Math.cos(theta) * radius;
double dy = Math.sin(theta) * radius;
int x = (int)(cx + dx);
int y = (int)(cy + dy);
repaint();
theta += dtheta; // step the angle
You program has some problems:
int x = 300;
int y = 500;
You should use a floating point data type like double to store the coordinates. You can cast them to int when you want to draw them. If you store them in int, you'll lose precision.
x += Math.sin(angle);
y--;
This doesn't work, since y is decremented instead of calculated using Math.sin(angle). (us Math.cos for x)
This is your fixed code (unchanged parts are omitted):
double x = 300;
double y = 500;
float angle = 30;
double radius = 10D; // new variable to increase the radius of the drawn circle
Game game;
// main method
// constructor
#Override
public void paint(Graphics g) {
// ... stuff omitted
g.fillOval((int)x, (int)y, 10, 10); // you can cast to int here
g.dispose();
}
#Override
public void run() {
while (true) {
angle -= 0.1; // is the same as `angle += -0.1`
x += radius * Math.cos(angle);
y += radius * Math.sin(angle);
repaint();
// ... try catch block
}
}
This currenlty draw the circle counter-clockwise. If you want to draw it clockwise, then change angle to:
angle += 0.1;
How can I draw in java figure like this?
Here is my code which has to draw at least half of this figure
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class Main {
public static void main(String[] a) {
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new MyCanvas());
window.setVisible(true);
}
}
class MyCanvas extends JComponent {
private static final long serialVersionUID = 1L;
public void paint(Graphics g) {
int i =0;
for ( i = 0; i < 100; i++) {
int x=1+i*3;
g.drawLine(x, 200, 2+(x+(i/2)), 400-((i*i)/20));
}
}
}
And I get this one.
A little animation to show you the logic you need to be looking for in terms of line rotation. Think of the line like a hand on a clock. How would to animate a hand on a clock. It's pretty much the exact same concept. The only difference is that the x1 (the x point for the center point of the clock hand), instead of remaining still, it moves along the x axis (which is the y1 constant) while the hand is turning. So for every tick of the clock (hand rotation), the x location is also moved horizontally. That's the way I looked at it.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] a) {
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.getContentPane().add(new MyCanvas());
window.pack();
window.setVisible(true);
}
}
class MyCanvas extends JPanel {
int x1 = 0;
int rotate = 50;
List<Line> lines;
Timer timer = null;
public MyCanvas() {
lines = new ArrayList<>();
timer = new Timer(75, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (rotate < -50) {
((Timer) e.getSource()).stop();
} else {
lines.add(new Line(x1, rotate));
repaint();
x1 += 5;
rotate--;
}
}
});
JButton start = new JButton("Start the Magic");
start.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
timer.start();
}
});
add(start);
}
public Dimension getPreferredSize() {
return new Dimension(502, 400);
}
private static final long serialVersionUID = 1L;
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
for (Line line : lines) {
line.drawLine(g);
}
}
class Line {
int x1;
int rotate;
int y1 = 200;
public Line(int x1, int rotate) {
this.x1 = x1;
this.rotate = rotate;
}
void drawLine(Graphics g) {
int Radius = (int) (Math.min(getWidth(), getHeight()) * 0.4);
int sLength = (int) (Radius * 0.9);
int xSecond = (int) (x1 + sLength * Math.sin(rotate * (2 * Math.PI / 100)));
int ySecond = (int) (y1 - sLength * Math.cos(rotate * (2 * Math.PI / 100)));
g.setColor(Color.GREEN);
g.drawLine(x1, y1, xSecond, ySecond);
}
}
}
Me so much :D
float centerY = 250;
float x1 = 0;
float x2 = 0;
float y2 = 400;
float way2 = 0;
for (int i = 0; i < 125; i++)
{
x2 += cos(way2*PI/-180)*10;
y2 += sin(way2*PI/-180)*10;
way2 += centerY/y2*0.235*10;
x1 += y2/600*10;
g.drawLine(x1,centerY,x2,y2);
}
Here's what I figured out, little different though :)
public void paint(Graphics g) {
for (int i = 0; i < 100; i++) {
int x = 1 + i * 3;
g.drawLine(x, 200, x + i, 400 - i * i / 20);
g.drawLine(600 - x, 200, 600 - (x + i), 400 - i * i / 20);
}
}
We need to rework on the function '400 - i * i / 20'.
Given the following code :
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
*
* #author X2
*
*/
public class PolygonnerJframe
{
public static void main (String[] args)
{
JFrame frame = new JFrame("Draw polygons");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new DrawingPanel());
frame.pack();
frame.setVisible(true);
}
}
/**
* Main class
* #author X2
*
*/
class DrawingPanel extends JPanel implements MouseListener, MouseMotionListener
{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final Dimension MIN_DIM = new Dimension(300, 300);
private static final Dimension PREF_DIM = new Dimension(500, 500);
private boolean polygonDone = false;
private final Point trackPoint = new Point(); // The 'dummy' point tracking the mouse
private ArrayList<Point> points = new ArrayList<Point>(); // The list of points making up a polygon
private ArrayList<Point> helper = new ArrayList<Point>(); // The list of points making up a polygon
public ArrayList<Point> copyCreate(ArrayList<Point> input , ArrayList<Point> output)
{
int i = 0;
if (output == null)
output = new ArrayList<Point>();
while (i < input.size())
{
output.add(input.get(i));
i++;
}
return output;
}
/**
* Setting the dimensions of the windows
*/
public Dimension getMinimumSize() { return MIN_DIM; }
public Dimension getPreferredSize() { return PREF_DIM; }
/**
* The only constructor needed for this class
*/
DrawingPanel()
{
super();
addMouseListener(this);
addMouseMotionListener(this);
}
/**
* The drawing itself
*/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int numPoints = points.size();
if (numPoints == 0)
return; // nothing to draw
Point prevPoint = (Point) points.get(0);
// draw polygon
Iterator<Point> it = points.iterator();
while (it.hasNext())
{
Point curPoint = (Point) it.next();
draw(g, prevPoint, curPoint);
prevPoint = curPoint;
}
// now draw tracking line or complete the polygon
if (polygonDone == true)
{
draw(g, prevPoint, (Point) points.get(0));
}
else // polygonDone == false
draw(g, prevPoint, trackPoint);
}
/**
* MouseListener interface
*/
public void mouseClicked(MouseEvent evt)
{
int x = evt.getX();
int y = evt.getY();
switch (evt.getClickCount())
{
case 1: // single-click
if (polygonDone == true)
{
this.helper = this.copyCreate(this.points, this.helper); // copy the new coordinates into the helper
points.clear();
polygonDone = false;
}
points.add(new Point(x, y));
repaint();
break;
case 2: // double-click
polygonDone = true;
points.add(new Point(x, y));
// repaint();
break;
default: // ignore anything else
break;
}
}
/**
* MouseMotionListener interface
*/
public void mouseMoved(MouseEvent evt)
{
trackPoint.x = evt.getX();
trackPoint.y = evt.getY();
repaint();
}
/**
* draw points and lines
* #param g
* #param p1
* #param p2
*/
private void draw(Graphics g, Point p1, Point p2)
{
int x1 = p1.x;
int y1 = p1.y;
int x2 = p2.x;
int y2 = p2.y;
// draw the line first so that the points
// appear on top of the line ends, not below
g.setColor(Color.green.darker());
g.drawLine(x1 + 3, y1 + 3, x2 + 3, y2 + 3);
g.drawLine(x1 + 4, y1 + 4, x2 + 4, y2 + 4);
g.drawLine(x1 + 5, y1 + 5, x2 + 5, y2 + 5);
g.setColor(Color.green);
g.fillOval(x1, y1, 8, 8);
g.setColor(Color.black);
g.fillOval(x2, y2, 8, 8);
}
public void mouseDragged(MouseEvent evt) { /* EMPTY */ }
public void mousePressed(MouseEvent evt) { /* EMPTY */ }
public void mouseReleased(MouseEvent evt) { /* EMPTY */ }
public void mouseEntered(MouseEvent evt) { /* EMPTY */ }
public void mouseExited(MouseEvent evt) { /* EMPTY */ }
}
I can only draw one polygon each time , meaning - when I try to start a new polygon
the "old" polygon vanishes , but I don't understand why .
So how can I draw multiple polygons ?
What causes the old polygon to vanish ? I thought maybe due to repaint() , but I tried without it but it didn't help .
I'd appreciate your help
The polygons are indeed being erased by calling points.clear(). To counter this, you could to maintain co-ordinate information using the Polygon class about previous polygons in a separate List which could be painted along with the "in-progress" polygon. This is outlined in Custom Painting Approaches.