TiledStack - Tiled map rendering, messaging and support project for MonoGame

Recently in my spare time, I have been playing around with idea of using a simple HTTP services for messaging between game clients, specifically updating Tiled (opens new window) maps. Traditionally, networking between games has been done by TCP sockets as it is a far more efficient and light weight tech to use for games.However, as an enterprise developer by trade and games developer hobbyist, I'm not too fussed by the perhaps heavy handed approach of HTTP. Also, it's the tech I (and many others) know and use on a daily basis.

I also want to create these services to assist in the production/testing on Tiled maps as the game was running to create a short iteration cycle and tight feedback look into a developers workflow when using Tiled and MonoGame.

Continuing with my use of Tiled map editor (opens new window) for creating levels in my game, I first looked at how to move away from the Content Pipeline of XNA (a build time process) to a runtime process for loading of these Tiled maps. The original Content Pipeline I used for Tiled was written for XTiled library (opens new window) written by Michael C. Neel (ViNull on twitter (opens new window)). I was going to first look at using a lot of what he has done and just move the parsing code some where else, but I then remembered that Tiled has a 'Save As' function supporting various formats, including JSON.

JSON is a really easy data format to parse and thankfully there are many good .NET frameworks that handle this nicely. ServiceStack.Text (opens new window) is one of my current favourite serialization libraries in the .NET world. I made a quick set of DTOs (Data Transfer Objects) and proceeded to write an adapter from Tiled DTOs to a slightly modified XTiled's data structures which I still like for rendering etc. However, although I like the data structures, there was a lot of behaviour on these objects that I just didn't want there and also wanted to replace with my own rendering and processing code that I've written before. The main reason behind going straight from JSON instead of the native Tiled XML format (TMX) is mainly to save time and lines of code. Note: I intend to support TMX format once I have some more of the core functionality working.

public class MapManager
{
    public const UInt32 FlippedHorizontallyFlag = 0x80000000;
    public const UInt32 FlippedVerticallyFlag = 0x40000000;
    public const UInt32 FlippedDiagonallyFlag = 0x20000000;

    public ITilesetProvider TilesetProvider { get; set; }
    public IMapLoader MapLoader { get; set; }

    public Map Load(string jsonOfMap)
    {
        var map = MapLoader.Load(jsonOfMap);
        return Load(map);
    }

    public Map Load(MapDto mapDto)
    {
        var map = new Map();
        var gid2Id = new Dictionary<UInt32, Int32> {{0, -1}};
        var id2gid = new Dictionary<Int32, UInt32>();
        map.Height = mapDto.Height;
        map.Width = mapDto.Width;
        map.TileHeight = mapDto.TileHeight;
        map.TileWidth = mapDto.TileWidth;
        map.Orientation = mapDto.Orientation;
        map.Properties = mapDto.Properties.ToXTiled();
        map.Bounds = new Rectangle(0, 0, map.Width * map.TileWidth, map.Height * map.TileHeight);
        map.Tilesets = ParseTilesets(mapDto, gid2Id, out map.SourceTiles).ToArray();
        map.TileLayers = new TileLayerList();
        map.ObjectLayers = new ObjectLayerList();
        foreach (var i in gid2Id)
        {
            if (!id2gid.ContainsKey(i.Value))
            {
                id2gid.Add(i.Value, i.Key);
            }
        }

        map.GuidToId = gid2Id;
        map.IdToGuid = id2gid;
        foreach (var layer in mapDto.Layers)
        {
            if (layer.type == "tilelayer")
            {
                map.TileLayers.Add(ParseTileLayer(layer, map));
            }

            if (layer.type == "objectgroup")
            {
                map.ObjectLayers.Add(ParseObjectLayer(layer,map));
            }
        }
        return map;
    }

Once this was done and I incorporated some rendering and updating code, a few unit tests to confirm parsing the data was working as expected, I gave it a quick test.

Yay, up and running again. Loading at runtime, check! Wired up some simple input for moving around to confirm everything is rendering where it should be in an Isometric map.

OK, server time. One of the best things about ServiceStack (opens new window) is the sheer speed of getting up and running with simple web services. I like to separate my data objects from my request objects because they aren't always exactly the same and I don't want to litter my data objects with request/response meta data.

namespace TiledStack.Entities.Requests
{
    public class MapObjectRequest : TiledRequestBase
    {
        public string LayerName { get; set; }
        public MapObjectDto MapObject { get; set; }
    }
}

So I've got empty placeholder services with endpoints for a couple of the Tiled map updates that I want to be able to communicate with various clients, however ServiceStack doesn't really help me with telling other clients what has happening on another client, we'll need another framework to make this happen with as little code as possible. SignalR (opens new window), if you haven't used it, is pretty cool library for distributing messages to connected clients, take a look at Scott Hanselman (opens new window)'s post on SignalR (opens new window) with nice examples showing how little server code you have to write for simple messaging. It supports various different clients including .NET and Javascript, both of which I plan to utilise. A little bit too much magic? Maybe. Takes care of a whole bunch of plumbing I'm not interested in at the moment? Definitely!

So, how do you wire up ServiceStack and SignalR together is a very clean way? Checkout Filip W (opens new window).'s  post of using incoming and outgoing hubs via attributes , and it's exactly what I did to get this going, write a very simple hub (literally just repost the request to other clients) and presto! We have a server redistributing messages to all clients (including the original requester). 

namespace TiledStack.Web.SignalR
{
    public class IncomingTiledHubAttribute : Attribute, IHasRequestFilter
    {
        public string Name { get; set; }
        public string Method { get; set; }

