Using a tmx map in monogame

From TRCCompSci - AQA Computer Science
Jump to: navigation, search

Install TiledSharp

The easiest method is to create your MonoGame project then:

  1. Click on project & select Nuget Package Manager
  2. Search online for TiledSharp & Install
  3. In the Game1.cs of your project add:
    using TiledSharp;
    

Simple Setup

Declarations

Add the following declaration within the main class in Game1.cs:

        TmxMap map;
        Texture2D tileset;

        int tileWidth;
        int tileHeight;
        int tilesetTilesWide;
        int tilesetTilesHigh;

Load Content

Download the files here. You will need to copy these into the content folder of your project, you will also need to add the png file to the content pipeline and build it. Without the xnb file an error will be thrown.

In the LoadContent method add the following to load your map & tiles, it also sets the size of the tiles:

            map = new TmxMap("Content/exampleMap.tmx");
            tileset = Content.Load<Texture2D>(map.Tilesets[0].Name.ToString());

            tileWidth = map.Tilesets[0].TileWidth;
            tileHeight = map.Tilesets[0].TileHeight;

            tilesetTilesWide = tileset.Width / tileWidth;
            tilesetTilesHigh = tileset.Height / tileHeight;

Draw the Map

In the draw method, add the following code to draw each tile of the map:

        spriteBatch.Begin();
        foreach(var layer in map.Layers) // loop to cycle through each layer
        {  
            for (int i = 0; i < layer.Tiles.Count; i++) // loop to cycle through each tile
            {
                int gid = layer.Tiles[i].Gid;

                // Empty tile, do nothing
                if (gid == 0)
                {

                }
                else
                {
                    int tileFrame = gid - 1;
                    int column = tileFrame % tilesetTilesWide;
                    int row = (int)Math.Floor((double)tileFrame / (double)tilesetTilesWide);

                    float x = (i % map.Width) * map.TileWidth;
                    float y = (float)Math.Floor(i / (double)map.Width) * map.TileHeight;

                    Rectangle tilesetRec = new Rectangle(tileWidth * column, tileHeight * row, tileWidth, tileHeight);

                    spriteBatch.Draw(tileset, new Rectangle((int)x-(screen.Width / 2), (int)y-(screen.Height / 2), tileWidth, tileHeight), tilesetRec, Color.White);
                }
            }
        }
        spriteBatch.End();

Moving the map

We are going to setup the player controls to get the map movement required, we will record the movement and then adjust the spriteBatch.Draw() command in the Draw method.

So firstly create a new rectangle called bounds, add it to the main declarations at the top of the program:

Rectangle bounds;

In the initialise method set this to be (0,0,0,0):

bounds = new Rectangle(0, 0, 0, 0);

If your map has an object to show the starting point you could set bounds in the LoadContent method as well.

In the Update method add this code to get the keyboard input from the user:

KeyboardState keyState = Keyboard.GetState();

int scrollx = 0, scrolly = 0;

if (keyState.IsKeyDown(Keys.Left))
   scrollx = -10;
if (keyState.IsKeyDown(Keys.Right))
   scrollx = 10;
if (keyState.IsKeyDown(Keys.Up))
   scrolly = 10;
if (keyState.IsKeyDown(Keys.Down))
   scrolly = -10;
            
bounds.X = bounds.X + scrollx;
bounds.Y = bounds.Y + scrolly;

For each of the arrow keys this will increase or decrease the value of X or Y. You could introduce a speed variable, for example speed=10 and leave the changes in the if statements to be either 1 or -1. your program could then vary speed accordingly.

Now in the draw method change the code to this:

Rectangle newView = new Rectangle((int)x - bounds.X, (int)y - bounds.Y, tileWidth, tileHeight);
spriteBatch.Draw(tileset, newView, tilesetRec, Color.White);

Firstly it replaces the code

new Rectangle((int)x, (int)y, tileWidth, tileHeight)

from the spriteBatch.Draw() command with a rectangle we create called newView.

newView is the same as the code we replaced except we add the bounds.X to the x first, and the bounds.Y to y first. Now any movement of the arrow keys will change the X & Y in bounds, which should then change the view of the map.

Map Collisions

You can load layers of your map individually:

var collision = map.Layers[2];

This can be used to load in a collision layer from your map, you can then use this to test for collisions between the player and the tiles in the layer.

            bool check = false;
            for (int i = 0; i < collision.Tiles.Count; i++) // loop to cycle through each tile
            {
                int gid = collision.Tiles[i].Gid; // get the id of the tile used

                // ignore empty tiles
                if (gid != 0)
                {
                    float x = ((i % map.Width)+1) * map.TileWidth; // calculate the x coordinate
                    float y = ((float)Math.Floor(i / (double)map.Width)+1) * map.TileHeight; // calculate the y coordinate

                    Rectangle tilerec = new Rectangle((int)x,(int)y,map.TileWidth, map.TileHeight); // create a rectangle for the tile
                    if (tilerec.Intersects(bounds))
                    {
                        check = true;
                        break;
                    }
                }
            }

Using Map Objects

You can also access the object groups and objects within your map:

var hiddenChest = map.ObjectGroups["Chests"].Objects["hiddenChest"];

You could set an object for the start or end point, spawn locations etc.

Annoyingly

The creator of TiledSharp has declared all of the set accessors to be private. This means you will not be able to interact with the map object directly (i.e. move the player object or hide objects collected). You can get around this by downloading the source code from GitHub and make which ever set accessors public. If you do this, just be aware the NuGet package as map.Layers[] the code from GitHub uses map.TileLayers[].

Drawing Objects

foreach(TmxObject to in map.ObjectGroups["Objects"].Objects)
{
    if(to.Visible)
        _spriteBatch.Draw(pixel, new Rectangle((int)to.X - bounds.X + (screen.Width / 2), (int)to.Y - bounds.Y + (screen.Height / 2), (int)to.Width, (int)to.Height), Color.White);

}

I have integrated this into the main drawing code of the map, within the foreach loop which cycles through the layers:

if(layer==collision)
{
    foreach(TmxObject to in map.ObjectGroups["Objects"].Objects)
    {
        if(to.Visible)
            _spriteBatch.Draw(pixel, new Rectangle((int)to.X - bounds.X + (screen.Width / 2), (int)to.Y - bounds.Y + (screen.Height / 2), (int)to.Width, (int)to.Height), Color.White);
    }
}