Refactoring the single tile drawing into a tile renderer

In this part we are refactoring the code from rendering a single tile and start with implementing a tile renderer. The tileset that is being rendered is from Aleksandr Makarov at itch.io. To see how to add the tileset png to the project see adding media resources.

In the previous part all the code was in the PixelApp class itself. To make the code more reusable it is going to be split up in 2 new classes that are then used by the PixelApp class. The first class is the TileSet. The TileSet contains the bitmap image and holds the information to extract the tiles from the image. Currently, it has a TileSize property, meaning that the height and width of a tile are the same, so this only supports square tiles.

    public class TileSet
    {
        public TileSet(string resourceId, int tileSize)
        {
            TileSize = tileSize;

            // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/basics/bitmaps
            Assembly assembly = GetType().GetTypeInfo().Assembly;

            using (Stream stream = assembly.GetManifestResourceStream(resourceId))
            {
                TilesBitmap = SKBitmap.Decode(stream);
            }

            Width = TilesBitmap.Width;
            Height = TilesBitmap.Height;
        }

        public SKBitmap TilesBitmap { get; }

        public int Width { get; set; } = 128;
        public int Height { get; set; } = 192;

        public int TileSize { get; set; } = 8;

        public int NumTilesX => Width / TileSize;
        public int NumTilesY => Height / TileSize;
    }
	

The TileRenderer is the class responsible for rendering a single tile from the TileSet. It contains a reference to the TileSet to render tiles from. It also contains a TileScaling property to scale the size of the tiles from the TileSet so the tile can be rendered larger or smaller on the screen. The TileRenderer does not do a lot right now and you could add the RenderTile method and the TileScaling property to the TileSet to simplify things but this allows to use a different renderer in the future with the same TileSet.

    public class TileRenderer
    {
        private SKRect sourceRect;
        private SKRect targetRect;

        public int TileScaling { get; set; } = 4;

        public TileSet ActiveTileSet { get; set; }

        public void RenderTile(float x, float y, int tx, int ty, SKCanvas canvas)
        {
            var sourceTileSize = ActiveTileSet.TileSize;
            sourceRect.Left = tx * sourceTileSize;
            sourceRect.Right = sourceRect.Left + sourceTileSize;
            sourceRect.Top = ty * sourceTileSize;
            sourceRect.Bottom = sourceRect.Top + sourceTileSize;

            var targetTileSize = TileScaling * sourceTileSize;
            targetRect.Left = x;
            targetRect.Right = targetRect.Left + targetTileSize;
            targetRect.Top = y;
            targetRect.Bottom = targetRect.Top + targetTileSize;

            canvas.DrawBitmap(ActiveTileSet.TilesBitmap, sourceRect, targetRect);
        }
    }
	

Finally, the TileSet and the TileRenderer are constructed in the Init method of the PixelApp and the tile is drawn in the Render method with the TileRenderer. The code for the PixelApp is now simplified compared to the code from the last post.

    public class PixelApplication
    {
        private SKColor _fillColor = new SKColor(20, 20, 40);
        private double width;
        private double height;
        TileSet tileSet;
        TileRenderer tileRenderer;

        public void Init(float w, float h)
        {
            width = w;
            height = h;

            // https://iknowkingrabbit.itch.io/mas-grass-land
            string resourceID = "pixelapp.Media.tileset.png";

            tileSet = new TileSet(resourceID, 8);
            tileRenderer = new TileRenderer { ActiveTileSet = tileSet, TileScaling = 4 };
        }

        public void Render(SKCanvas canvas, double updateDelta)
        {
            canvas.Clear(_fillColor);

            // Grass (1,0), Flowers (2,0), Tree (0,4)

            int tx = 0;
            int ty = 4;

            tileRenderer.RenderTile((float)width / 2.0f, (float)height / 2.0f, tx, ty, canvas);
        }
    }
	

A single tile depicting a tree should be rendered just as in the previous article when running the code as shown in the image below. In the next part a tile map will be rendered in order to draw multiple tiles as a background.

Render a single tile