HowTo: native iOS plugins for Unity3D

Categories:Lab, Technology, Tutorial

Here at TWNKLS, we are used to develop in many different programming languages and platforms. One of the platforms we use a lot is Unity3D.

When developing a new mobile application, we would often see a need for custom mobile logic that Unity3D does not support out of the box.
Actions like taking a picture and saving it to the camera roll or even sending an in app email would fit the “custom mobile logic” description and would thus be build as a native plugin. So how is a native plugin built you may ask? Well, that’s exactly what this article is about!
In this article I am going to take you through the steps of building a native mobile compiled plugin for use in Unity3D.

When talking about mobile development, we generally mean 1 of 2 platforms.
Namely iOS or Android.
Let’s start with iOS.

 

1. Toolset

  • An Apple computer.
  • XCode IDE.
  • An Apple device to compile on (also Apple simulators).
  • Objective-C coding knowledge, as I am not covering the language itself.

 

2. XCode project setup

A. Open XCode and create a new iOS static library project.

iOS_create_xcode_project

 

B. Give your plugin a name and namespace.
Namespaces generally follow these rules : Domain – Company – PluginName
iOS_namespace

 

 C. Finish the project creation process.
Your project structure should look like the following:
iOS_project_view

The files in red indicate that those files are not yet valid.
For now, don’t worry about them. We will cover them in a later stage.

 

3. The Objective-C part.


Now that your project has been setup correctly, let’s dive into some objective-C code.
A typical Objective-C class has 2 components, a header (.h ) and a body (.m) file.
We will start with our header file.
The header file is typically viewed as an “interface”, defining parameters and methods.

 

//import the basics.
#import "Foundation/Foundation.h"

//start interface.
@interface MyNativePlugin : NSObject  //extend from basic object.

//define variables.
@property ( retain, nonatomic ) NSString* dummyString;

//define methods.
-(id) init;
-(void) doMyNativePluginStuff: (NSString*) passed_name; 

//end interface.
@end

In the piece of code described above, we define an interface called “MyNativePlugin.h” which has two methods, namely “init” and “doMyNativePluginStuff”.
The first accepting no parameters and the latter accepting one string parameter. The interface also defines a class variable called “dummyString” of type NSString.

Next up is the corresponding body file.
A body typically incorporates the variables and methods defined in the header.

//import the header file.
#import "MyNativePlugin.h"

//start implementation.
@implementation MyNativePlugin

//synthesize variables.
@synthesize dummyString;

//define methods.
-(id) init {
   return self;
}

-(void) doMyNativePluginStuff: (NSString*) passed_name {

  //Do something with the passed name.
  //Here we just log the passed_name
  NSLog( @"%@", passed_name );

}

//end implementation.
@end

That’s it for our plugin code.

Next step is to setup a connection point for Unity3D with which it can communicate with our code. This is typically done through the UnityAppController. When Unity3D creates a XCode project, it creates a main entry point for the Objective-C application. This entry point is the UnityAppController.

There are multiple ways to make the connection with our plugin:

  • Subclass the AppController
  • Inject code at compile time through Post Process Build scripts.
  • Open the UnityAppController and hand-code your custom code inside.

 

Our goal is to get our class initialized on start-up AND call our class after some unity event.
We could open the UnityAppController files and code our initialization and implementation code inside of these files. But the downside is that every time Unity3D builds ( or replaces ) a new xCode project, you will have to do this manual coding stuff again…. and again…. and again.

We definitely need something more robust and  automated.
What we need is to subclass the UnityAppController to keep all unity generated codes but easily implement our own.

Enter “IMPL_APP_CONTROLLER_SUBCLASS
IMPL_APP_CONTROLLER_SUBCLASS is a method that will take a class as parameter, subclasses it from UnityAppController and make it the new AppControlller being called by Unity3D as entry point.
For this to work we need to make our own UnityAppController implementation with our codes.
This can be fairly simple.
Below a quick 1 file implementation of an AppControlller subclass (header and body in 1 file )

 

#import "MyNativePlugin.h"       //our newly made plugin.
#import "UnityAppController.h"   //our link to the base class.

