Emil Emil - 3 months ago 44
C# Question

How to use the Input.Location and mock location in the unity editor

I am building a location based game, a bit like pokemon go.
I am reading the location on my android phone without any problem, but I can't get any location data when I'm developing in the unity editor since Input.location.isEnabledByUser is false in the editor

It would be ok to mock/hard code a location, just so I can try it without deploying to my phone.

I tried to hard code it like this:

LocationInfo ReadLocation(){
#if UNITY_EDITOR

var location = new LocationInfo();
location.latitude = 59.000f;
location.longitude = 18.000f;
location.altitude= 0.0f;
location.horizontalAccuracy = 5.0f;
location.verticalAccuracy = 5.0f;

return location;
#elif
return Input.location.lastData;
#endif
}


But all of the properties of the location are read only, so I'm getting compilation errors.
Is there a way to enable location service in the editor, or hardcode a location?

Answer

Is there a way to enable location service in the editor, or hardcode a location?

This is one of the reasons why Unity Remote 4 was made. Setup Unity Remote then connect your mobile device to the Editor. You can now get a real location from the Editor.

But all of the properties of the location are read only

If you really want to develop a way to mock the location, you have to abandon Unity's LocationInfo structure. Make your own custom LocationInfo and name it LocationInfoExt. Ext is = Extended.

Do the-same thing for LocationService too, then Wrap the official LocationService into your custom LocationServiceExt class. You can use LocationServiceExt to decide if you should mock location by using LocationInfoExt or not mock location by using LocationInfo internally to provide the result.

In the example below, the official LocationService, LocationInfo and LocationServiceStatus class/struct/enum are replaced with LocationServiceExt, LocationInfoExt and LocationServiceStatusExt. They also have the-same functions and properties implemented. The only difference is that you can pass true/false to the constructor of LocationServiceExt in order to use it in the Editor.

LocationServiceExt wrapper class:

Create a class called LocationServiceExt then copy the code below into it: It has every function and property from the original LocationService class.

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class LocationServiceExt
{
    private LocationService realLocation;

    private bool useMockLocation = false;
    private LocationInfoExt mockedLastData;
    private LocationServiceStatusExt mockedStatus;
    private bool mIsEnabledByUser = false;

    public LocationServiceExt(bool mockLocation = false)
    {
        this.useMockLocation = mockLocation;

        if (mockLocation)
        {
            mIsEnabledByUser = true;
            mockedLastData = getMockLocation();
        }
        else
        {
            realLocation = new LocationService();
        }
    }

    public bool isEnabledByUser
    {
        //realLocation.isEnabledByUser seems to be failing on Android. Input.location.isEnabledByUser is the fix
        get { return useMockLocation ? mIsEnabledByUser : Input.location.isEnabledByUser; }
        set { mIsEnabledByUser = value; }
    }


    public LocationInfoExt lastData
    {
        get { return useMockLocation ? mockedLastData : getRealLocation(); }
        set { mockedLastData = value; }
    }

    public LocationServiceStatusExt status
    {
        get { return useMockLocation ? mockedStatus : getRealStatus(); }
        set { mockedStatus = value; }
    }

    public void Start()
    {
        if (useMockLocation)
        {
            mockedStatus = LocationServiceStatusExt.Running;
        }
        else
        {
            realLocation.Start();
        }
    }

    public void Start(float desiredAccuracyInMeters)
    {
        if (useMockLocation)
        {
            mockedStatus = LocationServiceStatusExt.Running;
        }
        else
        {
            realLocation.Start(desiredAccuracyInMeters);
        }
    }

    public void Start(float desiredAccuracyInMeters, float updateDistanceInMeters)
    {
        if (useMockLocation)
        {
            mockedStatus = LocationServiceStatusExt.Running;
        }
        else
        {
            realLocation.Start(desiredAccuracyInMeters, updateDistanceInMeters);
        }
    }

    public void Stop()
    {
        if (useMockLocation)
        {
            mockedStatus = LocationServiceStatusExt.Stopped;
        }
        else
        {
            realLocation.Stop();
        }
    }

    //Predefined Location. You always override this by overriding lastData from another class 
    private LocationInfoExt getMockLocation()
    {
        LocationInfoExt location = new LocationInfoExt();
        location.latitude = 59.000f;
        location.longitude = 18.000f;
        location.altitude = 0.0f;
        location.horizontalAccuracy = 5.0f;
        location.verticalAccuracy = 5.0f;
        location.timestamp = 0f;
        return location;
    }

    private LocationInfoExt getRealLocation()
    {
        if (realLocation == null)
            return new LocationInfoExt();

        LocationInfo realLoc = realLocation.lastData;
        LocationInfoExt location = new LocationInfoExt();
        location.latitude = realLoc.latitude;
        location.longitude = realLoc.longitude;
        location.altitude = realLoc.altitude;
        location.horizontalAccuracy = realLoc.horizontalAccuracy;
        location.verticalAccuracy = realLoc.verticalAccuracy;
        location.timestamp = realLoc.timestamp;
        return location;
    }

    private LocationServiceStatusExt getRealStatus()
    {
        LocationServiceStatus realStatus = realLocation.status;
        LocationServiceStatusExt stats = LocationServiceStatusExt.Stopped;

        if (realStatus == LocationServiceStatus.Stopped)
            stats = LocationServiceStatusExt.Stopped;

        if (realStatus == LocationServiceStatus.Initializing)
            stats = LocationServiceStatusExt.Initializing;

        if (realStatus == LocationServiceStatus.Running)
            stats = LocationServiceStatusExt.Running;

        if (realStatus == LocationServiceStatus.Failed)
            stats = LocationServiceStatusExt.Failed;

        return stats;
    }
}

