Displaying Maps in Unity3D

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:

  1. Determine where the markers are in pixel coordinates in the static map texture.
  2. Calculate the UV coordinate of the pixel coordinate.
  3. 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.

3D objects floating over marker locations on a Google Static Map.

3D objects floating over marker locations on a Google Static Map.

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.

27 thoughts on “Displaying Maps in Unity3D

  1. 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.

  2. 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;
    }

  3. 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

    • 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.

    • 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.

  4. 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?

  5. Pingback: Real time 3d rendering navigation - DL-UAT

  6. 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

  7. 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

  8. Pingback: So, You Wanna Make A Pokemon Go Clone? | Ralph Barbagallo's Self Indulgent Blog

  9. 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s