I wrote this draft months ago but realizing I won’t be able to finish it, I just decided to post it as is. I hope it’ll at least explain the idea. I’m also thinking about going back to square tiled version of this soon and finish it properly this time
I kept saying I’ll blog this for so long but finally, here it is; creating tiled maps using Mapbox and Unity3d! Well at least I’m starting, we’ll see how it’ll turn out.
First of all, a little bit of background;
It actually all started with some 2d map experiments I did like 8 months ago. I was trying to create a 2d fantasy theme map and after checking some examples online, I decided to go with a hexagon tile based map. I always loved hexagons to begin with but on the technical side, I thought it would also help to place visuals/models to represent map entities.
If you check the forests or water, you probably can notice the hexagon grid. I still love the style but it was pretty hard to find/create art assets for this so it ended up kinda… ugly? I didn’t get too much attention either as it was rather unpolished, more like a proof of concept at the time. I’m pretty sure it could have been soo much better with a 2d artist.
Then one night, while searching for art assets, I stumbled upon a beautiful package in Unity asset store with beautiful toonish hexagon tiles. With a little change in the code, I created this;
This one got quite popular, probably mostly due to beautiful art assets. It’s just a lot more fun and interesting to look. Also this time I spent a little more time to polish it with some animations and units moving around.
But shortly, the idea was to turn a regular vector map into a tiled map;
It’s actually quite a fun real map and I believe it can be useful for a lot of different type of applications; PokemonGo type of mobile games, Civilization type of desktop games or mobile location based messaging apps.
About this post
Now I want to clearly note that I won’t share any code in this post. This was an experimental project for me and my goal was to see what I can do (and collect some internet points) so my implementation was rather rough, buggy, pretty unoptimized. I will, however, try to explain who process so you can do better!
So how do we start?
I started with exploring the vector data and try to see what we can get out of that one first. All vector layers aren’t available on all levels and their detail level varies quite a bit as well so I had to try a few settings until I created this;
This strange looking map will be our starting point. Yellow textured part here is “land”, pink is “mountain”, green is “forests” and white is “grass”.
Actually I had to do some tricky stuff to achieve some of these layers. Mapbox vector data doesn’t have “land” for example, or mountains. To be able to create them, I used “contour” layer (see https://www.mapbox.com/vector-tiles/mapbox-terrain/#contour). In case you’re not familiar with contour lines, this is what they look like;
Now our goal is to find the polygons/heights we want to define as “land” and “mountain”.
Land is easy, elevation=0 should be land, right?
Mountains though a little bit trickier as contour layer uses different intervals on each zoom level so this value kinda depends on it. For example, “elevation 200” would work for all levels above 10 but it won’t work for zoom level 9 as it uses 500 meter intervals and there won’t be “elevation 200” in that level.
I created my demos using zoom level 10 as it felt the best for big areas, and at z10 I used;
- Contour layer elevation=0 for land
- Landcover layer class=grass for grass
- Landuse layer for forests (I didn’t filter by type/class as landuses are mostly national parks at this level)
- Contour layer elevation=200 for mountains
I also ordered them bottom to top so the latter will be on top of former and it all results in that top down shot above.
So now we have the polygons, what’s next?
Wikipedia defines rasterization as; “the task of taking an image described in a vector graphics format (shapes) and converting it into a raster image (pixels or dots).”
Exactly what we’re looking for!
We have the polygons so now what we have to do is (basically) to rasterize these polygons, which will yield us a set of tiles, and visualize those tiles.
Now it gets a little more implementation specific, I’ll try to describe how I did it but I’m pretty sure there might be better approaches as well.
We already know these polygons generally overlap and then we’ll also want to play with neighbourhood relations in between tiles (i.e. roads or special models for city by water/mountain) so instead of creating tiles one by one (as they are created), I decided to pass them all to a controller class first. And only after it’s all finished, I process them and create visuals in one final loop.
Also, I tried not to change SDK code at all and built this on top of it as a separate layer. Having whole SDK and a new layer on top of it means an unoptimized structure but it’s easy and faster to develop.
Mapbox Unity SDK Components
If you’re familiar with the Mapbox Unity SDK, you know it’s rather easy to create polygons from features. Most demos and example do much more than creating the polygon so it might be easy to overlook.
This is my “land” layer in the SDK side for example. It uses custom styling and “contour” layer name. Then filters by the “ele” property and takes the ones with value 0 (coastline).
PolygonMeshModifier creates the polygon, AddToHexMap modifier adds it to our other layer, tiled map controller. And finally DisableMeshRenderer hides the original polygon.
And actually, depending on your rasterization technique, you might not need the PolygonMeshModifier at all. I used it because if I remember correct, I was using triangles for rasterization and needed the triangulation inside PolygonMeshModifier.
So shortly, whole idea here is to pass features to the TiledMapController and do everything
tiled there. We’re using SDK only to parse, categorize, filter etc the features.
AddToHexMap here is a very simple custom game object modifier and all it does is passing a few values to the tiled map controller, including feature data, tile, layer (land, forest etc) and type of data (point/line/polygon). My implementation didn’t include dynamic tile loading so it didn’t destroy objects. When you need that though, AddToHexMap can send signal to controller for the destruction of features and tiles as well.
This way tiled map controller will get all features, layers and types. Then I rasterized polygons to find the list of tiles covering given polygon and kept them in a
Dictionary<Vector2, TileFeature> where the first parameter is the tile position and second one is an enum corresponding to the layer name (land/forest/mountain etc). There’s a few advantages of this; first of all, tile coordinates are rather easy to calculate and Dictionary provides O(1) read complexity in that very common case. Second, it helps to layer things like using forest over land. Vector layer execution order should also be enough but this is nice to have. Third, again it helps with some basic tile type relations; i.e. if current value is forest and you get mountain, you can switch to a custom type like mountain forests.
I kept and processed roads separately as they were kinda afterthought in my case. I’m sure you can process them in same pipeline but might require a little more effort.
At this point our dictionary should be full of tiles. Now we’ll wait for the
AbstractMap.MapVisualizer.OnMapVisualizerStateChanged event, which means all features are processed and you can start creating your tile visuals.
For visuals, I used
POLYGON MINI - Fantasy Pack by Synty Studios (https://assetstore.unity.com/packages/3d/props/exterior/polygon-mini-fantasy-pack-96800). Created bunch of variations for each layer type and saved them in lists. Then went through the tile dictionary and picked a random tile from the list corresponding to tile
Looking back, the most difficult part of this project was the rasterization algorithm which I shamelessly avoided in this post. I only had a subpar rasterization implementation but it worked so I didn’t bother to improve it for now. I’m sure there are much better hex/square tile rasterization formulas online though.
I think it covers the main idea behind tiled maps. I really love this project and will definitely get back to it at some point.
Please let me know if you have any questions or if you like it or not!