How to Create a Cross-Platform Mobile (iOS and Android) Plugin For Unity
By: Matthew Bajorek
Tags: Unity, Android, Plugins, iOS

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.

Adding Modules to your Unity Installation

Make sure Android Build Support and iOS Build Support are checked. Click Done to add if not already added the build supports.

Adding Android and iOS Build Support to Unity

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.

External Tools Preferences

Question: How do I fix this error?

Invalid Android SDK directory 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 .

How to Create a Folder in Assets

The directory structure recognized by Unity for native plugins

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.

How to Create a C# Script

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:

Setting up the static class to bridge native code

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 the DllImport 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:

Setting up game object in static constructor

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 .

Example calling script for our plugin’s methods

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.

How to Create a new Game Object

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.

How to Attach a Script to a Game Object

Android

Create a new Android Project.

Android Project Template for Android Plugin

Select Empty Activity .

Android Project Configuration for Android Plugin

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:

Creating a new Module

Selecting Android Library

Selecting Android Library

Configuring Android Library

Make sure you use the same Package Name as used in NativeCalculations.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.

New Android Library’s build.gradle

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 .

Creating a new Java Class

Quick notes:

UnityPlayer.UnitySendMessage requires the same game object name as well as the method name inside NativeCalculations.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.

Other Settings in Player Settings for Android

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.

Android SDK is outdated

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 .

Android Logcat results for example

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.

Other Settings Target

3. Make sure the Package Name is properly set (as well as the Signing Team ID if your target SDK is the device SDK).

Other Settings Identification

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.

Adding BuildPostProcessor.cs

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 a const char * from copying the NSString 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.

iOS log results

Check out the full code and be sure to subscribe for more programming intricacies.