Trying to create disappearing squares in Java - java

class GraphicsExampleComponent extends JComponent
{
//#Override
public void paintComponent(Graphics g)
{
//make the first call to your recursive routine
drawSquare1(g, 0, 0, 80);
}
public void drawSquare1(Graphics g, int x, int y, int size)
{
//draw a rectangle
g.drawRect(x, y, size, size);
g.fillRect(x, y, size, size);
//reset the parameters
x = x + size + 10;
y = y + (size/4);
size = size/4;
//determine if you should call it again.
if (size<4 || x>600)
drawSquare1(g, x, y, size);
}
My assignment is to create disappearing squares that get 25% smaller as they move to the right. When I run the code, it just creates the one square and stops. Can anyone help me understand what I am doing wrong.

disappearing squares that get 25% smaller as they move to the right.
Ok, let's step back for a second and break this down a bit.
You need to know...
The amount of space to be covered
The amount of space already covered (by the square)
The original size of the square
The size of the square should be when it reaches the other side (25% smaller)
When you have all this, you can calculate the size of the square at any point along its journey.
To determine the amount of space, you can use the component's width, via getWidth().
To determine the space already covered, you could start by having a look at box's current x position
// Assuming that box is a instance of Rectangle
double progress = (double)box.x / (double)getWidth();
We could argue that we should look at the middle of the box, or the trailing edge, but both of those are easy to implement.
Next, we need know the range of change (from start to end size), we can then use that to calculate the delta to be applied to the box...
double range = startSize - endSize;
double value = (range * progress);
box.width = (int)startSize - (int)value;
box.height = (int)startSize - (int)value;
Soooo, this will provide with the means to determine the size of the component based on it's current location (horizontally) through the component.
Next, you need some way to update the box's position and update the UI.
One of the better solutions is to use a Swing Timer. This will allow you to perform a repeating action (with a specified delay between updates) which won't block the UI and will generate updates within the Event Dispatching Queue, which is important because Swing is not Thread safe.
Have a look at How to Use Swing Timers for more details.
And finally, all we need, is to update the component with current state, via it's paintComponent method ... easy :P
Runnable example
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
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());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
protected static double startSize = 50;
protected static double endSize = startSize * 0.25;
private Rectangle box;
private Timer timer;
public TestPane() {
box = new Rectangle(0, 100 - 25, 50, 50);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (box.x + box.width >= getWidth()) {
box.x = getWidth() - box.width;
box.width = (int)endSize;
box.height = (int)endSize;
timer.stop();
repaint();
}
box.x += 1;
double progress = (double)box.x / (double)getWidth();
double range = startSize - endSize;
double value = (range * progress);
box.width = (int)startSize - (int)value;
box.height = (int)startSize - (int)value;
repaint();
}
});
}
#Override
public void addNotify() {
super.addNotify();
box.x = 0;
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fill(box);
g2d.dispose();
}
}
}

The condition if (size < 4 || x>600) is never true because when drawSquare1 is invoked for the first time size=80 and x=0.
Changing it to say if (size > 4 && x<600) will paint 3 squares on the screen without any noticeable animation.
To animate it we'll need to add some delay between paintings and remove previously painted squares.
To do so we use a swing Timer. We use the timer to repeatedly invoke drawSquare1.
drawSquare1 should modify the parameters controlling the painting, and call repaint.
import java.awt.*;
import javax.swing.*;
public class Main {
Main() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
GraphicsExampleComponent board = new GraphicsExampleComponent();
frame.add(board);
frame.pack();
frame.setVisible(true);
board.animate();
}
public static void main(String[] args) {
new Main();
}
}
class GraphicsExampleComponent extends JComponent{
private final static int W = 200, H = 100, MIN_SIZE = 4, STEP = 10, DELAY = 2000;
private int x= 0, y = 0, size = 80;
private Timer timer;
void animate(){
timer = new Timer(DELAY, e->drawSquare());
timer.start();
}
public void drawSquare(){
//check stop criteria
if (size < MIN_SIZE || x >= getWidth()) {
timer.stop();
return;
}
//reset the parameters
x = x + size + STEP;
y = y + size/4;
size = size/4;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//draw a rectangle
g.fillRect(x, y, size, size);
g.dispose();;
}
#Override
public Dimension preferredSize() {
return new Dimension(W, H);
}
}
(Test in online here)

Thank you everyone for the help. I unfortunately could not use a timer due to us not learning about it in class and we are not allowed to use methods/codes that we haven't gone over. To fix this I had to fix my base case by making it size >= 4 && x < 600.

Related

Java - Pac-Man - GUI - Drawing Graphics issue, and general tips for an aspiring programmer

I am making Pac-Man and I'm having trouble with drawing graphics on a frame, when i draw my point image it looks like a game of snake, i tried putting my drawing methods for background and char both in the render method, but than my point image flickers
What it currently looks like, feel free to ignore the random face it was an inside joke.
Also this is my very first game so any tips on structure, pointers on what I am doing right (if anything) and what I'm doing wrong, and general tips would be extremely helpful!
Also I am aware that i have a couple unused methods
Code:
package game;
import graphics.map;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
public class main extends Canvas implements Runnable{
private static final long serialVersionUID = 1L; //not sure why it wanted me to do this, maybe ask bender, or just google it later
public static boolean running = false;
public static int HEIGHT = 800;
public static int WIDTH = 600;
public static int posX = 50;
public static int posY = 50;
public static final String name = "Pac Man Alpha 1.4";
private static final double speed = 1.2;
public input input;
static BufferedImage background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage pacman = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage settingsBackground = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level1 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level2 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage points = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage point = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static JFrame frame;
private input keypress = new input();
private map map;
private static boolean charLoaded = false;
public static boolean MAIN_MENU = true;
public static boolean GAME = false;
public static boolean level1test = true;
public static boolean level2test = false;
public static boolean level3test = false;
public static boolean level4test = false;
static boolean drawn = false;
public static boolean key_down;
public static boolean key_up;
public static boolean key_right;
public static boolean key_left;
//private Screen screen;
JButton startButton = new JButton("Start"); //Start
JButton settingsButton = new JButton("Settings"); //Settings
JButton exitButton = new JButton("Exit"); //Exit
public main()
{
setMinimumSize(new Dimension(WIDTH , HEIGHT ));
setMaximumSize(new Dimension(WIDTH , HEIGHT )); // keeps the canvas same size
setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame = new JFrame(name);
if(MAIN_MENU == true && GAME == false){
buttons(frame.getContentPane());
}
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ends program on
// close
frame.addKeyListener(new input() );
frame.add(this, BorderLayout.CENTER);
frame.pack(); // keeps size correct
frame.setResizable(false);
frame.setVisible(true);
this.addKeyListener(keypress);
}
public static void main(String[] args)
{
try {
background = ImageIO.read(new File("res\\Background.png"));
pacman = ImageIO.read(new File("res\\pacmansprites.png"));
settingsBackground = ImageIO.read(new File("res\\Background.png"));
level1 = ImageIO.read(new File("res\\level1.png"));
//level2 = ImageIO.read(new File("res\\level2.png"));
point = ImageIO.read(new File("res\\Points for pacman.png"));
} catch (IOException e) {
}
running = true;
new main().start();
}
public void run()
{
long lastTime = System.nanoTime();
double nsPerTick = 1000000000 / 60D;
long lastTimer = System.currentTimeMillis();
double delta = 0;
int frames = 0;
int ticks = 0;
while (running == true) {
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
boolean render = false;
while (delta >= 1) {
ticks++;
tick();
delta -= 1;
render = true;
}
try {
Thread.sleep(3); //keep the Frames from going to high
} catch (InterruptedException e) {
e.printStackTrace();
}
if(render == true){
frames++;
render();
}
if (System.currentTimeMillis() - lastTimer >= 1000) {
lastTimer +=1000;
//System.out.println("Frames: " + frames + " Ticks: " + ticks);
frames = 0;
ticks = 0;
}
}
}
public synchronized void start()
{
new Thread(this).start();
run();
}
public synchronized void stop()
{
running = false;
}
public void tick()
{
if (key_up) posY -= speed / 2;
if (key_down) posY += speed;
if (key_left) posX -= speed / 2;
if (key_right) posX += speed;
}
public void render()
{
drawn = false;
if(MAIN_MENU == false && GAME == true)
{
drawMap();
drawChar();
}
else if(MAIN_MENU == false && GAME == false) {
Graphics g = getGraphics();
{
g.drawImage(settingsBackground,0,0,getWidth(),getHeight(),null);
g.dispose();
}
} else {
Graphics g = getGraphics();{
g.drawImage(background,0,0,getWidth(), getHeight(),null);
g.dispose(); //kill it
}
}
}
public void drawMap(){
if(level1test == true){
Graphics g = getGraphics();
{
g.drawImage(level1,0,0,getWidth(),getHeight(),null);
g.dispose();
}
}
if(level2test == true && drawn == false){
Graphics g = getGraphics();
{
g.drawImage(level2,0,0,getWidth(),getHeight(),null);
}
g.dispose();
}
drawn = true;
}
public void drawChar(){
//drawMap();
Graphics g = getGraphics();{
g.drawImage(point,posX,posY,20, 20,null);
g.dispose();
revalidate();
}
}
public void begin() {
if (key_up) System.out.println("up");
if (key_down) System.out.println("down");
if (key_left) System.out.println("left");
if (key_right) System.out.println("right");
}
public void loadMap(){
if(!drawn && level1test){
}else if(!drawn && level2test){
//draw 2nd map here
}else if(!drawn && level3test){
//draw 3rd map here
}
}
public void buttons(Container pane)
{
pane.setLayout(null);
startButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = true;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
drawMap();
System.out.println("Start Button Clicked");
}
} );
settingsButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = false;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
frame.repaint();
System.out.println("Settings Button Clicked");
}
} );
exitButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.out.println("Exit Button Clicked");
System.exit(0);
}
} );
pane.add(startButton);
pane.add(settingsButton);
pane.add(exitButton);
Insets insets = pane.getInsets();
Dimension size = startButton.getPreferredSize();
startButton.setBackground(new Color(0, 0, 0));
startButton.setForeground(Color.CYAN);
startButton.setFocusPainted(false);
startButton.setFont(new Font("Calabri", Font.BOLD, 16));
settingsButton.setBackground(new Color(0, 0, 0));
settingsButton.setForeground(Color.RED);
settingsButton.setFocusPainted(false);
settingsButton.setFont(new Font("Calabri", Font.BOLD, 16));
exitButton.setBackground(new Color(0, 0, 0));
exitButton.setForeground(Color.YELLOW);
exitButton.setFocusPainted(false);
exitButton.setFont(new Font("Calabri", Font.BOLD, 16));
startButton.setBounds((WIDTH - 125) + insets.left, 10 + insets.top,
size.width + 50, size.height + 10);
settingsButton.setBounds((WIDTH - 125) + insets.left, 55 + insets.top,
size.width + 50, size.height + 10);
exitButton.setBounds((WIDTH - 125) + insets.left, 100 + insets.top,
size.width + 50, size.height + 10);
}
}
getGraphics is not how custom painting is done. You should, in your case, override the paint method, and make sure you call super.paint before doing any custom painting.
getGraphics returns the Graphics context last used to paint the component, which could be discarded on the next paint cycle, be null or no longer used by the component
Remember, painting uses the "painters canvas" approach, that is, just like painting in a physical canvas, when you paint into it, you paint over what was previously there, but not erasing it.
Now, if you override paint, you will find that you will have a flickering problem. This is because Canvas
is not double buffered
To solve this, you should consider user a BufferStrategy, which allows you to not only generate multiple buffers to paint to, but also to take control of the paint process itself
Just don't forget to clear each buffer before you paint to it...
Double buffering is the trick that allows you to have flicker-free animation. Basically you have two representations of your canvas, one that's currently being displayed and one that you can draw on. If you're finished with drawing, you copy the draw-canvas over the display-canvas. Depending on system and hardware there are more elegant ways where you can just tell the hardware to switch canvases (page flipping).
Without double buffering or a similar techniques, it is almost impossible to have flicker-free animation.
With double buffering you can afford to draw the background and then the foreground sprites. It is possibly more efficient to draw only those parts of the background that have been destroyed by the foreground sprites (there are various techniques for that as well, including of taking a snapshot image of the affected areas before you paint the sprites).
You can find a simple example for Java double buffering here. Java's BufferStrategy is a more complex solution that can use hardware features for page flipping.
I think the problem is that you only draw onto the image background, never erasing the old drawing from your image. You will need to clear the area and then start drawing in order to get your desired results.
I have never attempted to make a game but when I do simple animations I usually will do them on a JFrame or JPanel. With a JFrame you can Override the paint() method and with a JPanel, the paintComponent() method. It helps to keep everything that I'm drawing centralized, which makes it much easier for me to modify my code. When you call the respective super method in your overridden method, it will start you off with a clean slate, meaning you will have to paint the (image) background and your characters all over again. Calling the super method is also necessary to paint that component's children if you decide to add anything onto the JFrame/JPanel.
If you chose to use one of the above then I would recommend a JPanel due to it offering double buffering, which will help make your animations look smooth/fluid. Also, do not forget to call repaint();.
Here is a quick example, which can be used to duplicate your issue if you comment out super.paintComponent(g);.
*Note that I am extending and using a JPanel for this example.
Code:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Trial extends JPanel{
public static void main(String[] args)
{
new Trial();
}
int x = 5; // will represent the x axis position for our crude animation.
javax.swing.Timer timer = new javax.swing.Timer( 500, new ActionListener(){
// Timer used to control the animation and
// the listener is used to update x position and tell it to paint every .5 seconds.
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
if ( x > 250)
timer.stop();
repaint(); // will call the paintComponent method.
}
});
Trial()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(this);
frame.setSize(300, 200);
frame.setVisible(true);
timer.start();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g); // calling the super paintComponent method to paint children. can comment it
// out to duplicate your error, which happens when the frame isn't "refreshed".
Graphics2D g2d = (Graphics2D) g.create(); // creating a copy of the graphics object. // I do this to not alter the original
// Good practice and also Graphics2D
// offers a bit more to work with.
g2d.drawString("Graphics", x, 60); // painting a string.
g2d.drawRect(x, 80, 10, 10); // painting a rectangle.
}
}
Edit:
If you have a bunch of stuff to do and don't want to add it all into your paintComponent(); method you could create a method for various things and call them from inside your Overriden paint method, you could also pass them your graphics object. It will help you keep things relatively simple.

