Friday, August 8, 2014

Game-development Log (1. Introduction + WebAPI for map tiles)


Work-in-progress: https://site-win.azurewebsites.net

Although my blog has been mostly dormant for the last few months I've kept myself busy making various experiments with mapping and gaming. I've expanded some of the ideas that I've already posted and I've prototyped some new stuff.

My intent: to create a massive online strategy game where there's a single shared persistent battleground and a pseudo-realtime gameplay mechanic. Yeah, lots of fancy words without saying much, but if it all goes as planned could be quite interesting. In a worst case scenario should be fun to build.

My plan is to document the progress on my blog (particularly as what I have now are mostly disconnected pieces) and deploy the new iterations as frequently as possible.

Technology
After some careful consideration I've decided to go with the following technologies:
  • Bing Maps for all the client-side mapping interaction. I've got tons of code on this but I still have some challenges around scalability and rich interaction elements like explosions and stuff. 
  • C#, although Javascript on Node was a great candidate and most of my prototypes were done on it. My final decision on C# was mostly due to my higher proficiency on it and to the fact that I intend to create a client in Unity (that's the plan, one can dream).
  • Azure, as it maps really well to the scale and roadmap I'm trying to achieve. Also, it gets better everyday and I plan to use lots of its capabilities including web-sites, table-storage, blobs, redis, sql server, message-bus, etc.


First Iteration - Server-side tiles generated on ASP.NET MVC WebApi

I don't want to unveil too much right now (particularly as everything is highly likely to change) but the target for my first iteration is simply a map that displays server-side generated tiles with hexagons.




I'm going to use ASP.NET MVC WebAPI 2 for this.

The solution consists on:
  • Creating a TileController that handles routes such as /tile/{z}/{x}/{y}.png
  • Returning a "HexagonTile" model
  • Creating custom formatters that are able to convert an "HexagonTile" to the destination formats, like images.
  • Displaying the tiles on Bing maps, overlaid with the base maps.
The WebAPI action will be defined as:
public HexagonTile Get(int z, int x, int y)
{
    var tile = new HexagonTile { 
    {
        Z = z,
        X = x,
        Y = y;    
    };
    return tile;
}

Eventually another option for this would be to declare the action as:
public HttpResponseMessage Get(int z, int x, int y)
{
    // (...)
    MemoryStream dataStream = new MemoryStream(resourceByteArray);
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StreamContent(dataStream);
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
    return response;
}
(ex: http://stackoverflow.com/questions/21567052/return-images-to-web-page-using-web-api)

But in my opinion this would be a sub-optimal approach as it's breaking the usefulness of HTTP and best-practices when creating a WebApi . The action should, in theory, just return the model. Either returning json, an image, a bson or any other format should leverage the HTTP protocol, like through the HTTP Accept headers.

Thus I'm using the first option and associating specific accept headers with different formatters on my web api configuration:
public static void Register(HttpConfiguration config)
{
    var imageFormatter = new TileImageFormatter();
    imageFormatter.MediaTypeMappings.Add(
        new UriPathExtensionMapping(
            "png", 
            new MediaTypeHeaderValue("image/png")));
    config.Formatters.Add(imageFormatter);

    var jsonFormatter = new TileJsonFormatter();
    jsonFormatter.MediaTypeMappings.Add(
        new UriPathExtensionMapping(
            "json", 
            new MediaTypeHeaderValue("application/json")));
    config.Formatters.Add(jsonFormatter);

    // Web API routes
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
 }

Note: The "UriPathExtensionMapping" allows the output format to be overwriten by passing an extension on the route.

For reference, this is my TileImageFormatter source-code:
public class TileImageFormatter : MediaTypeFormatter
{
    private readonly ITileGenerator _tileGenerator;

    public TileImageFormatter(ITileGenerator tileGenerator)
    {
        _tileGenerator = tileGenerator;
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
    }

    public override bool CanReadType(Type type)
    {
        return typeof(HexagonTile) == type;
    }

    public override bool CanWriteType(Type type)
    {
        return typeof(HexagonTile) == type;
    }

    public override async Task WriteToStreamAsync(
            Type type, 
            object value, 
            Stream writeStream, 
            HttpContent content, 
            TransportContext transportContext)
    {
        var tile = value as HexagonTile;
        if (tile == null) 
            throw new InvalidOperationException("Invalid Type!");

        using (var bmp = _tileGenerator.RenderTile(tile)
        {
            var memStream = new MemoryStream();
            bmp.Save(memStream, ImageFormat.Png);
            byte[] bytes = memStream.ToArray();
            await writeStream.WriteAsync(bytes, 0, bytes.Length);
        }
    }
}

Note: The image rendering logic is encapsulated in the TileGenerator class, although not that interesting to show here, particularly as it's using GDI+ to generate the tile and I need to find a better alternative.

The routes themselves are defined at the action level using the Route attributes from WebAPI 2:
[Route("v1/tile/{z:int}/{x:int}/{y:int}.{ext}")]
[Route("v1/tile/{z:int}/{x:int}/{y:int}")]
public HexagonTile Get(int z, int x, int y)
This basically means that a request for "v1/tile/12/1940/1565" will be handed by this action, as will the requests that include the optional extensions, as in "v1/tile/12/1940/1565.png" or "v1/tile/12/1940/1565.json".

I've already deployed this to Azure. Some example requests are:

http://tile-win.azurewebsites.net/v1/tile/10/490/391.png

 Should return this image:

Issuing the request:
http://tile-win.azurewebsites.net/v1/tile/10/490/391.json

Should return the json representation of those hexagons.

I've deployed a simple Bing Maps that's using this tile layer. This will be just the starting point as I don't plan to deliver the tiles completely on-the-fly with GDI+.

On my next iteration I'm going to delve on the data loader tool. It loads real data from various GIS sources so that the hexagons can represent land, roads, rivers, etc.

I've not yet decided on how I'm going to store the parsed data afterwards, but I'm tending for the option of pre-generating vector tiles with the meta-data.  Anyway, more on that later :)

1 comment:

  1. Are you trying to make cash from your visitors by running popunder advertisments?
    If so, have you considered using Clickadu?

    ReplyDelete