More camera work and padded tilesets

I did some more work on the camera today, since it was suffering from a serious lack of smoothness. It definitely looks a lot nicer now:

I also took some time to correct an issue that’s been bugging me for quite a while now: graphical artifacts from drawing a tile sheet. A tile sheet is a big image composed of many smaller discrete images (the tiles) that are all the same size. When you draw from one, you tell the graphics card which portion of the sheet you’re referencing and where on the screen you want it to go. Unfortunately, graphics cards are fundamentally floating-point machines, and if you have sub-pixel smoothing turned on (which you really want), then you run the risk of drawing a tiny portion of the adjacent tile. This looks terrible.

Those little lines just look like ass

The solution to this problem is to pad each tile by surrounding it with the same color pixels as its own edges. This problem is ubiquitous, so I was very surprised that I couldn’t find any pre-rolled software that solved it for me. I ended up writing my own, which you can download here. It probably needs the .NET runtime to work, but I haven’t tried it on another computer yet. In case that doesn’t work for whatever reason, the algorithm to pad the tiles looks like so:

Image tilesheet = Bitmap.FromFile(_inputFileString);
int numCols = tilesheet.Width / tileWidth;
int numRows = tilesheet.Height / tileHeight;
Image padded = new Bitmap(tilesheet.Width + numCols * 2, tilesheet.Height + numRows * 2);

using ( Graphics g = Graphics.FromImage(padded) ) {
    for ( int row = 0; row < numRows; row++ ) {
        for ( int col = 0; col < numCols; col++ ) {

            int xOffset = col * tileWidth;
            int yOffset = row * tileHeight;
            int destXOffset = xOffset + col * 2 + 1;
            int destYOffset = yOffset + row * 2 + 1;

            // draw the main body of the image
            Rectangle srcRect = new Rectangle(xOffset, yOffset, tileWidth, tileHeight);
            Rectangle destRect = new Rectangle(destXOffset, destYOffset, tileWidth, tileHeight);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // then a strip on each edge

            // left
            srcRect = new Rectangle(xOffset, yOffset, 1, tileHeight);
            destRect = new Rectangle(destXOffset - 1, destYOffset, 1, tileHeight);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // right
            srcRect = new Rectangle(xOffset + tileWidth - 1, yOffset, 1, tileHeight);
            destRect = new Rectangle(destXOffset + tileWidth, destYOffset, 1, tileHeight);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // top
            srcRect = new Rectangle(xOffset, yOffset, tileWidth, 1);
            destRect = new Rectangle(destXOffset, destYOffset - 1, tileWidth, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // bottom
            srcRect = new Rectangle(xOffset, yOffset + tileHeight - 1, tileWidth, 1);
            destRect = new Rectangle(destXOffset, destYOffset + tileHeight, tileWidth, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // then a pixel for each corner
            srcRect = new Rectangle(xOffset, yOffset, 1, 1);
            destRect = new Rectangle(destXOffset - 1, destYOffset - 1, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            srcRect = new Rectangle(xOffset + tileWidth - 1, yOffset, 1, 1);
            destRect = new Rectangle(destXOffset + tileWidth, destYOffset - 1, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            srcRect = new Rectangle(xOffset + tileWidth - 1, yOffset + tileHeight -1, 1, 1);
            destRect = new Rectangle(destXOffset + tileWidth, destYOffset + tileHeight, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            srcRect = new Rectangle(xOffset, yOffset + tileHeight - 1, 1, 1);
            destRect = new Rectangle(destXOffset - 1, destYOffset + tileHeight, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);
        }
    }
}

padded.Save(_outputFileString);

Now I can take the tile sets output by Pyxel Edit, run them through this tool, then feed them to Tiled Map Editor. But since that would have been too easy, I discovered that the Tiled map reader library I'm working with didn't support offset, padded tile sets, which made everything look like this:

It's not supposed to look like that. It's not that kind of game.

So I had to add support for this feature, which could have been much worse than it was. You can download the updated version of the library here. It also includes a bug fix for drawing rotated or flipped tiles.

One thought on “More camera work and padded tilesets

  1. Nice work! One thing I noticed, if you multi-select tiles from a tile set when you place them (using shift click in tiled) then the map does not render correctly in game. Looks like it is not rendering the bottom left tile in a 2×2 selection.

    Still much better than any other tmx loader I have tried for C#. Thanks!

Leave a Reply