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?
You'd need access to the device's location services (which would require a permissions request first) to pull that data. Otherwise, if you want it selected manually, you can make a list of countries with their associated country codes, and when they select the country from the list, get the matching code and use that.
Is there a way to enable location service in the editor, or hardcode a location?
This is one of the reasons why Unity Remote 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();
}
Steps to follow:
First you need to start the GPS calling:
Input.location.Start();
The "start" takes some time initializing all the needed processes so you could do this:
IEnumerator InitLocationTracker()
{
// Start service before querying location
Input.location.Start();
// Wait until service initializes
if (true == disableGPSMode)
{
//delegate to call again "InitLocationTracker"
if (null != OnRetryLocation)
{
OnRetryLocation ();
}
//variable to check the location available
isLocationAvailable = false;
yield return null;
}
// I chose 20s to retry the call
int maxWait = 20;
while (Input.location.status == LocationServiceStatus.Initializing && maxWait > 0)
{
// Service didn't initialize in 20 seconds
if (maxWait < 1)
{
Debug.Log("LocationTracker: Timed out");
//GameObject to show error text
errorText.text = "LocationTracker: Timed out";
errorPanel.SetActive(true);
//delegate to call again "InitLocationTracker"
if (null != OnRetryLocation)
{
isLocationAvailable = false;
OnRetryLocation ();
yield break;
}
}
yield return new WaitForSeconds(1f);
maxWait--;
}
// Connection has failed
if (Input.location.status == LocationServiceStatus.Failed)
{
Debug.Log("LocationTracker: Unable to determine device location");
errorText.text = "LocationTracker: Unable to determine device location";
errorPanel.SetActive(true);
if (null != OnRetryLocation)
{
OnRetryLocation ();
}
isLocationAvailable = false;
}
else
{
Debug.Log("LocationTracker Location: " + current_lat + " " + current_lon + " " + Input.location.lastData.timestamp);
isLocationAvailable = true;
StartCoroutine(TrackLocation());
}
}
Then you can call and use the location data calling:
private float current_lat;
private float current_lon;
current_lat = Input.location.lastData.latitude;
current_lon = Input.location.lastData.longitude;
However I would suggest you calling a coroutine and make an infinite loop to check that you have GPS signal at any time. The pseudo code could be something like this:
IEnumerator TrackLocation()
{
while (true)
{
//5 seconds for example
yield return new WaitForSeconds (5f);
if (Input.location.status == LocationServiceStatus.Running)
{
//Get the location data here
}
}
}
One last note, if you need the GPS data in certain points you could move the coroutine to the zone where you need it. Is up to you how to program the GPS check and data usage.
Resource links (From Unity documentation)
LocationInfo
LocationService
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With