Better way to make a thread sleep - java

I've been doing a tutorial on 2D graphics in Java 8, when NetBeans gave me a hint that doing Thread.Sleep would affect performance. However, though I've been able to find several better ways, I haven't been able to find a way to include them without messing you the code.
package platformer;
import java.awt.*;
import javax.swing.*;
import java.util.Scanner;
#SuppressWarnings("serial")
public class Platformer extends JPanel {
int x = 0;//Sets the starting coords. of the ball
int y = 0;
private void moveBall() {//How much the ball moves by
x = x+1;
y = y+1;
}
#Override
public void paint(Graphics g) {//Essentially al the graphics functions
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(x, y, 30, 30);
}
public static void main(String[] args) throws InterruptedException {
Scanner reader = new Scanner(System.in);
Scanner reader1 = new Scanner(System.in);
System.out.print("Enter an x-value for the window (whole numbers only): ");
int setx = reader.nextInt();
System.out.print("Enter a y-value for the window (whole numbers only): ");
int sety = reader.nextInt();
JFrame gameFrame = new JFrame("Sample Frame");//Makes the window variable w/ the name in quotations.
Platformer game = new Platformer();//'Copies' the graphics functions above into a variable
gameFrame.add(game);//Adds the above variable into th window
gameFrame.setSize(setx,sety);//Sets the resolution/size of the window(x,y)
gameFrame.setVisible(true);//Makse the window visible
gameFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//Makes the window close when the close button is hit
while (true){
game.moveBall();
game.repaint();
Thread.sleep(10);
}
}
}
I'm wondering how I can include a better way to make the thread sleep in a loop, or otherwise make NetBeans just do the loop.

though I've been able to find several better ways
Well don't you think you should tell us the ways you have found so we don't spend time making suggetions you already know about? We are not mind readers we can't guess what you have tried.
I haven't been able to find a way to include them without messing u the code.
Well, your code should be redesigned anyway.
The animation of your code should be a function of your game panel, not the main() method. So you should have methods like startGame() and stopGame() built into your panel that you can invoke.
Netbeans gave me a hint that doing thread.sleep would affect performance.
Yes in general you should not use Thread.sleep() because usually code is executed as a result of some user Action and the code executes on the Event Dispatch Thread (EDT). Since the EDT is responsible for painting the GUI is you keep telling it to sleep then this obviously will affect performance.
However, in your case the looping logic is not executed on the EDT so it should not be an issue (other than the overall design issue).
In reality you should not be using a loop. Instead you should be using a Swing Timer to schedule the animation.
Also check out the section from the tutorial on Concurrency in Swing which will explain more about the EDT and why Swing components should be updated on the EDT.

Instead of using Thread.sleep(10) in a while(true), you can use ScheduledExecutorService to invoke moveBall() and paint() for every 10 seconds as shown below which gives much cleaner way as shown below:
public class Platformer extends JPanel implements Runnable {
int x = 0;//Sets the starting coords. of the ball
int y = 0;
public void add() {
JFrame gameFrame = new JFrame("Sample Frame");//Makes the window variable w/ the name in quotations.
Platformer game = new Platformer();//'Copies' the graphics functions above into a variable
gameFrame.add(game);//Adds the above variable into th window
gameFrame.setSize(setx,sety);//Sets the resolution/size of the window(x,y)
gameFrame.setVisible(true);//Makse the window visible
gameFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//Makes the window close when the close button is hit
}
private void moveBall() {//How much the ball moves by
x = x+1;
y = y+1;
}
#Override
public void paint(Graphics g) {//Essentially al the graphics functions
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(x, y, 30, 30);
}
#Override
public void run() {
//this code will be executed for every 10 seconds
moveBall();
paint();
}
public static void main(String[] args) throws InterruptedException {
Scanner reader = new Scanner(System.in);
Scanner reader1 = new Scanner(System.in);
System.out.print("Enter an x-value for the window (whole numbers only): ");
int setx = reader.nextInt();
System.out.print("Enter a y-value for the window (whole numbers only): ");
int sety = reader.nextInt();
JFrame gameFrame = new JFrame("Sample Frame");//Makes the window variable w/ the name in quotations.
Platformer game = new Platformer();//'Copies' the graphics functions above into a variable
gameFrame.add(game);//Adds the above variable into th window
gameFrame.setSize(setx,sety);//Sets the resolution/size of the window(x,y)
gameFrame.setVisible(true);//Makse the window visible
gameFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//Makes the window close when the close button is hit
//schedule it to run for every 10 seconds
//this calls the run() method above
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(game, 0, 10, TimeUnit.SECONDS);
}
}

Related

Java JFrame issue with dialog box

