I am trying to describe in a JPanel the evolution of a pendulum arm over time.
The pendulum has a fix node and the other node is calculated based on the fixed one and some angles fetched from a file. And every 1 second I expect to see the pendulum redrawn with new coordinates.
For the purpose of describing my issue, I have eliminated the file and the angle calculations and please consider that the mobile Point is saved into an ArrayList of Points.
I tried to achieve the gradual rotation over time by calling the drawRotatingLine() method from within the constructor of the RotateLine object.
In the drawRotatingLine() method I have a for loop which:
sets the coordinates of the mobile Point based on the values of ArrayList of Points
introduces a sleep of 1 second
and calls the repaint() method
Trouble is that I only had my program draw the initial position and then the last one, the intermediary ones not getting painted.
The code is quite patchy having put it together from here and there.Please excuse me for having used abusively the BufferedImage, Graphics2D, and the calls to these objects in the paintComponent(...) method not being entirely clear to me, I just needed the program done and at this stage of my experience, I find quite intricate drawing on JPanels.
Below is the whole code:
public class RotateLine extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = 600;
private static final int X1 = 100;
private static final int Y1 = 100;
private BufferedImage image;
private Graphics2D bufferedGraphics;
private static ArrayList<Point> pointsList;
private static Point p;
private int counter = 0;
public RotateLine () {
pointsList = new ArrayList<Point>();
p = new Point(X1, Y1);
int X2 = 400;
int Y2 = Y1;
for (int count = 0; count < 4; count++) {
pointsList.add(new Point(X2, Y2));
X2 = X2 - 100;
Y2 = Y2 + 100;
}
image = new BufferedImage(PREF_W, PREF_H, BufferedImage.TYPE_INT_RGB);
bufferedGraphics = image.createGraphics();
drawRotatingLine();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
bufferedGraphics.clearRect(0, 0, PREF_W, PREF_H);
bufferedGraphics.setColor(Color.WHITE);
bufferedGraphics.fillRect(0, 0, PREF_W, PREF_H);
bufferedGraphics.setColor(Color.BLACK);
bufferedGraphics.drawLine(X1, Y1, p.x, p.y);
g.drawImage(image, 0, 0, this);
Toolkit.getDefaultToolkit().sync();
}
public static void main(String[] args) {
JFrame frame = new JFrame("clock");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new RotateLine());
frame.pack();
frame.setVisible(true);
}
public void drawRotatingLine() {
for (int i = 0; i < pointsList.size(); i++) {
p.x = pointsList.get(i).x;
p.y = pointsList.get(i).y;
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(Pendul.class.getName()).log(Level.SEVERE, null, ex);
}
repaint();
}
}
}
Your problem is common: you are calling Thread.sleep(...) on the Swing event thread, which will put your entire application to sleep. Instead read up and use a Swing Timer. After you Google the Swing Timer tutorial, search this site for Java Swing Timer Animation for decent examples of how to use it for animation.
So,
have your timer's delay be whatever time slice delay you wish the animation to have, although I recommend it not be < 12 msecs.
In the Timer's ActionListener's actionPerformed, set the coordinates of the mobile Point based on the values of ArrayList of Points and an index
Increment the index (very important)
mod the index to maximal value
call repaint
Based on Hovercraft Full of Eels's answer, this is how I altered the initial code using the Java Swing Timer Animation:
public class RotateLine extends JPanel **implements ActionListener**{
private static final int PREF_W = 800;
private static final int PREF_H = 800;
private static final int X1 = 100;
private static final int Y1 = 100;
private static ArrayList<Point> pointsList;
private static Point p;
private int counter = 0;
private int index = 0;
**Timer time = new Timer(10, (ActionListener) this);**
public RotateLine () {
pointsList = new ArrayList<Point>();
p = new Point(X1, Y1);
int X2 = 400;
int Y2 = Y1;
for (int count = 0; count < 300; count++) {
pointsList.add(new Point(X2, Y2));
X2 = X2 - 1;
Y2 = Y2 + 2;
}
**time.start();**
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
drawRotatingLine(g2d);
}
public static void main(String[] args) {
JFrame frame = new JFrame("clock");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new RotateLine());
frame.pack();
frame.setVisible(true);
}
public void drawRotatingLine(Graphics2D g) {
g.drawLine(p.x, p.y, pointsList.get(index).x, pointsList.get(index).y);
}
**public void actionPerformed(ActionEvent arg0) {
if (index < pointsList.size() - 1){
time.setDelay(20);
repaint();
index++;
}
}**
Related
I am making an asteroid game. Every so often an asteroid needs to be generated and fly across the screen. For some reason when more then 1 asteroid is created, the screen glitches out. If you maximize the screen you will be able to see the glitching. I have tried using paint instead of paintComponent. I have also tried extending JFrame instead of JPanel but that just makes it worse. The class below sets up the screen and handles the game loop
public class Game extends JPanel {
static ArrayList<Asteroids> rocks = new ArrayList<Asteroids>();
//This variable determines whether the game should keep running
static boolean running = true;
//Counter to access arraylist
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
//Creating the window
JFrame frame = new JFrame("Asteroid Game");
frame.getContentPane().setBackground(Color.BLACK);
frame.setSize(1100, 1000);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Asteroids a = new Asteroids();
frame.add(a);
//Game loop
while(running) {
if(counter % 4 == 0) {
rocks.add(new Asteroids());
frame.add(rocks.get(rocks.size() - 1));
}
for(int i = 0; i < rocks.size(); i++) {
rocks.get(i).repaint();
rocks.get(i).move();
if(!rocks.get(i).isPosFine()) {
rocks.remove(i);
i--;
}
}
Thread.sleep(17);
counter++;
}
}
}
The class below sets up the asteroids
public class Asteroids extends JPanel {
//These arrays store the coordinates of the asteroid
private int[] xPos = new int[8];
private int[] yPos = new int[8];
//Determines whether asteroid should be generated from top or bottom
private int[] yGen = {-100, 1100};
//Determines the direction the asteroid shold go
int genLevel;
/**
* #param g Graphics
* This method paints the asteroid
*/
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D r = (Graphics2D)g;
r.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
r.setColor(Color.decode("#52575D"));
r.fillPolygon(xPos, yPos, 8);
}
/**
* This constructor sets up the asteroid location points
*/
public Asteroids() {
int x = (int)(Math.random() * (700 - 1) + 100);
int y = yGen[(int)(Math.random() * (1 + 1 - 0))];
updateAsteroids(x, y);
genLevel = y;
System.out.println("Created!");
}
/**
* #param x int
* #param y int
* This method generates the asteroid based on the points passed in
*/
public void updateAsteroids(int x, int y) {
xPos[0] = x;
xPos[1] = x + 20;
xPos[2] = x + 40;
xPos[3] = x + 35;
xPos[4] = x + 40;
xPos[5] = x + 4;
xPos[6] = x - 16;
xPos[7] = x - 20;
yPos[0] = y;
yPos[1] = y + 7;
yPos[2] = y + 20;
yPos[3] = y + 40;
yPos[4] = y + 80;
yPos[5] = y + 70;
yPos[6] = y + 40;
yPos[7] = y;
}
/**
* This moves the asteroid
*/
public void move() {
int moveSpeedx = (int)(Math.random() * (10 - 1) + 1);
int moveSpeedy = (int)(Math.random() * (10 - 1) + 1);
for(int i = 0; i < 8; i++) {
if(genLevel > 0) {
xPos[i] -= moveSpeedx;
yPos[i] -= moveSpeedy;
}
else {
xPos[i] += moveSpeedx;
yPos[i] += moveSpeedy;
}
}
}
/**
* #return if the asteroid should be kept on the screen or not
*/
public boolean isPosFine() {
for(int i = 0; i < 8; i++) {
if(xPos[i] > 1250 || xPos[i] < -150)
return false;
if(yPos[i] > 1250 || yPos[i] < - 150)
return false;
}
return true;
}
}```
Your biggest problem that I can see is that you made your Asteroids class extend JPanel, making it much heavier weight than it should be, and making it difficult for more than one to show and for them to interact well and easily.
I recommend that you:
Make Asteroid a non-component logical class,
one that knows how to draw itself by giving it a public void draw(Graphics2D g2) method
one that knows how to move itself in response to your game loop's tick
Create one JPanel just for drawing the entire animation
Give this JPanel a collection of Asteroid objects, say in an ArrayList
In this JPanel's paintComponent, loop through all Asteroids in the collection, calling each one's draw(...) method
Drive the whole animation in a Swing thread-safe and controllable way using a Swing Timer. In this timer's actionPerformed, tell each asteroid to move, and then call repaint() on the drawing JPanel
Don't call .repaint() within the loop, but rather after the loop is finished
Create a small BufferedImage sprite from your Shapes, and draw those as the asteroid
A simple example illustrating what I mean:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Game2 extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 800;
private static final int TIMER_DELAY = 20;
private List<Asteroid2> asteroids = new ArrayList<>();
public Game2() {
setBackground(Color.BLACK);
int rows = 5;
int cols = 5;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
Asteroid2 asteroid = new Asteroid2();
asteroid.setX(j * (PREF_W / cols));
asteroid.setY(i * (PREF_H / rows));
asteroids.add(asteroid);
}
}
new Timer(TIMER_DELAY, e -> {
for (Asteroid2 asteroid2 : asteroids) {
asteroid2.move();
}
repaint();
}).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Asteroid2 asteroid : asteroids) {
asteroid.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
Game2 mainPanel = new Game2();
JFrame frame = new JFrame("Game2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class Asteroid2 {
private static final int[] POLY_X = { 20, 40, 60, 55, 60, 24, 4, 0 };
private static final int[] POLY_Y = { 0, 7, 20, 40, 80, 70, 40, 0 };
private static final Color ASTEROID_COLOR = Color.decode("#52575D");
private Image image;
private int x;
private int y;
public Asteroid2() {
Polygon poly = new Polygon(POLY_X, POLY_Y, POLY_X.length);
image = new BufferedImage(60, 80, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(ASTEROID_COLOR);
g2.fill(poly);
g2.dispose();
}
public void move() {
x++;
y++;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void draw(Graphics g) {
if (image != null) {
g.drawImage(image, x - 20, y, null);
}
}
}
I am done with randomizing circles, now my challenge is on adding circles to a straight line queue like the actual queue(i want the circles to queue from left to right preferably at the top of the Frame. When i call the removeCircle(), i want the circle to leave the queue and move down the frame. Can you help me with the addCircle() and removeCircle() methods. Thank you in advance.
public class AirTrafficLanding
{
private static void createAndShowUI()
{
PlanePanel panel = new PlanePanel(1);
JFrame frame = new JFrame("Air traffic Landing System");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.add( panel );
frame.pack();
frame.setVisible( true );
panel.startAnimation();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
static class PlanePanel extends JPanel implements ActionListener
{
private ArrayList<Plane> planes = new ArrayList<Plane>();
public PlanePanel(int planeCount)
{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setLayout( null );
setBackground( Color.BLACK );
Random random = new Random();
for (int i = 0; i < planeCount; i++)
{
Plane plane = new Plane();
plane.setRandomColor(true);
plane.setLocation(0, 700);
//plane.setLocation(random.nextInt(screenSize.width), random.nextInt(screenSize.height));
plane.setMoveRate(32, 32, 1, 1, true);
plane.setSize(32, 32);
planes.add( plane );
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Plane plane: planes)
{
plane.draw(g);
}
}
public void startAnimation()
{
Timer timer = new Timer(55, this);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
move();
repaint();
}
private void move()
{
for (Plane plane : planes)
{
plane.move(this);
}
}
}
static class Plane
{
public Color color = Color.BLACK;
public int x = 0;
public int y = 0;
public int width = 1;
public int height = 1;
private int moveX = 1;
private int moveY = 1;
private int directionX = 1;
private int directionY = 1;
private int xScale = moveX;
private int yScale = moveY;
private boolean randomMove = false;
private boolean randomColor = false;
private Random myRand = null;
public Plane()
{
myRand = new Random();
setRandomColor(randomColor);
}
public void move(JPanel parent)
{
int iRight = parent.getSize().width;
int iBottom = parent.getSize().height;
x += 5 + (xScale * directionX);
y += 5 + (yScale * directionY);
if (x <= 0)
{
x = 0;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (x >= iRight - width)
{
x = iRight - width;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (y <= 0)
{
y = 0;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
if (y >= iBottom - height)
{
y = iBottom - height;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
}
public void draw(Graphics g)
{
g.setColor(color);
g.fillOval(x, y, width, height);
}
public void setColor(Color c)
{
color = c;
}
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
}
public void setMoveRate(int xMove, int yMove, int xDir, int yDir, boolean randMove)
{
this.moveX = xMove;
this.moveY = yMove;
directionX = xDir;
directionY = yDir;
randomMove = randMove;
}
public void setRandomColor(boolean randomColor)
{
this.randomColor = randomColor;
switch (myRand.nextInt(3))
{
case 0: color = Color.ORANGE;
break;
case 1: color = Color.GREEN;
break;
case 2: color = Color.RED;
break;
default: color = Color.BLACK;
break;
}
}
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
}
}
I am expecting to start with zero circles in a queue and add one circle at 10 seconds interval. I want one circle to move from the queue to the bottom of the queue after every 15 seconds. I am done with randomizing circles, now my challenge is on adding circles to a straight line queue like the actual queue(i want the circles to queue from left to right preferably at the top of the Frame. When i call the removeCircle(), i want the circle to leave the queue and move down the frame. Can you help me with the addCircle() and removeCircle() methods. Thank you in advance.
Problem solving should be done one problem at a time. Don't attempt to write the entire program before you start testing.
So break your program down to the basics:
You need a Timer for the animation to move each object. So start by adding a single object to the ArrayList and then when the Timer fires you iterate through the ArrayList and move each object. It doesn't matter that there is only a single object in the ArrayList to start.
Then you create another Timer that fires every 15 seconds to add another object to the ArrayList.
Then you add logic to give this object a random color
Then you add logic to give this object a priority.
Once all the above is done you add another Timer to remove and object from the ArrayList.
The basic structure of your code needs to be changed.
You should NOT be using static variables. So you need to redesign the structure of your classes.
You should have a CirclePanel class. This class will contain the properties needed to paint the objects, so it will contain the ArrayList. It will also need methods like "addCircle()" and "removeCircle()". These methods will be invoke by the appropriate Timer.
You need to use a Swing Timer for all the Timers, not a util Timer. When the Swing Timer fires the code will execute single threaded on the Event Dispatch Thread which should prevent the ConcurrentModificationException from happening.
Check out: How do I paint multiple objetcs that move at different speeds in Java? for an example to help with the restructuring.
It doesn't do everything you need but it is a good start. It will animate an ArrayList of object. So you will need to modify the code to populate the ArrayList one object at a time and remove one object at a time.
Edit:
If you want to add more circles then you need another Timer. Using the original code I provide you with you can do something like:
public void startAnimation()
{
//Timer timer = new Timer(75, this);
//timer.start();
Timer timer = new Timer(1000, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
move();
repaint();
}
});
timer.start();
Timer timer2 = new Timer(3000, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
addBalls(1);
}
});
timer2.start();
}
So the first Timer animates the balls already in the ArrayList. The second Timer adds a new ball to the ArrayList.
I'll let you write the Timer to remove a ball from the ArrayList. I have already suggested this is a single statement in the ActionListener.
I am working on homework for class, and its late because I can't seem to understand the material despite all the research that I am doing. I am a beginner and do not know much in the way of java. Also, this is my first post so please be forgiving when you are reading this.
I am building on source code from my textbook, which I updated recently for past homework, but now I am trying to generate a class that draws multiple squares and moves those objects independently and at different speeds. They will all need to rebound off the walls as well. I followed the instructions and created two arrays that will hold the random x and y values between 1 and 10. However, I struggle with arrays and I am sure that I am not doing it correctly. So, I would love some feedback to see if I have it set up correctly.
I have a the jpanel pulling up and drawing, and as long as there is 1 square it is working fine bouncing off the walls, but things change when I draw more than one. The do not move independently and they also share the same speed. Some even disappear from time to time. This has really thrown me off. I appreciate any help!
In short, I am trying to paint new squares that all travel in different directions and at different speeds. Per the instructions we are suppose create and use a two arrays that handle the x and y values.
Here is what I have so far:
public class DotsPanel extends JPanel
{
private int delay = 15;
private final int SIZE = 7, IMAGE_SIZE = 3; // radius of each dot
private Timer timer;
private int x, y, i;
private ArrayList<Point> pointList;
static int [] xarray = new int [1000];
static int [] yarray = new int [1000];
Random rand = new Random();
//-----------------------------------------------------------------
// Constructor: Sets up this panel to listen for mouse events.
//-----------------------------------------------------------------
public DotsPanel()
{
pointList = new ArrayList<Point>();
int [] xarray = new int [1000];
int [] yarray = new int [1000];
timer = new Timer(delay, new ReboundListener());
addMouseListener (new DotsListener());
addMouseMotionListener (new DotsListener());
setBackground(Color.gray);
setPreferredSize(new Dimension(700, 500));
for(int i = 0; i < xarray.length; i++)
{
xarray[i] = rand.nextInt(7);
yarray[i] = rand.nextInt(7);
}
timer.start();
}
//-----------------------------------------------------------------
// Draws all of the dots stored in the list.
//-----------------------------------------------------------------
public void paintComponent(Graphics page)
{
super.paintComponent(page);
page.setColor(Color.BLUE);
for (Point spot : pointList)
{
page.fillRect(spot.x-SIZE, spot.y-SIZE, 25, 25);
page.drawString("Count: " + pointList.size(), 5, 15);
}
}
//*****************************************************************
// Represents the listener for mouse events.
//*****************************************************************
private class DotsListener implements MouseListener, MouseMotionListener
{
//--------------------------------------------------------------
// Adds the current point to the list of points and redraws
// the panel whenever the mouse button is pressed.
//--------------------------------------------------------------
public void mousePressed(MouseEvent event)
{
pointList.add(event.getPoint());
repaint();
}
public void mouseDragged(MouseEvent event)
{
// initially I had two xarray and yarray in here just like in
// mouseClicked
// but it did not change anything when removed
}
//--------------------------------------------------------------
// Provide empty definitions for unused event methods.
//--------------------------------------------------------------
public void mouseClicked(MouseEvent event)
{
xarray[i] = rand.nextInt(7);
yarray[i] = rand.nextInt(7);
}
public void mouseReleased(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
public void mouseMoved(MouseEvent e) {}
}
private class ReboundListener implements ActionListener
{
//--------------------------------------------------------------
// Updates the position of the image and possibly the direction
// of movement whenever the timer fires an action event.
//--------------------------------------------------------------
public void actionPerformed(ActionEvent event)
{
for (Point spot : pointList)
{
spot.x += xarray[i];
spot.y += yarray[i];
if (spot.x <= 0 || spot.x >= 700)
xarray[i] = xarray[i] * -1;
if (spot.y <= 0 || spot.y >= 500)
yarray[i] = yarray[i] * -1;
repaint();
}
}
}
}
However, I struggle with arrays and I am sure that I am not doing it correctly.
I wouldn't use Arrays.
Instead, have a Ball object manage its own state. Then you can have different color, speed, size etc for each Ball. Then when the Timer fires you just calculate the new position and repaint the Ball.
Here is an example to get you started:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class BallAnimation4
{
private static void createAndShowUI()
{
BallPanel panel = new BallPanel();
JFrame frame = new JFrame("BallAnimation4");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setSize(800, 600);
frame.setLocationRelativeTo( null );
//frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setVisible( true );
panel.addBalls(5);
panel.startAnimation();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
class BallPanel extends JPanel implements ActionListener
{
private ArrayList<Ball> balls = new ArrayList<Ball>();
public BallPanel()
{
setLayout( null );
setBackground( Color.BLACK );
}
public void addBalls(int ballCount)
{
Random random = new Random();
for (int i = 0; i < ballCount; i++)
{
Ball ball = new Ball();
ball.setRandomColor(true);
ball.setLocation(random.nextInt(getWidth()), random.nextInt(getHeight()));
ball.setMoveRate(32, 32, 1, 1, true);
// ball.setMoveRate(16, 16, 1, 1, true);
ball.setSize(32, 32);
balls.add( ball );
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Ball ball: balls)
{
ball.draw(g);
}
}
public void startAnimation()
{
Timer timer = new Timer(75, this);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
move();
repaint();
}
private void move()
{
for (Ball ball : balls)
{
ball.move(this);
}
}
class Ball
{
public Color color = Color.BLACK;
public int x = 0;
public int y = 0;
public int width = 1;
public int height = 1;
private int moveX = 1;
private int moveY = 1;
private int directionX = 1;
private int directionY = 1;
private int xScale = moveX;
private int yScale = moveY;
private boolean randomMove = false;
private boolean randomColor = false;
private Random myRand = null;
public Ball()
{
myRand = new Random();
setRandomColor(randomColor);
}
public void move(JPanel parent)
{
int iRight = parent.getSize().width;
int iBottom = parent.getSize().height;
x += 5 + (xScale * directionX);
y += 5 + (yScale * directionY);
if (x <= 0)
{
x = 0;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (x >= iRight - width)
{
x = iRight - width;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (y <= 0)
{
y = 0;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
if (y >= iBottom - height)
{
y = iBottom - height;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
}
public void draw(Graphics g)
{
g.setColor(color);
g.fillOval(x, y, width, height);
}
public void setColor(Color c)
{
color = c;
}
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
}
public void setMoveRate(int xMove, int yMove, int xDir, int yDir, boolean randMove)
{
this.moveX = xMove;
this.moveY = yMove;
directionX = xDir;
directionY = yDir;
randomMove = randMove;
}
public void setRandomColor(boolean randomColor)
{
this.randomColor = randomColor;
switch (myRand.nextInt(3))
{
case 0: color = Color.BLUE;
break;
case 1: color = Color.GREEN;
break;
case 2: color = Color.RED;
break;
default: color = Color.BLACK;
break;
}
}
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
}
}
Since your Arrays only contain the Point you want to paint you don't have any information about the speed each point should be moved at. The best you could do is create a random amount each point should be moved each time its location is changed. This would give erratic movement as each time you move a point the distance would be random.
If you want more constant speed then you would need to create a second Array to contain the distance each point should move every time.
This starts to get messy creating a new Array every time you want a new property to be unique for the object you want to paint. That is why the approach to create a custom Object with multiple properties is easier to manage.
I created an app that contains a square that bounces every time it touches an edge of the frame.I don't have issues lunching the app,the problem is that i don't know how to create various threads in order to have multiples squares inside the frame.
I tried multiple things but i can't figure out where i should create the threads.
I also noticed that the square is visible only when i add it directly inside the frame and not when i put it inside a JPanel.
Square.java
public class Square extends JComponent implements ActionListener {
int width = 20;
int height = 20;
double y = Math.random() * 360;
double x = Math.random() * 360;
boolean xMax = false;
boolean yMax = false;
boolean xMin = true;
boolean yMin = true;
Rectangle2D.Double square = new Rectangle2D.Double(x, y, width, height);
public Square() {
Timer t = new Timer(2, this);
t.start();
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g);
g2.setColor(Color.BLUE);
g2.fill(square);
x_y_rules();
}
public void x_y_rules() {
if (xMax == true) {
x = x - 0.5;
if (x <= 0) {
xMax = false;
}
} else {
x = x + 0.5;
if (x >= this.getWidth()) {
xMax = true;
}
}
if (yMax == true) {
y = y - 0.5;
if (y <= 0) {
yMax = false;
}
} else {
y = y + 0.5;
if (y >= this.getHeight()) {
yMax = true;
}
}
square.setFrame(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent arg0) {
repaint();
}
}
App.java
public class App extends JFrame {
public static void main(String[] args) {
JFrame jf = new JFrame();
Square sqr = new Square();
jf.setSize(400, 400);
jf.setVisible(true);
jf.add(sqr);
jf.setDefaultCloseOperation(EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
}
}
Is it normal that despite i put a time of 2 inside the Timer,the square moves very slowly?
Issues:
you've got program logic, the x_y_rules() method call, inside of the paintComponent method. Get it out as it does not belong there, and instead put it into the Timer's ActionListener code where it belongs.
you can give each Square its own Swing Timer if you want. This isn't really a threading issue since each Timer's ActionListener will run on the EDT.
Two milliseconds is an unrealistic time slice to expect to use in a Swing Timer and no timer will run that fast. 11 to 13 is about the fastest to expect or hope for.
if you want your sprite to move faster, give it a greater value for delta-x and delta-y in your movement code.
Your JComponent has no preferred size defined which is likely why it's not showing up in the JPanel, since the default FlowLayout will size it then to [0, 0]. Override its getPreferredSize() and have it return a reasonable Dimension value.
you're calling setVisible(true) on your JFrame before adding all components, a no-no.
Ok,i put a getPrefferedSize() inside the square class but i've encountered a problem: the squares are not "together",it's like they're bouncing on separate panels
Then your program structure is broken. You really don't want create separate Swing components, and in fact your Square class shouldn't extend JComponent or JPanel. Rather
Square should be a logical class, one that extends from nothing (other than default Object).
Give it a drawing method, say public void draw(Graphics g) {....}
Create one class that extends JPanel, say called DrawingPanel, and override its paintComponent method.
Give the DrawingPanel class an ArrayList<Square> so that it can hold multiple Square objects.
Give the DrawingPanel class a Swing Timer
In the DrawingPanel class's Timer, have it update the position of all the Squares in the ArrayList, and then call repaint()
In the paintComponent method, iterate through all the Squares in the list, using a for loop, and call each one's draw method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Color[] SQUARE_COLOR = { Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.BLACK, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.YELLOW };
List<Square> squareList = new ArrayList<>();
public DrawingPanel() {
// create a bunch of squares
for (int i = 0; i < SQUARE_COLOR.length; i++) {
squareList.add(new Square(SQUARE_COLOR[i], PREF_W, PREF_H));
}
setBackground(Color.WHITE);
// create and start the timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// simply draw all the squares in the list
for (Square square : squareList) {
square.draw(g);
}
}
// set size of JPanel
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// simply iterate through list and move all squares
for (Square square : squareList) {
square.move();
}
repaint(); // then repaint the GUI
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// this class does *not* extend JPanel or JComponent
class Square {
public static final int WIDTH = 20;
// location of Square
private double sqrX;
private double sqrY;
// X and Y speed
private double deltaX;
private double deltaY;
// width and height of DrawingPanel JPanel
private int dpWidth;
private int dpHeight;
// image to draw
private Image image;
public Square(Color color, int dpWidth, int dpHeight) {
this.dpWidth = dpWidth;
this.dpHeight = dpHeight;
// create square at random location with random speed
sqrX = Math.random() * (dpWidth - WIDTH);
sqrY = Math.random() * (dpHeight - WIDTH);
deltaX = Math.random() * 10 - 5;
deltaY = Math.random() * 10 - 5;
// one way to draw it is to create an image and draw it
image = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(0, 0, WIDTH, WIDTH);
g.dispose();
}
public void move() {
// check that we're not hitting boundaries
if (sqrX + deltaX < 0) {
deltaX = Math.abs(deltaX);
}
if (sqrX + deltaX + WIDTH >= dpWidth) {
deltaX = -Math.abs(deltaX);
}
sqrX += deltaX;
// check that we're not hitting boundaries
if (sqrY + deltaY < 0) {
deltaY = Math.abs(deltaY);
}
if (sqrY + deltaY + WIDTH >= dpHeight) {
deltaY = -Math.abs(deltaY);
}
sqrY += deltaY;
}
public void draw(Graphics g) {
int x = (int) sqrX;
int y = (int) sqrY;
g.drawImage(image, x, y, null);
}
}
I am currently using JCreator, and can not find what is wrong with my code, for some reason its not reading what i input in the JTextField. I am not looking to change my code drastically, if any1 could just point out what i did wrong, or give me some code examples of what it should look like, that would be great. again, not looking for "this is better then this" when they do the same thing.
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Wall extends JApplet implements ActionListener {
double count;
Boolean Submit = false;
JButton btn1;
JTextField tf,field;
int x = 0;
int y = 575;
int x1 = 50;
int y1 = 25;
int textnumber;
Random randomNum = new Random();//initialize random variable
int count2;
public void init() {
setLayout( new FlowLayout( ) );
tf = new JTextField(5);
field = new JTextField( "<===== Enter Brick Rows 1-20", 16 );
add( tf );
add( field );
btn1 = new JButton("Submit");
add(btn1);
btn1.addActionListener(this);
}
public void actionPerformed(ActionEvent event){
if (event.getSource()== btn1){
Submit = true;
}
}
public void paint(Graphics g, double BrickNumbers) {
super.paint(g);
while(Submit == true){
DrawBrick(g,BrickNumbers);
}
}
public void DrawBrick(Graphics g, double BrickNumbers){
String Value = tf.getText();
BrickNumbers = Double.parseDouble(Value);
if(Submit == true){
count = BrickNumbers;
for(double count = BrickNumbers; ((count>=1) && (count <=20)); count--){
int d = 1+ randomNum.nextInt(255);//get d variable
int e = 1+ randomNum.nextInt(255);//get e variable
int f = 1+ randomNum.nextInt(255);//get f variable
Color randomcolor = new Color(d,e,f);
g.setColor(randomcolor);
g.fillRect(x, y, x1, y1);
g.fillRect(x+ 50, y, x1, y1);
g.fillRect(x+100, y, x1, y1);
g.fillRect(x+150, y, x1, y1);
g.fillRect(x+200, y, x1, y1);
g.fillRect(x+250, y, x1, y1);
y = y - 25;
}
}
repaint();
}
}
You've got some bad code in your painting method including:
you've got a while (true) loop in your paint(...) method which will lock your GUI and prevent it from responding to anything.
You're trying to read in from the JTextField in a method called from the paint method. You should never have program logic in your painting code or in methods it calls.
You shouldn't be overriding paint(...) of a JApplet to begin with but rather paintComponent(...) in a JPanel that the JApplet holds.
Consider adding code to read the JTextField(s) in your actionPerformed method as that seems to be the best place for this logic.
Edit
Your paint method won't ever get called since it isn't a true overload of JApplet's paint method. Yours has 2 parameters and a paint method should only have one.
In your actionPerformed method, get the value from the JTextField,
convert it into an int with Integer.parseInt(...) not a double since you're never going to draw a fraction of a brick
and with the int obtained set an int class field, perhaps called brickCount or something like that, and then call repaint().
In your JPanel's paintComponent(...) method (which like paint should only have one parameter, Graphics), call paintBricks(), and have this method use the brickCount field value to decide how many bricks to paint.
Never call repaint() from within paint(...) paintComponent(...) or from any methods called from within these methods.
Edit 2
Here's an example that doesn't do what your program needs to do, but illustrates how to get information from a JTextField and use it in a drawing:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimpleApplet extends JApplet {
#Override
public void init() {
getContentPane().add(new SimpleAppletMainPanel());
}
}
class SimpleAppletMainPanel extends JPanel {
private static final Color CIRCLE_COLOR = Color.red.darker();
private static final int CIRCLE_STROKE_WIDTH = 10;
private static final int GAP = 3;
private static final Stroke CIRCLE_STROKE = new BasicStroke((float)CIRCLE_STROKE_WIDTH);
private JTextField textField = new JTextField(5);
private JButton myButton = new JButton("Submit");
private int count = 0;
public SimpleAppletMainPanel() {
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
try {
count = Integer.parseInt(textField.getText());
repaint();
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
};
myButton.addActionListener(actionListener);
textField.addActionListener(actionListener);
add(new JLabel("Enter a number, 1-10:"));
add(textField);
add(myButton);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < count; i++) {
drawCircle(g, i);
}
}
private void drawCircle(Graphics g, int layer) {
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = layer * (CIRCLE_STROKE_WIDTH + GAP) + GAP;
int x = centerX - radius ;
int y = centerY - radius;
Graphics2D g2b = (Graphics2D) g.create();
g2b.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2b.setStroke(CIRCLE_STROKE);
g2b.setColor(CIRCLE_COLOR);
g2b.drawOval(x, y, radius * 2, radius * 2);
g2b.dispose();
}
}
This will result in the following: