There have been a few recent examples of real-world maps displayed in Unity3D apps. The first one I noticed was the playfield in the infamous Halo 4 iPhone app that came out late last year. For unknown reasons, I was really into this game for a few months. I hung around my local 7-11 scanning bags of Doritos so much that I thought I was going to get arrested for shoplifting. Eventually this obsession led to me wanting to duplicate the map display used in the game. Here’s how I did it.
Google Maps Plug-In
Naturally the first place I looked was the Asset Store. It turns out there is a free Google Maps plug-in available. The only catch is that it requires UniWeb to work. UniWeb lets you call REST APIs and generally have more control over HTTP requests than Unity’s own WWW class allows. It can be a necessity if you’re using REST API calls but it restricts your code stripping options. This will bump up your binary size.
This asset’s sample scene works flawlessly. It downloads a map from the Google Static Map API and textures it on a cube. The code is clean and well documented, featuring the ability to request paths and markers to be added to the static map. Most attributes can be tweaked through the inspector–such as map resolution, location, etc.
I made a lot of changes to this package. I really wish it was open source. Free code assets really should be in most cases. I will try to isolate my changes into another C# file and post a Gist.
The first change I made was to add support for themed Static Maps. If you look at this wizard, you can see that there are a lot of styling options. This appears to be the same technique used in the Halo 4 app because with the right set of options you can get something that looks really close. Supporting styling in Unity3D is just a simple act of appending the style parameters to the end of the URL used by the Google Maps plug-in.
Displaying Markers in 3D
The next thing I wanted to do is display the markers as 3D objects on top of the map instead of having them inside the texture itself. This requires 3 steps:
- Determine where the markers are in pixel coordinates in the static map texture.
- Calculate the UV coordinate of the pixel coordinate.
- Calculate the world coordinate of the texel the UV coordinate resides at.
Step 1 can be tricky. You have to project the latitude and longitude of the marker with the Mercator projection Google Maps uses to get the pixel coordinate. Luckily, this guy already did it in PHP to create image maps from static maps. I adapted this code to C# and it works perfectly. You can grab the Google Maps utility functions here. (All this great free code on the net is making me lazy–but I digress)
Step 2 is easy. This code snippet does the trick. The only catch is that you have to flip the V so that it matches with how Unity uses UV coordinates.
Step 3 is also tricky. However, someone with much better math skills than I wrote a JavaScript method to compute the world coordinate from a UV coordinate. It searches through each triangle in the mesh and sees if the UV coordinate is contained inside it. If so, it then calculates the resultant world coordinate. The key to using this is to put the static map on a plane (the default scene in the plug-in uses a cube) and use the C# version of this function I wrote here.
Here’s the end result–in this case it’s a display for the Donut Dazzler prototype. 3D donuts are floating over real-world donut shops and cupcakes over cupcake bakeries. I got the locations from the Foursquare API. This is quite easy to do using UniWeb.
Slippy Maps
The aforementioned technique works great if you just want a static map to display stuff around the user’s current location. What if you want to be able to scroll around and see more map tiles, just like Google Maps when you move around with your mouse? This is called a Slippy Map. Slippy Maps are much more elaborate–they require dynamically downloading map tiles and stitching them together as the user moves around the world.
Thankfully Jonathan Derrough wrote an amazing free Slippy Map implementation for Unity3D. It really is fantastic. It displays markers in 3D and pulls map tiles from multiple sources–including OpenStreetMap and Bing/VirtualEarth. It doesn’t use Google Maps because of possible TOS violations.
I couldn’t find a way to style map tiles like Google Static Maps can. So the end result was impressive but kind of ugly. It is possible with OpenStreetMap to run your own tile server and run a custom renderer to draw styled tiles. I suspect that’s how Rescue Rush styles their OpenStreetMap tiles–unless they are doing some image processing on the client.
Either Or
For my prototype I ended up using Google Static Maps because Slippy Maps were overkill. Also, pulling tiles down from the servers seemed much slower than grabbing a single static map. I suppose I could add some tile caching, but in the end static maps worked fine for my purposes.
Keep in mind that Google Maps has some pretty fierce API usage costs. If your app goes viral, you will likely be on the hook for a huge bill. Which is why it might be worth figuring out how to style free OpenStreetMap tiles.
Hey Ralph, awesome post! I’ve been trying this out and can’t quite seem to get it to fully work. I’m pulling a map from the static api, though not using the uniweb method, if that makes any difference.
Anyhow would you happen to have an example script implementing this available?
And secondly.. Do you happen to be able to move the map around by making another API call, or move the map around at all? And if so: Are you able to get your objects to reflect the map change without any offsetting of the objects to the map?
Or would you recommend a slippy map at that point?
Appreciate it!
Well I’m pretty sure you have to use UniWeb to get the Static Maps API to work. The Google Maps plug-in explains why, I think it requires some header parameters you can’t set with WWW. So you kind of have no choice there–buy UniWeb, grab the free Google Maps plug-in and it just works right out of the box. Super simple.
But for scrolling, you’re going to have to use Slippy Maps. Youre in luck–that doesn’t require UniWeb, it’s all totally free.
incidentally, ever since Unity 3.5.x you could load google maps static tiles using http://www.texture and regular WWW() – ie no uniweb needed
also +1 on using the free donut on turbosquid 🙂
I actually bought that donut off the Asset Store. I had to get the cupcake off of some 3D model site, though.
When I said donut, I meant turbosquid cupcake 🙂 http://www.turbosquid.com/3d-models/3d-cupcake-cake-model/690675
just tried your code – I keep on getting 0,0,0 no matter what lat long I enter? Did you post the right working version?
public GameObject goMarker;
Pixel2UV p2uv;
MeshPixelTools mpt;
// Use this for initialization
void Start () {
p2uv = GetComponent();
mpt = GetComponent();
// 1 market
float x = MapUtils.adjustLonByPixels(37.7940782f,268435456,10);
float y = MapUtils.adjustLatByPixels(-122.3951331f,268435456,10);
Vector2 uv = p2uv.P2UV(new Vector2(x,y)); print (uv);
Vector3 v = mpt.UvTo3D(uv);
print (v);goMarker.transform.position=v;
}
Hmm, it’s the exact code from my project.
* Are you sure the lat/long is in the map? Did you try testing with the center of the map?
* Are you sure the texture dimensions are correct? If you are using a 1024 map, for instance, you might find that it actually comes down as a 2048 texture and then Unity compresses it into some weird non power of 2 texture. Hence your pixel/UV calcs will be all wrong.
I ended up using a 512×512 with double res option so I got a 1024 back from Google. You have to make sure your’e using the right texture resolution–that tripped me up for a bit.
Yes, to all of the above and it’s still returning 0,0,0 world pos!
uv’s return (0.0, 1.1)
Take a look @ https://www.dropbox.com/s/viyepfurv21r5pq/latlng2worldcoordTest.unitypackage
And nope, it’s certainly not at the center…
http://maps.googleapis.com/maps/api/staticmap?&size=512×512&scale=2&maptype=roadmap&sensor=false&markers=size:mid|color:black|label:|1+market+street%2c+san+francisco&markers=size:tiny|color:black|label:|500+market+street%2c+san+francisco
looks like your system messes up the google map links – try this http://bit.ly/X2ztlr
Hey yosun,
Have you finally made this stuff work.
I would appreciate some help… I am getting suck using the GetComponent routine.
Seems it does not exist in Unity 5.1 and I am not an expert in Unity….
Thanks,
Fabrice
Sorry, I meant stuck…
Hi Ralph,
Thank you for this helpful post.
The part with the 3d Markers should it be working as is or is it something missing?
My marker no mater what my uvs are ( from the pixelToUvs script ), it always goes to same point. To be honest i don’t see in what way they are used inside the “UvTo3D”
Thanks for any helpful reply
same problem, the MapUtils returns wrong uv coordinates.
Hmm. Ok I think I’ll have to cut out the plugin (you can just re-download it) and put up the whole project. Because it works for me. Maybe it has to do with how you’ve set up your material/game object.
Hi, is there a way to download the sample of the project you have mentioned about?
hi,i wanna to know whether unity load google tile maps.
and can unity load map from arcgis_server?
thanks
Check out that Slippy Map project–I believe he skips Google because of their licensing terms. But, you could probably modify the source to use Google as it has support for a bunch of different tile systems. arcgis, I’m not sure about–but I bet the same applies there.
Hi!!
I’m using the SLIPPY project for an application and had a question I do not know if it’s OK to raise it in this blog.
I need to implement limits when shifted the map with your finger so you do not see in black tiles because they don’t exist in the MBTILES I use because they need to be OFFLINE.
Any hints on how I can do?
Pingback: Real time 3d rendering navigation - DL-UAT
hello
i no very good english speak,
i need base url satellite map for unityslippymap.beacause openstreatmap no details of world
i need your help
Hi Ralph!
We’ve spent some time creating World Political Map for Unity which could also be an alternative to Google Maps if you just need country or state/province zoom levels.
It draws frontiers procedurally and allows you to select any region, decorate it, and much more.
You can view some video demos on our Youtube channel: https://www.youtube.com/user/KronnectGames
I think it’s kinda fun!
Hi,
Thank you, this post make my day!
Anyway, i just use first and second step to display map on 2D Image,
Really useful post!
Hi Ralph,
Thanks a lot for your post (yes I know I am a bit late 🙂 ).
I wonder if you can share a full Unity project.
I actually I am quite new in 3d programming and I have no idea how to include floating objects the same way you did.
Thanks a lot!
Fabrice
hey ralph i get the same 0,0,0 vector3 like yosun
public class MeshPixelTools : MonoBehaviour
{
public Transform marker;
int x;
int y;
Vector2 uv;
// Use this for initialization
void Start()
{
x = MapUtils.LonToX(40.579801f);
y = MapUtils.LatToY(22.948940f);
uv = new Vector2((float)x / (float)GetComponent().material.mainTexture.width, 1f – (float)y / (float)GetComponent().material.mainTexture.height);
}
// Update is called once per frame
void Update()
{
Vector3 markerWorldpos = UvTo3D(uv);
marker.position = markerWorldpos;
}
and the center address : kalamaria
Pingback: So, You Wanna Make A Pokemon Go Clone? | Ralph Barbagallo's Self Indulgent Blog
I was having some problems with the code provided so i made some modifications avaiable in the package in the link bellow:
http://romulolima.me/downloads/GeoLocApp.unitypackage
Hi,
I’ve implemented this in Unity 5. I commented on the asset store to show people how to change the script to not use UniWeb by the way. In my version I update my position every 3 seconds and get a static map which turned out really bad as in 30 minutes I downloaded 2gigs of data on my phone. Is there a way to scroll a current static map and only grab a new one as requires when the user moves into a new area?
Andy H.
Hello Ralph,
Is there any way to show route from current location to destination in unity?