2
\$\begingroup\$

I'm trying to split a tileset (from here) into individual tiles.

For debugging purposes I wrote some code to split the tileset into individual tiles and display them on screen, but some aren't being split correctly. Screenshot

There are some small icons after the mob in the 3rd line from the bottom that got mixed up because they seemed to be smaller than 32x32. Here is my code for splitting them:

public class TilesetEngine : Game
{ 
    Dictionary<int, Texture2D> individualTiles = new Dictionary<int, Texture2D>();

    public void LoadTiles(string szFileName, ContentManager content, GraphicsDevice graphicsDevice)
    {
        var foo = content.Load<Texture2D>(szFileName);

        Color[] imageData = new Color[foo.Width * foo.Height];
        foo.GetData<Color>(imageData);

        var horizontalElements = foo.Width / 32;
        var verticalLines = foo.Height / 32;

        var i = 0;

        for (int y = 0; y < verticalLines; y++)
        {

            for (int x = 0; x < horizontalElements; x++)
            {
                var startRect = new Rectangle(x * 32, y * 32, 32, 32);

                var colorPiece = getImageData(imageData, foo.Width, startRect);
                var subTexture = new Texture2D(graphicsDevice, 32, 32);
                subTexture.SetData<Color>(colorPiece);
                individualTiles.Add(i, subTexture);

                i++;
            }
        }
    }

    public IEnumerable<Texture2D> GetTiles()
    {
        return individualTiles.Values.AsEnumerable<Texture2D>();
    }

    Color[] getImageData(Color[] colorData, int width, Rectangle rectangle)
    {
        var color = new Color[rectangle.Width * rectangle.Height];
        for (int x = 0; x < rectangle.Width; x++)
            for (int y = 0; y < rectangle.Height; y++)
                color[x + y * rectangle.Width] = colorData[x + rectangle.X + (y + rectangle.Y) * width];
        return color;
    }
}

Usage

        tileEngine = new TilesetEngine();
        tileEngine.LoadTiles("fantasy-tileset", Content,GraphicsDevice);

Drawing code

        spriteBatch.Begin();

        int i = 0;
        int padding = 10;
        var tiles = tileEngine.GetTiles();

        var maxPerLine = 10;
        var count = 0;
        var vertLine = 0;


        foreach (var p in tiles)
        {
            spriteBatch.Draw(p, new Rectangle(count * 32 + padding * count, vertLine * 32 + padding * vertLine, p.Width, p.Height), Color.White);
            count++;

            if (count == maxPerLine)
            {
                count = 0;
                vertLine++;
            }
        }      

        spriteBatch.End();