Why isn't my JFrame object animating as I would expect?

I'm trying to animate a circle as per a Head First Java exercise. The original exercise calls for using an internal "MyDrawPanel" class within the "AnimatedBall" class, but I'm trying to do an external MyDrawPanel class. I have no errors, but there also is no animation. I'm assuming it has something to do with me passing values to the MyDrawPanel constructor that don't update when I run my for loop, but I'd like to know why this is.
AnimatedBall2.java:
import javax.swing.JFrame;
public class AnimatedBall2 {
JFrame frame = new JFrame();
int height = 300;
int width = 300;
int x = 70;
int y = 70;
public static void main(String[] args){
AnimatedBall2 gui = new AnimatedBall2();
gui.go();
}
public void go(){
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyDrawPanel panel = new MyDrawPanel(x, y, height, width);
frame.getContentPane().add(panel);
frame.setSize(height,width);
frame.setVisible(true);
for(int i = 0; i < 100; i++){
x++;
y++;
panel.repaint();
try{
Thread.sleep(50);
} catch(Exception ex){}
}
}
}
MyDrawPanel.java:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class MyDrawPanel extends JPanel{
int x;
int y;
int height;
int width;
public MyDrawPanel(int x1, int y1, int z1, int z2){
x = x1;
y = y1;
height = z1;
width = z2;
}
public void paintComponent(Graphics g){
g.setColor(Color.white);
g.fillRect(0, 0, height, width);
g.setColor(Color.orange);
g.fillOval(x, y, 100, 100);
}
}
Swing is a single threaded framework, that is, there is a single thread which is responsible for processing all events within the system and scheduling painting.
Anything that blocks this thread, will prevent it from processing new events or paint requests.
This is a very bad idea...
public void go(){
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyDrawPanel panel = new MyDrawPanel(x, y, height, width);
frame.getContentPane().add(panel);
frame.setSize(height,width);
frame.setVisible(true);
for(int i = 0; i < 100; i++){
x++;
y++;
panel.repaint();
try{
Thread.sleep(50);
} catch(Exception ex){}
}
}
Add could potentially lock up your application...
Take a look at Concurrency in Swing for more details. You should also have a look at Initial Threads
In this case, I would recommended using a javax.swing.Timer, see How to use Swing Timers for more details.
The main problem is, you've decarled x and y in your AnimatedBall2 class...
public class AnimatedBall2 {
//...
int x = 70;
int y = 70
And in your MyDrawPanel class...
public class MyDrawPanel extends JPanel{
int x;
int y;
But your loop is updating the values in the AnimatedBall2 class, meaning that the values in MyDrawPanel never change
You will need to add an "update" method of some kind which can tell the MyDrawPanel that it should update it's x/y values accordingly...
Which brings us to...Swing is not thread safe, all modifications or interactions with the UI should be done within the context of the Event Dispatching Thread...This is why I'd recommend using a Swing Timer, as it triggers it's notifications within the context of the EDT.
When performing custom painting, you should call super.paintComponent before performing any custom painting of your own. You should also not relay on "magic" numbers
g.fillRect(0, 0, height, width);
The size of the component can be changed by the layout manager depending on it's needs, instead, you should be using getWidth and getHeight which will tell you exactly what the size of the current component is.
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AnimatedBall2 {
public static void main(String[] args) {
new AnimatedBall2();
}
public AnimatedBall2() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final MyDrawPanel panel = new MyDrawPanel();
JFrame frame = new JFrame("I'm a banana");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
Timer timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
panel.update();
}
});
timer.start();
}
});
}
public class MyDrawPanel extends JPanel {
int x;
int y;
int deltaX = 0;
int deltaY = 1;
public MyDrawPanel() {
x = 150;
y = 150;
setBackground(Color.WHITE);
}
public void update() {
x += deltaX;
y += deltaY;
if (x < 0) {
x = 0;
deltaX *= 1;
} else if (x > getWidth()) {
x = getWidth();
deltaX *= 1;
}
if (y < 0) {
y = 0;
deltaY *= -1;
} else if (y > getHeight()) {
y = getHeight();
deltaY *= -1;
}
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.orange);
g.fillOval(x - 50, y - 50, 100, 100);
}
}
}
for(int i = 0; i < 100; i++){
x++;
y++;
panel.repaint();
try{
Thread.sleep(50);
} catch(Exception ex){}
}
x and y are int types, not pointers. so when you do x++ and y++. it is actually not updating the x, y values which you passed in in the constructor

