How to Create a Cross-Platform Mobile (iOS and Android) Plugin For Unity
Unity is a great platform for developing VR and AR applications. When developing a cross-platform application there are often native mobile plugins that need to be leveraged or developed.
Unity’s documentation around this is limited. This article will serve as an entry point to your native cross-platform mobile plugin’s development.
This tutorial uses a new project from Unity Version 2018.4.19f1, but will also work with newer versions.
Example Plugin Layout
This plugin will demonstrate three important concepts:
- Sync call to native
- Async call to native
- Call from native to Unity
To do this, we will be passing a rectangle’s height and width both sync and async to native. Native will calculate the rectangle’s diagonal, perimeter, and area passing these values back to Unity.
Required Unity Build Support Modules
Open your Unity Hub → Click Installs → Click the Three Dots → Add Modules.
Make sure Android Build Support and iOS Build Support are checked. Click Done
to add if not already added the build supports.
Required Tools for Android
Download and Install Android Studio. It is not required for Unity, but it will make building the plugin a whole lot easier.
However, Unity requires Command line tools
so at the bottom of the download page make sure you download Command line tools only
for your proper platform. Extract the zip and place the tools
folder inside of the sdk folder created by Android Studio.
Make sure the Android SDK is set in Unity by going to File → Preferences → External Tools → Android SDK.
Question: How do I fix this error?
Solution: Make sure the tools
folder is inside the selected Android SDK directory and is spelled properly. The command line tools folder name was changed to build-tools
in newer versions of Android Studio and Unity does not recognize this new folder name.
Required Tools for iOS
- Mac OS
- XCode
- If your plugin package requires capabilities not able to run on a simulator, you will need to have an Apple developer account to run on an iOS device.
Folder Structure
Create a folder inside Assets
called Plugins
. Inside Plugins
create two folders: Android
and iOS
.
Files that are added in the Android
and iOS
folders will automatically be attached to the build.
Bridging Script
Right click the Plugins
folder or inside of the Plugins
folder right click the empty space and create a C# script named PluginBridge
. This script will be called from external scripts to send and receive data from native scripts.
NOTE: There must be at least one game object attached to a script to initialize this plugin. This is usually taken care of by the calling script, which we will add an example the next section.
First we will set up the plugin bridge class:
Quick notes:
- We are using a static class to allow for easy to use calls from Unity.
- Make sure you take note of the Java object name as it will be used in creating the plugin.
- iOS requires the native methods used to be explicitly defined. The
#if
and#endif
are required around theDllImport
so Android will not throw an error and can use the same class. PlatformNotSupportedException
is just used for easy debugging when implementing the plugin.
Second, we will add the game object to receive messages from native as well as initiate the Java class:
Quick notes:
- The game object is added for calls from native to Unity can be received. Creating a new game object just for this plugin makes it easy to not have to create an external game object when adding the plugin.
- Android needs to instantiate the object. Every argument after the second argument in
new AndroidJavaObject()
gets passed to the Java object constructor. - HandleException callback is used internally by native code to notify Unity of any exceptions.
- The callback handlers can only receive strings as native can only send back strings through its messaging protocol. We will be sending json and then deserializing it into our
CalculationResults
object.
Finally, we will add the interface methods:
Quick notes:
- We are saving the callback locally to be called once we received a message from Native. This works well when there is only one callback set at a time. If multiple callbacks are needed they should be handled externally through the single passed callback.
Calling Example Script
Create a new folder under Assets
called Scripts
. Inside scripts create the script: CallingExample
.
Now we will create a game object in the scene to attach to the CallingExample
script.
Right click inside the SampleScene and click Create Empty.
Right click, rename GameObject
to Calling Example
. When left clicking it check out the Inspector tab on the right side. Click Add Component and type in CallingExample
in the search box. Select the script to add it.
Android
Create a new Android Project.
Select Empty Activity
.
Fill in the configuration details. Make sure you take note of the Save location
as it will be used later. Also make sure the save location does not have a space in it as it may cause problems with the NDK tools. NDK tools may be needed when compiling low level externally connected devices.
For this example we will place the Android Plugin in a neighbor folder to the Unity Project folder.
Creating the Android Library
Android studio will automatically create an app folder, but we will not be using it. We will need to create a new Android Library:
File → New Module:
Make sure you use the same Package Name
as used inNativeCalculations.cs
. The Minimum SDK depends on the capabilities of the package you are going to build. It can be changed later so API 16 is sufficient.
After clicking Finish
there will be a new Android library folder in the directory structure.
Android Dependencies
Because this library is going to be used inside of Unity, there are specific dependencies that need to be set for proper compilation and runtime.
Open the build.gradle for the library and edit the dependencies section as shown below.
Some questions you may be having:
- Why can I not find the Unity classes jar location?
You may not have the Android Build Support Module. To add this check out the first section of the article. This jar is required to send messages back to Unity.
2. Why is 'androidx.appcompat:appcompat:1.1.0'
set as api
instead of implementation
?
The appcompat library is used for dealing with activities, among many things. A common use case would be requesting permissions. The Unity build will error out during runtime saying appcompat does not exist. Changing it to api will allow this library to be shared during runtime of Unity. This example will not go into how to request permissions, but it will be useful to keep this in mind when debugging runtime errors. For more explanation on the different types of dependencies. For official information on different types of dependencies.
3. Why can’t I just use implementation 'com.google.code.gson:gson:2.8.6'
?
Unity will error out with:
UnityEngine.AndroidJavaException: java.lang.NoClassDefFoundError: Failed resolution of Lcom/google/gson/Gson;
The library must be including in the aar. An easy way is to just include it in libs.
Android Library Class
We can now add our implementation. Open up java, right click com.example.nativecalculations
→ New → Java Class. We will name it NativeCalculationsPlugin
.
Quick notes:
UnityPlayer.UnitySendMessage
requires the same game object name as well as the method name insideNativeCalculations.cs
.- Unity called methods can return basic types (like int, float, boolean), but not class types. Java class types are easiest sent by stringifying into json and then parsing back into a C# class.
Android Library Build
We will also be adding some code at the bottom of build.gradle
to automatically copy the built Android Library into the Unity Android Plugin folder.
To build the Android Library go to Build → Make Module ‘nativecalculations’.
Android Unity Build
File → Build Settings. Click on Android and click Switch Platform if you have not do so already. Click on Player Settings. Make sure the Package Name is properly set as well as the minimum API level depending on your Android code. For this example API level 16 is sufficient.
You can either click Build
or Build and Run
. You may get a notification that your Android SDK is outdated. In actuality it is newer than expected by Unity. This is fine however for most plugins. Just clicking Use Highest Installed
will allow building.
Viewing Android Logs
After the build is complete and the application is running, click Logcat at the bottom. In the search bar you can filter by Regex. Type in Unity
.
iOS
Unlike Android where we will create a library in Android Studio and built the filt to be compiled by Unity, iOS requires building/modifying an XCode project. Each subsequent build will override some of the previously edited files in XCode. Therefore any permanent edits to XCode have to be done inside Unity (unless you use my custom script in build post processor).
iOS Unity Build
File → Build Settings. Click on iOS and click Switch Platform if you have not do so already. Check Development Build
to be able to see the logs.
Click on Player Settings.
2. Make sure your Target SDK is set properly. For this demo we will use the Simulator SDK.
3. Make sure the Package Name is properly set (as well as the Signing Team ID if your target SDK is the device SDK).
Build post processor
Inside the iOS folder create a new folder called Editor
. Inside Editor
create a new Script called BuildPostProcessor.cs
. The Editor
folder is a reserved folder in Unity that will run specific scripts and this script must be inside there for the imports to work as well as the script to be executed properly.
Quick Notes:
Foundation.framework
added below is not necessary for the example but a demonstration of how to add system frameworks- Add specific build properties and flags
We will now add a custom script that will automatically copy back edited files in XCode to the source files in Unity.
Why? Any time a project is rebuilt, the Unity project files will override the edited changes in XCode even if the XCode changes are newer. This is prone to errors as many of the editing will take place in XCode because of its IDE capabilities. This shell script will ensure edited files in XCode will be reflected in the Unity source files once a successful run takes place.
NOTE: If there was no successful run and you want to save your changes, you will need to manually copy the changes back to the original file.
Now we can add our native plugin script. Inside of Plugins/iOS add NativeCalculationsPlugin.mm
.
Quick Notes:
extern "C"
are the interface methods called from Unity. These functions need to return C types and not Objective C classes. Note we are returning aconst char *
from copying theNSString
with a helper function.- The Objective C class is a singleton. The init helper method is used for initialization code similar to the constructor in Java.
- When sending JSON it is easiest to make dictionaries and use the utility function to stringify the dictionary.
Viewing iOS Logs
After the build is complete and the application is running, you can view the logs in XCode.
Check out the full code and be sure to subscribe for more programming intricacies.