Josh-CO Dev

Solving the worlds problems one line of code at a time.

Basic Tile Engine Tutorial – Creating our map class

1 Comment


(Update) Just like our first tutorial, I have been recreating this tutorial from scratch to give you a code example. You can download this example here. Also, I have added another method that was missing from the original post named addNewSprite. I’ve also taken the time to really clean up the code and make it more readable.

This is the second part of our basic tile engine tutorial. This tutorial will cover the major programming concepts needed to create a platformer type game. In the previous tutorial, we covered creating our tile class which will be used for our individual tiles. You can find this tutorial if you missed it.

Now that we have our basic tile sprite class, we can start building out our class that will hold our map. Start by creating a new class and naming it Level.cs.

-Include the usual stuff. These are all the namespaces that are needed by default in an XNA game.

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Xml;
using System.IO;

-Now let’s make our properties

//new list to hold our actual level
public List Map
{
get { return map; }
set { map = value; }
}
List map;

GraphicsDeviceManager Graphics
{
get { return graphics; }
set { graphics = value; }
}
GraphicsDeviceManager graphics;

//what stage is this?
public int Stage
{
get { return stage; }
set { stage = value; }
}
int stage;

public Vector2 StartingPosition
{
get { return startingPosition; }
set { startingPosition = value; }
}
Vector2 startingPosition; 

-So, some explanation, though most of this should look familiar if you followed the first tutorial. Map creates a list of our tilesprites that we created in our previous tutorial. It seems that most people use an array to hold their maps. This is all personal preference and comes from all the .net programming that I have done. I just like lists better and find them easier to work with. Graphics is our graphics manager. Stage is what level your a loading. Starting Position is the starting position of the player which we will add in the next tutorial.

-Back to the code. Here is our constructor which is much simpler than our tile constructor. All we are doing here is setting the start position of the player to (0,0), instantiating our map, and setting our graphics device.

//constructor
public Level(GraphicsDeviceManager graphics)
{
Map = new List();
this.graphics = graphics;
this.startingPosition = Vector2.Zero;
}

-Now we get to the code that actually loads our level. First the code, we’ll do some explaining after.

public void LoadLevelText(int stageToLoad, ContentManager cm)
{
//pull the stage to load from our properties
this.stage = stageToLoad;

//build the name of the xml file that holds our stage. In this case the file is stored in the Levels folder of our project
//and is named Level and then the number of the level
string fileName = @"Levels\Level" + stageToLoad.ToString() + ".txt";

// Load the level and ensure all of the lines are the same length.
int width;
List lines = new List();

//create a new filestream to read in our file
using (Stream fStream = TitleContainer.OpenStream(fileName))
{
using (StreamReader reader = new StreamReader(fStream))
{
//pull out the first line and see how many characters are in the line
string line = reader.ReadLine();
width = line.Length;

//
while (line != null)
{
lines.Add(line);
//if one line is a different size than the others, we throw an error
if (line.Length != width)
throw new Exception(String.Format("The length of line {0} is different from all preceeding lines.", lines.Count));
line = reader.ReadLine();
}
}
}

//now we basically loop through our string array and pull out each numerical value
int yPos = 0;
foreach (string s in lines)
{
int xPos = 0;

foreach (char c in s)
{
TileSprite ts;

switch (c)
{
//depending on the number, we will create a different tilesprite
case '0':
//empty tile, do nothing
break;
//we currently only have one tile in our engine
case '1':
ts = new TileSprite();
ts.Texture = cm.Load("Tiles/grassTile2");
ts.Landable = true;
ts.Position = new Vector2(xPos * ts.SpriteWidth, yPos * ts.SpriteHeight);
this.addNewSprite(ts);
break;
case '3': //load a flower
ts = new TileSprite();
ts.Texture = cm.Load("Tiles/Flower");
ts.Landable = false;
ts.Position = new Vector2(xPos * ts.SpriteWidth, yPos * ts.SpriteHeight);
this.addNewSprite(ts);
break;
case '4': //load an ice tile
ts = new TileSprite();
ts.Texture = cm.Load("Tiles/Iceland");
ts.Landable = true;
ts.Position = new Vector2(xPos * ts.SpriteWidth, yPos * ts.SpriteHeight);
this.addNewSprite(ts);
break;
case '5': //load an ice tile
ts = new TileSprite();
ts.Texture = cm.Load("Tiles/dirtTile2");
ts.Landable = false;
ts.Position = new Vector2(xPos * ts.SpriteWidth, yPos * ts.SpriteHeight);
this.addNewSprite(ts);
break;
case 'P': //set our starting position
this.startingPosition = new Vector2(xPos * 32, yPos * 32);
break;
default:
throw new Exception(String.Format("Invalid Tile Loaded"));
}

xPos++;
}

yPos++;
}
}

-This code looks worse than it really is. All we are doing is passing in a stage to load and then finding the text file that holds our level. You can create these manually using notepad or an equivalent text editor and store them anywhere in your project. I like to create a folder for them. The text file will look something like this:

000000000222220000
000000002222200000
etc

We then open up a file stream to read and parse this file. We start to check every line and check the length. If a line is a different size than the others (more or less tiles) we throw an error. If that checks out, we call a foreach loop for each line (string) and then loop through each character (c). We read the value and then create a new tilesprite (see previous tutorial) and add it to our list. That’s it for the stage loading, pretty simple.

-All that’s left is to write our draw method and call this from your main class.

//draw our tiles
public void Draw(SpriteBatch sb)
{
//draw our tiles
foreach (TileSprite ts in this.map)
{
if (ts.Position.X + ts.SpriteWidth > 0 && ts.Position.X <= graphics.PreferredBackBufferWidth)
ts.Draw(sb);
}
}

-This is nothing more than a foreach loop. Since we created a draw method in our tilesprite class, all we have to do is loop through our map list and draw each tile, this is another advantage of using a list as opposed to an array. We are performing a check to make sure each tile is in the viewable window so we do not draw anything outside of our screen, thus wasting resources. In a later tutorial, we’ll improve on this.

-Next we need to add our addNewSprite method. All this code does is take the tile that you are passing in and add it to our map list.

        private void addNewSprite(Tile t)
        {
            this.Map.Add(t);
        }

-Now, we just add all this to whatever your main class is. Create a new map object as a variable. Then simply call the map.LoadLevel method. Then be sure in your main draw method to call map.Draw. That’s it! If you build your project you should see a basic tilemap.
Here is what I have in my main game class:
First, a global level declaration

 Level level;

-Then we declare it and call our level

            level = new Level(graphics);
            level.LoadLevelText(1, content);

-Then we handle our draw

            SpriteBatch sb = new SpriteBatch(_device);
            
            sb.Begin();
            
            level.Draw(sb);
            
            sb.End();
            
            base.Draw(gameTime);

If you run the project at this point, you should have a basic map. If you have any issues, please download the solution above.

As usual, feel free to drop any questions in the comments.

Advertisements

One thought on “Basic Tile Engine Tutorial – Creating our map class

  1. Hey man this is a great series, but I’d love it if you improved on the collision and give an example of collision on all sides, like for a maze game or something,

    Thanks,
    Andrew

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s