Delay is not working in java graphics

This is a code for drawing points on calculated locations by Bresenham's algorithm:
public void drawBresenhamPoints(Graphics2D g2, List<Point> bresenham) throws InterruptedException
{
Graphics2D g = (Graphics2D) g2;
if(bresenham == null)
return;
g.setColor(Color.DARK_GRAY);
for(int i = 0; i < bresenham.size(); i = i+20)
{
int x = bresenham.get(i).x - pointWidth1/2;
int y = bresenham.get(i).y - pointWidth1/2;
int ovalW = pointWidth1;
int ovalH = pointWidth1;
g.fillOval(x, y, ovalW, ovalH);
// delay
try
{
Thread.sleep(10);
}
catch(Throwable e)
{
System.out.println(e.getMessage());
}
}
}
The list 'bresenham' contains all the points which are pre-calculated with the help of Bresenham's line drawing algorithm. I want to set a delay of 1 second inside the 'for' loop so that each and every point is drawn after an interval of 1 second. The portion listed in the 'delay' section doesn't work. How to make 'delay' work?
More specifically, I want to see all the points being drawn one by one on the screen in an interval of 1 second.
I'm assuming you're calling this method in a paint/paintComponent method.
Just a pointer: Never ever ever sleep the paint process
Instead use a javax.swing.Timer for repeated tasks. What I would do is
Have two Lists. Your List<Point> bresenham and another List<Point> paintList. The bresenham will hold your data, and the paintList will be initially empty.
Use the paintList to paint your points
#override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
for (Point p : paintList) {
int x = bresenham.get(i).x - pointWidth1/2;
int y = bresenham.get(i).y - pointWidth1/2;
int ovalW = pointWidth1;
int ovalH = pointWidth1;
g.fillOval(x, y, ovalW, ovalH);
}
}
Though there's nothing initially in the paintList, you will add a new Point to the list every firing of a timer event.
Timer timer = new Timer(100, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if (bresenham.isEmpty()) {
((Timer)e.getSource()).stop();
} else {
paintList.add(bresemham.get(0));
bresenham.remove(0);
}
repaint();
}
});
timer.start();
The basic timer of the constructor is firs the delay, which is the time delayed between "iterations", and second argument in the listener that actually listens for the timer event that is fired every delay milliseconds. So what the code above basically does is add a Point to the paintList taken from the bresenham list, then removes the item the repaint which calls the paintComponent. When the list is empty, the timer will stop.
UPDATE
Here's a complete running example
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class BresenhamPoints extends JPanel {
private static final int D_W = 500;
private static final int D_H = 500;
private List<Point> bresenhamList;
private List<Point> paintList;
public BresenhamPoints() {
bresenhamList = createRandomPoints();
paintList = new ArrayList<>();
Timer timer = new Timer(100, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (bresenhamList.isEmpty()) {
((Timer) e.getSource()).stop();
} else {
paintList.add(bresenhamList.get(0));
bresenhamList.remove(0);
}
repaint();
}
});
timer.start();
}
private List<Point> createRandomPoints() {
Random rand = new Random();
List<Point> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(new Point(rand.nextInt(D_H), rand.nextInt(D_H)));
}
return list;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Point p : paintList) {
g.fillOval(p.x - 5, p.y - 5, 10, 10);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new BresenhamPoints());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
The value for the sleep method is in milliseconds, so there you are sleeping for 10ms. Changing it to 1000 will create a more noticeable interrupt.
As pointed out, you should never have any time consuming or even worse locking mechanisms on the EDT since it will hang your entire application. You could use a Timer to fire off events and draw one point at a time. This previous SO post should do what you need.

