Can I Improve this level loader?

I am in the process of practising loading from files, using 2D arrays and such and decided to make a project off it. It will basically be a top down 2D shooter with predefined “puzzle” levels.

At first I tried to create these levels in a text file using certain values for certain tile types, that was just shit to look at.

So decided to, instead draw my levels in Photoshop and read the pixel color data and determine what should be created at that location. I am using Box2D and each of my tiles is 1x1 at the moment, I might make them smaller but anyway, enough talking.

I am looking for ways to improve this, as it stands it can read an image file easy enough, it knows that a pixel of pure black = a wall and white = empty etc etc.

Here it is:

This is my LevelLoader class, it reads an image file (hard coded atm) and reads the pixel data which is then converted to a binary string and stored in 2D array. The Level class calls both methods in here, obviously it calls Load() first then Generate().



public class LevelLoader {

	/** How many cells along the x axis the level has */
	public int width;
	/** How many cells along the y axis the level has */
	public int height;
	/** Pixel information for each cell */
	public String[][] cells;

	/** Image to read the file from */
	Pixmap levelInfo;
	
	// Constructor omitted, it's default


/**
	 * Load an image from file and store it in memory, read the information and
	 * store it in the array for generation
	 */
	public void load() {

		try {
			levelInfo = new Pixmap(
					Gdx.files.internal("data/levels/leveltest.png"));
			width = levelInfo.getWidth();
			height = levelInfo.getHeight();
			cells = new String[width][height];
			for (int x = 0; x < levelInfo.getWidth(); x++) {
				for (int y = 0; y < levelInfo.getHeight(); y++) {
					String val = Integer.toBinaryString(levelInfo
							.getPixel(x, y));
					cells[x][y] = val;
					// System.out.print(" " + cells[x][y]);
				}
				// System.out.println("");
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			levelInfo.dispose();
		}

	}

/**
	 * Generate the level
	 * 
	 * @param level
	 *            give reference to level in order to add the tiles to an array
	 */
	public void generate(Level level) {
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				TileType type = TileType.getType(cells[x][y]);
				if (type == null)
					continue;
				switch (type) {
				case WALL:
					level.tiles.add(new Wall(x + 1, y + 1));
					break;
				case PLAYER_SPAWN:
					level.player = new Player(x + 1, y + 1);
					break;
				case EMPTY:
					level.tiles.add(new Empty(x + 1, y + 1));
				default:
					break;
				}
			}
		}

	}



The Generate() method is what I am not sure of, it works but is it ideal/efficient? It basically reads the data in the array (which is a bunch of binary data) and then compares to to an enum using a hashmap here:



public enum TileType {
		PLAYER_SPAWN("111111110000000011111111"),
		WALL("11111111"),
		EMPTY("11111111111111111111111111111111");

		/** Used to search through the ID's of the cell enums */
		private static final HashMap<String, TileType> search = new HashMap<String, TileType>();

		/** Binary ID of the cell */
		String binary;

		private TileType(String binary) {
			this.binary = binary;
		}

		public String getBinary() {
			return binary;
		}

		/** Iterate through enum and assign values to map */
		static {
			for (TileType type : EnumSet.allOf(TileType.class)) {
				search.put(type.getBinary(), type);
			}
		}

		/** Get the type of this cell given its binary data */
		public static TileType getType(String id) {
			return search.get(id);
		}

	}


Is there any improvements you can see on this? At first I used text files, then I used buffered image and read the RBG formats and converted to Hex, that was just horrible. I can easily draw my levels in an image editor this way and any time I want to add a new object to the generation, all I need to do is take the hex value of the colour from the editing program and convert it to binary and add it to the enum.

At the moment all it will be generating is walls, the player and empty cells.

Just to clarify why I am creating empty cells, it just makes it easier to spawn enemies/drops during the game without them landing inside any walls and stuff.

Writing your own files instead of drawing stuff on images is a lot flexible. If you aren’t looking for flexibility, you can just use images, but its not flexible at all.

If you’re going to use text files, I hope you didn’t manually edit the values. You’re supposed to make your level editor or use this one if you’re looking for simple editing.

I think it will be flexible enough for what I plan on using it for, I have created 3 levels varying from 32x32 tiles and 128x128 tiles, seems to be working OK.

So for flexibility I should write my own for that I can parse and read in as a level?

It’s not necessarily an optimization (although it kind of is). When code converts one data type to another there should be a clear reason, the data should be better represented by the newly converted to type.
Converting a pixel, which can quite easily be represented by an integer, to a string feels strange. What does a string have to do with a pixel? Even if you want it as a string for readability, I would still store it as an integer and just toString it when you want to see it’s value.

In this code…


for (int x = 0; x < levelInfo.getWidth(); x++) {
      for (int y = 0; y < levelInfo.getHeight(); y++) {
            String val = Integer.toBinaryString(levelInfo
                  .getPixel(x, y));
            cells[x][y] = val;
            // System.out.print(" " + cells[x][y]);
      }
      // System.out.println("");
}

I feel like the String[][] cell array could be int[][] instead.

Then in the enum, just change the binary ID of each enum to an integer like so…


public enum TileType {
      PLAYER_SPAWN( Integer.parseInt("111111110000000011111111", 2) ),
      WALL( Integer.parseInt("11111111", 2) ),
      EMPTY( Integer.parseInt("11111111111111111111111111111111", 2) );

