This blog is no longer updated. Feel free to copy any useful information to other blogs or wikis as this site may not exist for much longer. Thanks.


Know when to be lazy

March 6th, 2010 | 21 comments

There are times when making a game that you absolutely can’t be lazy. You need to focus and get things done if you ever want to ship. But there are times when being lazy is the best way to solve a problem. Today was one of those times for me.

I’ve started working on a new ninja game that was going to use largely tile based maps. I first started down the same road of Pixel Man, using Paint.NET for my level editor. I quickly realized that the increased complexity meant a confusing palette which made life too hard. It also ruled out things like cutscenes or any other interesting level markers. So I started working on my own editor. My method for resizing 2D arrays was so I could support the editor as I went. I never got very far into making an editor because, frankly, it’s a lot of work.

So I started feeling burnt out. I want to make this game but I was stuck from the get-go without having any means to create my levels. I could have gone ahead and hard coded a few maps but I find that’s not a workflow I tend to like. I prefer to solve the content problem first that way I know what data I will be able to code around. Today I came up with a solution for all of this. I would simply be lazy and find a tile editor someone else had made, thus saving me time.

During some random searches, I found a nice tile editor called Tiled. Tiled is a near perfect editor for tile maps. You can attach metadata to pretty much everything and you can make layers that just hold arbitrary rectangles of metadata. It’s really quite nice. Here’s one of my test levels in Tiled:

Test Level in Tiled

You can see I dedicated a rectangle for a spawn point and two for cutscenes which I could use to display some text about the game or whatever (this is a test level so none of those really mean anything).

So this editor is great. It does pretty much everything I need out of an editor, but how can I use the levels? Thankfully all levels are saved as XML files which means it’s not a whole lot of work to parse them and use them. Since I was on a streak of using other people’s work, I went out in search of XNA GS code to use the level files.

My search started at the Tiled wiki which lead me to Kevin Gadd’s excellent website which pointed me to Stephen Belanger’s blog post expanding on Kevin’s code. I took a look at the code in excitement and was a bit let down. All of the XML parsing was done at runtime into very mutable objects with some design choices I didn’t quite agree with. So I decided that this is something to take into my own hands.

Starting at around 7:30 this morning (and ending just a few minutes ago), I feverishly wrote up a custom content pipeline extension project for parsing and processing the TMX files produced by Tiled and turning them into an easy to use, largely immutable structure. Why immutable? I’m personally a fan of closed systems until you need them open. Why would you make the width of the map mutable? What happens if I accidentally change that? So in my code, most variables are read only to stop me from shooting myself in the foot. Using C#’s ‘internal’ keyword, I’ve made it so most objects are only internally constructable with a lot of private members. This keeps all the data out of the hands of those meddling games (or people like me who accidentally change something and break it all).

The extensions are nice and minimal in what they require of you. Just drop a TMX file into your content project and you’re basically done. You will also need any tile sheets used by the map. You can place them anywhere in the content folder directory and you don’t need to add them to the content project; the map will build anything it needs and it also handles loading those in for you. The only parameter on the TMX processor is the directory where those sheets can be found, relative to the content project. In my project, I have a Maps directory in my content library with my TMX file and then a TileSets directory next to that with the sheets. Therefore my TMX files need to set their TileSet Directory values to “TileSets”.

Since I spent a good five hours creating this (which is likely orders of magnitude less than it would take for me to write something on par with Tiled), I decided to share with the community. I’m sure there are others out there who would want to use Tiled for their level editor and now you can have a great base on which to build up your own library for using those maps. The code is all MS-Pl so feel free to do with it what you will. I, of course, take no responsibility if the code manifests itself into the end of the world, a black hole, or your ex-girlfriend so use at your own risk. However I’ve yet to see any of those three things, so I think we’re good.

Enough blabbing. Links:

License
Libraries and Demo Project


Possibly Related Posts

