[This entry was updated in Feb 11, 2015 ] [Initial post date: Apr 28, 2012] This tutorial is about creating and compiling your own Unity GPS Plugin for Android. I wrote this tutorial in order to help those who are getting angry and fed up with missing information about the process of writing their own Unity Android plugin.

My motivation was to get real speed values out of the GPS of my device, because the inbuilt Unity Input.location functions returned only latitude and longitude values that where (now that we’ve solved the problem I can assume that ) only computed on network basis and not on the data provided by the inbuilt GPS receiver (http://forum.unity3d.com/threads/132587-GPS-Input.Location-accuracy-question). This was pretty annoying so I decided to write my own GPS Plugin for Android.

Retrouvez la version française de ce tutoriel à l’adresse suivante : http://www.unity3d-france.com/unity/2012/creation-dun-plugin-gps-pour-unity-par-matthieu-deru/

Before starting this tutorial, I would also like to credit one site that helped me to solve the problem.

http://randomactsofdev.wordpress.com/2011/08/19/accessing-the-android-compass-through-unity-3d/

I’ve extended my tutorial by using an Ant script to compile the classes but also to enable an Eclipse integration.
I hope that those how are new to Unity and want to develop their own GPS plugin will find this tutorial interesting.

And of course there are also some other interesting links that helped me to better understand the whole process and to write this tutorial:

http://stackoverflow.com/questions/938719/android-using-locationmanager-does-not-give-a-geo-fix

http://forum.unity3d.com/threads/126241-How-to-use-a-self-plugin-in-unity-project

http://forum.unity3d.com/threads/100751-Android-Plugin-JNI-Question

Prerequisites

  • Mininal version: Unity 3.5 (3.5.1 would be better, but the process is the same). [update Feb 11, 2015 : the tutorial also works with the latest Unity 4.x versions and Unity 5 version]
  • Android SDK
  • Eclipse (Helios) with ADT tools (not mandatory)
  • A working Ant environment

So let’s start to develop our GPS Plugin

Create the Java file for accessing the GPS functions

Open Unity and create a new project put a camera. Under Project create a new directory named “Plugins” and then a directory “Android

Create a Java file GPSTest.java with following code in it:

package com.test.app;

import com.unity3d.player.UnityPlayerActivity;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Config;
import android.util.Log;

public class GPSTest extends UnityPlayerActivity {

    private static final String TAG = "GPS_Unity";

    /** Stores the current location */
    public static Location currentLocation;

    public static LocationManager myLocationManager;

    /** Listeners for the gps and network location */
    static LocationListener networkLocationListener;
    static LocationListener gpsLocationListener;

    /** Starts the GPS stuff */
    public static void startLocationListeners() {
        /**
         * Gps location listener.
         */
        gpsLocationListener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                currentLocation = location;
                Log.i(TAG, "Getting Location over GPS " + currentLocation.toString());
            }

            public void onProviderDisabled(String provider) {
            }

            public void onProviderEnabled(String provider) {
            }

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
            }
        };

        /**
         * Network location listener.
         */
        networkLocationListener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                currentLocation = location;

                Log.i(TAG,
                        "Getting Location over GPS" + currentLocation.toString());
            }

            public void onProviderDisabled(String provider) {
            }

            public void onProviderEnabled(String provider) {
            }

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
            }
        };

        myLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,0, 0,
                networkLocationListener);
        myLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
                gpsLocationListener);
    }

    @Override
    protected void onCreate(Bundle myBundle) {

        super.onCreate(myBundle);
        myLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        // Starts the listeners
        startLocationListeners();
    }

    @Override
    protected void onResume() {
        if (Config.DEBUG)
            Log.d(TAG, "onResume");

        super.onResume();
        startLocationListeners();
    }

    @Override
    protected void onPause()
    {
        myLocationManager.removeUpdates(networkLocationListener);
        myLocationManager.removeUpdates(gpsLocationListener);

        super.onPause();

    }

    @Override
    protected void onStop() {
        if (Config.DEBUG)
            Log.d(TAG, "onStop");
        myLocationManager.removeUpdates(networkLocationListener);
        myLocationManager.removeUpdates(gpsLocationListener);
        super.onStop();
    }

    /** Returns the speed in km/h */
    public static String getSpeed()
    {
        if(currentLocation!=null)
            return "" + currentLocation.getSpeed()*3.6;
        else
            return "Unknown";

    }
}

