Search code examples
javainstanceloadingjlabelimageicon

How to speed up load times for a huge tile grid of JLabels


I am attempting to create a sky-view RPG game, but the initial load times have become a bit of an issue. I am fairly new to programming and Java, so I don't know much about how Java loads things into memory or anything. So the issue is that when I initially run my program, it can take a very long time for all of my JLabels(set up in my grid) to appear. I am hoping to make a map that is very large, consisting of hundreds of thousands of tiles in total. After doing some math and tests, it turns out that 30 seconds of loading would result in merely about 6,724 tiles. This is only a bit over 8 screens worth of map, which is very small, since my character is a little smaller than 50x50 pixels. So for a map of a size from which I may be satisfied with would take over 6 minutes to load, upon every run. That is insane. And my map creator loads even more slowly.

Each tile is 50x50 pixels. Every tile is set up to have multiple layers of JLabels. For example, the first is for just a JLabel that the other labels will be added to, then the second is for Terrain(like Grass), and the third is for Interactive elements such as doors, so that the door may appear on top of the grass.

All of my Tile objects are created and inserted into a 2-dimensional Tile array. Every Tile instance has:

2 JLabels.

Icons added to at least, and usually only, the first JLabel. Every time a tile requests an icon, it gets it from the MapTiles class.

5 String arrays

7 booleans

4 ints

4 Layer instances in a Layer array of that size

Each Tile instance also implements MouseListener

Each Layer instance has:

