Native plugins can be extremely useful, especially on mobile platforms. They can expose system functions that Unity has not (or will not) provided access to. On iOS in particular, native plugins can give Unity access to shared system resources such as location events or music from the device’s music library. The plugin interface can be intimidating at first, but once you’ve written a plugin or two, you can figure them out pretty quickly.
Note that this tutorial was written for Unity 4.2. There are some new plugin interfaces for iOS in Unity 4.3 that I haven’t had a good chance to investigate yet.
Basics - Bridging
Like many things, getting started is the hardest part. The first thing to do is start a new project to develop your plugin in. The whole point of a plugin is to be reused, so build it on it’s own, then integrate it into your project. That way you have a nice, clean package that you can install into future projects if you need those features again. Developing your plugin separately also forces you to think generically. Sure, you have an idea of what you’ll need for the specific project you’re building it for, but since it’s completely decoupled, you’ll be forced to build a plugin that’s flexible enough to be reused in many situations. This also gives you a great starting point if you decide you want to put your plugin on the Asset Store!
Let’s start from the beginning, with a new Unity project, with a basic folder structure:
Now, on to the real stuff. We'll create a new C# script, and stub out the Unity end of the plugin. One of the most frustrating things about building plugins is the fact that the two code bases are completely separate. I find that stubbing the basics for one half first, then filling in the other side to match works well for me, but it's really up to you.
Identify what basic functionality you'll need, and declare the minimum methods you'll need. Stay as simple as possible, it'll make your life much easier when you need to expand in the future.
Next, we need to point Unity/Mono to the external methods which match the ones we've defined.
There's a few things to note here.
The most obvious, is the [DllImport("__Internal")]. The DllImport attribute tells Mono to look for this method in the named DLL, and use it. On iOS, everything gets built into one big binary, so “__Internal” is the name of the DLL to use. This differs on other platforms, which we’ll see later. Second, you’ll see that the native method is wrapped in a platform check. This is mostly to prevent errors in the Editor. Again, we’ll come back to it later when it won’t be so necessary.
Now that we have the basics stubbed out on the Unity side, let’s start on the iOS side. We’ll create a new Xcode project, where we’ll work on our Objective-C code. Create a Single View Application, as it’s the simplest to get up and running. We don’t actually need a full application as it will never actually be run, but building into a basic gives you access to code-complete and other good things.
Xcode will give you a new project with an empty view. Disregard all that, and immediately add a new file. You want an Objective-C class, that inherits from NSObject. When it asks you where to save the file do not put it into your Xcode project, but instead navigate to the Plugins/iOS directory of your Unity project, and save it there. This way, you can edit the file in Xcode with all the code-complete goodness, but have those changes automatically save into your Unity project as well.
While writing this, I noticed that Xcode 5 no longer gives you an option to disable ARC when creating a file. Since Unity projects have ARC disabled, you’ll need to either manually enable ARC for your plugin sources in your Unity build, or disable ARC in your development Xcode project. I think the latter is a better solution. To do so, add the -fno-objc-arc flag in your Build Phases:
Once that's done, it's back to code! In your header file, add the method declarations:
Then fill in the bodies in the .m:
Although we're using some Objective-C (and will use a lot more soon!) the Unity-to-Native bridge is done in plain old C. Declaring methods as we have is fine in C, but if you want to use Objective-C++, you'll run into issues. In order to avoidname mangling you'll need to wrap any of the C methods with:
There's a brief note about this in the Unity documentation. I find this leads to headaches, so I stick to standard Objective-C.
Let's quickly go back to Unity, and do a test:
By adding this to our start function, we'll be calling our native code. Add the script to something in your scene, and hit play in the Editor. Nothing should happen, as you'd expect. But if you build your project to a device:
At this point, you’ve got a (very) basic native plugin working! You can do a lot, with only this much. Passing data back and forth is fairly simple, so you can use a plugin to perform expensive calculations, which may be faster than doing them in C#. Primitive types get transferred directly, so you can use return values as you normally would. Check out this talk from Unite 2013 for some more info on that.