Important: please notice the package name it has to be the same as in the player settings (Bundle identifier) in Unity. You will find the following setting box by clicking on File>Build Settings > Player Settings

If it doesn’t match you will get problems with the calls because your class definition won’t be found by the VM and you will get a crash at launch (you can check it by using the DDMS tools under /android-sdk/tools/ddms.bat

Code explanation

The first thing you will notice is the import of the UnityPlayerActivity. Compiled through the Build & Run option of Unity the Unityplayer runs it’s own activity and loads all the 3d stuff in it and shows your 3D scene on the screen. Note: it’s not mandatory to extend your Activity from UnityPlayerActivity. But as we want also that the Activity  loads our own stuff (GPS listeners), we are going to let our new class inherit the UnityPlayerActivity.

Usually you will see an additional line called setContentView(int) that will load your layout (R.Layout.main) into the Activity. We are not using it and only use the super.onCreate(myBundle) method.

At the start of the program you’d also like to start the GPS and the Network listeners (but it’s almost improbable that network will return you a speed). This code is pretty straight forward: when the inbuilt GPS device receives the data from the satellites it will trigger the event onLocationChanged. Then you only need to store this location (that will contain latitude, longitude and speed) in a variable currentLocation that you will then reach out to Unity.

If you need a more robust version of the Android GPS Listener I highly suggest you to have a look onto this post:

http://stackoverflow.com/questions/3145089/what-is-the-simplest-and-most-robust-way-to-get-the-users-current-location-in-a/3145655#3145655

Setting up the manifest.xml

Now like any other Android application we need to setup a manifest.xml file. This manifest file will tell at compilation time which activities should be launched and which functions are allowed to be accessed. In our case we must give the name of the Activity the same name as our compiled.jar. Don’t worry we are going to see how to compile this .jar in the next section. So let’s create under /Plugins/Android the file AndroidManifest.xml and place following content in it:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test.app"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" />
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-feature android:name="android.hardware.location.gps" android:required="true" />
    <application android:label="@string/app_name">
        <activity android:name=".GPSTest"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Notice the following important lines:

 <activity android:name=".GPSTest">

Classe name (must match the bundle identifier of your application and the name of the package of your GPSTest.java)

package="com.test.app"

Permissions

We must tell our program that it is allowed to use the GPS and the network for locating our phone – that’s why we have ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION in the permissions block.

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-feature android:name="android.hardware.location.gps" android:required="true" />

If you are going to extend this project with Honeycomb or Ice-Cream Sandwich particular functions you will need to change following line:

android:minSdkVersion="8"

Now we have told which activity should be launched. When you are going to click on Build and Run, Unity will check for this manifest file (and will not take the default one) and will check which Activity (by extension which jar has to be loaded) and integrated in the package (*.apk-file) before pushing it to your device.

Building the .jar for with ANT

Now we have our AndroidManifest.xml and our GPSTest.java under /Plugins/Android

Instead of writing a long cmd line for compiling with javac with classpathes and so one ( i hate to type long command lines especially with all the classpath stuff and additional javac options ;-)), we are going to simplify the whole stuff by building an Ant script.