1 JLabel with no icon, unless it is Grass. (75% of Layer instances aren't Grass)

4 booleans

1 int

1 String

The MapTiles class will create an ImageIcon for the thing, such as "Grass", if it hasn't already. If it has already created an ImageIcon for it, then it simply returns that ImageIcon. From reading about improving loading times, I was led that this may help speed things up, rather than creating a new ImageIcon for each Tile.

Here, I will provide the beginnings for each of my 3 main classes involved in creating/loading the grid. By beginnings, I mean only the constructor and the creating of the objects. There are a few other methods in each class, but not very many, and none used during the initial loading.

Tile class:

package Tiles;
import Datas.*;
import Images.*;
import MapCreation.*;
import UniversalVariables.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
public class Tile implements MouseListener{
    public Layer[] layers = new Layer[4];
    //MAKE SURE THAT THE FOLLOWING INTEGERS MATCH THE ARRAY IN MAPDATA
    int terrain = 0;
    int interactive = 1;
    int destroyable = 2;
    int obstruction = 3;

    public JLabel image = new JLabel();
    JLabel mouseDetector = new JLabel();
    boolean hasBarrier = false;
    boolean hasModifiedBarrier = false;
    boolean isInteractive = false;

    boolean leftClickPressed = false;
    boolean rightClickPressed = false;
    boolean scrollClickPressed = false;
    String[] layerTypes = MapData.uni.tileLayerTypes;
    String[] terrainTypes = MapData.uni.terrainTypes;
    String[] interactiveTypes = MapData.uni.interactiveTypes;
    String[] destroyableTypes = MapData.uni.destroyableTypes;
    String[] obstructionTypes = MapData.uni.obstructionTypes;
    int tileSize = MapData.uni.tilePixelSize;
    int row = 0;
    int col = 0;
    boolean mapCreatorOpen = UVars.uni.mapCreatorRunning;

    public Tile(int rowNum, int colNum){
        row = rowNum;
        col = colNum;
    setLayer("Terrain", "Grass");
    for(int c = 1; c < layers.length; c++){ //Sets all layers except terrain to have new layerName
        layers[c].layerName = MapData.uni.tileLayerTypes[c] + "NLT";
    }
    mouseDetector.addMouseListener(this);
    image.add(mouseDetector);
    mouseDetector.setBounds(0, 0, tileSize, tileSize);
    //image.setComponentZOrder(mouseDetector, 0);
    for(int c = 0; c < layers.length; c++){
        image.setComponentZOrder(layers[c].image, c);
    }
    image.setComponentZOrder(mouseDetector, 0);
}

}

Layer class:

package Tiles;
import Datas.*;
import Images.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Layer{
    public JLabel image = new JLabel();
    int tileSize;
    public String layerName = "No Layer Name";
    boolean hasBarrier;
    boolean hasModifiedBarrier;
    boolean isInteractive;
    boolean affectsMovement;

    public Layer(Tile tile, String layerType){
        //For empty layers
        tile.add(image);
    }
    public Layer(Tile tile, String layerType, String name){
        if(layerType.equals("Terrain") && name.equals("No Layer Name")){
            name = "Grass";
        }
        layerName = name;
        tileSize = tile.tileSize;
        tile.add(image);
        image.setBounds(0, 0, tileSize, tileSize);
        image.setIcon(MapTiles.uni.getIcon(layerName));
        if(layerType.equals("Terrain")){
            Terrain terrain = new Terrain(layerName);
            terrain.exchangeValues(this, layerName);
        } else if(layerType.equals("Interactive")){
            Interactive interactive = new Interactive(layerName);
            interactive.exchangeValues(this, layerName);
        } else if(layerType.equals("Destroyable")){
            Destroyable destroyable = new Destroyable(layerName);
            destroyable.exchangeValues(this, layerName);
        } else if(layerType.equals("Obstruction")){
            Obstruction obstruction = new Obstruction(layerName);
            obstruction.exchangeValues(this, layerName);
        }
    }
}

MapTiles class:

package Images;
import Datas.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
public class MapTiles{
    public static MapTiles uni;
    String tileLoc = "Tile Graphics\\" + String.valueOf(MapData.uni.tilePixelSize) + "PX\\";
    ArrayList<ImageIcon> icons = new ArrayList<ImageIcon>();
    ArrayList<String> iconList = new ArrayList<String>();
    public MapTiles(){
        uni = this;
    }
    public ImageIcon getIcon(String tileType){
        for(int c = 0; c < iconList.size(); c++){
            if(iconList.get(c).equals(tileType)){
                return icons.get(c);
            }
        }
        iconList.add(tileType);
        ImageIcon newIcon = new ImageIcon(tileLoc + tileType + ".png");
        icons.add(newIcon);
        return newIcon;
    }
}

So, why does my grid take so long to load? It is all created in the default Java thread, but that's because currently, I don't have any menu or anything else to distract a user with before getting to the map. The map starts immediately. What appears is the frame is just a blank panel. Eventually, once it finally creates the tiles in the top left corner once it has gotten that far(where the user's view defaults), you can see it creating each and the user may even hover on one. (Hovering over it changes the icon set on it) Hovering over it runs very well. Only the load times is the issue. So first off, are there any better ideas you guys have for creating such a grid? Secondly, are there better ways you can think of for doing the things I am attempting to do? And finally, is there anything that I have done wrong that I should improve upon? The main question I guess that I am asking is... What is it that is making Java take so long to load new Tile instance? By the way, I have a fairly high-end PC. It's not some piece of junk. For example, I can run most pre-2015 PC games on maximum graphical settings, and I have 16GB of RAM. I also have another question you guys may be able to answer. I read that using the GPU to load things rather than mainly loading them into RAM runs MUCH more quickly, but it uses 3D Java or something like that. Would I be able to use my GPU to load many of the tiles somehow and improve the load times that way?


Solution

  • Well, it looks like you're loading the same images multiple times from disk for tiles. Only loading what you need, then sharing that same resource in memory could help.

    Also you probably shouldn't try to hold your entire map in memory but instead load each section as needed.

    I'd recommend you ditch Swing and use LWJGL or an engine backed by LWJGL. This will give you hardware accelerated rendering typically not available through Swing (unless passing certain runtime flags.) If you're concerned about quality Minecraft uses LWJGL. Its really the way to go if you're going to use Java for game development.

    Specifically I recommend LIBGDX as your LWJGL backed game framework. I've used it extensively and its a great way to get your game on multiple OS and mobile devices.