I have written about how to use iOS
and Android Libraries in Cocos2d-x , in mobile game industry, there
is another cross platform framework named unity3d , which is more elegant, powerful,
and, super expensive!
This post will show how to enable the App using unity3d framework to
use native libraries of iOS and Android.
Unity3d Plugin Mechanism
Unity3d supports plugins well, compared with Cocos2D-X .
In order to use a plugin in unity3d, two things have to be done.
Write functions in a C-based language and compile them into a
library
Create a C# script which calls functions in the library
Seems similar to the cocos2d-x
way , but unity3d organizes things well, by which, plugin can be
distributed as a package file, in App, simply import the package file,
and, it's all.
To create an iOS and Android plugin, five steps have to be
followed.
Create a new unity3d project
Make a folder "Plugins" in "Assets" folder, then, make "iOS" and
"Android" folders in "Plugins" folder
Put iOS .a
and Android .jar
lib files to
their folders respectively
Write the C wrapper code to expose the native APIs to C#
Click the "Assets - Export Package" menu to export the plugin as a
unity3d package
At last the file structure will be similar to this.
Assets
└── Plugins
├── Android
│ ├── AndroidManifest.xml
│ └── humbleAssistant.jar
├── HumbleAssistant.cs
└── iOS
├── HumbleAssistant.h
├── HumbleAssistantWrapper.h
├── HumbleAssistantWrapper.m
└── libHumbleAssistant.a
iOS
I have an iOS lib named libHumbleAssistant.a
, and its
header file HumbleAssistant.h
.
1 2 3 @interface HumbleAssistant : NSObject + (void )doSomething:(NSString *)order; @end
In order to expose doSomething:
interface to C#, a C
wrapper need to be implemented. We know, lovely Objc integrates with C
very well.
At first, create an empty header file "HumbleAssistantWrapper.h"
1 2 @interface HumbleAssistantWrapper : NSObject @end
Then its implementation,
1 2 3 4 5 6 #import "HumbleAssistant.h" #import "HumbleAssistantWrapper.h" void humble_assistant_do_something(char *order) { [HumbleAssistant doSomething:[NSString stringWithUTF8String:order]]; }
Now, the C function is exposed, then, how to call C functions in C#
scripts?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using UnityEngine;using System.Collections;using System.Runtime.InteropServices;using System.Collections.Generic;public class HumbleAssistant { #if UNITY_IPHONE [DllImport("__Internal" ) ] private static extern void humble_assistant_do_something (string order ) ; public void doSomething (string order ) { humble_assistant_do_something(order); } #endif }
Macro UNITY_IPHONE
means those code only take effect in
iOS system, [DllImport("__Internal")]
means to statically
link the C wrapper code.
Then, make the doSomething
method, which is the real
interface that can be used by other C# scripts.
Android
My android lib is humbleAssistant.jar
, and has an
interface as follows.
1 2 3 4 5 6 package com.my.libs;public final class HumbleAssistant { public static void doSomething (String order) { } }
In unity3d, C# can interact with java APIs by JNI
directly.
No C wrapper is needed, just add elif UNITY_ANDROID
(those code only take effect in Android system), and begin to write the
real code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using UnityEngine;using System.Collections;using System.Runtime.InteropServices;using System.Collections.Generic;public class HumbleAssistant { #if UNITY_IPHONE #elif UNITY_ANDROID public void doSomething (string order ) { AndroidJavaClass mHumbleAssistantClass = new AndroidJavaClass("com.my.libs.HumbleAssistant" ); mHumbleAssistantClass.CallStatic("doSomething" , order); } #endif }
My lib API is a static method, I need to create an instance of
AndroidJavaClass
initialized with my class name
"com.my.libs.HumbleAssistant" , then call the CallStatic
method of this instance to finish the JNI invocation.
If some APIs need android Activity
object as parameter,
following code can get the Activity instance.
1 2 AndroidJavaClass unityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer"); AndroidJavaObject mCurrentActivity = unityPlayer.GetStatic<AndroidJavaObject> ("currentActivity");
If my iOS lib depend on some other libs or frameworks, what
can I do?
In a unity3d project, export to iOS will generate a Xcode project,
then you can add your dependencies there.
If my Android lib need to declare some permissions to Android
system, what can I do?
Not like iOS, export to Android generate the APK file
directly, but "AndroidManifest.xml" can be put into
"Assets/Plugins/Android/" folder to solve this problem.
For example, I declare READ_PHONE_STATE
,
INTERNET
, and other permissions as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" android:installLocation="preferExternal" android:versionCode="1" android:versionName="1.0"> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.INTERNET"/> <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true"/> <application android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true"> <activity android:name="com.unity3d.player.UnityPlayerProxyActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"> </activity> <activity android:name="com.unity3d.player.UnityPlayerNativeActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"> <meta-data android:name="android.app.lib_name" android:value="unity" /> <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" /> </activity> <activity android:name="com.unity3d.player.VideoPlayer" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"> </activity> </application> </manifest>
How am I supposed to do if my iOS objc interface need a
NSDictionary object as one parameter?
Although in .m
file, Objc and C code can be written
together, but C wrapper is about to provide a pure C interface, Objc
object can't be used directly in method parameters.
Following are what I have done to solve this.
First, in the C wrapper, use a const char *
as input,
then parse the string to recreate the NSDictionary
instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void humble_assistant_doComplicatedThing(const char *attributes) { NSString *attris = [NSString stringWithUTF8String:attributes]; NSArray *attributesArray = [attris componentsSeparatedByString:@"\n" ]; NSMutableDictionary *oAttributes = [[NSMutableDictionary alloc] init]; for (int i=0 ; i < [attributesArray count]; i++) { NSString *keyValuePair = [attributesArray objectAtIndex:i]; NSRange range = [keyValuePair rangeOfString:@"=" ]; if (range.location != NSNotFound ) { NSString *key = [keyValuePair substringToIndex:range.location]; NSString *value = [keyValuePair substringFromIndex:range.location+1 ]; [oAttributes setObject:value forKey:key]; } } [HumbleAssistant doComplicatedThing:oAttributes]; }
In the "HumbleAssistant.cs" script file, we should convert C#
Dictionary
to const char *
.
1 2 3 4 5 6 7 8 9 public void doComplicatedThing (Dictionary<string , string > attributes ) { string attributesString = "" ; foreach (KeyValuePair<string , string > kvp in attributes) { attributesString += kvp.Key + "=" + kvp.Value + "\n" ; } humble_assistant_doComplicatedThing(attributesString); }
How am I supposed to do if my Android jar interface need a
Map object as a parameter?
Java Map object can be generated in C# code by JNI.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void doComplcatedThing (Dictionary<string , string > attributes ) { using (AndroidJavaObject obj_HashMap = new AndroidJavaObject("java.util.HashMap" )) { System.IntPtr method_Put = AndroidJNIHelper.GetMethodID(obj_HashMap.GetRawClass(), "put" , "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" ); object [] args = new object [2 ]; foreach (KeyValuePair<string , string > kvp in attributes) { using (AndroidJavaObject k = new AndroidJavaObject("java.lang.String" , kvp.Key)) { using (AndroidJavaObject v = new AndroidJavaObject("java.lang.String" , kvp.Value)) { args[0 ] = k; args[1 ] = v; AndroidJNI.CallObjectMethod(obj_HashMap.GetRawObject(), method_Put, AndroidJNIHelper.CreateJNIArgArray(args)); } } } mHumbleAssistantClass.CallStatic("doComplicatedThing" , obj_HashMap); } }