I was kinda inspired by the Ant script you will get at (http://unity3d.com/support/documentation/Images/manual/AndroidJavaPlugin.zip) and slightly adapted it to have a quicker and compacter script.

Ant enables to create quick script to create directories, call .exe or like in our case to generate the classes. Another nice stuff is that you have the possibility to import your Ant script into Eclipse.

Notice: if you are using other classes or libs you will need to adapt the following Ant script (you can check the official documentation at http://ant.apache.org/manual/ The following Ant script is only for the purpose of this tutorial, but I think you’ve already checked it!

Create a file name build.xml under /Plugins/Android/ with following content

<?xml version="1.0" encoding="UTF-8"?>
<project name="CompileGPSAndroidJava">

    <!-- Change this in order to match your configuration -->
    <property name="sdk.dir" value="C:\android-sdk-windows"/>
    <property name="target" value="android-8"/>
    <property name="unity.androidplayer.jarfile" value="C:\Program Files (x86)\Unity\Editor\Data\PlaybackEngines\androiddevelopmentplayer\bin\classes.jar"/>

    <!-- Source directory -->
    <property name="source.dir" value="." />
    <!-- Output directory for .class files-->
    <property name="output.dir" value="./classes"/>
    <!-- Name of the jar to be created. Please note that the name should match the name of the class and the name
    placed in the AndroidManifest.xml-->
    <property name="output.jarfile" value="GPSTest.jar"/>

      <!-- Creates the output directories if they don't exist yet. -->
    <target name="-dirs"  depends="message">
        <echo>Creating output directory: ${output.dir} </echo>
        <mkdir dir="${output.dir}" />
    </target>

   <!-- Compiles this project's .java files into .class files. -->
    <target name="compile" depends="-dirs"
                description="Compiles project's .java files into .class files">
        <javac encoding="ascii" target="1.6" debug="true" destdir="${output.dir}" verbose="${verbose}" includeantruntime="false">
            <src path="${source.dir}" />
            <classpath>
                <pathelement location="${sdk.dir}\platforms\${target}\android.jar"/>
                <pathelement location="${unity.androidplayer.jarfile}"/>
            </classpath>
        </javac>
    </target>

    <target name="build-jar" depends="compile">
        <zip zipfile="${output.jarfile}"
            basedir="${output.dir}" />
    </target>

    <target name="clean-post-jar">
         <echo>Removing post-build-jar-clean</echo>
         <delete dir="${output.dir}"/>
    </target>

    <target name="clean" description="Removes output files created by other targets.">
        <delete dir="${output.dir}" verbose="${verbose}" />
    </target>

    <target name="message">
     <echo>Android Ant Build for Unity Android Plugin</echo>
        <echo>   message:      Displays this message.</echo>
        <echo>   clean:     Removes output files created by other targets.</echo>
        <echo>   compile:   Compiles project's .java files into .class files.</echo>
        <echo>   build-jar: Compiles project's .class files into .jar file.</echo>
    </target>

</project>

Notice that you must adjust three pathes (source.dir, output.dir and of course the name of the output jar (output.jarfile) and of course the sdk.dir should point to the directory in which you have your Android SDK installed.

Version 1: With cmd The Android SDK includes an Ant distribution so you have the possibility to launch Ant scripts directly over a shell /command line.

Open a command (cmd) and type following command to launch the build script:

ant build-jar clean-post-jar

you will get after a few seconds the message that every was correctly built!

You can now go to section Creating the C# code for calling our Java function.

Version 2: With Eclipse The integration with Eclipse is the best solution if you want to be able to get implement/call Android functions like Sensor data, Audioplayer or like in our case the GPS functions. Also note that at this level you will get auto-completion and a lot of nice Android-Eclipse features that will make the debugging and the testing a lot easier.

Launch Eclipse

Go to File ->New and select make a New Java Project from ant Buildfile and press Next

In the box select Java Project from an Ant Buildfile click on the Browse button and browse to your build.xml

Ok so we have our build.xml and now press Finish

You will have following structure: But there is something wrong!

We have two build.xml. In fact the Wizard automatically creates a build.xml referencing to the workspace but not on your own build.xml placed under your Unity project. Delete the build.xml (and keep the one that is under the /Android directory tree)

Now go under default package and change the package name of your GPSTest.java file (if not already done).

So now you have the following structure. Note that you can remove the classes directories from the project. You should have following project structure:

Our project structure is now correct with the correct directories and path. You can now modify your GPSTest.java if you want and you will get all the Eclipse support. Nice isn’t it ?

Now we need to launch our Ant file. Go to External Tools -> Ant Build..

A window will open and will show the following options. Go to the “Main” tab. The path is false (we don’t want it to be relative to our workspace but to our Plugins/Android path. So press on “Browse File System”. You should get this:

Now press on the “Targets” tab and select build-jar and then clean-post-jar.

Press on “Apply” and “Run”.

Hurray! you’ve compiled your jar! Notice the new file GPSTest.jar in the directory.

Leave Eclipse and switch to Unity. You will have following structure in the Project view.

Hierarchy of the Unity project

Creating the C# code for calling our Java function.

In Unity go to create new and C# Script. Give the script a name (in our case GPSManager.cs) and paste following code in it:

using UnityEngine;
using System.Collections;

/** Accessing the GPS Data over JNI calls
 *  Please check the Java sources under Plugins/Android
 * */
public class GPSManager : MonoBehaviour {

    static string speedMessage;

    AndroidJavaClass gpsActivityJavaClass;

    void Start () {
        AndroidJNI.AttachCurrentThread();
        gpsActivityJavaClass = new AndroidJavaClass("com.test.app.GPSTest");
    }

    void Update() {

        speedMessage = gpsActivityJavaClass.CallStatic<string>("getSpeed");

        float speed = 0;
        if(speedMessage!="Unknown")
        {
            speed = float.Parse(speedMessage);
            GameObject.Find("gps_output").guiText.text = speed + "km/h";

        }
        else
        {
            GameObject.Find("gps_output").guiText.text = "No speed.";
        }
    }
}

The code is pretty easy to understand: we tell the JVM to fetch the reference of our class (notice the name of our class). In our case we are “storing” this instance (“com.test.app.GPSTest“) in the Unity object named gpsActivityJavaClass of type AndroidJavaClass

Now that we have our object reference we can tell it to call a method stored in our class GPSTest.java. In our case we’ve prepared in GPSTest.java the method getSpeed(). As we know that this static method returns a string, we also tell this to the CallStatic method of the AndroidJavaClass object. Please note that the return type should always match otherwise you will get errors while calling the method from C#.

speedMessage = gpsActivityJavaClass.CallStatic<string>("getSpeed")

So now we have our speedMessage that will contain a speed value as a string. Now it’s pretty easy to extract the float value of the speed from a string:

speed = float.Parse(speedMessage);

Note: You will certainly ask yourself what is the best solution: to generate all getters in Java and to check each value with AndroidJavaClass. CallStatic<typeOfReturn> or to only extract the toString() value of the object?

I personally would say it’s the first solution is the cleanest way to realize it. If you are lazy you could of course go this way: serializing the Location by only storing the Location.toString() value and you will parse the content on C# side instead of calling the getters over the AndroidJavaClass calls. Another point is to know if you want to modify both Unity C# and Java classes each time you will integrate a new function or only the Unity C# part, letting the “intelligence” of your Java activity on a lower level.

The other possibility would be to use the UnitySendMessage out of Java. This would be in fact a performance improvement because you would only make the call to the C# method when the location has in fact changed, instead of constantly checking (over the C# Update() function for the last value of the speed). It’s up to you to choose which method could fit the best. Assuming you have a setSpeed(int) function on C# side.

UnityPlayer.UnitySendMessage("Main Camera","setSpeed",Integer.toString())

Now create a new GUI Text and name it gps_output

Final structure of your Unity GPS project

Attach the script to your MainCamera: you’re done  !

The final step: build and run !

Now if you have carefully followed all the instructions mentioned above your scene should compile and you will get a beautiful blue screen with a tiny text. Go outside and run (or drive) around your block and check how the current speed will be displayed!

If you launch the DDMS tool you can see the log outputs :

The DDMS will log out when the location is updated

Lazy? or it’s too late at night to type all the obscure code above ?, here are the files to download 😉 :

GPSAndroidPlugin_Example.zip

Oh no something went wrong !!! – A small troubleshooting guide

I was struggling myself hours to find out why something went wrong. Here is a small list of things that may go wrong:

  1. The packages of the class if wrong, not matching the bundle name specified in Unity
  2. The name of the Activity in the AndroidManifest.xml does not match the name of the jar being compiled.
  3. Calling the CallStatic with the bad parameters or return type
  4. The class in AndroidClass is not correctly referenced (ClassNotFound Exception)

  1. Missing permissions in the Android Manifest.xml especially when using sensors
  2. If you are working with Eclipse be always sure to edit files (.java or build.xml) from the /Plugins/Android directory and not from the workspace.
  3. Using UnitySendMessage, to call a missing C# function or with unknown typed parameters

And don’t forget: DDMS is your friend and will help you to find out where the problem is (or could be).

💬 Comments

Don't hesitate to share your comments. I'm always happy to read your input!