Unreal Engine (UE) is one of the most popular platforms for creating cutting-edge content in immersive interactive experiences. Those immersive experiences, e.g., Augmented Reality (AR) applications, are often deployed to mobile platforms such as Android and iOS. In this article, focusing on Android, I will show how to make an Android plugin for UE so that you could customize your own Java functions to interact with UE Blurprints.
(1) Prepare the plugin structure
Customized plugins can be put under the Plugins folder either in UE source code directory or in your project directory. In UE source code, plugins are located at “UnrealEngine\Engine\Plugins”. In your own project, you can just create a folder called “Plugins”, if it doesn’t exist.
Then, inside the Plugins folder you created, let’s create a plugin source code directory and name it Transmitter (we are going to realize the function of transmitting data from UE to Java and from Java to UE). This directory should contain your plugin’s source code, a readme (optional), a LICENSE, and a Transmitter .UPLUGIN file for your plugin description, e.g., what functions does your plugin provide and what platforms does it aim at.
Next, inside the Source folder, let’s write the Transmitter UE plugin source code, which contains a Classes folder, a Private folder, a Public folder, a .Build.cs file, and a XML file.
Inside Transmitter.Build.cs, module dependencies and path dependencies need to be listed. For example, “GoogleARCoreBase” should be added to PublicDependencyModuleNames.AddRange() for Google ARCore related functionalities.
Inside Transmitter_APL.XML, you can define the location of your Android source code to be copied to your UE build directory and customized functions to be added to GameActivity.java. For example, we expose a function called public void AndroidThunkJava_TransmitterSendArrayFloat(Float[] bytesToSend) {} such that floating data in UE could be sent to Android main activity. Then, this function will be automatically added to GameActivity.java during compilation and you could customize the functionality further.
In Public folder, we create a ITransmitter.h to present the public interface for you Transmitter module. In Private folder, we create a Transmitter.cpp file which has the class FTransmitter that implements the ITransmitter interface declaring the StartupModule() and ShutdownModule() behaviors, as well as a TransmitterFunctions.cpp to implement all function we want to realize in the plugin. For example, the function we added to Transmitter_APL.XML file previously, AndroidThunkJava_TransmitterSendArrayFloat, will be implemented in this TransmitterFunctions.cpp. In Classes folder, we need to create TransmitterFunctions.h to expose all function headers that can be called in UE Blueprints.
(2) Add Blueprints callable in UE
After the plugin structure is prepared, we need to expose function headers in TransmitterFunctions.h. For example, we implement a UTransmitterFunctions UObject to expose the following function. It is BlueprintCallable and has TArray of float as input.
class UTransmitterFunctions: public UObject {
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, meta = (DisplayName = “Transmitter Send Float”))
static void TransmitterSendArrayFloat(const TArray<float>& dataToSend);
};
Then, we are going to write code in C++ to implement the above function in TransmitterFunctions.cpp. Here, we direct connect this function TransmitterSendArrayFloat with Android through Java Native Interface (JNI).
(3) Connect UE Blueprints callable to Java through JNI
First, we need to declare the JNI version of the function such that code in Java could locate this function in compiled C++ binaries.
DECLARE_JAVA_METHOD(AndroidThunkJava_TransmitterSendArrayFloat);
Then, we initiate it such that the correct function signature could be verified.
INIT_JAVA_METHOD(AndroidThunkJava_TransmitterSendArrayFloat, “([Ljava/lang/Float;)V”);
The INIT_JAVA_METOD is implemented as the following. It is generic to other JNI function implementations.
#define INIT_JAVA_METHOD(name, signature)
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv(true)) {
name = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, #name, signature, false);
check(name != NULL);
} else {
check(0);
}
(4) Convert UE data to Java
The Blueprints callable and its JNI version is now ready. Then, we need to convert the float data passed from UE to Java float type. The following TransmitterSendArrayFloat is finally implemented in TransmitterFunctions.cpp. This function first gets the jclass environment, create a new data holder based on the imput data, and then converts UE float to Java float one element by one element. After that, the data is passed through JNI using pointers.
void UTransmitterFunctions::TransmitterSendArrayFloat(const TArray<float>& dataToSend)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv(true))
{
jclass floatClass = Env->FindClass(“java/lang/Float”);
auto dataForJava = NewScopedJavaObject(Env, (jobjectArray)Env->NewObjectArray(dataToSend.Num(), floatClass, NULL));
jmethodID floatConstructor = Env->GetMethodID(floatClass, “<init>”, “(F)V”);
if (dataForJava)
{
for (uint32 Param = 0; Param < dataToSend.Num(); Param++)
{
jobject wrappedFloat = Env->NewObject(floatClass, floatConstructor, static_cast<jfloat>(dataToSend[Param]));
Env->SetObjectArrayElement(*dataForJava, Param, wrappedFloat);
}
FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, AndroidThunkJava_TransmitterSendArrayFloat, *dataForJava);
}
}
#endif
}
(5) Process the transmitter data in Java
At Android Java level, inside MainActivity.java, remember that we automatically inserted a function in the plugin’s XML file AndroidThunkJava_TransmitterSendArrayFloat(Float[] bytesToSend). Once its Blueprints callable version is called in UE that has some data fed in, the corresponding Java version will automatically have the data ready in bytesToSend. You are able to use that data freely. Notably, you can also expose JNI the other way around to pass data from Java to UE.
Let me know if you have any questions.