I don't believe this is the best way, since I always found tilesets with pictures with variable size, what would be the best way to detect an individual tile from a tileset and split it?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I'm not sure there even is a way to do this automatically in a way that is applicable to tilesheets where each tile might themselves be of arbitrary size, except maybe if you are guaranteed to have some sort of border between them in a specific colour you can detect (and that isn't used in any of the sprites), which doesn't seem to be the case here. If there are different sizes in one sheet, I don't think you can automatically detect where a tile begins and ends, and I'm afraid you'll have to make code specifically for this sheet or split the sheet up. \$\endgroup\$
    – Christian
    Commented Apr 24, 2013 at 13:34
  • \$\begingroup\$ But there might be some clever way to do this that I don't know about, so I'm not posting this as a definitive answer. \$\endgroup\$
    – Christian
    Commented Apr 24, 2013 at 13:34
  • 3
    \$\begingroup\$ You shouldn't be "splitting" them like this - keep them in the one texture and use the sourceRectangle parameter to SpriteBatch.Draw \$\endgroup\$ Commented Apr 24, 2013 at 14:01

3 Answers 3

2
\$\begingroup\$

This is very much something that I would hard-code. If there were fewer tiles, I'd be tempted to simply hard-code the rectangles for each tile. But in this case, you can hard-code the regions and do a bit of maths to get the rectangles for individual tiles:

public static class TilesetInfo
{
    private static Rectangle TileInRegion(int regionX, int regionY,
            int regionWidthInTiles, int tileWidth, int tileHeight, int tileNumber)
    {
        int row = tileNumber / regionWidthInTiles;
        int column = tileNumber % regionWidthInTiles;
        return new Rectangle(regionX + column * tileWidth, regionY + row * tileHeight,
                tileWidth, tileHeight);
    }

    public static Rectangle GetRectangle(int tile)
    {
        if(tile < 184) return TileInRegion(0, 0, 8, 32, 32, tile); // Large tiles
        if(tile < 192) return TileInRegion(0, 800, 8, 32, 32, tile - 184); // Fancy tiles
        if(tile < 235) return TileInRegion(0, 736, 16, 16, 16, tile - 192); // Icons
        if(tile < 237) return TileInRegion(64, 784, 2, 16, 16, tile - 235); // 2 more icons
        if(tile < 243) return TileInRegion(0, 784, 6, 8, 8, tile - 237); // Hearts
        if(tile < 250) return TileInRegion(0, 792, 7, 8, 8, tile - 243); // Bar graph
        if(tile < 270) return TileInRegion(176, 768, 10, 8, 16, tile - 250); // Big font
        if(tile < 290) return TileInRegion(96, 784, 10, 8, 8, tile - 270); // Small font
        throw new ArgumentOutOfRangeException();
    }
}

(I got the numbers here with a combination of simply counting tiles, and selecting sections in an image editor and looking at the pixel coordinates.)

If this code seems ugly - that's because it is. But it's perfectly ok to write ugly bits of code for once-off things like this. Save the beautiful code for the rest of your codebase, you can always come back and replace this section with a complex, beautiful system later on, if the needs arises. Google these acronyms: DTSTTCPW and YAGNI.

If it makes you feel better - pretend this code is just another piece of content, standing in for an XML file or something that has the same data.


You could then take the above code and use it in a Draw method like the following. This uses the sourceRectangle parameter to Draw, as I mentioned in comments, to avoid having to slice up the texture (which is slow at both load time and draw time - fewer texture swaps means fewer batches).

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    int tileBaseWidth = 32;
    int tilePadding = 10;
    int displayTilesPerRow = (GraphicsDevice.Viewport.Width - tilePadding) / (tileBaseWidth + tilePadding);

    spriteBatch.Begin();
    for(int i = 0; i < 290; i++)
    {
        int row = i / displayTilesPerRow;
        int column = i % displayTilesPerRow;
        Vector2 position = new Vector2(tilePadding + column * (tileBaseWidth + tilePadding),
                tilePadding + row * (tileBaseWidth + tilePadding));
        spriteBatch.Draw(tilesetTexture, position, TilesetInfo.GetRectangle(i), Color.White);
    }
    spriteBatch.End();

    base.Draw(gameTime);
}
\$\endgroup\$
4
  • \$\begingroup\$ how can I remove the white background in the image? \$\endgroup\$ Commented Apr 24, 2013 at 16:59
  • \$\begingroup\$ The white background, as in your screenshot? I'm not sure how you introduced that. I couldn't find the cause in your source code with a brief glance. And it doesn't happen in the code I've posted above (assuming a standard method of loading the original png with the content pipeline). \$\endgroup\$ Commented Apr 25, 2013 at 11:00
  • \$\begingroup\$ If you use the tileset I'm using, and with this code, try this: Draw a floor and then, on top of it, draw some smaller icon. You'll see that it wont be right, it will have a white background, and not transparent as we see on RPG games. That's what I wanted to know how to fix \$\endgroup\$ Commented Apr 25, 2013 at 12:31
  • \$\begingroup\$ The only thing that I can think of is that you've somehow inadvertently removed the alpha channel from the image. Possibly by opening it up in an image editor that doesn't support transparency (like Microsoft Paint) and then saving it again. Try downloading the image fresh again, and testing it in a fresh XNA project with my above code. (If you need a better image editor, try Paint.NET or The GIMP.) \$\endgroup\$ Commented Apr 25, 2013 at 13:09
0
\$\begingroup\$

Leandro, you can simply use Tiled and a tmx map loader for XNA. Tiled will load your tileset image and split it into several different tiles, allowing you to visually create your map without coding. Then, you'll use the tmx map loader to import the map into your game.

Tiled: http://www.mapeditor.org/

TMX Map Loader for XNA and other resources: https://github.com/bjorn/tiled/wiki/Support-for-TMX-maps

\$\endgroup\$
1
  • \$\begingroup\$ I'm not sure this really solves the problem - most of that sprite sheet is interactive stuff (monsters, loot, icons, graphs, etc). \$\endgroup\$ Commented Apr 24, 2013 at 15:08
0
\$\begingroup\$

If I read it right try checking the center 9px for an alpha color. If there is not that much deviation you probably need to split your 32px sprites into 4 16px sprites.

The center 9 being

x-1, y-1 :x, y-1 :x+1, y-1
x-1, y   :x, y   :x+1, y
x-1, y+1 :x, y+1 :x+1, y+1

where x,y = top+16,left+16

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .