Part 6 - Side Topic - Auto Transitions


Back in Part 2 of the series, I mentioned an article on creating tile map terrain in such a way that the game can generate the transitions between regions for you instead of having to draw them all yourself. It has long been an interesting article to me, but I never got around to actually doing anything with it.

A few weeks ago, I received an e-mail from Alexander Weiß, who had read the tutorial and the article mentioned above and actually gone ahead and implemented the auto-transition system. Mr. Weiß was kind enough to forward his implementation of the system on to me, and I'm sharing it here. I just want to be clear that all the credit here goes to Mr. Weiß. I simply typed up the method and am posting it here for everyone to enjoy.

Setting Up


We will begin with the project code as it existed at the end of Part 2. Mr. Weiß has provided the following tileset which we will use to implement his auto transition generation:



Save this file to the Textures/Tilesets folder in your content project and modify the LoadContent method to load this tileset instead of the Part 2 tileset:

Tile.TileSetTexture = Content.Load(@"Textures\TileSets\part6_tileset");


This tileset spaces out the terrain tiles along the left edge of the image, meaning that the tiles we will actually draw with will be 32 tile indexes apart (in other words, we will draw with tiles 0, 32, 64, and 96). The remainder of the tiles on the image are used to generate the automatic transition effect.

Because of this change in the indexing, we need to update the constructor of the TileMap class to generate our map using the new tile indexes. Go ahead and replace the constructor with the following:

        public TileMap()
        {
            for (int y = 0; y < MapHeight; y++)
            {
                MapRow thisRow = new MapRow();
                for (int x = 0; x < MapWidth; x++)
                {
                    thisRow.Columns.Add(new MapCell(64));
                }
                Rows.Add(thisRow);
            }

            // Create Sample Map Data
            Rows[0].Columns[3].TileID = 96;
            Rows[0].Columns[4].TileID = 96;
            Rows[0].Columns[5].TileID = 32;
            Rows[0].Columns[6].TileID = 32;
            Rows[0].Columns[7].TileID = 32;

            Rows[1].Columns[3].TileID = 96;
            Rows[1].Columns[4].TileID = 32;
            Rows[1].Columns[5].TileID = 32;
            Rows[1].Columns[6].TileID = 32;
            Rows[1].Columns[7].TileID = 32;

            Rows[2].Columns[2].TileID = 96;
            Rows[2].Columns[3].TileID = 32;
            Rows[2].Columns[4].TileID = 32;
            Rows[2].Columns[5].TileID = 32;
            Rows[2].Columns[6].TileID = 32;
            Rows[2].Columns[7].TileID = 32;

            Rows[3].Columns[2].TileID = 96;
            Rows[3].Columns[3].TileID = 32;
            Rows[3].Columns[4].TileID = 32;
            Rows[3].Columns[5].TileID = 0;
            Rows[3].Columns[6].TileID = 0;
            Rows[3].Columns[7].TileID = 0;

            Rows[4].Columns[2].TileID = 96;
            Rows[4].Columns[3].TileID = 32;
            Rows[4].Columns[4].TileID = 32;
            Rows[4].Columns[5].TileID = 0;
            Rows[4].Columns[6].TileID = 0;
            Rows[4].Columns[7].TileID = 0;

            Rows[5].Columns[2].TileID = 96;
            Rows[5].Columns[3].TileID = 32;
            Rows[5].Columns[4].TileID = 32;
            Rows[5].Columns[5].TileID = 0;
            Rows[5].Columns[6].TileID = 0;
            Rows[5].Columns[7].TileID = 0;

            doAutoTransition();
        }


If you comment out the call to doAutoTransition() and run the project, you will see that it generates the same small sample map we had before.

Implementing the Transitions


The magic, of course, comes in the implementation of the doAutoTransition() method. In order to implement this method, we need a couple of helpers.

        private int GetTileBaseHeight(int tileID)
        {
            // Returns the Base Height of a Tile, we have 32 Tiles for one Terrain so divide with 32
            // 1 -> 0, 16 -> 0, 33 -> 1       
            return tileID / 32;
        }


Recall from the original article that with this type of transition generation, terrain stacks in a particular order. In this case, the lower the tile number, the further down on the stack the terrain is, so water will always be the lowest type, followed by dirt, followed by grass, and finally rock will cover everything else. the GetTileBaseHeight() method simply returns a 0, 1, 2, or 3 depending on the passed tileID value.