//------- start header ----------------
//start interface.
@interface MyNativePluginAppController : UnityAppController  //extend from UnityAppController.

@property ( retain, nonatomic ) MyNativePlugin* myNativePlugin;

//define methods.
-(void) initNativePlugin;
-(void) doCallNativePlugin:(NSString*) name;

//end interface.
@end

//------- start body ---------------
@implementation MyNativePluginAppController

//synthesize variables.
@synthesize myNativePlugin;

// overriding main unity entry point.

-(void) startUnity: (UIApplication*) application {         
   [super startUnity: application];  //call the super.
   [self initNativePlugin];          //call method to initialize our plugin class.
}

-(void) initNativePlugin {
   myNativePlugin = [[myNativePlugin alloc] init];  //initialize our plugin class.
}

-(void) doCallNativePlugin:(NSString*) name {
  [myNativePlugin doMyNativePluginStuff:name ];  //call plugin
}

@end

//setting this as app controller.
IMPL_APP_CONTROLLER_SUBCLASS( MyNativePluginAppController )

Save this file as MyNativePluginAppController.mm

A small recap of what we just did.

1. We created a class to hold our custom plugin code called MyNativePlugin.
2. We created a main entry point which subclasses UnityAppController and initialises our plugin class on start-up called MyNativePluginAppController.

Let’s continue our journey.

 

4. Building the connection with Unity3D.

In order for Unity3D to call our Objective-C, we will need something that is called an “ extern C ” method. This method defines a method which will be exposed externally and can be called from Unity3D. Because our flow goes from Unity3D to Objective-C, this method will need to be in our Objective-C entry point, thus the MyNativePluginAppController.mm needs to be modified to have such a method.

 

//import the basics.
#import "MyNativePlugin.h"       //our newly made plugin.
#import "UnityAppController.h"   //our link to the base class.

//------- start header ----------------
//start interface.
@interface MyNativePluginAppController : UnityAppController  //extend from UnityAppController.

@property ( retain, nonatomic ) MyNativePlugin* myNativePlugin;

//define methods.
-(void) initNativePlugin;
-(void) doCallNativePlugin:(NSString*) name;
//end interface.
@end

//------- start body ---------------
static MyNativePluginAppController* delegateObject;   // --- a static object is defined to be called from "extern" method.

@implementation MyNativePluginAppController

//synthesize variables.
@synthesize myNativePlugin;

// overriding main unity entry point.

-(void) startUnity: (UIApplication*) application {         
   [super startUnity: application];  //call the super.
   [self initNativePlugin];          //call method to initialize our plugin class.

   delegateObject = self;                                       // --- we assign this instance to the static delegateObject.
}

-(void) initNativePlugin {
   myNativePlugin = [[myNativePlugin alloc] init];  //initialize our plugin class.
}

-(void) doCallNativePlugin:(NSString*) name {
  [myNativePlugin doMyNativePluginStuff:name ];  //call plugin
}

extern "C" {                                                             // -- we define our external method to be in C.
    void CallNativePlugin( const char* name ){
        NSString* justName = [[NSString alloc] initWithUTF8String:name]; // -- convert from C style to Objective C style.

        [delegateObject doCallNativePlugin:justName ];                   // -- call method to plugin class
    }
}

@end

//setting this as app controller.
IMPL_APP_CONTROLLER_SUBCLASS( MyNativePluginAppController )

This will conclude the MyNativePluginAppController.mm

A small recap.

  1. We created our plugin class which will do our native stuff.
  2. We created our UnityAppController subclass, which has our custom initialization methods.
  3. We added a “delegateObject”, which is a static variable in the UnityAppController class of type MyNativePluginAppController.
    We need this to be static because the “extern” method cannot access variables defined inside the AppController subclass. This way, we ensure it is always accessible.
  4. We assigned the instance of the UnityAppController subclass to the delegateObject in startUnity.
  5. We added an “Extern” method which Unity3D can call and will trigger our plugin.

!! It is important to have our newly made UnityAppController subclass saved outside of our xCode project.
At compile time, Unity3D creates a new ( or overwrites the old ) project. By saving the file inside your xCode project Unity3D will remove / overwrite it.
We will add this later inside the Unity Project.

 