      /** Used to search through the ID's of the cell enums */
      private static final HashMap<Integer, TileType> search = new HashMap<Integer, TileType>();

      /** Binary ID of the cell */
      int binary;

      private TileType(int binary) {
         this.binary = binary;
      }

      public int getBinary() {
         return binary;
      }

      /** Iterate through enum and assign values to map */
      static {
         for (TileType type : EnumSet.allOf(TileType.class)) {
            search.put((Integer)type.getBinary(), type);
         }
      }

      /** Get the type of this cell given its binary data */
      public static TileType getType(int id) {
         return search.get((Integer)id);
      }

   }

Granted, this might require a few other changes in your code, but probably not many.
Just my thoughts, not necessarily right or wrong either way.

I had this originally, the problem I had with it was that the value of a 4 channel image (rgba) had some insane value such as -162578543, which looked to me a little sore on the eyes. I chose binary because I could easily take the hex value from the editor and change it binary.

The way you suggested it worked fine but I dunno, just couldn’t settle on it

why don’t you write it in directly in hex than


PLAYER_SPAWN(0xFF_00_FF),
WALL(0x00_00_FF),
EMPTY(0xFF_FF_FF);

I’ll change it back to hex I think, it gave me weird results for some reason, maybe my code was just buggy and I got impatient.

Will let you know.

I have a similar system, but I use a program called Tiled to draw my maps.
Natively, Tiled outputs a few specific TMX map formats which you can get a java library to read (complicated), but I have had little success with.

I simply load my spritesheet into tiled, and draw a map. Then I export the .TMX map file as CSV. Once that is done, I simple open the .TMX with notepad, copy the relevant array of integers the program has made for me, and plunk it into my own text file. All I use tiled for is numbering the spritesheet really, though it has all kinds of advanced features.

This makes it really easy to design maps visually instead of with text.

package Manager.Map;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class MapFileLoader {

    // size of map
    private int numCols, numRows, spriteSheetIndex, layerID, MAPID;

    // holds integer array of read map data
    private int[][] parsedLayer;
    
    private BufferedReader br;
    private final String delims = "\\s+";

    public MapFileLoader(String path) {
        InputStream in = getClass().getResourceAsStream(path);
        br = new BufferedReader(new InputStreamReader(in));

        parseFileHeader(br);
        parseMapFile(br);
        close(br);
        
    }

    // PRIVATE METHODS
    private void parseFileHeader(BufferedReader br) {
        try {
            MAPID = Integer.parseInt(br.readLine());
            numCols = Integer.parseInt(br.readLine());
            numRows = Integer.parseInt(br.readLine());
            spriteSheetIndex = Integer.parseInt(br.readLine());
            layerID = Integer.parseInt(br.readLine());
        } catch (IOException | NumberFormatException e) {
            e.printStackTrace();
        }
    }

    private void parseMapFile(BufferedReader br) {
        try {
            parsedLayer = new int[numRows][numCols];

            for (int row = 0; row < numRows; row++) {
                String line = br.readLine();

                String[] tokens = line.split(delims);
                for (int col = 0; col < numCols; col++) {
                    parsedLayer[row][col] = (Integer.parseInt(tokens[col]));
                                        
                }                
            }
        } catch (IOException | NumberFormatException e) {
            e.printStackTrace();
        }
    }

    private void close(BufferedReader br) {
        try {
            br.close();
        } catch (IOException | NumberFormatException e) {
            e.printStackTrace();
        }
    }
    
    // PUBLIC METHODS

    public int getWidth() {
        return numCols;
    }

    public int getHeight() {
        return numRows;
    }
    
    public int getSpriteSheetIndex(){
        return spriteSheetIndex;
    }

    public int[][] getMapData() {
        return parsedLayer;
    }
    public int getLayerID(){
        return layerID;
    }
    public int getMapID(){
        return MAPID;
    }

}

package Manager.Map;

import gfx.SpriteSheet;
import java.awt.image.BufferedImage;

public class MapTranslator {
    
    // change int[][] into BufferedImage[][] using Spritesheets[]

    private final int[][] parsedLayer;
    private final BufferedImage[][] Map;
    private final SpriteSheet ss1;
    
    public MapTranslator(int[][] inputArray, SpriteSheet inputSS){
        this.parsedLayer = inputArray;
        this.ss1 = inputSS;
        this.Map = new BufferedImage[parsedLayer.length][parsedLayer[0].length];
        
        Translate();
        
    }
    
    private void Translate(){ // int array into image array
        for (int y = 0; y < parsedLayer[0].length; y++){
            for (int x = 0; x < parsedLayer.length; x++){
                Map[x][y] = ss1.getSingleTile(parsedLayer[y][x]);
            }
        }
    }
    public BufferedImage[][] getMap(){
        return Map;
    }
}

I’ve used Tiled before, briefly and it is a damn powerful tool, however in the name of knowledge I am trying to do some things myself just to get a better understanding. It’s just nice to know that if I wanted to make a map editor… I could, im not going to but it’s good to say I can Lol.

Helps for portfolio and stuff as well.

Appreciate all the replies, I changed the id of the tile to a hex format but was giving me strange results. I am sticking to binary values just now as it seems to be working, although horrible to look at. I will probably create so sort of algorithm later to resolve it to a more readable format.