How do you create multiple animated components with timer/actionevents with two different time intervals on the same panel?

I need help with a simple animation assignment. It goes as follows.
I have two stop lights on a JPanel and the object is for the two of them to have different time intervals i.e there lights cycle at different times.
Everything works fine if I only have one light at a time. I am relatively new to this but I believe I know the problem.
In the code under this text, I use this several times. I believe my issue occurs in the public void cycle() method in which it just says this.repaint(); I have a feeling that the panel is being repainted at the two different time periods and it gives me a somewhat random light changing instead of a nice cycle.
Is there a way I can have these two components on the same JPanel with a more specific repaint method (maybe a bounding box around the individual light fixtures) or would creating separate panels be a better option (and a little help if that is the case because I understand the basic layouts but have never used them before).
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class DrawingPanel extends JPanel implements Lighter
{
// instance variables
private final int INTERVAL1 = 2000;
private final int INTERVAL2 = 5000;
private TrafficLight _light1, _light2;
private LightTimer _timer1,_timer2;
/**
* Constructor for objects of class DrawingPanel
*/
public DrawingPanel()
{
// initialise instance variables
super();
this.setBackground(Color.CYAN);
_light1 = new TrafficLight(50,50);
_light2 = new TrafficLight(200,50);
_timer1 = new LightTimer(INTERVAL1,this);
_timer2 = new LightTimer(INTERVAL2,this);
_timer1.start();
_timer2.start();
}
public void cycle(){
_light1.cycle();
_light2.cycle();
this.repaint();
}
public void paintComponent(Graphics pen)
{
super.paintComponent(pen);
Graphics2D aBetterPen = (Graphics2D)pen;
_light1.fill(aBetterPen);
_light2.fill(aBetterPen);
}
}
Running two timers in the fashion is achievable. Personally, I would write a "signal" class that controls a single light with it's own timing and painting routines, but that's not what you've asked.
What you need to do is maintain some kind of state variable for each signal and update them separately.
You would then need to modify the paint code to detect these states and take appropriate actions...for example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestBlink {
public static void main(String[] args) {
new TestBlink();
}
public TestBlink() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Timer blink1;
private Timer blink2;
private boolean light1 = false;
private boolean light2 = false;
public TestPane() {
blink1 = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
light1 = !light1;
repaint();
}
});
blink2 = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
light2 = !light2;
repaint();
}
});
blink1.start();
blink2.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int radius = 20;
int x = (getWidth() - (radius * 2)) / 2;
int y = (getHeight() - (radius * 2)) / 2;
Ellipse2D signal1 = new Ellipse2D.Float(x, y, radius, radius);
Ellipse2D signal2 = new Ellipse2D.Float(x + radius, y, radius, radius);
g2d.setColor(Color.RED);
g2d.draw(signal1);
if (light1) {
g2d.fill(signal1);
}
g2d.setColor(Color.GREEN);
g2d.draw(signal2);
if (light2) {
g2d.fill(signal2);
}
g2d.dispose();
}
}
}
Updated
A better solution (as I stated earlier) would be to contain all the logic for a single sequence in a single class. This isolates the painting and allows you to change the individual nature each sequence.
For example...
This is a simple example which uses a fixed rate of change, so each light gets the same about of time...
public class TraficLight01 extends JPanel {
public static final int RADIUS = 20;
private Timer timer;
private int state = 0;
public TraficLight01() {
timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
state++;
if (state > 2) {
state = 0;
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(RADIUS, (RADIUS + 1) * 3);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int radius = 20;
Ellipse2D light = new Ellipse2D.Float(0, 0, RADIUS, RADIUS);
int x = (getWidth() - radius) / 2;
int y = ((getHeight()- (radius * 3)) / 2) + (radius * 2);
Color color[] = new Color[]{Color.RED, Color.YELLOW, Color.GREEN};
for (int index = 0; index < color.length; index++) {
g2d.translate(x, y);
g2d.setColor(color[index]);
g2d.draw(light);
if (state == index) {
g2d.fill(light);
}
g2d.translate(-x, -y);
y -= radius + 1;
}
g2d.dispose();
}
}
Or you provide a variable interval for each light...
public static class TraficLight02 extends JPanel {
public static final int RADIUS = 20;
private Timer timer;
private int state = 0;
// Green, Yellow, Red
private int[] intervals = new int[]{3000, 500, 3000};
public TraficLight02() {
timer = new Timer(intervals[0], new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
timer.stop();
state++;
if (state > 2) {
state = 0;
}
timer.setInitialDelay(intervals[state]);
repaint();
timer.restart();
}
});
timer.start();
timer.setRepeats(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(RADIUS, (RADIUS + 1) * 3);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int radius = 20;
Ellipse2D light = new Ellipse2D.Float(0, 0, RADIUS, RADIUS);
int x = (getWidth() - radius) / 2;
int y = ((getHeight()- (radius * 3)) / 2) + (radius * 2);
Color color[] = new Color[]{Color.GREEN, Color.YELLOW, Color.RED};
for (int index = 0; index < color.length; index++) {
g2d.translate(x, y);
g2d.setColor(color[index]);
g2d.draw(light);
if (state == index) {
g2d.fill(light);
}
g2d.translate(-x, -y);
y -= radius + 1;
}
g2d.dispose();
}
}
It wouldn't take much to change these two concepts to seed them with variable intervals via a setter method.
Equally, you could use three Timers, each time one fires, you would simply start the next one. This way you could define a chain of timers, each one firing at different intervals after the completion of it's parent...
As a Note, your comment
it gives me a somewhat random light changing instead of a nice cycle.
What are you expecting it to look like?
With the Time intervals you have set it may appear somewhat random but it is actually working ie your intervals would work like this (well my assumptions of what the Interval variable are for)
Time(s) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Light1 ON OFF ON OFF ON OFF ON OFF
Light2 ON OFF ON