5. Compiling our native library.

Now that we have our project setup and coded correctly we are about to compile the project into a native static library ( .a file ).
For this to work correctly we have to connect our iOS device ( preferably a 64 bit ) to our computer.
Select the project in the project view and make sure you have the correct architectures selected.
iOS_architectures

 

For this plugin I am going to support the following device architectures:

  1. ARMv7
  2. ARM64

Other device architectures that can be supported are

  1. ARMv6
  2. ARMv7S

These are the architectures on which your plugin will run. Other architectures will fail loading the plugin as you have not supported them.

Now select the correct target and set the target device to the connected device.
iOS_targets
with these steps setup correctly, press “RUN” or the  Play button.

If all goes well, your static library has now been made.
You will get errors if this fails, but if not you’ll see your “red files” in project view become black.
iOS_compiled

Right click the .a file and select “Show in finder”, which will take you to the file location.

Congratulations!
You have successfully compiled an iOS library which can be used in Unity3D projects for iOS devices.

 

6. Implementing the native plugin inside Unity3D.

Reproduce the following steps inside Unity3D:

  1. Open Unity3D and load your project.
  2. Create a “Plugins” folder with an “iOS” folder inside. Place the “Plugins” folder inside the “Assets” folder.
  3. Place the generated .a file inside the “iOS” folder together with the AppController subclass and plugin header ( MyNativeAppController.mm and MyNativePlugin.h )

Your native plugin is now ready to go!
Let’s look at the C# code to call our native plugin.

     //connect to the extern method in Objective-C
     [System.Runtime.InteropServices.DllImport("__Internal")]
     extern static public void CallNativePlugin( string filename);

     //Call the method from Unity3D.
     CallNativePlugin( "this is a string");

     //Voila! 
     // Native plugin has been called.

 

And that’s it!
To test it, build your project for iOS and test it on your device.

Happy coding :D

 

7 comments on “HowTo: native iOS plugins for Unity3D

  1. Alex F on said:

    Hi, I tried this and am getting an error when I play in Unity: EntryPointNotFoundException: CallNativePlugin

    When I play through xCode it seems like the plugin isn’t called (no log file) when I expect it to be.

    Any idea how to fix that? Thanks for the tutorial :)
    a

  2. Robin KollauRobin Kollau on said:

    Hi Alex,
    Did you place your plugin file inside the plugins/iOS folder ?

  3. Yakjang on said:

    Wow !!!, Great Tutorial. It’s really useful for me.
    Thank you very much, Mr. Robin

  4. I’ve followed your tutorial (very new to iOS development). I am using xCode8 and Unity 5.4. I am having issues even compiling the plugin since the file “UnityAppController.h” can not be found. Where and/or how to I add it as a reference to the .mm file? I created this project outside of a unity project if that makes any difference.

  5. Robin KollauRobin Kollau on said:

    Hi Chris,
    This applies when you build an iOS project from a Unity3D project.
    The iOS project would contain the “UnityAppController.h”. Unity3D itself would create this file for you.
    The reference to this appcontroller needs to be OUTSIDE of your plugin and INSIDE the generated iOS project. So you do not have to compile it in your plugin.

    • Quentin on said:

      Hey! Thanks for the tutorial, it’s very easy to follow and understand.

      However I have the same problem as Chris, and I don’t understand your answer.

      If I understand well, the reference to the UnityAppController.h will be made after the Unity build with the plugin inside. But to build the plugin and put it in the Unity folder, we need a reference to this same file otherwise there is a error saying UnityAppController.h not found. So how to compile the plugin without the UnityAppController.h reference ?
      It must be stupid but sorry i’m totally new to iOS development.
      Thanks a lot for your help,
      Quentin

  6. Jerome on said:

    Correct me if i’m wrong but i have a error in initNativePlugin function. The error come from myNativePlugin which is not Uppercase to refere at Class name:

    myNativePlugin = [[myNativePlugin alloc] init];
    myNativePlugin = [[MyNativePlugin alloc] init];

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

What is 15 + 14 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)