(Automatically Generated)
Pixel Man Post Mortem #2
Pixel Man Post Mortem #3
Editing The Default XNA Game Studio Project Templates

  1. March 7th, 2010 at 00:22
    Quote | #1

    Awesome! Thanks so much for this! I had seen the blog post and website before in my searches, but wasn’t quite as clever as you to take it to the next level (I’m still starting out with XNA development). Anyway, I think this is great, being able to easily use Tiled in projects saves a ton of time and work.

  2. March 7th, 2010 at 17:41
    Quote | #2

    Yeah, I didn’t really put much work into the TMX loader. It was my first project with XNA and C# too, so I was still learning and I only spent a few hours on it.

    It didn’t really need to be super optimized or anything for my uses.

  3. Tangeleno
    March 7th, 2010 at 21:12
    Quote | #3

    Thanks so much for posting this code, there is one small problem when it comes to the TmxProcessor in regards to adding the tile properties. The code doesn’t hit all the values in tileSet.TileProperties. TmxProcessor.cs lines 85-89;

    // get any properties from the tile set
    if (tileSet.TileProperties.ContainsKey(y + x))
    {
    tile.Properties = tileSet.TileProperties[y + x];
    }

    Should Be

    // get any properties from the tile set
    if (tileSet.TileProperties.ContainsKey((y * frameCountX) + x))
    {
    tile.Properties = tileSet.TileProperties[(y * frameCountX) + x];
    }

  4. March 7th, 2010 at 21:15
    Quote | #4

    Oh yeah. Whoops. :)

  5. Crowbar
    March 10th, 2010 at 12:04
    Quote | #5

    Having been working on my own Platformer using XML and dreading the whole “I need to make a level editor now :( ” thing, Tiled looks to be a blessing. I shall be playing with this later tonight for sure.

    Thanks!

  6. March 11th, 2010 at 15:12
    Quote | #6

    Hey this is very nice, thank you. I was thinking in making a loader myself, but never thought it could get that complex. But I have some problems. I’m using XNA for 6 months now, maybe, and I don’t know how to use the code you gave. There are 3 different projects on the solution. How can I use what you’ve created in my project??

    I just need anyone to point me to the right direction.
    Thanks.

  7. Andi_S
    March 14th, 2010 at 06:27
    Quote | #7

    Nick,
    thanks a lot for this! Could you maybe add a little example on how to do more than just draw the map? It’s not clear to me how I can access different objects from game which are in the map.
    My goal is to integrate this with farseer, so I have a collision layer,where I have drawn rectangles (like your cutscenes in your examples). Now I need to get all the rectangles I have drawn in my collision layer so that I can construct the collision geometry for farseer. I know probalby a stupid question, and one that shows that I still need to learn a lot in programming, but how can I access all the objects in a layer? So that I can go trough all of them with “foreach” and create for all of them a collision rectangle.? Also, how do I read the size of such a rectangle?
    Hopefully anyone can shed some light into this for me.

  8. March 14th, 2010 at 18:07
    Quote | #8

    May have found a bug or I am not doing something right. I added a object layer to my map and from what I can tell it never reads the object layer, so it never gets added to the layers list. More info would be great!

  9. March 14th, 2010 at 18:21
    Quote | #9

    @Rabid: Download the project again. When I first uploaded it had that bug, but I fixed it and reuploaded it. Or you can fix it yourself. Just make sure line 51 of MapContent.cs looks like this:

    foreach (XmlNode layerNode in document.SelectNodes(“map/layer|map/objectgroup”))

    I had an error in my query string in the first upload.

    @Michael: You need to add the content project and library project to your solution. Then add a reference on your game to the library and a reference on the content to the content project. The demo has this set up so look at the project references in there if you’re confused.

    @Andi: You can get a named layer from the Map.GetLayer method. Then cast that to a MapObjectLayer which lets you use the GetObject method to get an object by name or you can simply enumerate the Objects collection.

  10. Andi_S
    March 15th, 2010 at 12:34

    Nick, thanks for the reply, but I still don’t quite understand. Would you mind to maybe write one or two lines of code that would show me how to do it?
    I have a lyer named “Collision” where I have all my collision rectangles.

    I have two variables, one Layer object “layer” and one MapObjectLayer “Collisionlayer”.

    now i do in Loadcontent, just after loading the map:

    layer = map.GetLayer(“Collision”);
    collisionLayer = (MapObjectLayer)layer;

    But when I insert a breakpoint in this line, collisionlayer is null? What am I doing wrong? I guess I have not understood this casting somehow. Can you please help?

  11. Andi_S
    March 15th, 2010 at 12:50

    sorry, I jsut noticed it actually seems to work, collisionlayer is not null, but contains my single object…yeeesss ;-)

  12. March 16th, 2010 at 07:46

    This is pretty neat. I’ve been playing with it for the last couple of days, and I like it. Is it alright if I do a blog post expanding about this on my site? (I’m using reflection to automatically create game objects based on the stuff in the MapObject layers.)

    I had a bit of a problem with the paths. I setup my folders just like you did in the example, but then Tiled couldn’t find the png file, since it was looking in the same folder. If I fix that, Tiled uses a relative path like “../TileSets/Tiles.png” which confuses Path.Combine in the TmxProcessor. So I changed that bit to this:

    Path.Combine(TileSetDirectory, Path.GetFileName(tileSet.Image))

  13. March 16th, 2010 at 18:37

    It’s all Ms-Pl so you can extend it, re-release it, blog it, or do whatever you want with it. :)

    That’s probably a good fix there. The property of TmxProcessor was sort of an afterthought for me. I tend to just keep the tile images with the maps.

  14. March 18th, 2010 at 06:06

    My posts are up. Thanks a ton for posting about Tiled, and for your code. I like it alot.

    Part 1
    Part 2

  15. Ziple
    March 20th, 2010 at 06:41

    I would like to say YEEAAAHHH…
    But it doesn’t work for me.
    I just get this error while compiling your demo:

    Error 1 Building content threw FormatException: Le format de la chaîne d’entrée est incorrect (translation: incorrect string format).
    in System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
    in System.Number.ParseSingle(String value, NumberStyles options, NumberFormatInfo numfmt)
    in System.Single.Parse(String s, NumberStyles style, NumberFormatInfo info)
    in System.Single.Parse(String s)
    in TiledContentPipeline.LayerContent..ctor(XmlNode node) dans C:\Users\Me\Documents\Projects\XNA\TiledDemo\TiledDemo\TiledContentPipeline\Content Types\LayerContent.cs:line 25

    I suppose it is due to the fact that the decimal separator in the french language is “,” instead of “.” .
    I do not know how to fix the problem so, could you help me please(I’m sure it will also help a lot of other people)?

    Thank you.

  16. shalarim
    March 21st, 2010 at 09:29

    Nick,
    After following your excellent Alien Aggressors tutorial (just started trying to learn XNA this week), I started reading around for more tutorials and read this blog post. I managed to get a map up and running without too much difficulty.

    After getting a basic map displaying, I moved on to trying to add some objects, first up was a pair of start points for my players. Here’s where I found a problem, the MapObjectContent class assumes that the objects will have a width/height. Whereas I had just added points.
    I tweaked the code to get around this:
    Location = new Rectangle(
    int.Parse(node.Attributes["x"].Value),
    int.Parse(node.Attributes["y"].Value),
    node.Attributes["width"] == null ? 0 : int.Parse(node.Attributes["width"].Value),
    node.Attributes["height"] == null ? 0 : int.Parse(node.Attributes["height"].Value));

    Hope this helps prevent other people from running into this error.

    Thanks for the tutorials and sample codes
    Shalarim

  17. March 21st, 2010 at 09:33

    @Ziple: You can fix that by finding any of the int.Parse or float.Parse in the library and adding a second parameter of CultureInfo.InvariantCulture. I should have remembered that.

    @Shalarim: Nice find. I had no idea you could end up with an object that didn’t have a width and height.

  18. Ziple
    March 22nd, 2010 at 11:22

    Thank you, it’s working like a charm now!
    Yeaaahh!

  19. Poopa
    May 19th, 2010 at 03:51

    I’m having trouble just building the demo you made…

    Error 1 Building content threw FormatException: Input string was not in a correct format.

  20. Poopa
    May 19th, 2010 at 05:11

    Got it to work in my own project, but what if i would like to change the position of the map etc? I have a rather big tileset and would like to change position of the camera.. the map.Draw method just seem to take Spritebatch..

  21. Poopa
    May 28th, 2010 at 04:29

    Managed to get some things to work, however it seems that some Tiles wont read its properties correct..some are null even if i’ve placed properties for them :/

Comments are closed.