public struct LocationInfoExt
{
    public float altitude { get; set; }
    public float horizontalAccuracy { get; set; }
    public float latitude { get; set; }
    public float longitude { get; set; }
    public double timestamp { get; set; }
    public float verticalAccuracy { get; set; }
}

public enum LocationServiceStatusExt
{
    Stopped = 0,
    Initializing = 1,
    Running = 2,
    Failed = 3,
}

Usage:

Create a mock location

LocationServiceExt locationServiceExt = new LocationServiceExt(true);

Create a real location

LocationServiceExt locationServiceExt = new LocationServiceExt(false);

Modify the location later on

LocationInfoExt locInfo = new LocationInfoExt();
locInfo.latitude = 59.000f;
locInfo.longitude = 18.000f;
locInfo.altitude = -3.0f; //0.0f;
locInfo.horizontalAccuracy = 5.0f;
locInfo.verticalAccuracy = 5.0f;

locationServiceExt.lastData = locInfo; //Apply the location change

Full ported working example from Unity Doc.

public Text text;
IEnumerator StartGPS()
{
    text.text = "Starting";
    //Pass true to use mocked Location. Pass false or don't pass anything to use real location
    LocationServiceExt locationServiceExt = new LocationServiceExt(true);

    LocationInfoExt locInfo = new LocationInfoExt();
    locInfo.latitude = 59.000f;
    locInfo.longitude = 18.000f;
    locInfo.altitude = -3.0f; //0.0f;
    locInfo.horizontalAccuracy = 5.0f;
    locInfo.verticalAccuracy = 5.0f;
    locationServiceExt.lastData = locInfo;

    // First, check if user has location service enabled
    if (!locationServiceExt.isEnabledByUser)
    {
        text.text = "Not Enabled";
        yield break;
    }
    else
    {
        text.text = "Enabled!";
    }

    // Start service before querying location
    locationServiceExt.Start();


    // Wait until service initializes
    int maxWait = 20;
    while (locationServiceExt.status == LocationServiceStatusExt.Initializing && maxWait > 0)
    {
        text.text = "Timer: " + maxWait;
        yield return new WaitForSeconds(1);
        maxWait--;
    }

    // Service didn't initialize in 20 seconds
    if (maxWait < 1)
    {
        print("Timed out");
        text.text = "Timed out";
        yield break;
    }

    // Connection has failed
    if (locationServiceExt.status == LocationServiceStatusExt.Failed)
    {
        print("Unable to determine device location");
        text.text = "Unable to determine device location";
        yield break;
    }
    else
    {
        // Access granted and location value could be retrieved
        string location = "Location: " + locationServiceExt.lastData.latitude + " "
            + locationServiceExt.lastData.longitude + " " + locationServiceExt.lastData.altitude
            + " " + locationServiceExt.lastData.horizontalAccuracy + " " + locationServiceExt.lastData.timestamp;
        Debug.Log(location);
        text.text = location;
    }

    // Stop service if there is no need to query location updates continuously
    locationServiceExt.Stop();
}
Comments