When I run the program I'm facing some problem with JFrame Buffer, I don't know what the problem exactly is. When I run the program, it displays some dialog box part on the top left corner of the buffer.
Here is output of my program:
And following is the code
Thank you.
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class Main extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int[] x1 = new int[10];
int[] y1 = new int[10];
int i,n;
Polygon p=new Polygon();
n = Integer.parseInt(JOptionPane.showInputDialog("Enter no. of co-ordinates of polygon: "));
System.out.println(" no. of co-ordinates of polygon are :"+n);
for(i=0;i<n;i++)
{
x1[i] = Integer.parseInt(JOptionPane.showInputDialog("Enter x co-ordinates of polygon: "));
y1[i] = Integer.parseInt(JOptionPane.showInputDialog("Enter y co-ordinates of polygon: "));
}
for(i=0;i<n-1;i++)
{
g.drawLine(x1[i],y1[i],x1[i+1],y1[i+1]);
}
g.drawLine(x1[n-1],y1[n-1],x1[0],y1[0]);
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setTitle("Polygon");
frame.setSize(500,500);
Container contentPane = frame.getContentPane();
contentPane.add(new Main());
frame.setVisible(true);
}
}
Never display an JOptionPane from a painting method. Painting methods are for painting only, they are not for getting user input.
Instead you need to do the following:
The JOptionPane should be displayed from the main method to gather the x/y parameters.
Modify your Main() class to have a method like addPoint(int x, int y).
The above method will then save the x/y values to an ArrayList object in your class. I would store Point objects in this list.
The painting method will then iterate through the List and then paint each line.
The paintComponent( ... ) method is called whenever 'something' (the AWT EDT thread) thinks the component needs to be repainted. That method is called often and often at moments you didn't expect it to be called. So, don't show a JOptionPane in the body of that method.
So it is better to call from main method only as camickr said.

How do I make the lines draw without an actionlistener?