Swing timer - time fluctuating

I am using a Swing Timer in my game but when the game is running it appears to have moments when it runs smoothly and moments when it slows down.
Why is the time fluctuating?
And how do I fix it?
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main extends JFrame {
public Main() {
super("JFrame");
// you can set the content pane of the frame
// to your custom class.
setContentPane(new ImagePanel());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 400);
setResizable(false);
setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new Main();
}
class ImagePanel extends JPanel {
Timer movementtimer;
int x, y;
public ImagePanel() {
x = 0;
y = 0;
movementtimer = new Timer(12, new ActionListener() {
public void actionPerformed(ActionEvent e) {
long timstarted = System.currentTimeMillis();
moveImage();
repaint();
long timefinished = System.currentTimeMillis() - timstarted;
System.out.println(timefinished + " to run");
};
});
movementtimer.start();
}
public void moveImage() {
x++;
y++;
if (x > 800) {
x = 0;
}
if (y > 400) {
y = 0;
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillRect(0, 0, 800, 400);
g.setColor(Color.BLUE);
g.fillRect(x, y, 50, 50);
}
}
}
Here is an example of my code. In my actual program I am drawing Images and not just a rectangle. There is also a lot of collision detection and other small calculations happening.
Also, here is a link to the Jar file for the game so you can run it and (hopefull) see what I mean. http://dl.dropbox.com/u/8724803/Get%20To%20The%20Chopper%201.3.jar
Thanks
Tom
Because the rendering is trivial, I find this variation of your example to be very smooth. The render time is well below a half millisecond, so the 12 millisecond period (~83 Hz) is plenty of time to finish a frame, typically taking less that 10% of one core. As the render time grows, the timer thread becomes saturated, and events are coalesced. The effect is magnified on a single core, as rendering competes with garbage collection and external processing demands. Java is not a real-time system, and not all schedulers are created equal.
You'll certainly want to profile your actual code, as suggested here, to see any correlation with fluctuating performance. One alternative approach is to lengthen the period (decrease the frequency) to meet your rendering deadline and use a larger increment in moveImage() to get the same velocity.
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main extends JFrame {
private static final int W = 800;
private static final int H = 400;
public Main() {
super("JFrame");
this.add(new ImagePanel());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
setSize(W, H);
this.setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Main();
}
});
}
class ImagePanel extends JPanel {
Timer movementTimer;
int x, y;
public ImagePanel() {
x = 0;
y = 0;
movementTimer = new Timer(12, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
moveImage();
repaint();
}
});
movementTimer.start();
}
public void moveImage() {
x++;
y++;
if (x > W) {
x = 0;
}
if (y > H) {
y = 0;
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
long start = System.nanoTime();
g.setColor(Color.RED);
g.fillRect(0, 0, W, H);
g.setColor(Color.BLUE);
g.fillRect(x, y, 50, 50);
double delta = (System.nanoTime() - start) / 1000000d;
g.drawString(String.format("%1$5.3f", delta), 5, 15);
}
}
}
The Swing Timer is notorious for its inaccuracy. Use something else instead.
On prompting, I've decided to undelete this post. OTOH most of the extra information that makes it worth reinstating comes from trashgod, so I'll merely quote/paraphrase their comments.
I'd argue that it's reasonably accurate but easy to saturate.
And trashgod goes on to add in a separate comment that:
(I) might cite Clock Quality. javax.swing.Timer isn't very accurate, but it has usefully precise resolution on modern platforms.
In the panel constructor do:
setBackground(Color.RED);
Then you do not need to erase the background, as you are calling super.paintComponent.
In general calculate positions on actual time passed (System.nanoTime()) and do not rely on timer frames. There are a couple of gaming frameworks out there, so maybe it is worth looking at their solution. I liked the sample.

Categories