Storing a level in a picture

I once saw a live stream of Notch making a game, and he planned out his 2D game levels with a picture. Each pixel represented a tile in the level, and I assume that at the start of the game his code reads the picture and translates the different color pixels into different tiles in the game. As in, a black pixel could represent a block of air, a white pixel could represent a solid block that the character can stand on, and a yellow pixel could represent some enemy

That’s what I want to do. I want to be able to take a picture, and based on the color of the pixel. It’ll make editing level maps so much easier.

So… What’s the fastest way to do this? What functions should I use?
Doesn’t seem like it should be too hard

Thanks

Basically the only thing you need to do is get the images pixels as an array (it’s raster), and then look at each pixel for the rgb color information and based on that info build your map.

I mean basically each pixel can hold an int, so you can have Integer.MAX_VALUE amount of different ID’s but it’s going to get hard to create the maps if you don’t have a fixed number of easily differentiable colors.

Imho, it’s too much of a hassle to save the map as an image (because you need to have a list of colors -> id’s -> actual object in your game) but it’s certainly possible.

Like, what if you have two objects in the same place? Maybe have each byte of the int decide what object to create. So you can have 4 different objects per pixel but then you’re only left with 255 id’s. Then again, 255 id’s is probably way more than enough :}

My motivation behind this is that I’m making an RPG, and originally I thought I was going to have randomly generating maps, and even though I succeeding in making convincing maps, I decided that carefully thought out and paced levels will add more character and is the better option.

Storing a map in an image makes it easier to edit, I think. And I don’t have nearly enough block types for it to be a problem.

What methods would you use to get the RGB values from an image?

If you use BufferedImages, use .getRGB()…

Yeah, I found the source code for that game I was talking about, so I’m studying it. getRGB() seems to be the right thing
Especially since you can get it to just return the array, meaning that you don’t have to muck around with weird loops
Cool cool, my plan is working just as expected

Well, given the java default tools, reading pixel info from an image is much easier than parsing a xml file or your own data format. Simpler too.
And it is almost perfect for a platformer, since you can work with multiple layers

PS: Are you reading Metagun source code? Cause he ran into some problems using this on Prelude of Chambered, as he wanted to put more decoration on the walls, but you can only have 1 object per tile. But i definitely wouldn’t mind it if i had 48h to make a game :smiley:

I do the same in 3D :wink:

Honestly, it depends on how you want to organize your color information. You can get 256 (0-255) out of each colour channel that you have (Three if it’s a 24-bit image, Four if it’s a 32-bit). This means that you can end up storing quite a bit of information in one and it’s “Easy” to use.

However, remember that once you get to about six or seven colours (Beyond Black/White) it becomes harder and harder for the human eye to differentiate between them, even on a computer screen. This is especially true if they’re all shades/tints of the same primary colour. If you’re trying to draw it, it gets rather messy rather fast, especially if you are trying to provide several different forms of information in a single image (IE- Red is the logical tile definition, Blue is the graphical tile definition, Green is the item definition, and Alpha is something else).

If you’re planning on writing a level editor that would output it to an image file, then you’re probably still better off using some other format that isn’t constrained that way.

That isn’t to say that it’s an expressly bad idea to have a parser that will translate an image into level data, for quick prototyping of your level data, it’ll work very well (All of the levels that I’ve been messing around with in my Platformer are stored as graphical data). But when you get to actually attempting to design everything, you’ll probably be better off with a different method.

Edit: Another thread on the question of storing levels as images

PNG and GIF could be very useful storage formats for compressing large amounts of map data, as their algorithms work on repeating adjacent pixels. A typical tiled map may have a lot of neighbouring repetition, which means there will be lots of room for compression. An 8-bit, opaque and indexed colour image would lead to a very small filesize.

You could also very easily upload the map’s data to a GL texture and do some really crazy (useless) stuff with shaders.

If you wanted to get really ambitious, you could write your own encoder/decoder based on PNG/GIF compression… but that’s just overkill. :stuck_out_tongue:

[quote]However, remember that once you get to about six or seven colours (Beyond Black/White) it becomes harder and harder for the human eye to differentiate between them
[/quote]
This is why you should build your own visual editor, or use Tiled. :slight_smile:

True! However, if you’re going to use something like Tiled why not use the XML it generates and the classes it has that read it?

And yes, I did already mention making your own editor. :3 Hah. But like I said, you do get really constrained by the amount of information that you can have within a single pixel. For anything more complicated than “This is this tile”, “This item spawns here” and “This enemy spawns here”, and only having a maximum of one each in a given pixel, then it starts to get too complicated to use (Yes, you could use several different images, compressed into a single file, to indicate multiple depths of items/possibilities, however that’s really getting complicated. xD)

This is why you should build your own visual editor, or use Tiled. :slight_smile:
[/quote]
GIF is 8-bit, so PNG is the way to go, everywhere. Unless you want animation.

AFAIK, GIF is 8-bit only

PNG is the way to go, everywhere. Unless you want animation.
[/quote]
I wouldn’t say that you should store animations for games in .GIF images. It just feels wrong, and there’s probably something technical to it too.

Cheers :slight_smile:

I was just listing the advantages of GIF over PNG, internet-wise.

Because if i wanted animation on a game i would separate the frames side-by-side on a big image and then split and animate it. Using PNG. ;D

GIF is 8-bit, so PNG is the way to go, everywhere. Unless you want animation.
[/quote]
Many 2D games won’t have more than 255 tiles in a tile set, so GIF works fine. The compression algorithm works a bit differently, so it may be beneficial to use GIF in some situations over an 8bit PNG.

