Tile Map XML Tutorial Tiled Map Format



If you take a look at http://doc.mapeditor.org/reference/tmx-map-format and look at you will see
  • version: The TMX format version, generally 1.0.
  • orientation: Map orientation. Tiled supports "orthogonal", "isometric", "staggered" (since 0.9) and "hexagonal" (since 0.11).
  • renderorder: The order in which tiles on tile layers are rendered. Valid values are right-down (the default), right-up, left-down and left-up. In all cases, the map is drawn row-by-row. (since 0.10, but only supported for orthogonal maps at the moment)
  • width: The map width in tiles.
  • height: The map height in tiles.
  • tilewidth: The width of a tile.
  • tileheight: The height of a tile.
  • hexsidelength: Only for hexagonal maps. Determines the width or height (depending on the staggered axis) of the tile's edge, in pixels.
  • staggeraxis: For staggered and hexagonal maps, determines which axis ("x" or "y") is staggered. (since 0.11)
  • staggerindex: For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted. (since 0.11)
  • backgroundcolor: The background color of the map. (since 0.9, optional, may include alpha value since 0.15 in the form #AARRGGBB)
  • nextobjectid: Stores the next available ID for new objects. This number is stored to prevent reuse of the same ID after objects have been removed. (since 0.11)
The tilewidth and tileheight properties determine the general grid size of the map. The individual tiles may have different sizes. Larger tiles will extend at the top and right (anchored to the bottom left).
A map contains three different kinds of layers. Tile layers were once the only type, and are simply called layer, object layers have the objectgroup tag and image layers use the imagelayer tag. The order in which these layers appear is the order in which the layers are rendered by Tiled.
Can contain: properties, tileset, layer, objectgroup, imagelayer

Now if you create different types of maps with different settings, layers, tilesets, and properties save them and then open them in a text editor or xml editor you will see something similar to these.





In xml you have things called elements and things called attributes. Every xml document will start with the next line is called the root element. Every xml document will contain at least one root element. Remember when we created the TMXMap class we added the [XmlRoot("map")] attribute/tag(from here on out to remove confusion when talking about xml documents and C# code I will be referring to these as tags). Notice that all of the things with built points on the tile map format documentation are all listed before the “>”, these are referred to as attributes in xml. And the things listed as a map can contain are all in between and these are called child elements. Elements in an xml document will look like
   
Or
(if no child elements exist)
or /> is knowen as the ending tag for an element. Xml is done this way so it is easy to figure out where one element starts and where it ends as well as which elements contain children and where the attributes of the elements are. When writing code in C# to get xml attributes to load in when using serialization you need to add [XmlAttribute()] tag if your varable name is the same as your attribute name or [XmlAttribute(“AttributeName”)] tag if the variable name is not the same.
For example for the version attribute you would use
[XmlAttribute()]
public float version;
or
[XmlAttribute("version")]
public float versionNumber;
For child elements you need to use a public variable with the same name or use the [XmlElement("ElementName")] tag. You can also use [XmlElement("layer", typeof(LayerElement))] if you want to load several different types into a list when using inheritance. I will show you an example of this when we get to loading in the different layer’s that a map can contain.
For now, let’s take a look at the different xml attributes that a map can contain and get them to load into our map. Every attribute is a string so you can load these all as string’s if you want to, but we are going to use types that are as close as possible to represent our attributes, this will make it easier on us when we go to displaying our map in Unity.
version is a number and in code you have different number data types, int, float, decimal, just to name a few. We will be using float in this tutorial. As of Tiled 0.16.1 which is the current version of Tiled as of this writing the version number will always be 1.0 unless you manually change it after saving the map.
orientation is orthogonal, isometric, staggered, or hexagonal. We will create our own enum for this.
renderorder is right-down, right-up, left-down, left-up. We will be using a string type for this since you cannot use the – character in an enum. We will not be using this to draw our map in Unity, this is used in Tiled to control the order in which the tiles are drawn to the screen. We will be looping thru our tiles on the tile layer right down, a tile with a position of 0,0 will be the first tile or the top left of your map and position n, -n will be the last tile or the bottom right of your map. This is how Tiled saves out the tiles when you set the Tile Layer Format to xml, regardless of the render order.
width, height, tilewidth, tileHeight, and hexsidelength are all whole numbers so we will use the int type for these.
staggeraxis is either x or y and staggerindex is either even or odd so we will be making our own enum types for these.
backgroundcolor is a hex value in the form of #AARRGGBB since we are using xml serialization this will be loaded in as a string. I may show you how to convert this to a color and use this value to set the background color of our scene when drawing the map in Unity.
nextobjectid is a whole number so we will use the int type for this. Again this is really just for use in Tiled when creating new objects in Tiled, we will more than likely not be using this within unity.
In Unity Create a new folder in TileMapXML->Scripts called Map. Then create 3 new C# scripts in TileMapXML->Scripts->Map called TMXMapOrientation, TMXStaggeraxis, and TMXStaggerIndex. Open these three files and delete all of the generated code, we are going to use these to hold our enums. You can skip this step and put all of your enums in TMXMap.cs outside of the class if you want. I just like to keep my enums in their own file so I can easily find them if I want to make a change to them.
TMXMapOrientation.cs
namespace TileMapXML.Map
{
    ///
    /// Map orientation.
    ///
    /// Tiled supports "orthogonal", "isometric" and "staggered" (since 0.9) at the moment.
    ///
    /// Tiled Map Editor Version 0.16.1
    /// orthogonal
    /// isometric
    /// staggered-isometric staggered
    /// hexagonal-hexagonal staggered
    ///
    [System.Serializable]
    public enum TMXMapOrientation
    {
        none,
        orthogonal,
        isometric,
        staggered,
        hexagonal,
    }//enum MapOrientation
}//namespace TileMapXML.Map
Nothing special here except we are using the [Systm.Serializable] tag and this is a public enum instead of a class. Also notice that the namespace is TileMapXML.Map. The Serializable tag is more for the internal workings of Unity and this being displayed in the inspector automatically when we use it in other classes derived from Monobehavior or ScriptableObject. Notice that the first entry is none which is not listed in the Tiled documentation. This is for us to use in error checking later on, we will also be assigning none as the default when this variable is used. This will be the same for TMXStaggerAxis and TMXStaggerIndex.
TMXStaggerAxis.cs
namespace TileMapXML.Map
{
    ///
    /// Stagger Axis.
    ///
    /// For staggered and hexagonal maps, determines which axis("x" or "y") is staggered. (since 0.11)
    ///
    [System.Serializable]
    public enum TMXStaggerAxis
    {
        none,
        x,
        y
    }//enum TMXMapStaggerAxis
}//namespace TileMapXML.Map

TMXStaggerIndex.cs
namespace TileMapXML.Map
{
    ///
    /// Stagger Index.
    ///
    /// For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted. (since 0.11)
    ///
    [System.Serializable]
    public enum TMXStaggerIndex
    {
        none,
        odd,
        even
    }//enum TMXStaggerIndex
}//namespace TileMapXML.Map

Now in TMXMap.cs add using TileMapXML.Map; At the top with the other using statements.
And then add the following code inside the Attributes region.
       #region Attributes
        ///
        /// The TMX format version, generally 1.0
        ///
        [XmlAttribute()]
        public float version = 0;

        ///
        /// Map orientation.
        /// Tiled supports "orthogonal", "isometric", "staggered" (since 0.9) and "hexagonal" (since 0.11).
        ///
        [XmlAttribute()]
        public TMXMapOrientation orientation = TMXMapOrientation.none;

        ///
        /// The order in which tiles on tile layers are rendered.
        /// Valid values are right-down(the default), right-up, left-down and left-up.
        /// In all cases, the map is drawn row-by-row. (since 0.10, but only supported for orthogonal maps at the moment)
        ///
        [XmlAttribute()]
        public string renderorder = "";

        ///
        /// The map width in tiles
        ///
        [XmlAttribute()]
        public int width = 0;

        ///
        /// The map height in tiles.
        ///
        [XmlAttribute()]
        public int height = 0;

        ///
        /// The width of a tile.
        ///
        [XmlAttribute()]
        public int tilewidth = 0;

        ///
        /// The height of a tile.
        ///
        [XmlAttribute()]
        public int tileheight = 0;

        ///
        /// Only for hexagonal maps.
        /// Determines the width or height (depending on the staggered axis) of the tile's edge, in pixels.
        ///
        [XmlAttribute()]
        public int hexsidelength = -1;

        ///
        /// For staggered and hexagonal maps, determines which axis("x" or "y") is staggered. (since 0.11)
        ///
        [XmlAttribute()]
        public TMXStaggerAxis staggeraxis = TMXStaggerAxis.none;

        ///
        /// For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted. (since 0.11)
        ///
        [XmlAttribute()]
        public TMXStaggerIndex staggerindex = TMXStaggerIndex.none;

        ///
        /// The background color of the map. (since 0.9, optional, may include alpha value since 0.15 in the form #AARRGGBB)
        ///
        [XmlAttribute()]
        public string backgroundcolor = "";// = new Color32(128, 128, 128, 255);

        ///
        /// Stores the next available ID for new objects. This number is stored to prevent reuse of the same ID after objects have been removed. (since 0.11)
        ///
        [XmlAttribute()]
        public int nextobjectid = 0;
        #endregion
Notice how every attribute has the [XmlAttribute()] tag. Also notice that I have initialized every variable when declaring them, this is to prevent null reference errors and also for error/type checking latter on. Also notice that I have included every attribute that can be included in a TMX file. If you do not want to support loading in staggered, isometric, hexagonal or background color you do not have to include the related variables in your code. Just be aware that by using xml serialization to load in our TMX files that if one of these attributes is included in the TMX file you may get an error when trying to load the file if you do not have it included in your C# script. These errors can be hard to track down latter if your maps are not loading correctly.
Now all that is left is for you to create your NUnit test, add the following test code to TMXTest.cs
    [Test]
    public void TMXMapLoaded()
    {
        // Every map has these
        // The version of the map should be 1.0
        Assert.AreEqual(1.0f, tmx.map.version);
        // The orientation of the map should not be none
        Assert.AreNotEqual(TileMapXML.Map.TMXMapOrientation.none, tmx.map.orientation);
        // The render-order should not be null or empty
        Assert.IsNotNullOrEmpty(tmx.map.renderorder, "Failed to load in a render order");
        // The width should be > 0
        Assert.Greater(tmx.map.width, 0);
        // The height should be > 0
        Assert.Greater(tmx.map.height, 0);
        // The tilewidth should be > 0
        Assert.Greater(tmx.map.tilewidth, 0);
        // The tileheight should be > 0
        Assert.Greater(tmx.map.tileheight, 0);
        // The nextobjectid should be > 0
        Assert.Greater(tmx.map.nextobjectid, 0);

        // check values based on the maps orientation
        switch(tmx.map.orientation)
        {
            case TileMapXML.Map.TMXMapOrientation.orthogonal:
                // hexsidelength is only for hexagonal maps
                Assert.AreEqual(-1, tmx.map.hexsidelength, "hexsidelength is only for hexagonal maps");
                // staggeraxis is only for hexagonal and staggered maps
                Assert.AreEqual(0, (int)tmx.map.staggeraxis, "staggeraxis is only for hexagonal and staggered maps");
                // staggerindex is only for hexagonal and staggered maps
                Assert.AreEqual(0, (int)tmx.map.staggerindex, "staggerindex is only for hexagonal and staggered maps");
                break;
            case TileMapXML.Map.TMXMapOrientation.isometric:
                // hexsidelength is only for hexagonal maps
                Assert.AreEqual(-1, tmx.map.hexsidelength, "hexsidelength is only for hexagonal maps");
                // staggeraxis is only for hexagonal and staggered maps
                Assert.AreEqual(0, (int)tmx.map.staggeraxis, "staggeraxis is only for hexagonal and staggered maps");
                // staggerindex is only for hexagonal and staggered maps
                Assert.AreEqual(0, (int)tmx.map.staggerindex, "staggerindex is only for hexagonal and staggered maps");
                break;
            case TileMapXML.Map.TMXMapOrientation.staggered:
                // hexsidelength is only for hexagonal maps
                Assert.AreEqual(-1, tmx.map.hexsidelength, "hexsidelength is only for hexagonal maps");
                // staggeraxis is only for hexagonal and staggered maps
                Assert.Greater((int)tmx.map.staggeraxis, 0, "staggeraxis is only for hexagonal and staggered maps");
                // staggerindex is only for hexagonal and staggered maps
                Assert.Greater((int)tmx.map.staggerindex, 0, "staggerindex is only for hexagonal and staggered maps");
                break;
            case TileMapXML.Map.TMXMapOrientation.hexagonal:
                // hexsidelength is only for hexagonal maps
                Assert.Greater(tmx.map.hexsidelength, -1, "failed to load in a hexsidelength");
                // staggeraxis is only for hexagonal and staggered maps
                Assert.Greater((int)tmx.map.staggeraxis, 0, "staggeraxis is only for hexagonal and staggered maps");
                // staggerindex is only for hexagonal and staggered maps
                Assert.Greater((int)tmx.map.staggerindex, 0, "staggerindex is only for hexagonal and staggered maps");
                break;
            default:
                break;
        }
    }//void MapLoaded
Here we check that every attribute was loaded in correctly. Notice that the switch statement we check to make sure that for maps that are not supposed to have a value do not.  Also notice that when checking the enums for the staggerindex and stageraxis we cast them to an int value, this is one of the benefits of using enum types and also why are first entry is none. You could have also done
Assert.AreEqual(TileMapXML.Map.TMXStaggerAxis.none, tmx.map.staggeraxis); and Assert.AreNotEqual(TileMapXML.Map.TMXStaggerAxis.none, tmx.map.staggeraxis);
Also notice that the test does not check the backgroundcolor attribute, this is the one case where it is only set if you change it in Tiled so it is possible that it will be an empty string, or it may have a value of some sort. You could add a check to see if it is empty then check to see if it is in the right format #AARRGGBB. I will leave that up to you to figure out how to do on your own. At this point if you run the test in Unity all of your test should pass no matter what map you are using and how it is configured.

No comments:

Post a Comment