Offline GPS-enabled maps with Unity
Using GPS with Unity isn’t rocket science (unlike GPS itself) if you use the Input class. Combining GPS coordinates with a map can be quite challenging however. Not having the budget for any pre-baked solutions from the Unity Asset Store I had to slap together something of my own.
As mentioned in an earlier post I have tried this before. This worked with map-tiles being downloaded on demand via way of the digital super highway. This worked fine assuming the player has a reliable internet connection. I planned to implement this system for my final prototype, but I never tested it properly for real world situations. When I did it showed that downloading new tiles for when the player moves over the current tile edge cripples any mobile data connection until they start begging for mercy.
I left it for a while to work on other components of TugGame. When I came back I realised that it doesn’t have to be as complicated as past me thought it should. Why not pre-load the game with one big map of the entire area the game takes place? Not only does this conserve bandwidth, it also gives players a clear boundary of the playing area.
Google Maps is really no help if you want to generate a big high resolution map of a given area. First you have to register as a Maps developer, then you have to activate the correct licence, set up an development environment the correct way, find or write a script to convert Google’s format in a file format that is more usable, to then finally find or write a script to export that data as a PNG… Or I could just take a screenshot. But that is even worse.
Instead I went to the helpful people of OpenStreetMaps. It was really easy: specify the boundaries of the area you want to export… After that you only have to choose one of their exotic file formats, go to OSM-Wiki hoping to find an easy solution. Not being familiar with any of the OSM/Cartography jargon, you try to download all the odd file-formats to trow them at TileMill hoping for something to work.
While considering arguments why not having a map would somehow improve the player-experience, I find a solution that actually works. With BigMap 2 you select a OSM style (Mapnik worked for me) and a place. Hit submit to fine-tune the exact boundaries of the map you want to export. Note that this will always be a square area. Once you’re done click the links that say Py and OZI. This first link should get you a Python script that downloads all the map-tiles from your specified area and stitches them together into a single PNG image. The other link gets you a .map file which will come in handy later on in this text that just suddenly became a tutorial.
To align the position of the players with the correct location on the map we need to know the coordinates at the corners of the map. You can find these coordinates in the .map file. Define a new Rect.MinMaxRect variable with the coordinates of the top left and botton right corners.
private var mapRect : Rect = new Rect.MinMaxRect(5.278931, 51.692990, 5.29541, 51.682774);
Assuming you already written code that initialises GPS you have to make a function that scales the current player geolocation within the boundaries of the Rect onto the map texture. Since the size of the mapsprite doesn’t change during runtime I recommend you save its size in a integer somewhere in the awake or start function of your script.
// Get current GPS position and position player corresponding point on the map
function GetGeoLocation () {
if (!dummyGPS) {
lon = Input.location.lastData.longitude;
lat = Input.location.lastData.latitude;
} else {
lon = dummyLocationX;
lat = dummyLocationY;
}
// Position the player by scaling longitude/latitude within boundaries of mapRect with size of the mapTexture
player[player.length-1].transform.position = Vector2(
SuperLerp(0, mapTextureSize, mapRect.xMin, mapRect.xMax, lon),
SuperLerp(0, mapTextureSize, mapRect.yMax, mapRect.yMin, lat)
);
}
// Scaling function
function SuperLerp (from : float, to : float, from2 : float, to2 : float, value : double) : float {
if (value <= from2) {
return from;
} else if (value >= to2) {
return to;
} else {
return (to - from) * ((value - from2) / (to2 - from2)) + from;
}
}