UE4 – Making a Android Plugin in 10 minutes

Let’s say we’re developing a game for mobile and we want to use one of the device functionalities, like reading the data from the light sensor, get the data from the contacts, or send a SMS: UE4 doesn’t provide an API for this, so we have no choice, we have to make this API ourselves. Fortunately, it’s really easy (if we follow this guide)! It’s easier if you’re familiar with the plugin system of UE4, but even if you’re not, this tutorial should be accessible (you should be confident with Android and Java development though).

 

Android Plugin Template.

Let’s not lose time! We want to make this plugin quickly (we only have ten minutes if you look at the title of this article), so we will use a template for an Android plugin. Unreal Engine,  at least in its current version, doesn’t offer a template for a Android plugin. But you can download ours:

Android API Template

Just download this archive, create a “Plugins” directory next to your “Content” directory and unzip the archive inside this “Plugins” Directory. Now, we have a “Plugins/AndroidAPITemplate” folder inside the game directory. Now, we need to start (or restart) the engine so it can build the plugin.

Once the engine is started and the plugin is built, let’s go in “Settings”, then “Plugins”: under the “Mobile” category (at the end) we must see the “AndroidAPITemplate” plugin. Let’s click on the “Enabled” checkbox to enable the plugin. At this point we might have to restart (again) the engine.

Right now, this plugin contain only one function (callable from a blueprint): Show Toast under the category Android API Template. This function simply shows a Toast message with the given text parameter. Let’s make an actor which calls this function in its Begin Play event, then we put this actor in a scene, and send it to the Android phone (or tablet).

As expected, a Toast text is shown at the beginning.

 

The principle behind this.

Before we go any further, some explanations must be made. Let’s open the project C++ solution.

The functions exposed to blueprints are in the class UAndroidAPITemplateFunctions (in the files Classes/AndroidAPITemplateFunctions.h and Private/AndroidAPITemplateFunctions.cpp). As usual, the header file is giving the prototype of the functions and the meta data (in the UFUNCTION() macro) to allow them to be called by UE4, and in the Cpp file we have the implementation of these functions. Currently, we only have one function whose name is AndroidAPITemplate_ShowToast. Let’s look at its implementation.

void UAndroidAPITemplateFunctions::AndroidAPITemplate_ShowToast(const FString& Content)
{
#if PLATFORM_ANDROID
	if (JNIEnv* Env = FAndroidApplication::GetJavaEnv(true))
	{
		// First step, we convert the FString (UE4) parameter to a JNI parameter that will hold a String
		jstring JavaString(Env, TCHAR_TO_UTF8(*Content).c_str());
		
		// Then we call the method, we the Java String parameter
		FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, AndroidThunkJava_AndroidAPI_ShowToast, JavaString);
	}
#endif
}

First, we can note that the prototype is a pure classical UE4 prototype, with an FString as parameter. Then we also note that the whole is wrapped in a #if PLATFORM_ANDROID block, so this code won’t be called if we’re another platform. Now, the interresting part comes: we get a reference to the Java environement (which is of type JNIEnv), we convert the FString to a Java String and we use CallVoidMethod to call a method returning void named AndroidThunkJava_AndroidAPI_ShowToast which is a Java method.

And, in AndroidAPITemplate_APL.xml we can see the Java code associated to this function:

public void AndroidThunkJava_AndroidAPI_ShowToast(String toast) {
    Toast.makeText(getActivity(), toast, Toast.LENGTH_LONG).show();
}

So, to sum up:

  1. A classical UE4 C++ UFUNCTION is defined
  2. In the implementation, we get a reference to the Java environement
  3. We convert all parameters to the Java format (so an FString must become a jstring, a float must become a jfloat and so on)
  4. We use the FJavaWrapper class to make a call to a Java function defined in AndroidAPITemplate_APL.xml

 

Adding new functions to this template.

Well, showing Toast texts is fun but not really useful (actually it can be pretty useful because UE4 doesn’t provide an API for this). So let’s try what I was talking about earlier: reading the data from the light sensor (most of phones usually have a light sensor so it must be OK). We will not talk about sending SMS or reading contacts here because it would require us to ask for the user permission, we may see how to do it in another tutorial.

Preparing everything (Java side)

So first, we need to write the Java code of the functionality we want to add. We will do it in AndroidAPITemplate_APL.xml. If we look closely at this file, we can see that it corresponds to a standard Android activity written in Java (yes, it’s Java in Unreal Engine). So like any Activity class, we can add class members, functions or we can override parent functions like onCreate(), onResume(), onPause(), etc. The functions which are defined in gameActivityClassAdditions can be called from the C++ side.

