Android Plugin Guide
Prerequisites
- Install Android Studio. We recommend Koala or later as that is what we will be using for this guide.
- Install Unity Hub
- Install Unity 2022.3 LTS or later
Overview
In this guide we will be creating an Android plug-in that we can use inside of Unity. The plug-in will obtain access to hardware sensors on the Magic Leap 2 device, receive sensor value updates, and have the ability to be accessed from a Unity application. These can be very useful as they enable you to access features, like third-party code libraries and operating systems calls, that would otherwise not be available to Unity.
Create Android Studio Project
- Open Android Studio, create a New Project, and select No Activity
- Name your project, for this guide we will name it
ExampleSensorPlugin
. Ensure you set Language to Java and Minimum SDK to API 29 ("Q"; Android 10.0). You can also edit your package name if you like. Please take note of this as we will need it later.
Create Library Module
- Now we will create our library module. In the project browser, right click the root of your project, New->Module, and select Android Library in the new window that appears.
- Give this library module a name. For this example, we will call it
AccelerometerLibrary
. Ensure that Java is the selected language and the minimum SDK level is set to API 29. - You now have an Android Studio project that is ready for you to begin creating a plugin for Unity! Your project file structure should now look something like this:
Create Library Class
- Next, we will create a Java Class that Unity will interact with to obtain sensor data. The location you create this will depend on what “project view” you have selected. For this example, we will set our project view to “Android”
- In the project panel, navigate to
AccelerometerLibrary/java/com.magicleap.accelerometerlibrary
, right click on this folder and select New->Java Class. - Give your class a name. We will call ours
AccelerometerListener
. You should now have a folder structure like this:
Write Library Logic
- In order to access sensor data, we will need to ensure the class we created implements the
SensorEventListener
Interface. This interface requires that 2 functions be overriden,onSensorChanged
andonAccuracyChanged
.
package com.magicleap.accelerometerlibrary;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
public class AccelerometerListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
- Next, we need to have a reference to the Unity Activity in order to access sensors from our Unity project that we will create later. Create a static
Activity
variable and a public function to set this activity from Unity
private static Activity unityActivity;
public static void setUnityActivity(Activity _activity)
{
unityActivity = _activity;
}
- Now, let's get a reference to our accelerometer sensor. To do this, we will need 2 new local variables and a new function called
startSensorListening
. This function will be called from Unity to initialize our sensor variables and allow us to start using theonSensorChanged
callback.
package com.magicleap.accelerometerlibrary;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public class AccelerometerListener implements SensorEventListener
{
private static Activity unityActivity;
private SensorManager sensorManager;
private Sensor accelerometer;
public static void setUnityActivity(Activity _activity)
{
unityActivity = _activity;
}
@Override
public void onSensorChanged(SensorEvent event)
{
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
public void startSensorListening()
{
//initialize sensorManager
sensorManager = (SensorManager) unityActivity.getSystemService(Context.SENSOR_SERVICE);
//use sensorManager to obtain the devices default accelerometer
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
//register this sensor to begin listening and receiving SensorEventListener callbacks
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
}
- Almost done! Now that we have our sensor setup and ready to receive events, we have to access the sensor data through the
onSensorChanged
event. We do this with theevent
parameter,event.Values
which is an array of floats. In this example, we will also need a new local variable, a float array calledlatestValues
, to store our sensor data. Inside of theonSensorChanged
event, let's setlatestValues
toevent.Values.clone()
.
Android sensors always return an array of float values. The number of values inside the array is dependant on what sensor is currently being used. This example uses the accelerometer sensor, which always returns an array of 3 float values. If we were to use the ambient light sensor, it would only return an array of 1 float value. You can find more information about all Android sensors here
- Finally, we will create a way for Unity to access our
latestValues
array with a public function calledgetLatestValues
that just returns thelatestValues
array.
Your AccelerometerListener
class should now looks like this:
package com.magicleap.accelerometerlibrary;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public class AccelerometerListener implements SensorEventListener
{
private static Activity unityActivity;
private SensorManager sensorManager;
private Sensor accelerometer;
float[] latestValues = new float[3];
public static void setUnityActivity(Activity _activity)
{
unityActivity = _activity;
}
@Override
public void onSensorChanged(SensorEvent event)
{
latestValues = event.values.clone();
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
public void startSensorListening()
{
//initialize sensorManager
sensorManager = (SensorManager) unityActivity.getSystemService(Context.SENSOR_SERVICE);
//use sensorManager to obtain the devices default accelerometer
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
//register this sensor to begin listening and receiving SensorEventListener callbacks
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
public float[] getLatestValues()
{
return latestValues;
}
}
Building our library into a .aar file
Before we move over to Unity to use our new android library, let's build it into a .aar (Android Archive) file.
Android Archive (AAR) is a file type that can contain C++ and Java code, resources such as assets, and a plug-in manifest file. You can use them to reuse components across multiple applications, or build variations of one application with the same core components.
In our project view, select the AccelerometerLibrary module.
Now, use the toolbar at the top to navigate to
Build->Make Module "ExampleSensorPlugin.AccelerometerLibrary.main"
Once this is done, open a file browser and navigate to where you saved your project. The aar file we just created will be at this file path:
ExampleSensorPlugin\AccelerometerLibrary\build\outputs\aar
Keep this file location up as we will need to copy it into our Unity project.
Create Unity Project
The first thing we need to do is create a new Unity project that is setup for Magic Leap 2 development. Please follow our guide here on how to do this.
Use Android Library in Unity
- In your project, Create a folder structure like the following if it doesn't already exist:
Assets/Plugins/Android
- In
Assets/Plugins/Android
, copy the .aar file from above that we built with Android Studio into this location - Create a new C# script anywhere in your Assets folder called
AccelerometerPluginManager
- Now, we will need to initialize 3 variables in order to interact with our Android Library from Unity. We will also need our Android Library Package Name and Java Class name we created.
using UnityEngine;
public class AccelerometerPluginManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject pluginInstance;
void Start()
{
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
//To reference our Android Library, we have to use the packagename and the name of the class we created
pluginInstance = new AndroidJavaObject("com.magicleap.accelerometerlibrary.AccelerometerListener");
}
}
- We now have a reference to our Android Library! From here we can begin calling functions from it. First, let's set the
unityActivity
withsetUnityActivity
. Then, we will callstartSensorListening
to initialize our sensor variables and begin receiving callbacks for sensor value updates.
using UnityEngine;
public class AccelerometerPluginManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject pluginInstance;
// Start is called before the first frame update
void Start()
{
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
pluginInstance = new AndroidJavaObject("com.magicleap.accelerometerlibrary.AccelerometerListener");
if (pluginInstance != null)
{
Debug.Log("Plugin Instance Loaded!");
pluginInstance.CallStatic("setUnityActivity", unityActivity);
pluginInstance.Call("startSensorListening");
}
}
}
- Now that we have initialized the accelerometer sensor and our library has begun receiving onSensorChanged events, let's see our values in Unity! To do this, inside the Update function, call
getLatestValues
and just log them to the console for now.
using UnityEngine;
public class AccelerometerPluginManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject pluginInstance;
// Start is called before the first frame update
void Start()
{
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
pluginInstance = new AndroidJavaObject("com.magicleap.accelerometerlibrary.AccelerometerListener");
if (pluginInstance != null)
{
Debug.Log("Plugin Instance Loaded!");
pluginInstance.CallStatic("setUnityActivity", unityActivity);
pluginInstance.Call("startSensorListening");
}
}
// Update is called once per frame
void Update()
{
if (pluginInstance != null)
{
float[] result = pluginInstance.Call<float[]>("getLatestValues");
Debug.Log($"[0]: {result[0]}, [1]: {result[1]}, [2]: {result[2]}");
}
}
}
Instead of using Unity's Update
function to get sensor values, you can use callbacks! There are various ways you can achieve this for your plug-in. We have guides you can follow for two of those methods, UnitySendMessage and AndroidJavaProxy
- Lastly, we will put this script component on a
gameobject
in our scene
You can now build and run this on your ML2 device! You should see log output of the sensor values in the format you specified in the Update
function.
Congratulations! You built your first Android Library for Unity!