Notch just took the easy out and had a huge if-statement sequence, checking for specific RGB values. Felix (an opensource game, made for you to look at) does the same thing. If you google Felix begins, it should come up. There’s only a few classes, so it should be easy to find.

Depending on how developed your game is, you can make it more sophisticated than just a bunch of ifs.

Here’s some partial code that shows how to load an image and then iterate over the pixels within it.

TerrainTile2 is a simple class storing an id, symbol, color, and description.

The only other gotcha is the load happens asynchronously.


    public SurfaceLayer() {
        URL url = getClass().getResource("top.gif");
        image = Toolkit.getDefaultToolkit().getImage(url);
        
        terrainTileCache.add(new TerrainTile2(
            new Integer(0), "default x", Color.GREEN, "."));
        terrainTileCache.add(new TerrainTile2(
            new Integer(1), "water 1", new Color(0, 0, 64), "~"));  // ~
        terrainTileCache.add(new TerrainTile2(
            new Integer(2), "water 2", new Color(0, 0, 128), "~"));
        terrainTileCache.add(new TerrainTile2(
            new Integer(3), "water 3", new Color(0, 0, 255), "~"));  // ~
        terrainTileCache.add(new TerrainTile2(
            new Integer(4), "brown 1", new Color(64, 64, 0), "."));
        terrainTileCache.add(new TerrainTile2(
            new Integer(5), "brown 2", new Color(128, 128, 0), "."));
        terrainTileCache.add(new TerrainTile2(
            new Integer(6), "brown 3", new Color(255, 255, 0), "."));
        terrainTileCache.add(new TerrainTile2(
            new Integer(7), "tree 1", new Color(0, 64, 0), "t"));
        terrainTileCache.add(new TerrainTile2(
            new Integer(8), "tree 2", new Color(0, 128, 0), "t"));
        terrainTileCache.add(new TerrainTile2(
            new Integer(9), "tree 3", new Color(0, 255, 0), "t"));
        terrainTileCache.add(new TerrainTile2(
            new Integer(10), "mountain peak 1", new Color(64, 64, 64), "^"));
        terrainTileCache.add(new TerrainTile2(
            new Integer(11), "mountain peak 2", new Color(128, 128, 128), "^"));
        terrainTileCache.add(new TerrainTile2(
            new Integer(12), "mountain peak 3", new Color(255, 255, 255), "^"));
        
        terrain[0][0] = terrainTileCache.get(0);
        elevation[0][0] = 0;
    }
    
    public void load() {
        height = image.getHeight(null);
        width = image.getWidth(null);

        if (height < 0 || width < 0) {
            System.out.println("image not loaded yet, try again");
            return;
        }
        
        // Create empty BufferedImage, sized to Image
        BufferedImage buffImage = 
          new BufferedImage(
              width, 
              height, 
              BufferedImage.TYPE_INT_ARGB);

        // Draw Image into BufferedImage
        Graphics g = buffImage.getGraphics();
        g.drawImage(image, 0, 0, null);
        
        terrain = new TerrainTile2[width][height];
        elevation = new int[width][height];
        
        for (int a = 0;  a < height;  a++) {
            for (int b = 0;  b < width;  b++) {
                int i = b;
                int j = height - a - 1;
                
                int rgba = buffImage.getRGB(b, a);
                int red = (rgba >> 16) & 0xff;
                //int green = (rgba >> 8) & 0xff;
                //int blue = rgba & 0xff;
                
                if (red <= 10) {
                    terrain[i][j] = terrainTileCache.get(1);
                } else if (red <= 25) {
                    terrain[i][j] = terrainTileCache.get(2);
                } else if (red <= 50) {
                    terrain[i][j] = terrainTileCache.get(3);
                } else if (red <= 75) {
                    terrain[i][j] = terrainTileCache.get(4);
                } else if (red <= 100) {
                    terrain[i][j] = terrainTileCache.get(5);
                } else if (red <= 125) {
                    terrain[i][j] = terrainTileCache.get(6);
                } else if (red <= 150) {
                    terrain[i][j] = terrainTileCache.get(7);
                } else if (red <= 175) {
                    terrain[i][j] = terrainTileCache.get(8);
                } else if (red <= 200) {
                    terrain[i][j] = terrainTileCache.get(9);
                } else if (red <= 225) {
                    terrain[i][j] = terrainTileCache.get(10);
                } else if (red <= 250) {
                    terrain[i][j] = terrainTileCache.get(11);
                } else {
                    terrain[i][j] = terrainTileCache.get(12);
                }
                
                elevation[i][j] = red;
            }
        }
    }

@loom_weaver Why not using Integer.valueOf(int) instead of new Integer(int)?

Why? Does it really make a difference?

Integer.valueOf can return the same Integer object, while ‘new’ is required to always return a new object. You can’t rely on this behavior of valueOf, but it does do this for small ints at least. Here it is in scala (eq is reference equality in scala)


scala> val x = java.lang.Integer.valueOf(123)
x: java.lang.Integer = 123

scala> val y = java.lang.Integer.valueOf(123)
y: java.lang.Integer = 123

scala> x eq y
res0: Boolean = true

scala> val a = new java.lang.Integer(123)
a: java.lang.Integer = 123

scala> val b = new java.lang.Integer(123)
b: java.lang.Integer = 123

scala> a eq b
res1: Boolean = false