I am writing a GUI that is supposed to write lines and circles to a panel and I am supposed to use sliders to change how fast they add to the panel. I am supposed to add a clear button that will clear the entire panel and then when I move the sliders they should make the circles and lines begin to write on the panel again. There should be a specific stop point at the beginning of the sliders. We have been told to do this without actionlisteners on the sliders. I am having some trouble understanding how to make that work.
Below are the requirements for the assignment:
Write a Swing program that provides the following functionality:
Draw random length lines of random color at random coordinates with pauses between the drawing of each line.
Allow the user to set the length of the pause between lines with a slider. Have the slowest value actually stop drawing lines (i.e., it slows to a stop once it is at that value on the slider).
Have a clear button that clears all the lines & circles. Be sure that the clear button is operational at all times.
Draw random size circles of random color at random coordinates with pauses between the drawing of each circle. (Use draw, not fill.)
Allow the user to set the length of the pause between circles with a slider. Have the slowest value actually stop drawing circles (i.e., it slows to a stop once it is at that value on the slider). This is independent of the lines' speed.
The circles and lines are both drawn independently, each in their own Thread.
Do not use Timer for this, extend Thread and/or Runnable.
public class OhMy extends JFrame
{
private static final int MAX_COLOR = 225;
private static final long STOP_SLEEP = 0;
public OhMy()
{
this.setTitle("Oh My Window");
Container canvas = this.getContentPane();
canvas.setLayout(new GridLayout(2,1));
JPanel panControl = new JPanel(new GridLayout(1,1));
JPanel panDraw = new JPanel(new GridLayout(1,1));
canvas.add(panControl);
canvas.add(panDraw);
panControl.add(createPanControl());
panDraw.add(createPanDraw());
this.setSize(800, 600);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private JPanel createPanControl()
{
JPanel panControl = new JPanel();
JLabel lines = new JLabel("Lines");
panControl.add(lines);
lines.setForeground(Color.RED);
JSlider sldSpeedLines = new JSlider(1, 30, 5);
panControl.add(sldSpeedLines);
JButton btnClear = new JButton("Clear");
panControl.add(btnClear);
btnClear.setForeground(Color.RED);
JSlider sldSpeedCircles = new JSlider(0, 30, 5);
panControl.add(sldSpeedCircles);
JLabel circles = new JLabel("Circles");
panControl.add(circles);
circles.setForeground(Color.RED);
btnClear.addActionListener((e)->
{
repaint();
});
return panControl;
}
private JPanel createPanDraw()
{
JPanel panDraw = new JPanel();
class LinesThread extends Thread
{
#Override
public void run()
{
try
{
Graphics g = panDraw.getGraphics();
while(g == null)
{
Thread.sleep(STOP_SLEEP);
g = panDraw.getGraphics();
}
Random rand = new Random();
int red = rand.nextInt(MAX_COLOR);
int green = rand.nextInt(MAX_COLOR);
int blue = rand.nextInt(MAX_COLOR);
Color color = new Color(red, green, blue);
int x1 = rand.nextInt(panDraw.getWidth());
int y1 = rand.nextInt(panDraw.getHeight());
int x2 = rand.nextInt(panDraw.getWidth());
int y2 = rand.nextInt(panDraw.getHeight());
g.setColor(color);
g.drawLine(x1, y1, x2, y2);
}
catch(InterruptedException e1)
{
//awake now
}
}
}
return panDraw;
}
/**
* #param args
*/
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new OhMy();
}
});
}
}
You state:
"We have been told to do this without actionlisteners on the sliders..."
Good, because JSliders won't accept an ActionListener.
JSliders will accept a ChangeListener though, but you likely don't even need to use this.
Instead, give the clear button an ActionListener (you've no way to get around using ActionListeners at all).
In that ActionListener, reset the drawing and get the values from the JSliders by simply calling getValue() on it.
Don't get your Graphics object by calling getGraphics() on the JPanel since the Graphics object thus obtained will not be stable risking a broken image, or worse, a NullPointerException (to see what I mean, minimize and restore your current application while its drawing).
Instead either draw on a BufferedImage that is displayed in the JPanel's paintComponent method or draw directly in paintComponent itself.
Avoid using a Thread and Thread.sleep, but instead use a Swing Timer -- it's much easier this way to be sure that your code is threading appropriately.
Use this value to adjust the speed of your Swing Timer.
Edit
Thanks to Abishek Manoharan for pointing out problems in my answer...
If the JSliders need to change the speed of drawing while the drawing is proceeding, then you will in fact need to use ChangeListener on the slider.
In that listener change a field that will tell the Thread how long to sleep.
I see that you're also required to use background threads. If so, then be sure to make all Swing calls on the Swing event thread. So if you're in the background thread and need to make a Swing call, then queue it on the Swing event thread by calling SwingUtilities.invokeLater(...) and pass in a Runnable that has your Swing call.

Java: Animated Sprites on GridLayout Part 2

This is a continuation from my last post Java: Animated Sprites on GridLayout. Thanks to a reply, it gave me an idea in where I just had to insert a loop in the trigger condition and call pi[i].repaint() in it. So far it works. Though I tried to integrate it to my game which composed of multiple sprites, it had no improvement in it. Without the animation, the sprites show on the grid with no problems. I inserted the animation loop in the GridFile class and it didn't show. I also tried to insert the animation loop in the MainFile, it showed irregular animations, kinda like a glitch. Can someone tell me where did I went wrong? Ideas are welcome.
MainFile class
public class MainFile {
JFrame mainWindow = new JFrame();
public JPanel gridPanel;
public MainFile() {
gridPanel= new GridFile();
mainWindow.add(gridPanel,BorderLayout.CENTER);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setSize(700,700);
mainWindow.setResizable(false);
mainWindow.setVisible(true);
}
public static void main(String[]args){
new MainFile();
}
}
GridFile class
public class GridFile extends JPanel{
ImageIcon gameBackground = new ImageIcon(getClass().getResource("Assets\\GridBackground.png"));
Image gameImage;
int[] pkmArray = new int[12];
int random = 0;
Pokemon[] pkm = new Pokemon[36];
JPanel[] pokeball = new JPanel[36];
int j = 0;
public GridFile(){
setLayout(new GridLayout(6,6,6,6));
setBorder(BorderFactory.createEmptyBorder(12,12,12,12));
gameImage = gameBackground.getImage();
for(int i = 0;i < 36;i++){
do{
random = (int)(Math.random() * 12 + 0);
if(pkmArray[random] <= 3){
pokeball[i] = new Pokemon(random);
pokeball[i].setOpaque(false);
pokeball[i].setLayout(new BorderLayout());
pkmArray[random]++;
}
}while(pkmArray[random] >= 4);
add(pokeball[i],BorderLayout.CENTER);
}
while(true){
for(int i = 0; i < 36; i++){
pokeball[i].repaint();
}
}
}
public void paintComponent(Graphics g){
if(gameImage != null){
g.drawImage(gameImage,0,0,getWidth(),getHeight(),this);
}
}
}
Use a swing Timer for the repainting, and give a bit time between the frames for swing to do the painting work. There's no point trying to draw faster than what could be displayed anyway. If you have the animation loop in main(), the repaint manager will try to drop some of the repaint requests that appear close to each other, which can be the cause of the irregular animation you see.
You should create and access swing components only in the event dispatch thread. You current approach is breaking the threading rules.
Addition: When the animation loop is where you have it now, the GridFile constructor never returns, which explains that you'll see nothing because the code never gets far enough to show the window.

Java repaint issue-seeing ovals each move

Heres part of my simple code.I want to achieve moving oval to cursors X axis location after clicking left button.Problem is that I can see only last position of oval (when it already stops).I think repaint method in while block doesnt work as I would like.I would like to see each move of oval as its getting into position of cursor.Thank you for suggestions.
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
public class Testfile extends JPanel implements Runnable,MouseListener{
public static JFrame frame;
public int x;
public int y;
public int pointX;
public int pointY;
public void paintComponent(Graphics g){
super.paintComponent(g);
g.fillOval(x, y, 20, 20);
}
public static void main(String args[])throws InterruptedException{
Testfile z=new Testfile();
z.setBackground(Color.cyan);
frame=new JFrame("Test");
frame.setSize(500,500);
frame.add(z);
frame.addMouseListener(z);
frame.setVisible(true);
}
public void mouseClicked(MouseEvent e){
pointX=(int)MouseInfo.getPointerInfo().getLocation().getX();
pointY=(int)MouseInfo.getPointerInfo().getLocation().getY();
try{
while(x!=pointX){
x=x+1;
Thread.sleep(10);
repaint();
}
}
catch(InterruptedException v){System.out.println(v);}
}
.I think repaint method in while block doesnt work as I would like
Your problem has nothing to do with repaint "not working" and all to do with your tying up the Swing event thread. If you run a long-running process on the Swing Event Dispatch Thread (or EDT), the thread responsible for painting the GUI and interacting with the user, your GUI freezes and won't paint itself or respond until the EDT is released.
Solution: don't use a while (true) loop or Thread.sleep(...) on the Swing event thread. Also:
Use a Swing Timer instead to act as your animation "loop".
Another possible solution is to use a background thread to do the Thread.sleep(...), but in my opinion this is not worth the trouble since a Swing Timer will work so well and is easier to implement correctly.
Also:
Don't add your MouseListener to the JFrame but rather to your drawing JPanel. Otherwise you'll find that you'll be off in the y direction by the height of the title bar.
Use the mousePressed(...) method not mouseClicked(...) since the former is more forgiving.
Get the deltaX and deltaY on mousePressed, the direction that the circle should go by subtracting x from pointX and y from pointY.
I've gotten your code to work by checking the Manhattan distance between x and y and pointX (manHattanDistance = Math.abs(x - pointX) + Math.abs(y - pointY);) and pointY, and stopping the timer if it gets below a minimum. I've also saved the prior Manhattan distance and have checked the differences between the old and new one to make sure that the oval doesn't over-shoot, kind of as a fail-safe.
Use doubles to hold your x, y, pointX, pointY, etc, and cast to int when drawing.
Don't forget to cast your Graphics object to a Graphics2D and use RenderingHints to turn antialiasing on. This will make for prettier graphics.
Avoid "magic" numbers. Use constants instead.
Consider using x and y for the center of your circle rather than the left upper corner.
For example, my paintComponent(...) method could look like:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// RADIUS is an int const and = 10
g.fillOval((int) x - RADIUS, (int) y - RADIUS, 2 * RADIUS, 2 * RADIUS);
}
As above. you need to do these things on separate threads.
Sometimes you will need to decrement x so check if it's already bigger than the the point
clicked, or the program will keep incrementing it indefinitely. Also you'll probably want to do the same with y

double buffering and paint

I'm using both double buffering and Swing Events which seem to conflict. I'm using a JSlider and trying to double buffer. It actually does the double buffering draw, but the double buffering gets painted again and I lose my image. I'm using a JSlider to do the double buffering draw, and the event system seems to re-draw the frame (with 2 components, an image and the slider). How do I do this the right way? I've tried setting a repaint variable to signal not to repaint in the component but this does not work. Is there some sort of event switch to stop repainting of certain components? Should I not use double buffering?
Here's a code snippet.
private void drawOneByOne(ImageComponent imgComponent, JFrame f,
MapObjects mapObjects, int number) {
f.createBufferStrategy(2);
BufferStrategy bufferStrategy = f.getBufferStrategy();
Graphics2D g = (Graphics2D)bufferStrategy.getDrawGraphics();
bufferStrategy = f.getBufferStrategy();
g = (Graphics2D)bufferStrategy.getDrawGraphics();
// draw the map and then the points
imgComponent.paint(g);
for (int i = 0; i < number; i++) {
imgComponent.drawPoint(mapObjects.get(i),g);
}
imgComponent.repaint = false;
bufferStrategy.show();
g.dispose();
imgComponent.repaint = true;
}
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider)e.getSource();
if (!source.getValueIsAdjusting()) {
int voterNumber = source.getValue();
System.out.println("Drawing One By One, " + voterNumber);
drawOneByOne(this.imgComponent, this.f, this.mapObjects, voterNumber);
}
}
.
.
.
Swing is easily doublebuffered via setDoubleBuffered(true) if that is all you wanted to achieve. There is nothing wrong with double-buffering, it just uses more memory, call it on the parentcontainer of your swingcomponents. You have no control over the repaint, the OS calls it whenever it deems necessary unless you call setIgnoreRepaint on the JFrame.

Categories