Next, we need a helper to get the tileID of the base tile for a particular map cell:

        private int getBaseTile(int y, int x)
        {
            if (x < 0 || y < 0 ||
                x >= MapWidth || y >= MapHeight)
            {
                return 0;
            }

            return Rows[y].Columns[x].TileID;
        }


While we could simply check Rows[y].Columns[x] directly, that would not allow us the opportunity to catch errors when a cell is requested that doesn't exist on the map. Since our transition code will look at the tiles around a given cell, it will frequently request tiles off of the edges of the map, so we need to make sure that doesn't crash our program.

Our last two helper functions will build the actualy tile offsets referred to in the original article:

        private int CalculateTransistionTileEdge(int y, int x, int iHeight)
        {
            int temp = 0;

            if (GetTileBaseHeight(getBaseTile(y, x - 1)) == iHeight)
            {
                // Left
                temp += 1;
            }

            if (GetTileBaseHeight(getBaseTile(y - 1, x)) == iHeight)
            {
                // Top
                temp += 2;
            }

            if (GetTileBaseHeight(getBaseTile(y, x + 1)) == iHeight)
            {
                // Right
                temp += 4;
            }
            if (GetTileBaseHeight(getBaseTile(y + 1, x)) == iHeight)
            {
                // bottom
                temp += 8;
            }

            if (temp > 0)
            {
                return temp;
            }

            return -1;
        }

        private int CalculateTransistionTileCorner(int y, int x, int iHeight)
        {
            int temp = 0;

            if (GetTileBaseHeight(getBaseTile(y - 1, x - 1)) == iHeight)
            {
                // Left top
                temp += 1;
            }
            if (GetTileBaseHeight(getBaseTile(y - 1, x + 1)) == iHeight)
            {
                // Top right
                temp += 2;
            }
            if (GetTileBaseHeight(getBaseTile(y + 1, x + 1)) == iHeight)
            {
                // bottem Right
                temp += 4;
            }
            if (GetTileBaseHeight(getBaseTile(y + 1, x - 1)) == iHeight)
            {
                // bottom left
                temp += 8;
            }
            if (temp > 0)
            {
                return temp;
            }

            return -1;
        }


These two methods work in a similar manner, checking the tiles around a given point to determine if they contain a tile of the level passed in with the iHeight variable. If they do, the appropriate offset into the 32 tiles that make up the tile set for that particular piece of terrain are added. As an example, of the current tile is a grass tile, and the tile has rocks both above and below it, CalculateTransitionTileEdge will return a 10 (top of 2 plus bottom of 8) when iHeight is equal to 3. If you count over 10 squares from the full grass tile on the tileset above, you will find a tile with a border of grass along the top and bottom edges.

Finally, we need to doAutoTransition() method itself:

        private void doAutoTransition()
        {
            for (int y = 0; y < MapHeight; y++)
            {
                for (int x = 0; x < MapWidth; x++)
                {
                    int height = getBaseTile(y, x);
                    int start = GetTileBaseHeight(height) + 1;
                    for (int i = start; i < 4; i++)
                    {

                        int tileID = CalculateTransistionTileEdge(y, x, i);
                        if (tileID > -1)
                        {
                            Rows[y].Columns[x].AddBaseTile(i * 32 + tileID);
                        }

                        tileID = CalculateTransistionTileCorner(y, x, i);
                        if (tileID > -1)
                        {
                            Rows[y].Columns[x].AddBaseTile(i * 32 + 16 + tileID);
                        }
                    }
                }
            }
        }


This method loops through all of the heights that exist above the current tile's height and adds tiles to the BaseTiles layer for all of the overlaid edges. Now that you have your automatic transition method in place, go ahead and start the project:



Adding Tiles


Mr. Weiß also forwarded me an image that he used to create the tileset above, along with usage instructions:



  • Create a new image with your favorite image editing software that is 768x96 pixels in size.
  • Fill the image with the 48x48 pixel sized tile image that will be used for this type of terrain.
  • Copy the alpha mask image into your new image's alpha channel.
  • Create several of these images, one for each type of terrain you will have on your map.
  • Merge the images into a single texture, stacking the terrain vertically with the lowest depth terrain at the top of the image and the highest depth terrain at the top.


Again, I want to thank Alexander Weiß for sending me his implementation of this method!





































 

 
 
Site Contents Copyright 2006 Full Revolution, Inc. All rights reserved.
This site is in no way affiliated with Microsoft or any other company.
All logos and trademarks are copyright their respective companies.
RSS FEED