Tile Maps: An Analytical View

  • Drawing Just One Tile From a Bitmap From a Tileset
  • The Two Basic Coordinate Points in Tile Drawing
  • Drawing a Strip of Tiles
  • Drawing a Full Tile Grid
  • Map Coordinates, Columns, and Row
  • Changing a Tile (Picturewise)
  • The 2D Map Array
  • Editor Example: Clicking on Tiles
  • Placing a Character on the Map
  • Moving a Character on the Map
  • Animating a Character on the Map
  • Map Scrolling: Camera Coordinates
  • Map Scrolling: Scrolling the Display
  • Hexagon Tile Maps: Metrics
  • Hexagon Tile Maps: Map Coordinate Transform
  • Hexagon Tile Maps: Drawing The Hexagon Map
  • Hexagon Tile Maps: Character

    Review from IMFD

    We'll go over the red-highlighted parts.
        Dim Backup As System.Drawing.Bitmap  'Memory buffer - will be drawn in one fell swoop.
        Dim GFX, FGFX As System.Drawing.Graphics   'Should draw to the memory buffer declared above.
        Dim B As Integer
        '*** 1 *** , other declarations go here.
    
        Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            Backup = New Bitmap(640, 480)  '640x480 is the size of the memory bitmap, which will 
            'effectively be the size of the display, since nothing can go off of it.
            GFX = Graphics.FromImage(Backup)  'Draws to the memory buffer.
            FGFX = Me.CreateGraphics()  'Draws to the display (form).
            '*** 2 *** , other instantiations go here.
        End Sub
    
        Private Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click
            B += 10
            'B symbolizes the location of something moving on the form.
            GFX.FillRectangle(Brushes.Black, Me.ClientRectangle)
            GFX.FillRectangle(Brushes.DarkGoldenrod, New Rectangle(B, B, 100, 100))
            'Draws to the backup bitmap.
            '*** 3 *** , delete this, we are not going to click a map.
    
    
            Present()
        End Sub
    
        Private Sub Present()
            '*** 4 *** , typically nothing else will go in this routine.
            FGFX.DrawImage(Backup, 0, 0)
            'Present the memory bitmap to the display.
        End Sub
    
        Private Sub Clock_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Clock.Tick
            '*** 5 *** , game processing and general logic goes here.
            '*** 6 *** , drawing the state of the game goes here.
            Present()
        End Sub
            System.Drawing.Bitmap backup;  //Memory bitmap that will hold drawings that will be drawn all at once.
            System.Drawing.Graphics gfx, fgfx;   //Draws the drawings to the memory bitmap above.
            int b;
            // *** 1 *** , all other declarations go here.
            private void Form1_Load(object sender, EventArgs e)
            {
                backup = new Bitmap(640, 480);
                //640x480 is the size of the memory bitmap, which is effectively going to be the display size.
    
    
                gfx = Graphics.FromImage(backup);
                //Set gfx to draw to this backup bitmap.
                fgfx = this.CreateGraphics();
                //Set fgfx to draw to the display (form).
                // *** 2 *** , all other instantiations go here.
            }
    
            void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
            {
            }
    
            void Form1_Click(object sender, System.EventArgs e)
            {
                b += 10;
                //b symbolizes a location of something on the form.
                gfx.FillRectangle(Brushes.Black, this.ClientRectangle);  //Black background.
                gfx.FillRectangle(Brushes.DarkGoldenrod, new Rectangle(b, b, 100, 100));
                // *** 3 *** , delete this routine, we will not be clicking a map.
    
                Present();
            }
    
            void Present()
            {
                // *** 4 *** , typically nothing else will go in this subroutine.
                fgfx.DrawImage(backup, 0, 0);
                //Present memory bitmap to the display.
            }
    
            private void clock_Tick(object sender, EventArgs e)
            {
                // *** 5 *** , game processing and general logic will go here.
                // *** 6 *** , drawing the state of the game goes here.  
                Present();
            }

    Drawing Just One Tile From a Bitmap From a Tileset

    For this part, we're merely going to draw one tile at the upper left corner of the form. This is the picture that we will be using:
    Save Me
    Now, in order to draw just one tile from the tileset, we'll use the 12th overload for .DrawImage (Of course, we were going to use .DrawImage - where would we be without it?).
    So, first, the rectangle that defines the location of the grass tile within the tileset will be (0, 0, 16, 16), since the grass is zero pixels from both the left and the top, and then the width and height of the tile are both 16. Then, we are going to draw it at the upper left corner of the form, so our values for fX and fY (final X and final Y) are both 0, as well. So, now for our drawing: Read those red lines in the code above. Typically, a game on the form will consist of four regions:

  • Declarations, marked 1 above
  • Instantiations, marked 2 above
  • Logic, marked 5 above
  • Artwork, marked 6 above.
    So, now, back to our drawing: we need to declare the bitmap that holds the tileset in (1), instantiate it so that it holds the actual picture of our tileset in (2), and draw the single tile in (6). The code below is color-coded to help illustrate what parts of our code are and where they go to give you an idea of where things go in the picture NOT to make it easier to copy-paste into a project (that's just a side-effect, plus I misspell things in code samples).
    ' *** 1 *** , all other declarations go here.
        Dim Tileset As Bitmap    'Holds the tiles that we will draw.
    ' *** 2 *** , all other instantiations go here.
            Tileset = New Bitamp("tileset.bmp")   'Put the picture into our bitmap.
    ' *** 6 *** , drawing the state of the game goes here.
            GFX.DrawImage(Tileset, 0, 0, New Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
            'Draw single piece of grass tile.
    
    // *** 1 *** , all other declarations go here.
            Bitmap tileset;  //Will hold our tileset picture.
    // *** 2 *** , all other instantiations go here.
                tileset = new Bitamp("tileset.bmp");
                // Put picture into our bitmap.
    // *** 6 *** , drawing the state of the game goes here.
                gfx.DrawImage(tileset, 0, 0, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel);
                //Draw a single tile to the display.
    

    The Two Basic Coordinate Points in Tile Drawing

    Now, if we wanted to draw the tile at another position on the board, say, 32, 48, we'd change the first two arguments, making the call look like this:
    (tileset, 32, 48, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel).
    So, the first two arguments to DrawImage, the placement coordinate, defines where the tile will appear on the form. The remaining coordinate is the second pair of 0s, found in the Rectangle constructor. These are used to specify a different tile from the tileset. So, if we wanted to use the snow tile in the tileset, our coordinate for the rectangle is (32, 0) (the 32 is 16 * 2). So, if we now wanted to draw snow instead of grass, our line would look like this:
    (tileset, 32, 48, new Rectangle(32, 0, 16, 16), GraphicsUnit.Pixel).
    Recap: the first coordinate is the placement coordinate, and the second coordinate in the rectangle is the tile source coordinate.

    Drawing a Strip of Tiles

    Now, for drawing the tiles, we could draw them all one by one:
    (tileset, 0, 0, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
    (tileset, 16, 0, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
    (tileset, 32, 0, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
    (tileset, 48, 0, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
    (tileset, 64, 0, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)

    A much simpler way is to use a For Loop. The index of the loop starts at 0, as shown in the little picture shown above, and goes up by one. The trick is to determine how much each value increases when the loop value goes up by one. The tile placement goes to the right by 16 when we go ahead to the next tile. The other three values (top placement coordinate and tile source coordinate) will all remain 0 for now.
    ' *** 6 *** , drawing the state of the game goes here.
            Dim LV As Integer
    
            For LV = 0 To 24
                GFX.DrawImage(Tileset, 16 * LV, 0, New Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
                'Draw a grass tile.
            Next  'The for loop will end up drawing 25 tiles.
    // *** 6 *** , drawing the state of the game goes here.
                int lv;
    
                for (lv = 0; lv < 25; lv++)
                {
                    gfx.DrawImage(tileset, lv * 16, 0, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel);
                    //Draw a single tile to the display.
                }

    Drawing a Full Tile Grid

    Of course, we are simply adding another dimension to our tile map, therefore we have to add another For Loop. The loops have to be nested because we want to draw a row For every column that we want.
    ' *** 6 *** , drawing the state of the game goes here.
            Dim LX, LY As Integer
    
            For LY = 0 To 15
                For LX = 0 To 24
                    GFX.DrawImage(Tileset, 16 * LX, 16 * LY, New Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
                    'Draw a grass tile.
                Next
            Next 'The for loop will end up drawing a grid of 400 tiles.
    // *** 6 *** , drawing the state of the game goes here.
                int lx,ly;
    
                for (ly = 0; ly < 16; ly++)
                {
                    for (lx = 0; lx < 25; lx++)
                    {
                        gfx.DrawImage(tileset, lx * 16, ly * 16, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel);
                        //Draw a single tile to the display.
                    }
                }
    Don't ask why I have the numbers last in C# but first in VB.

    Map Coordinates, Columns, and Row

    Tile map drawing has a lot of concepts. Before we actually discuss how to get many different tiles in the map, we should at least be comfortable with some of them, most importantly, map coordinates. The tile at the top left is at map coordinate 0,0. If we take three steps right and four down, we will be at map coordinate 3,4. The placement coordinates for this tile are 48, 64. This means that the tile at map coordinate 3,4 will be placed at 48, 64.

    Since this tile is grass, its source coordinates are 0,0. If we made it a desert tile, it would be source coordinate 16, 0. If we looked at the tileset at a map, the desert tile will be at a source map coordinate 1,0. A column in a map is a group of tiles that have the same X map coordinate. They're a vertical strip of tiles, not horizontal. Column 2 would be all the tiles at map coordinates 2,???. A row is the group of tiles with the same Y map coordinate. Here is a little visual aid.

    You might be saying to yourself "I know this already", but... I have seen some pretty odd descriptions of map tiles. Let's pretend everyone knows this already .

    Changing a Tile (Picturewise)

    So, if we really wanted to change the tile at 3,4 (map coordinates) to a desert tile (source coordinate 16,0), we could do something eccentric like:
    ' *** 6 *** 
                    If LX = 3 AndAlso LY = 4 Then
                        GFX.DrawIamge(Tileset, 16 * LX, 16 * LY, New Rectangle(16, 0, 16, 16), GraphicsUnit.Pixel)
                        'Hardcoded desert tile at coord 3,4.
                    Else
                        GFX.DrawImage(Tileset, 16 * LX, 16 * LY, New Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel)
                        'Draw a grass tile.
                    End If
    // *** 6 *** 
                        if (lx == 3 && ly == 4)
                            gfx.DrawIamge(tileset, lx * 16, ly * 16, new Rectangle(16, 0, 16, 16), GraphicsUnit.Pixel);
                            //Hardcoded desert tile.
                        else
                            gfx.DrawImage(tileset, lx * 16, ly * 16, new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel);
                        //Draw a single tile to the display.
    But, there's definitely a better way. This is where we introduce the map array.

    The 2D Map Array

    To help us in our pursuit of an easier method of drawing maps, we decide to have a two-dimensional array that will hold information about every single in our tilemap. Since what really matters (as far as the map by itself) is the map coordinates and the tile at this map coordinate, we can start off simple by filling each array entry with a source map coordinate value (the X one): 0 for grass, 1 for desert, 2 for snow, etc.
    ' *** 1 ***
        Dim Map(,) As Integer  'Holds map tile indices.
    
    ' *** 2 ***
            Map = New Integer(24, 15) {}
            Map(3, 4) = 1
            Map(5, 7) = 1
            Map(5, 11) = 2
            Map(16, 4) = 2
            Map(21, 3) = 3
            Map(22, 8) = 4
            'Put in some test tiles.
    
    // *** 1 ***
            int[,] map;  //Holds map tile indices.
    
    // *** 2 ***
                map = new int[25, 16];
                map[3, 4] = 1;
                map[5, 7] = 1;
                map[5, 11] = 2;
                map[16, 4] = 2;
                map[21, 3] = 3;
                map[22, 8] = 4;
                //Put in some values for our map here for now.
    The next problem is how to draw the map with this new tile information. Actually, it is very simple. Since the loop that we have for drawing the map loops through every map coordinate and our array has information about every map coordinate, we can use the same loop variable to get the source map coordinate for each tile. Note: when I say tile index, I am referring to the number that specifies which tile to draw.
    Our loop will then look like this:
    ' *** 6 ***
            Dim LX, LY As Integer
            Dim ThisIndex As Integer
            Dim Scx As Integer  'Source coordinate's X-location.
    
            For LY = 0 To 15
                For LX = 0 To 24
    
                    ThisIndex = Map(LX, LY)
                    'Gets the tile index for this tile on the map.
    
                    Sc = 16 * ThisIndex
                    'Returns the source coordinate's X-location for this tile index.
    
                    GFX.DrawImage(Tileset, 16 * LX, 16 * LY, New Rectangle(Scx, 0, 16, 16), GraphicsUnit.Pixel)
                    'Draw a tile.
                Next
            Next 'The for loop will end up drawing a grid of 400 tiles.
    // *** 6 ***
                int lx,ly;
                int thisindex;
                int scx;  //Source coordinate's x-location
    
                for (ly = 0; ly < 16; ly++)
                {
                    for (lx = 0; lx < 25; lx++)
                    {
                        thisindex = map[lx, ly];
                        //The tile index for this tile on the map.
    
                        sc = thisindex * 16;
                        //Source coordinate X location for this tile index.
    
                        gfx.DrawImage(tileset, lx * 16, ly * 16, new Rectangle(scx, 0, 16, 16), GraphicsUnit.Pixel);
                        //Draw a single tile to the display.
                    }
                }
    Again, read for understanding!

    Map Data From File

    There are plenty of ways to store and load map information to and from file. First, we'll go over storing method. The first method we'll call the single-digit method:
        Private Sub SaveMap()
            Dim SW As StreamWriter, LX, LY As Integer
            SW = New StreamWriter("C:\testmap.map")
    
            For LY = 0 To 15
                For LX = 0 To 24
                    SW.Write(Map(LX, LY).ToString())
                    'Converts the tile index to a one-character string, and saves it to the file.
                Next
            Next
    
            SW.Close() 'Don't forget to close the streamwriter.
        End Sub
        Private Sub LoadMap()
            Dim SR As StreamReader, LX, LY As Integer, LineIndex As Integer, Line As String
            SR = New StreamReader("C:\testmap.map")
            Line = SR.ReadToEnd()
            SR.Close()
    
            For LY = 0 To 15
                For LX = 0 To 24
                    LineIndex = LX + 25 * LY
                    'Gets the character index in the line that is supposed to correspond with these values for lx and ly.
                    Map(LX, LY) = Integer.Parse(Line.Substring(LineIndex, 1))
                Next
            Next
        End Sub
            private void savemap()
            {
                StreamWriter sw;
                int lx, ly;
                sw = new StreamWriter(@"C:\testmap.map");
    
                for (ly = 0; ly < 16; ly++)
                    for (lx = 0; lx < 25; lx++)
                        sw.Write(map[lx, ly].ToString());
    
                sw.Close();
            }
            private void loadmap()
            {
                StreamReader sr;
                int lx, ly, lineindex;
                string line;
    
                sr = new StreamReader(@"C:\testmap.map");
                line = sr.ReadToEnd();
                sr.Close();
    
                for (ly = 0; ly < 16; ly++)
                    for (lx = 0; lx < 25; lx++)
                    {
                        lineindex = ly * 25 + lx;
                        // The index in the line that would correspond to this map position.
                        map[lx, ly] = int.Parse(line.Substring(lineindex, 1));
                        //Convert the string into a one-digit number and put it back into the map.
                    }
                //Puts data into map.
            }
    Of course, a major disadvantage to this is that there are only ten digits and, consequently, only ten tiles can be loaded. The next step would be to store tile indices into the file by first converting them to characters. Immediately, you'll find that certain characters will make this challenging (10 is a carriage return, 13 is a linefeed, and 26 is an EOF marker, which is impossible to read with a streamreader). However, you can ramp up the lowest number to something like 32 or 48. (FYI: This is the technique I used in Quadrill to load maps.) This will be our single character method.
        Private Sub SaveMap()
            Dim SW As StreamWriter, LX, LY, Finalvalue As Integer
            SW = New StreamWriter("C:\testmap.map")
    
            For LY = 0 To 15
                For LX = 0 To 24
                    Finalvalue = 32 + Map(LX, LY)
                    SW.Write(ChrW(Finalvalue))
                    'Converts the tile index to a one-character string, and saves it to the file.
                Next
                SW.WriteLine()  'New line in the file.
            Next
    
            SW.Close() 'Don't forget to close the streamwriter.
        End Sub
        Private Sub LoadMap()
            Dim SR As StreamReader, LX, LY As Integer, LineIndex As Integer, Line As String
            SR = New StreamReader("C:\testmap.map")
    
            For LY = 0 To 15
                Line = SR.ReadLine()  'Read one line which corresponds to one row (all Y values same).
                For LX = 0 To 24
                    LineIndex = LX
                    'Gets the character index in the line that is supposed to correspond with these values for lx and ly.
                    Map(LX, LY) = AscW(Line.Chars(LineIndex)) - 32
                Next
            Next
            SR.Close()
        End Sub
            private void savemap()
            {
                StreamWriter sw;
                int lx, ly, finalvalue;
                sw = new StreamWriter(@"C:\testmap.map");
    
                for (ly = 0; ly < 16; ly++)
                {
                    for (lx = 0; lx < 25; lx++)
                    {
                        finalvalue = map[lx, ly] + 32;
                        //Get a final integer value that will convert this tile index to a readable character.
                        sw.Write((char)finalvalue);
                    }
                    sw.WriteLine();  //Write a new line into the file.
                }
    
                sw.Close();
            }
            private void loadmap()
            {
                StreamReader sr;
                int lx, ly, lineindex;
                string line;
    
                sr = new StreamReader(@"C:\testmap.map");
    
                for (ly = 0; ly < 16; ly++)
                {
                    line = sr.ReadLine();
                    for (lx = 0; lx < 25; lx++)
                    {
                        lineindex = lx;
                        // The index in the line that would correspond to this map position.
                        map[lx, ly] = (int)line[lx] - 32;
                        //Convert the string into a one-digit number and put it back into the map.
                    }
                }
                //Puts data into map.
                sr.Close();
            }
    Now, disadvantages to this method are not as severe as the single-digit method. However, we can only have 224 tiles if we start from 32 and go up to 255. If you want to go up past 256 into Unicode for an attempt to get 65504 tiles, go ahead, but you've been warned! However, the next technique we'll discuss should be far superior to either. This one uses BinaryWriters and BinaryReaders as opposed to StreamWriters and StreamReaders to write the map information. With Binary access, you do not have to worry about null characters and you can use any integer-type you need for the file information. With bytes, you now have access to 256 tiles, shorts with 65536 tiles, and integers come in at a little over 2 billion (and if that is still not enough tiles for you, you also have the Long integer). The method is not very complex if you already know how to set up a binarywriter or binaryreader. This is the binary method:
        Private Sub SaveMap()
            Dim BW As BinaryWriter, FS As FileStream, LX, LY As Integer
            FS = New FileStream("C:\testmap.map", FileMode.Create)
            BW = New BinaryWriter(FS)
    
            For LY = 0 To 15
                For LX = 0 To 24
                    BW.Write(Map(LX, LY))
                    'Converts the tile index to a one-character string, and saves it to the file.
                Next
            Next
    
            BW.Close() 'Don't forget to close the binarywriter.
            FS.Close()
        End Sub
        Private Sub LoadMap()
            Dim BR As BinaryReader, FS As FileStream, LX, LY As Integer
            FS = New FileStream("C:\testmap.map", FileMode.Open)
            BR = New BinaryReader(FS)
    
            For LY = 0 To 15
                For LX = 0 To 24
                    Map(LX, LY) = BR.ReadInt32()
                Next
            Next
    
            BR.Close()  'Close our streams.
            FS.Close()
        End Sub
            private void savemap()
            {
                BinaryWriter bw;
                FileStream fs;
                int lx, ly;
                fs = new FileStream(@"C:\testmap.map", FileMode.Create);
                bw = new BinaryWriter(fs);
    
                for (ly = 0; ly < 16; ly++)
                    for (lx = 0; lx < 25; lx++)
                        bw.Write(map[lx,ly]);
                    // Puts a 32-bit integer into the file.
                //Don't forget to close the streams.
                bw.Close();
                fs.Close();
            }
            private void loadmap()
            {
                BinaryReader br;
                FileStream fs;
                int lx, ly;
    
                fs = new FileStream (@"C:\testmap.map", FileMode.Open);
                br = new BinaryReader(fs);
    
                for (ly = 0; ly < 16; ly++)
                    for (lx = 0; lx < 25; lx++)
                        map[lx, ly] = br.ReadInt32();
                    //Gets a 32-bit integer from the file.
                //Don't forget to close the streams.
                br.Close();
                fs.Close();
            }

    Editor Example: Clicking on Tiles

    For this section, we're going to make ourselves a very simple map editor by clicking on tiles. In order to edit a tile, we have to know which tile was clicked, which requires us to know what display coordinate was clicked. We can get that easily from a MouseDown event. The MouseEventArgs contain e.X and e.Y, which are exact display coordinates of where the user clicked. The next question: how do we get the map coordinates for the tile at this location?
    Well, since each tile has a 16-pixel width and height, the map coordinates will go up 1 for every 16 pixels. Therefore, 1 map coordinate = 16 pixels, and since e.X and e.Y are in pixels, we simply divide them by 16 to convert them (unitwise) into map coordinates. Note that we should use integer division so that we do not end up with a fractional map coordinates - however, we will get to fractional map coordinates eventually.
    So far, we have our map coordinates are the e.coordinates integer divided by 16. Now, we have to edit this tile. For our simple editor, we will just cycle the tile index through 0 to 7. A more sophisticated editor would have a tile selection of some sort, a highlighting rectangle, and/or other tools for quick editing.
    Now, we have one last thing to do: we have to make sure that our mouse click actually falls on a tile in the map. How do we determine this? Simply by checking if the map coordinate that we received lies within the map coordinates of the array. This would be checking if the map x coordinate is from 0 to 24 and the map y coordinate is from 0 to 15. Now that that is done, our map editor code now looks like this:
        Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
            Dim Mx, My As Integer
            Dim ThisIndex As Integer
            Mx = e.X \ 16
            My = e.Y \ 16
            'Map coordinates of the tile we have clicked.
            If Mx >= 0 AndAlso Mx < 24 AndAlso My >= 0 AndAlso My < 15 Then
                ThisIndex = Map(Mx, My)
                ThisIndex = (ThisIndex + 1) Mod 8
                'Cycle this tile index to the next one, looping back at 8.
    
                Map(Mx, My) = ThisIndex
                'Update the map tile index.
            End If
    
        End Sub
            void Form1_MouseDown(object sender, MouseEventArgs e)
            {
                int mx, my, thisindex;
                mx = e.X / 16;
                my = e.Y / 16;
                //Get map coordinates of where the user clicked.
    
                if (mx >= 0 && mx <= 24 && my >= 0 && my <= 15)
                {
                    thisindex = map[mx, my];
                    thisindex = (thisindex + 1) % 8;
                    //Get the tile index at this map coordinate and move it up, cycling back to 0 at 8.
    
                    map[mx, my] = thisindex;
                    //Reassign the tile index to the map array
                }
            }
    If you would like to have your map saved when you close the form, call the savemap() routine and don't forget to exclude the savemap() that is in your form's load event procedure.

    Placing a Character on the Map

    Now, we will put a character on the map. This is quite easy to do; it is simply giving the character a map coordinate to stand on and to draw him at this map coordinate. You also have to realize that you actually need a picture of a character in order to draw the character's picture (that catches some people off guard). So, we use an old beginner picture.
    Save Me
    Now, we'll go through the usual: declare, instantiate, and draw.
    ' *** 1 ***
        Dim Charpic As Bitmap   'Holds picture of main character.
        Dim Charloc As Point    'Holds character map coordinates.
    
    ' *** 2 ***
            Charpic = New Bitmap("Riff.bmp")    'Put character picture in.
            Charpic.MakeTransparent(Color.White)  'Make the color white see-through.
            Charloc = New Point(1, 1)           'Set map coordinates for player to 1, 1.
    
    ' *** 6 ***
                    GFX.DrawImage(Tileset, 16 * LX, 16 * LY, New Rectangle(Scx, 0, 16, 16), GraphicsUnit.Pixel)
                    'Draw a tile.
    
                    GFX.DrawImage(Charpic, 16 * Charloc.X, 16 * Charloc.Y)
                    'Draw character on the map so that he appears at this map coordinate.
    // *** 1 ***
            Bitmap charpic;  //Holds the character picture.
            Point charloc;  //Holds character location.
    
    // *** 2 ***
                charpic = new Bitmap("Riff.bmp");
                charpic.MakeTransparent(Color.White);  //Make white to be transparent.
                charloc = new Point(1, 1);  //Put character at map coordinate 1,1.
    
    // *** 6 ***
                        gfx.DrawImage(tileset, lx * 16, ly * 16, new Rectangle(scx, 0, 16, 16), GraphicsUnit.Pixel);
                        //Draw a single tile to the display.
    
                        gfx.DrawImage(charpic, charloc.X * 16, charloc.Y * 16);
                        //Draw the character so that it appears at this map coordinate.
    
    Now, that was simple!

    Moving a Character on the Map

    Of course, we have to be able to move the character. The first part of this is very simple - just alter the value of the charloc to reflect the player's new position. However, if you have read my article on movement with the keys (the seamless movement part), you'll know there is a much more efficient way and that is what we are going to do. So let us first declare booleans, next handle keydown and keyup events to set them, and, finally, move the character according to which keys are pressed. The first two concepts (declaring booleans and handling keydown and keyup events can be brought over without any modifications required, the movement is a simple modification:
        Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
            'Check keycode against left, right, up, and down.
            If e.KeyCode = Keys.Left Then
                GoLeft = True
            ElseIf e.KeyCode = Keys.Right Then
                GoRight = True
            ElseIf e.KeyCode = Keys.Up Then
                GoUp = True
            ElseIf e.KeyCode = Keys.Down Then
                GoDown = True
            End If
        End Sub
        Private Sub Form1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
            'Check keycode against left, right, up, and down.
            If e.KeyCode = Keys.Left Then
                GoLeft = False
            ElseIf e.KeyCode = Keys.Right Then
                GoRight = False
            ElseIf e.KeyCode = Keys.Up Then
                GoUp = False
            ElseIf e.KeyCode = Keys.Down Then
                GoDown = False
            End If
        End Sub
    
        ''' <summary>
        ''' Changes the character's location based on which keys are pressed.
        ''' </summary>
        ''' <remarks>None.</remarks>
        Private Sub Movement()
            If GoLeft Then
                Charloc.Offset(-1, 0)  'Move left.
            End If
            If GoRight Then
                Charloc.Offset(1, 0)   'Move right... etc.
            End If
            If GoUp Then
                Charloc.Offset(0, -1)
            End If
            If GoDown Then
                Charloc.Offset(0, 1)
            End If
        End Sub
    
        Dim GoLeft, GoUp, GoRight, GoDown As Boolean
    ' *** 5 ***
            Movement() ' Changes the character's location based on which keys are pressed.
    
            void Form1_KeyUp(object sender, KeyEventArgs e)
            {
                if (e.KeyCode == Keys.Left)
                    goleft = false;
                else if (e.KeyCode == Keys.Right)
                    goright = false;
                else if (e.KeyCode == Keys.Up)
                    goup = false;
                else if (e.KeyCode == Keys.Down)
                    godown = false;
                // Check keycode against left, right, up, and down.
            }
    
            void Form1_KeyDown(object sender, KeyEventArgs e)
            {
                if (e.KeyCode == Keys.Left)
                    goleft = true;
                else if (e.KeyCode == Keys.Right)
                    goright = true;
                else if (e.KeyCode == Keys.Up)
                    goup = true;
                else if (e.KeyCode == Keys.Down)
                    godown = true;
                // Check keycode against left, right, up, and down.
            }
    
            /// <summary>
            /// Changes character location based on which keys are pressed.
            /// </summary>
            private void movement()
            {
                if (goleft)
                    charloc.Offset(-1, 0);
                if (goright)
                    charloc.Offset(1, 0);
                if (goup)
                    charloc.Offset(0, -1);
                if (godown)
                    charloc.Offset(0, 1);
            }
    
    // *** 5 ***
                movement();// Changes character location based on which keys are pressed.
    
    Remember, section 5 is immediately before section 6. From now on, section 5 refers to the movement section.
    Now, the second part to all of this movement stuff is that we need to have something for our character to collide with. For instance, tile index 3 is a block and in the demo map file, I have a bunch of these blocks set up. The collision detection for this is very simple, simply check for tile index 3.
    However, as most tile games will, there can be multiple tiles that we don't want the player to walk on. So, we'll start out assuming that the player cannot walk on tile indices 3 and 6. We'll put all tile indices that the player cannot walk on into an array.
    Also, we will have to keep the player on the field, so we will also have out-of-bounds collision checks as well, which are similar in appearance to the check we did to determine if the mouse clicked a valid map coordinate, except for one thing. The entire expression is negated.
    For collision, we will simply check if the player's location is valid and the player's tile index is in this blocked array. As with all collisions, you want to simulate the character movement first and check if the character will end up in a collision. If there is a collision, then it has to be resolved - in our case, we just move the player back to its original tile. Our blocked array and collision detection:
    ' *** 1 ***
        Dim Blocked() As Integer  'Holds tile indices that cannot be walked on.
    
    ' *** 2 ***
            Blocked = New Integer() {3, 6}  'These are tile indices that the player cannot walk on.
    
    ' *** 5 ***
            Dim Collided As Boolean
    
            If Not (Charloc.X >= 0 AndAlso Charloc.X <= 24 AndAlso Charloc.Y >= 0 AndAlso Charloc.Y <= 15) Then
                Collided = True
            ElseIf Array.BinarySearch(Blocked, Map(Charloc.X, Charloc.Y)) >= 0 Then
                Collided = True
            End If
    
            If Collided Then  'Move back to where you came from.
                If GoLeft Then
                    Charloc.Offset(1, 0)
                End If
                If GoRight Then
                    Charloc.Offset(-1, 0)
                End If
                If GoUp Then
                    Charloc.Offset(0, 1)
                End If
                If GoDown Then
                    Charloc.Offset(0, -1)
                End If
            End If
    // *** 1 ***
            int[] blocked;  //List of tiles that cannot be walked on.
    
    // *** 2 ***
                blocked = new int[] { 3, 6 };
    
    // *** 5 ***
                bool collided = false;
    
                if (!(charloc.X >= 0 && charloc.X <= 24 && charloc.Y >= 0 && charloc.Y <= 15))
                    collided = true;
                else if (Array.BinarySearch(blocked, map[charloc.X, charloc.Y]) >= 0)
                    collided = true;
    
                if (collided)
                {
                    if (goleft)
                        charloc.Offset(1, 0);
                    if (goright)
                        charloc.Offset(-1, 0);
                    if (goup)
                        charloc.Offset(0, 1);
                    if (godown)
                        charloc.Offset(0, -1);
                }
    
    If you desire to have only horizontal or vertical movement, you need to break down some of your checks.
        ''' <summary>
        ''' Changes the character's location based on which keys are pressed.
        ''' </summary>
        ''' <remarks>None.</remarks>
        Private Sub Movement()
            Dim Collided As Boolean = False
    
            If GoLeft Then
                Charloc.Offset(-1, 0)  'Move left.
            End If
            If GoRight Then
                Charloc.Offset(1, 0)   'Move right... etc.
            End If
            'Do horizontal movements first.
    
            'Then check for collision in the horizontal.
            If Not (Charloc.X >= 0 AndAlso Charloc.X <= 24) Then
                Collided = True
            ElseIf Array.BinarySearch(Blocked, Map(Charloc.X, Charloc.Y)) >= 0 Then
                Collided = True
            End If
    
            'A collision occured while moving horizontally.
            If Collided Then  'Move back to where you came from.
                If GoLeft Then
                    Charloc.Offset(1, 0)
                End If
                If GoRight Then
                    Charloc.Offset(-1, 0)
                End If
                Collided = False
            End If
    
            'Followed by vertical movements.
            If GoUp Then
                Charloc.Offset(0, -1)
            End If
            If GoDown Then
                Charloc.Offset(0, 1)
            End If
    
            'Then check for collision in the vertical.
            If Not (Charloc.Y >= 0 AndAlso Charloc.Y <= 15) Then
                Collided = True
            ElseIf Array.BinarySearch(Blocked, Map(Charloc.X, Charloc.Y)) >= 0 Then
                Collided = True
            End If
    
            'Collision occured while moving vertically.
            If Collided Then  'Move back to where you came from.
                If GoUp Then
                    Charloc.Offset(0, 1)
                End If
                If GoDown Then
                    Charloc.Offset(0, -1)
                End If
            End If
        End Sub
            /// <summary>
            /// Changes character location based on which keys are pressed.
            /// </summary>
            private void movement()
            {
                bool collided = false;
    
                if (goleft)
                    charloc.Offset(-1, 0);
                if (goright)
                    charloc.Offset(1, 0);
                //Move player to tile in this direction.
    
                if (!(charloc.X >= 0 && charloc.X <= 24 ))
                    collided = true;
                else if (Array.BinarySearch(blocked, map[charloc.X, charloc.Y]) >= 0)
                    collided = true;
    
                if (collided)
                {
                    if (goleft)
                        charloc.Offset(1, 0);
                    if (goright)
                        charloc.Offset(-1, 0);
                    collided = false;
                }
    
                if (goup)
                    charloc.Offset(0, -1);
                if (godown)
                    charloc.Offset(0, 1);
                //Move player to tile in this direction.
    
                if (!(charloc.Y >= 0 && charloc.Y <= 15))
                    collided = true;
                else if (Array.BinarySearch(blocked, map[charloc.X, charloc.Y]) >= 0)
                    collided = true;
    
                if (collided)
                {
    
                    if (goup)
                        charloc.Offset(0, 1);
                    if (godown)
                        charloc.Offset(0, -1);
                }
            }

    Animating a Character on the Map

    There are several ways to animate a character on a map, of course. For our purposes we will merely have a character frame number which tells us the character animation frame that we need to draw. Two efficient ways of storing character animation frames is either by having the frames all in one bitmap or in an array of bitmaps. Having the character frames in one bitmap will consume less memory than having separate bitmaps, though it is slightly easier to work with an array of bitmaps.
    Save us: . We'll load these into an array of bitmaps.
    ' *** 1 ***
        Dim Frames() As Bitmap
        'Holds the separate animation frames.
    
    ' *** 2 ***
            Frames = New Bitmap() {New Bitmap("Solagon.gif"), New Bitmap("Solagon2.gif"), New Bitmap("Solagon3.gif"), New Bitmap("Solagon4.gif")}
            'Put in each picture into our array... four elements from 0 to 3.
            Frames(0).MakeTransparent(Color.White)
            Frames(1).MakeTransparent(Color.White)
            Frames(2).MakeTransparent(Color.White)
            Frames(3).MakeTransparent(Color.White)
            'Make all white to be transparent.
    
    // *** 1 ***
            Bitmap[] frames;
            // Holds the frames of animation that we will use.
    
    // *** 2 ***
                frames = new Bitmap[] { new Bitmap("Solagon.gif"), new Bitmap("Solagon2.gif"), new Bitmap("Solagon3.gif"), new Bitmap("Solagon4.gif") };
                //Put frames into our array of animation frames.
                frames[0].MakeTransparent(Color.White);
                frames[1].MakeTransparent(Color.White);
                frames[2].MakeTransparent(Color.White);
                frames[3].MakeTransparent(Color.White);
                //Make all white to be transparent.
    
    Now, we'll change the frame every tick, so we'll need a frame counter (named Cyc). In our timer, we will update Cyc to the next frame index. However, when Cyc reaches 4, we need to make it automatically return to 0. We can do this by using a modulus to automatically cause the Cyc to cycle from 0 up to 3 and then back to 0. And then, when we draw our picture, we'll substitute our original character picture for the frame bitmap at the index represented by Cyc.
    ' *** 1 ***
        Dim Cyc As Integer  'Frame index cycler.
    
    ' *** 5 ***
            Cyc = (Cyc + 1) Mod 4
            'Cycles: 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, ...
    
    ' *** 6 ***
                    GFX.DrawImage(Frames(Cyc), 16 * Charloc.X, 16 * Charloc.Y)
                    'Draw animated character on the map so that he appears at this map coordinate.
    
    // *** 1 ***
            int cyc;  //Cycles through frame indices for animation.
    
    // *** 5 ***
                cyc = (cyc + 1) % 4;
                //Cycles through 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, ...
    
    // *** 6 ***
                        gfx.DrawImage(frames[cyc], charloc.X * 16, charloc.Y * 16);
                        //Draw the animated character so that it appears at this map coordinate.

    Map Scrolling: Camera Coordinates

    For map scrolling, we merely want the character to be centered in a small display. First, let's set up some constants that describe our system at the moment.
        Const MAPWIDTH As Integer = 25  'Number of columns in the map.
        Const MAPHEIGHT As Integer = 16  'Number of rows in the map.
        Const MAPWIDTHEX As Integer = MAPWIDTH - 1  'X - Index of the last column
        Const MAPHEIGHTEX As Integer = MAPHEIGHT - 1  'Y - Index of the last row
        Const TILESIZE As Integer = 16  'Width or height of each tile in pixels.
            const int themapwidth = 25;  //Number of columns in the map.
            const int themapheight = 16;  //Number of rows in the map.
            const int thempawidthex = themapwidth - 1;  //Index of the last column in the map.
            const int themapheightex = themapheight - 1;  //Index of the last row in the map.
            const int thetilesize = 16;  //Width or height of tile in pixels.
    Next, we want to decide how big we want our scrolling screen display to be. We'll start off with an odd number of rows and columns in the display so that the player can be immediately in the center. This will be our scrolling display size - the number of columns and rows that will be displayed in the scrolling map. We'll have two more constants for this system.
        Dim Camera As Point 'Has map coordinate that will appear in the upper-left of the scrolling display.
        Const DISPLAYEXTRAX As Integer = 3
        'Number of tiles that will appear between the player and the left (right) edge of the display.
        Const DISPLAYEXTRAY As Integer = 3
        'Number of tiles that will appear between the player and the top (bottom) edge of the display.
        Const DISPLAYWIDTH As Integer = DISPLAYEXTRAX * 2 + 1  'Number of columns in the display.
        Const DISPLAYHEIGHT As Integer = DISPLAYEXTRAY * 2 + 1  'Number of rows in the display.
            Point camera;  //Holds map coordinate that will appear in the upper-left corner of the scrolling display.
            const int thedisplayextrax = 3;  //Number of tiles shown between player and left/right edge.
            const int thedisplayextray = 3;  //Number of tiles shown between player and top/bottom edge.
            const int thedisplaywidth = 2 * thedisplayextrax + 1;  //Number of columns in the scrolling display.
            const int thedisplayheight = 2 * thedisplayextray + 1;  //Number of rows in the scrolling display.
    Our next goal is simply to figure out which part of the tile map will be displayed. To help us, we will first establish a camera location. The camera location will denote which tile coordinate will appear in the upper left corner of the display. We will use our previously declared constants to help us in the calculation (although you don't actually need the constants, they are very good for showing exactly which number we are using for a calculation. Now, if our character is dead center in the map at coordinate A,B, then our camera coordinate will be A - displayextrax, B - displayextray. Very simple, right?
            Camera.X = Charloc.X - DISPLAYEXTRAX
            Camera.Y = Charloc.Y - DISPLAYEXTRAY
            'Set camera coordinates based on the character's location.
                camera.X = charloc.X - thedisplayextrax;
                camera.Y = charloc.Y - thedisplayextray;
                //Update camera location based on where the player is.
    However, if you look again at the map and the camera, you should notice that there are a few places with low X or Y coordinates where the camera location will be off of the map. Also, there are some high X or Y coordinates that will cause the camera location to be too close to the bottom right corner of the map. These coordinates will cause the bottom right corner of the actual map to appear somewhere other than in the bottom, leading to empty rows or columns trying to be drawn.

    As you can see in the above picture, there are certain areas of the map (shaded blue) that will have invalid camera coordinates. The thickness of the border in tiles is equivalent to the value in displayextrax and displayextray. For a 7x7 display, the value of the displayextrax and displayextray is 3, so therefore we'd have 3 rows and columns from the end that are off limits. So, when the player moves to a place where the camera would be out of bounds, we have to move the camera to an in-bounds location. The pictures below show the regions where the camera would have the same location (for a 5x5 display, with extrax and extray both at 2).


    And the camera locations for those points.

    The following picture shows the valid locations for the camera. The number of X'ed columns will be equal to 2 * displayextrax, and the number of X'ed rows will be 2 * displayextray. You can also see the valid camera coordinates

    Now, how would we do this? Simply by checking if the character's location is more than the displayextrax and displayextray in both direction. For the left, we pick the greatest of the charloc.X - displayextrax, or 0. Similar application is done for the top. For the right, we pick the least of the charloc.X - displayextrax, or mapwidth - displaywidth and do similar for the bottom. Therefore, our final camera adjustment looks like this:
            If Charloc.X - DISPLAYEXTRAX < 0 Then
                Camera.X = 0
                'Player too far to the left.
            ElseIf Charloc.X - DISPLAYEXTRAX > MAPWIDTH - DISPLAYWIDTH Then
                Camera.X = MAPWIDTH - DISPLAYWIDTH
                'Player too far to the right.
            Else
                Camera.X = Charloc.X - DISPLAYEXTRAX
            End If
            If Charloc.Y - DISPLAYEXTRAY < 0 Then
                Camera.Y = 0
                'Player too far up.
            ElseIf Charloc.Y - DISPLAYEXTRAY > MAPHEIGHT - DISPLAYHEIGHT Then
                Camera.Y = MAPHEIGHT - DISPLAYHEIGHT
                'Player too far down.
            Else
                Camera.Y = Charloc.Y - DISPLAYEXTRAY
            End If
            'Set camera coordinates based on the character's location.
                if (charloc.X - thedisplayextrax < 0)
                    camera.X = 0;
                    //Player too far to the left.
                else if (charloc.X - thedisplayextrax > themapwidth - thedisplaywidth)
                    camera.X = themapwidth - thedisplaywidth;
                    //Player too far right.
                else
                    camera.X = charloc.X - thedisplayextrax;
    
                if (charloc.Y - thedisplayextray < 0)
                    camera.Y = 0;
                    //Player too far up.
                else if (charloc.Y - thedisplayextray > themapheight - thedisplayheight)
                    camera.Y = themapheight - thedisplayheight;
                    //Player too far down.
                else 
                    camera.Y = charloc.Y - thedisplayextray;
                //Update camera location based on where the player is.

    Map Scrolling: Scrolling the Display

    Now, that we have our camera coordinates, we need to tweak the ranges on our tile map drawing's For loop. Since our camera is already setup so that nothing will go out of range for our specified display size, we only need to start from the camera's coordinates and add 2 * displayextra for the upperbound on both loops. Note that we'll also have to make sure that our drawing occurs in the upper left corner by subtracting the camera location from certain map coordinates. Or, we can simply tell the for loops to go from 0 to 2 * displayextra and simply add the camera location to the other map coordinates.
        Const DISPLAYWIDTHEX As Integer = DISPLAYEXTRAX * 2
        'Number of visible scrolling columns that the player is not standing on.
        Const DISPLAYHEIGHTEX As Integer = DISPLAYEXTRAY * 2
        'Number of visible scrolling rows that the player is not standing on.
    
            For LY = 0 To DISPLAYHEIGHTEX
                For LX = 0 To DISPLAYWIDTHEX
    
                    ThisIndex = Map(LX + Camera.X, LY + Camera.Y)
                    'Gets the tile index for this tile on the map.
    
                    Scx = TILESIZE * ThisIndex
                    'Returns the source coordinate's X-location for this tile index.
    
                    GFX.DrawImage(Tileset, TILESIZE * LX, TILESIZE * LY, New Rectangle(Scx, 0, TILESIZE, TILESIZE), GraphicsUnit.Pixel)
                    'Draw a tile.
    
                    GFX.DrawImage(Frames(Cyc), TILESIZE * (Charloc.X - Camera.X), TILESIZE * (Charloc.Y - Camera.Y))
                    'Draw animated character on the scrolling map so that he is always visible.
    
                Next
            Next 'The for loop will end up drawing a grid of 400 tiles.
            const int thedisplaywidthex = 2 * thedisplayextrax;
            //Number of columns in scrolling display that the player is not on.
            const int thedisplayheightex = 2 * thedisplayextray;
            //Number of rows in scrolling display that the player is not on.
    
                for (ly = 0; ly < thedisplayheight ; ly++)
                {
                    for (lx = 0; lx < thedisplaywidth ; lx++)
                    {
                        thisindex = map[lx + camera.X , ly + camera.Y ];
                        //The tile index for this tile on the map.
    
                        scx = thisindex * thetilesize ;
                        //Source coordinate X location for this tile index.
    
                        gfx.DrawImage(tileset, lx * thetilesize, ly * thetilesize, new Rectangle(scx, 0, thetilesize,thetilesize), GraphicsUnit.Pixel);
                        //Draw a single tile to the display.
    
                        gfx.DrawImage(frames[cyc], (charloc.X - camera.X) * thetilesize, (charloc.Y - camera.Y) * thetilesize);
                        //Draw the animated character so that it appears at this map coordinate.
                    }
                }

    Hexagon Tile Maps: Metrics

    This is where things get interesting. Hexagon maps require a fair bit of detail to setup.

    What our hexagon map will look like. Notice how the new map coordinates change from tile to tile and any tile can be next to as many as six other tiles. The first thing we need to do is to get some hexagon tiles.
    Save Me. Each tile in the hexagon tile file can be used in a hexagon map. The next thing we need to do is, like we did for tile sizes, we need to have some hexagon metric constants to see exactly what measures our hexagon has.

    Fortunately, our file hexagon is 32x32. That puts HexagonFullWidth and HexagonFullHeight at 32. The HexagonEdgesHeight is half the full height, 16, HexagonEdgesWidth is a quarter of the full width, 8, and the HexagonTopEndWidth is half of the full width, 16.
        Const HEXAGONWIDTH As Integer = 32  'Width of tile in pixels
        Const HEXAGONHEIGHT As Integer = 32  'Height of tile in pixels.
        Const HEXAGONEDGESWIDTH As Integer = HEXAGONWIDTH \ 4  'Width of a hexagon's edge 
        Const HEXAGONEDGESHEIGHT As Integer = HEXAGONHEIGHT \ 2  'Height of a hexagon's edge
        Const HEXAGONTOPENDWIDTH As Integer = HEXAGONWIDTH \ 2  'Width of flat top part of hexagon.
            const int thehexagonwidth = 32;  //Width of hexagon.
            const int thehexagonheight = 32;  //Height of hexagon.
            const int thehexagonedgewidth = thehexagonwidth / 4;  //Width of the edge part of the hexagon.
            const int thehexagonedgeheight = thehexagonheight / 2;  //Height of the edge part of the hexagon.
            const int thehexagontopendwidth = thehexagonwidth / 2;  //Width of the top flat part of the hexagon.

    Hexagon Tile Maps: Map Coordinate Transform

    Now, we know the display sizes of various parts of our hexagons. The next thing we have to is transform map coordinates to display coordinates so that we can draw our hexagons in the right spot. For a regular square tile map the transform was simply:
    xfx = mcx * 16 and
    xfy = mcy * 16
    Not true for the hexagon. First, we have to determine where the map coordinate 0,0 will be in display coordinates.
    Looking again at our hexagon map:

    We can see tile 0,0 is against the top of the display so that's an easy 0. The X display coordinate requires some calculation:
    The distance that this tile is from the left end is a lot of HexagonEdgesWidths and HexagonTopEndWidths: one of each for (0,1), another for (0,2), and so on until we get to (0,7). So, we have 7 (HexagonEdgesWidths + HexagonTopEndWidths) as the X display coordinate for the tile at map coordinate (0,0). 7 is equal to the MapHeightEx for this map, therefore our (0,0) point is
    X = MapHeightEx * (HexagonEdgesWidth + HexagonTopEndWidth) ... and Y = 0. We'll put the coordinates into constants:
        Const ZEROPOINTX As Integer = MAPHEIGHTEX * (HEXAGONEDGESWIDTH + HEXAGONTOPENDWIDTH)
        'Distance 0,0 tile is from the left end of the display.
        Const ZEROPOINTY As Integer = 0
            const int thezeropointx = themapheightex * (thehexagontopendwidth + thehexagonedgewidth);
            //X display coordinate of the 0,0 map coordinate tile.
            const int thezeropointy = 0;  //Y display coordinate of the 0,0 map coordinate tile.
    Now, back to our transform: we now have the value for the transform at 0,0. Next we step in each map coordinate tile direction to see the amount that the display coordinate changes.
    So, taking a step in the positive X map coordinate direction moves us to the right by HexagonEdgesWidth + HexagonTopEndWidth and down by HexagonEdgesHeight. This would be two slope values for the transform. The HexagonEdgesWidth + HexagonTopEndWidth would be multiplied to the map X coordinate for the display X coordinate and the HexagonEdgesHeight would be multiplied to the map X coordinate for the display Y coordinate. So we have:
    xfx = (HexagonEdgesWidth + HexagonTopEndWidth) * mcx + ??? * mcy + zeropointx
    xfy = HexagonEdgesHeight * mcx + ??? * mcy
    Next, we have to find how much each coordinate changes as we step along (in the map coordinates) in the +Y direction. The xfx after this step will go down by (HexagonEdgesWidth + HexagonTopEndWidth), or (the negative) up by -(HexagonEdgesWidth + HexagonTopEndWidth). The xfy after this step will increase again by HexagonEdgesHeight. This leaves our transform like this:
    xfx = (HexagonEdgesWidth + HexagonTopEndWidth) * mcx - (HexagonEdgesWidth + HexagonTopEndWidth) * mcy + zeropointx
    xfy = HexagonEdgesHeight * mcx + HexagonEdgesHeight * mcy
    Or, after a little simplification:
    xfx = (HexagonEdgesWidth + HexagonTopEndWidth) * (mcx - mcy) + zeropointx
    xfy = HexagonEdgesHeight * (mcx + mcy)
    Now, we have a transform; we can draw our hexagon map.

    Hexagon Tile Maps: Drawing The Hexagon Map

    First, we need to get our hexagon tile bitmap and set it up.
        Dim Tileset As Bitmap    'Holds the tiles that we will draw.
    
            Tileset = New Bitmap("hextiles.bmp")   'Put the picture into our bitmap.
            Tileset.MakeTransparent(Color.White)  'Make the white part surround the hexagons to be transparent.
    
            Bitmap tileset;  //Will hold our tileset picture.
    
                tileset = new Bitmap("hextiles.bmp");
                // Put picture into our bitmap.
                tileset.MakeTransparent(Color.White);  //Make white areas around hexagon transparent.
    Now, our loop will, of course, have to be refurbished
            Dim Xfx, Xfy As Integer  'Transformed map coordinates to display coordinate.
    
            GFX.FillRectangle(Brushes.Black, Me.ClientRectangle)
            'Fill background with black to hide things not drawn due to transparency.
    
            For LY = 0 To MAPHEIGHTEX
                For LX = 0 To MAPWIDTHEX
    
                    ThisIndex = Map(LX, LY)
                    'Gets the tile index for this tile on the map.
    
                    Scx = HEXAGONWIDTH * ThisIndex
                    'Returns the source coordinate's X-location for this tile index.
    
                    Xfx = (HEXAGONEDGESWIDTH + HEXAGONTOPENDWIDTH) * (LX - LY) + ZEROPOINTX
                    Xfy = HEXAGONEDGESHEIGHT * (LX   LY)
                    'Transform map coordinates to display coordinates.
    
                    GFX.DrawImage(Tileset, Xfy, Xfy, New Rectangle(Scx, 0, HEXAGONWIDTH, HEXAGONHEIGHT), GraphicsUnit.Pixel)
                    'Draw a tile.
    
    
                Next
            Next 'The for loop will end up drawing a grid of 400 tiles.
                int xfx, xfy;  //Display coordinates for a particular map coordinate.
    
                gfx.FillRectangle(Brushes.Black, this.ClientRectangle);
                //Fill with black to erase anything not overdrawn due to transparency.
    
                for (ly = 0; ly < themapheight ; ly++)
                {
                    for (lx = 0; lx < themapwidth ; lx++)
                    {
                        thisindex = map[lx, ly ];
                        //The tile index for this tile on the map.
    
                        scx = thisindex * thehexagonwidth ;
                        //Source coordinate X location for this tile index.
    
                        xfx = (thehexagonedgewidth + thehexagontopendwidth) * (lx   ly) + thezeropointx;
                        xfy = thehexagonedgeheight * (lx + ly);
                        // Transforms the map coordinates to display coordinates for the tile.
    
                        gfx.DrawImage(tileset, xfx, xfy, new Rectangle(scx, 0, thehexagonwidth, thehexagonheight), GraphicsUnit.Pixel);
                        //Draw a single tile to the display.
    
                    }
                }

    Hexagon Tile Maps: Character

    Of course, we cannot leave out the character. The character can easily be drawn by applying the same transform that we just made, but by also adding one quarter of the hexagon size to get the coordinate for the character.
    Const CHARACTEROFFDISTANCE As Integer = HEXAGONHEIGHT \ 4  'Distance character is from corner of hexagon's square
    
            Xfx = (HEXAGONEDGESWIDTH + HEXAGONTOPENDWIDTH) * (Charloc.X - Charloc.Y) + ZEROPOINTX + CHARACTEROFFDISTANCE
            Xfy = HEXAGONEDGESHEIGHT * (Charloc.X + Charloc.Y) + CHARACTEROFFDISTANCE
            'Transform map coordinates to display coordinates.
    
            GFX.DrawImage(Frames(Cyc), Xfx, Xfy)
            'Draw animated character on the scrolling map so that he is always visible.
            const int thecharacteroffdistance = thehexagonheight / 4;  //Distance character is from top-left of hexagon.
    
                xfx = (thehexagonedgewidth + thehexagontopendwidth) * (charloc.X - charloc.Y) + thezeropointx + thecharacteroffdistance;
                xfy = thehexagonedgeheight * (charloc.X + charloc.Y) + thecharacteroffdistance;
                // Transforms the map coordinates to display coordinates for the tile.
    
                gfx.DrawImage(frames[cyc], xfx, xfy);
                //Draw the animated character so that it appears at this map coordinate.
    To move the character, you can use any keys you want, but for that full hexagonal feel, you'll need six... and six booleans to go with each key.
        Dim GoUpLeft, GoUp, GoUpRight, GoDownLeft, GoDown, GoDownRight As Boolean
    
        Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
            'Check keycode against left, right, up, and down.
            If e.KeyCode = Keys.NumPad7 Then
                GoUpLeft = True
            ElseIf e.KeyCode = Keys.NumPad9 Then
                GoUpRight = True
            ElseIf e.KeyCode = Keys.NumPad8 Then
                GoUp = True
            ElseIf e.KeyCode = Keys.NumPad2 Then
                GoDown = True
            ElseIf e.KeyCode = Keys.NumPad1 Then
                GoDownLeft = True
            ElseIf e.KeyCode = Keys.NumPad3 Then
                GoDownRight = True
            End If
        End Sub
        Private Sub Form1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
            'Check keycode against left, right, up, and down.
            If e.KeyCode = Keys.NumPad7 Then
                GoUpLeft = False
            ElseIf e.KeyCode = Keys.NumPad9 Then
                GoUpRight = False
            ElseIf e.KeyCode = Keys.NumPad8 Then
                GoUp = False
            ElseIf e.KeyCode = Keys.NumPad2 Then
                GoDown = False
            ElseIf e.KeyCode = Keys.NumPad1 Then
                GoDownLeft = False
            ElseIf e.KeyCode = Keys.NumPad3 Then
                GoDownRight = False
            End If
        End Sub
    
            If GoUpLeft Then
                Charloc.Offset(-1, 0)  'Move left.
            End If
            If GoDownRight Then
                Charloc.Offset(1, 0)   'Move right... etc.
            End If
            'Do horizontal movements first.
    
            'Then check for collision in the horizontal.
            If Not (Charloc.X >= 0 AndAlso Charloc.X <= MAPWIDTHEX) Then
                Collided = True
            ElseIf Array.BinarySearch(Blocked, Map(Charloc.X, Charloc.Y)) >= 0 Then
                Collided = True
            End If
    
            'A collision occured while moving horizontally.
            If Collided Then  'Move back to where you came from.
                If GoUpLeft Then
                    Charloc.Offset(1, 0)
                End If
                If GoDownRight Then
                    Charloc.Offset(-1, 0)
                End If
                Collided = False
            End If
    
            'Followed by vertical movements.
            If GoUpRight Then
                Charloc.Offset(0, -1)
            End If
            If GoDownLeft Then
                Charloc.Offset(0, 1)
            End If
    
            'Then check for collision in the vertical.
            If Not (Charloc.Y >= 0 AndAlso Charloc.Y <= MAPHEIGHTEX) Then
                Collided = True
            ElseIf Array.BinarySearch(Blocked, Map(Charloc.X, Charloc.Y)) >= 0 Then
                Collided = True
            End If
    
            'Collision occured while moving vertically.
            If Collided Then  'Move back to where you came from.
                If GoUpRight Then
                    Charloc.Offset(0, 1)
                End If
                If GoDownLeft Then
                    Charloc.Offset(0, -1)
                End If
            End If
    
            'And then diagonal movements.
            If GoUp Then
                Charloc.Offset(-1, -1)
            End If
            If GoDown Then
                Charloc.Offset(1, 1)
            End If
    
            'Then check for collision in the vertical.
            If Not (Charloc.X >= 0 AndAlso Charloc.X <= MAPWIDTHEX AndAlso Charloc.Y >= 0 AndAlso Charloc.Y <= MAPHEIGHTEX) Then
                Collided = True
            ElseIf Array.BinarySearch(Blocked, Map(Charloc.X, Charloc.Y)) >= 0 Then
                Collided = True
            End If
    
            'Collision occured while moving vertically.
            If Collided Then  'Move back to where you came from.
                If GoUp Then
                    Charloc.Offset(1, 1)
                End If
                If GoDown Then
                    Charloc.Offset(-1, -1)
                End If
            End If
            bool goupleft, goupright, godownleft, godownright, goup, godown;  //Indicates keystates for these keys.
    
            void Form1_KeyUp(object sender, KeyEventArgs e)
            {
                if (e.KeyCode == Keys.NumPad7)
                    goupleft = false;
                else if (e.KeyCode == Keys.NumPad9)
                    goupright = false;
                else if (e.KeyCode == Keys.NumPad8 )
                    goup = false;
                else if (e.KeyCode == Keys.NumPad2 )
                    godown = false;
                else if (e.KeyCode == Keys.NumPad1)
                    godownleft = false ;
                else if (e.KeyCode == Keys.NumPad3)
                    godownright = false ;
            }
    
            void Form1_KeyDown(object sender, KeyEventArgs e)
            {
                if (e.KeyCode == Keys.NumPad7)
                    goupleft = true;
                else if (e.KeyCode == Keys.NumPad9)
                    goupright = true;
                else if (e.KeyCode == Keys.NumPad8)
                    goup = true;
                else if (e.KeyCode == Keys.NumPad2)
                    godown = true;
                else if (e.KeyCode == Keys.NumPad1)
                    godownleft = true;
                else if (e.KeyCode == Keys.NumPad3)
                    godownright = true;
            }
    
                if (goupleft)
                    charloc.Offset(-1, 0);
                if (godownright)
                    charloc.Offset(1, 0);
                //Move player to tile in this direction.
    
                if (!(charloc.X >= 0 && charloc.X <= thempawidthex ))
                    collided = true;
                else if (Array.BinarySearch(blocked, map[charloc.X, charloc.Y]) >= 0)
                    collided = true;
    
                if (collided)
                {
                    if (goupleft)
                        charloc.Offset(1, 0);
                    if (godownright)
                        charloc.Offset(-1, 0);
                    collided = false;
                }
    
                if (goup)
                    charloc.Offset(-1, -1);
                if (godown)
                    charloc.Offset(1, 1);
                //Move player to tile in this direction.
    
                if (!(charloc.X >= 0 && charloc.X <= thempawidthex && charloc.Y >= 0 && charloc.Y <= themapheightex))
                    collided = true;
                else if (Array.BinarySearch(blocked, map[charloc.X, charloc.Y]) >= 0)
                    collided = true;
    
                if (collided)
                {
                    if (goup)
                        charloc.Offset(1, 1);
                    if (godown)
                        charloc.Offset(-1, -1);
                    collided = false;
                }
    
                if (goupright)
                    charloc.Offset(0, -1);
                if (godownleft)
                    charloc.Offset(0, 1);
                //Move player to tile in this direction.
    
                if (!(charloc.Y >= 0 && charloc.Y <= themapheightex))
                    collided = true;
                else if (Array.BinarySearch(blocked, map[charloc.X, charloc.Y]) >= 0)
                    collided = true;
    
                if (collided)
                {
    
                    if (goupright)
                        charloc.Offset(0, 1);
                    if (godownleft)
                        charloc.Offset(0, -1);
                }