2

I'm attempting to create a game and I'm starting with the loading screen, where it will load all the needed files. I want to show a percentage of completion but its not working. The JPanel Loading only repaints() after Loading_files is finished. I don't know whats wrong.

Main.java

package Main;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main extends JFrame{

//-------------- Final Variables --------------
//Screen Related
public final static int JFRAME_WIDTH = 800;
public final static int JFRAME_HEIGHT = 600;
public final static Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize();
public final static int SCREEN_WIDTH = SCREEN_SIZE.width;
public final static int SCREEN_HEIGHT = SCREEN_SIZE.height;
//Game Related
public final static String NAME = "Xubris";
public final static String IMAGE_DIRECTORY = System.getProperty("user.dir") + "\\images\\";
public static final int FPS = 1000 / 36;

//-------------- Dynamic Variables --------------
//Global
public static JFrame main;

public static void main(String[] args) {
    //Start the Loading screen
    Loading load = new Loading();

    main = new JFrame(NAME);

    main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    main.setSize(JFRAME_WIDTH, JFRAME_HEIGHT);
    main.setLocation((SCREEN_WIDTH-JFRAME_WIDTH)/2, (SCREEN_HEIGHT-JFRAME_HEIGHT)/2);

    //Add Content
    main.getContentPane().add(load);

    main.setResizable(false);
    main.setUndecorated(false);
    main.setVisible(true);  
}

}

Loading.java

package Main;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class Loading extends JPanel implements Runnable{

//Pictures
BufferedImage background;

//-------------- Final Variables --------------
private final int num_of_files = 32;

//-------------- Dynamic Variables --------------
//Global Variables
private double loading_percentage = 0;
private int num_of_loaded_files = 0;

public Loading(){
    try {
        background = ImageIO.read(new File(Main.IMAGE_DIRECTORY + "Background_Loading.jpg"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    //Start the thread
    new Thread(new LoadingFiles()).start();
    new Thread(this).start();
}

public void paint(Graphics g){
    g.drawImage(background, 0, 0, this);
    for(int i = 0; i < loading_percentage; i=i+15){
        g.setColor(new Color(20,241,47));
        g.drawRect(180 + (i/15)*(50+10), 375, 50, 50);
    }
}

@Override
public void run(){
    while(true){
        repaint();
    }
}

class LoadingFiles implements Runnable{
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num_of_loaded_files++;
        loading_percentage = (num_of_loaded_files/num_of_files)*100;

        if(num_of_loaded_files!=32)
            run();
    }

}
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Eric Sauer
  • 870
  • 4
  • 14
  • 27
  • 3
    Take a look at making a [splash screen](http://stackoverflow.com/questions/9997509/designing-a-splash-screen-java) in java. The `while(true) repaint();` is probably a bad idea, also... – Rob I Aug 11 '12 at 23:55
  • 3
    Also, the loop in `paint()` will be what prevents you from repainting more than once. You need to reschedule repaints another way - for example, using `SwingUtilities.invokeLater()`. – Rob I Aug 12 '12 at 00:00
  • 3
    Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens. Instead of calling `Thread.sleep(n)` implement a Swing `Timer` for repeating tasks or a `SwingWorker` for long running tasks. See [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/) for more details. – Andrew Thompson Aug 12 '12 at 02:18
  • A thought to why your **repaint()** request inside the `while loop` not working is this fact : ***It is important to note that repaint requests get “coalesced,” or combined. So, for example, if you request a repaint and there is already one on the queue that has not yet been serviced, then the second request is ignored because your request for a repaint will already be fulfilled by the earlier request.*** – nIcE cOw Aug 12 '12 at 05:08
  • ***This behavior is particularly helpful in situations where many repaint requests are being generated, perhaps by very different situations and components, and Swing should avoid processing redundant requests and wasting effort.*** – nIcE cOw Aug 12 '12 at 05:09
  • @GagandeepBali my first guess as well - but not true :-) Add a MouseListener to a panel with such a tight repaint loop, it will be sluggish but not completely cut off. – kleopatra Aug 12 '12 at 09:31
  • @AndrewThompson I regard the sleep here as a simulation of the actual loading process. As it doesn't happen on the EDT, all should be fine :-) – kleopatra Aug 12 '12 at 09:34
  • @RobI a repaint is fine to call from whatever thread (the actual paint requests gets scheduled on the EDT internally) – kleopatra Aug 12 '12 at 09:36

1 Answers1

7

Blocking the EDT and repaint coalescing where my first ideas as well (that's why I upvoted the comments). But got curious - neither is the real problem :-)

  • it's perfectly valid to call repaint from whatever thread (though doing so in a tight while-loop certainly slows down ui reactivity)
  • doesn't repaint as expected even after some cleanup (see below). The culprit for that is purely arithmetic

the following line:

loading_percentage = (num_of_loaded_files/num_of_files)*100;

which is 0 until

num_of_loaded_files == num_of_files

that is until everything loaded. We all are guilty of jumping to conclusions :-)

Some cleanup:

  • following java naming conventions makes code easier to read
  • don't override paint, instead override paintComponent
  • always call super in paintComponent (by default a JPanel reports to be opaque, that is, it must fill its area)
  • no need for intertwined threads, simply let the loading thread call repaint after having loaded the next image
  • get the arithmetic correct :-)

Code:

public class Loading extends JPanel { 

    // Pictures
    private BufferedImage background;

    // -------------- Final Variables --------------
    private final int numOfFiles = 32;

    // -------------- Dynamic Variables --------------
    // Global Variables
    private double loadingPercentage = 0;

    private int numOfLoadedFiles = 0;

    public Loading() {
        background = XTestUtils.loadDefaultImage("moon.jpg");
        // Start the thread
        new Thread(new LoadingFiles()).start();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(background, 0, 0, this);
        for (int i = 0; i < loadingPercentage; i = i + 15) {
            g.setColor(new Color(20, 241, 47));
            g.drawRect(180 + (i / 15) * (50 + 10), 375, 50, 50);
        }
    }

    class LoadingFiles implements Runnable {
        @Override
        public void run() {

            while (numOfLoadedFiles < 32) {
                numOfLoadedFiles++;
                loadingPercentage = (double) numOfLoadedFiles / numOfFiles
                        * 100;
                repaint();
                try {
                    // simulate the loading
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static void main(String[] args) {
        JFrame frame = new JXFrame("", true);
        frame.add(new Loading());
        frame.setSize(600, 600);
        frame.setVisible(true);
    }
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Always interested in reading your answers and learn everything from it :-) Though, in certain situations like inside the `paintComponent(...)` method, if one writes a for loop with `g.fillRect(x, h , size, size)` then mostly one will be able to see the first and the last Rectangle, Rectangles created in between the loop might get lost. You are really right, might be in certain contexts where the actual stuff takes a bit of time to call `repaint()` it surely can work. But inside the OP's thread, the `run()` method just got only `repaint()` call inside the `while loop`. – nIcE cOw Aug 12 '12 at 09:49
  • That's the real cause of the worry for me :-) – nIcE cOw Aug 12 '12 at 09:50
  • Here is [the thread](http://stackoverflow.com/q/10338163/1057230) related to this problem – nIcE cOw Aug 12 '12 at 10:08
  • @GagandeepBali you can do as much painting as you like to in the paintComponent, loop or not :-) If simple paint primitives seem to pose a problem, something is wrong elsewhere. As already mentioned: doing a repaint in a tight while loop doesn't make much sense and slows the reactivity (so _don't_) but isn't the problem here – kleopatra Aug 12 '12 at 10:22