To use sensors, we need to store a reference to the SensorManager and to the Sensor (let’s have a look here for mor information: https://developer.android.com/reference/android/hardware/SensorManager.html). So we add two members to the activity (like the example given on the android SDK documentation: the SensorManager and the Sensor), a SensorEventListener, and a variable to store the measure. All of this goes just before the definition of AndroidThunkJava_AndroidAPI_ShowToast.

private SensorManager mSensorManager;
private Sensor mLight;
private SensorEventListener mSensorListener;
private float mLastLightMeasure;

We will now initialize this class members, but it will be done in the onCreate() method of the activity. So in the <insert> of <gameActivityOnCreateAdditions>, we add this:

mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); mSensorListener = new SensorEventListener() {
onAccuracyChanged(Sensor sensor, int accuracy) {
  //
}
public void onSensorChanged(SensorEvent event) {
  if(event.sensor == mLight)
    mLastLightMeasure = event.values[0];
}
};

This code will initialize the SensorManager, initialize the light sensor, then make a new listener which will store the result of each light related event in the mLastLightMeasure variable. We now need to register this listener, we will do it in the onResume() method, so we put the following code in the <insert> of <gameActivityOnResumeAdditions>:

mSensorManager.registerListener(mSensorListener, mLight, SensorManager.SENSOR_DELAY_UI);

In the onPause() method we add the code to unregister the listener (so it doesn’t consume resources when the game’s process is not active):

mSensorManager.registerListener(mSensorListener, mLight, SensorManager.SENSOR_DELAY_UI);

Finally, we need to write the function that will return this measure and that will be called from the C++. This function is added next to the existing one, in <gameActivityClassAdditions>:

public void AndroidThunkJava_AndroidAPI_GetLastLightMeasure(float[] light) {
  light[0] = mLastLightMeasure;
}

We can note that we are returning the result by parameter (it’s because the FJavaWrapper from UE4 doesn’t provide a CallFloatMethod, so returned values are usually output parameters of void functions).

Now we can move on the C++ part.

Binding Unreal Engine and Android (C++ part)

In AndroidAPITemplateFunctions.h we add the prototype that we want:

UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Keywords = "AndroidAPI ", DisplayName = "Get Last Light Measure"), Category = "AndroidAPI")
		static float AndroidAPITemplate_GetLastLightMeasure();

The purpose of this function will be to return the last light measure. In AndroidAPITemplateFunctions.cpp, we will write what we need to call the Java method supposed to give us that value. The provided template already gives us some useful macros. So we just have to follow these steps:

  1. Add DECLARE_JAVA_METHOD(AndroidThunkJava_AndroidAPI_GetLastLightMeasure); next to the other DECLARE_JAVA_METHOD call.
  2. Add INIT_JAVA_METHOD(AndroidThunkJava_AndroidAPI_GetLastLightMeasure, “([F)V”); in InitJavaFunctions: the first parameter is the name of the Java function, the second is the Java signature (here it means “Array of float as parameter and void as returned value”).
  3. Write the implementation of AndroidAPITemplate_GetLastLightMeasure():
float UAndroidAPITemplateFunctions::AndroidAPITemplate_GetLastLightMeasure()
{
#if PLATFORM_ANDROID
	if (JNIEnv* Env = FAndroidApplication::GetJavaEnv(true))
	{
		jfloatArray Result = Env->NewFloatArray(1);
		FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, AndroidThunkJava_AndroidAPI_GetLastLightMeasure, Result);

		jfloat* ResultArr = Env->GetFloatArrayElements(Result, 0);

		const float ResultFloat = ResultArr[0];

		Env->DeleteLocalRef(Result);

		return ResultFloat;
	}
	else
	{
		UE_LOG(LogAndroid, Warning, TEXT("ERROR: Could not get Java ENV\n"));

		return 0;
	}
#else
	return 0;
#endif
}

This function works like the other one. We will make a call to the Java function using FJavaWrapper::CallVoidMethod, but first we need to prepare the parameter for this function, i we have to create a Java array (using NewFloatArray()). After the call, this array will contain the last measure from the light sensor. Then we extract this value from the Java float array, and we delete this array.

Now, we can rebuild this plugin, try to call this function from UE4, and for instance, adjust the luminosity of the game according to the ambient luminosity!

 

Conclusion

Well, I lied, if you follow this tutorial, it will take you more than 10 minutes 🙂 But I hope we’re not so far. The provided template should be useful for you: it gives you an example of how Android plugins are working, and it gives you a basis to develop your own plugin. If you want to go further, I suggest you to try new things and make different calls to the Android API. In a next tutorial, we may see how to do this for iOS (and it actually might be easier).

Leave a Reply