        public void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
        {
            if (string.IsNullOrWhiteSpace(Name) || string.IsNullOrWhiteSpace(Method)) return;
            var hub = GlobalHost.ConnectionManager.GetHubContext(Name);

            if (hub != null)
            {
                var tiledRequestBase = requestDto as TiledRequestBase;

                if (tiledRequestBase != null)
                {
                    tiledRequestBase.NotifyClients(hub, Method);
                }
            }
        }

        public IHasRequestFilter Copy()
        {
            return this;
        }

        public int Priority
        {
            get
            {
                return -1;
            }
        }
    }
}


namespace TiledStack.Web.Services
{
    public class MapPropertiesService : Service
    {
        [IncomingTiledHub(Name = "mapUpdates", Method = "MapPropertiesMessage")]
        public object Any(MapPropertiesRequest request)
        {
            return new { Success = true };
        }
    }

    public class MapLayerDataService : Service
    {
        [IncomingTiledHub(Name = "mapUpdates", Method = "LayerDataMessage")]
        public object Any(LayerDataRequest request)
        {
            return new { Success = true };
        }
    }

    public class MapObjectService : Service
    {
        [IncomingTiledHub(Name = "mapUpdates", Method = "MapObjectMessage")]
        public object Any(MapObjectRequest request)
        {
            return new { Success = true };
        }
    }
}

So now we are receiving messages, we should do something with them. I have decided to use a simple Blackboard design to deposit incoming messages and make the collections accessed in a lock as we are using separate thread for handling messaging than we are for the game loop.

namespace TiledStack.Client.Proxy
{
    public class MapChangesBlackBoard
    {
        private readonly  object queueLock = new object();
        private readonly Queue<TiledRequestBase> _updateRquests;

        public MapChangesBlackBoard()
        {
            _updateRquests = new Queue<TiledRequestBase>();
        }

        public void AddRequest(TiledRequestBase request)
        {
            lock (queueLock)
            {
                _updateRquests.Enqueue(request);
            }
        }

        /// <summary>
        /// Accessor to the queue of requests, callback method used to separate who is responsible for
        /// processing of updates, most likely a separate provider.
        /// </summary>
        /// <param name="processFunc">Method to process each update</param>
        /// <param name="map">Map ref which is updated</param>
        public void ProcessMapRequests(Action<TiledRequestBase,Map> processFunc, Map map)
        {
            var requestsToProcess = new List<TiledRequestBase>();
            lock (queueLock)
            {
                int requestCount = _updateRquests.Count;
                for (int i = 0; i < requestCount; i++)
                {
                    var req = _updateRquests.Dequeue();
                    requestsToProcess.Add(req);
                }
            }

            foreach (var tiledRequestBase in requestsToProcess)
            {
                processFunc(tiledRequestBase, map);
            }
        }
    }
}


Messages are deposited and checked every frame for updates. This will probably be optimised to be a controllable frame interval as we might not really want to check this every frame. Once the remote requests is found, we process them very much like the original parsing. Wire up some simple input to a key to generate/send map object requests and 'boom goes the dynamite', map updated from another client!



Though this is not the most efficient way to do messaging with games, it is an easy to to get some pretty cool functionality with some great frameworks that are already out there. I have quite a number of features I'd like to play with some time in the future like persistent changes on the server, updates straight from the Tiled map editor and hopefully I will be able to get these working in the near future. I do plan to open source this project in the near future once more of these core features have a nice structure to expand on.

Tags