Unity and the Android API
~ written 1 December 2012 elsewhere ~
⁂
WARNING: This is a rather technical post.
Several Ludum Dares ago, I made a game called
Surprisingly, it turns out that it's not all that easy to run around town escaping wifi hotspots while holding a laptop with an old and dying battery.
Cue the rise of mobile, wifi-enabled devices, however, and things get interesting. I'm an Android man myself, and since I'm slowly coming to terms with Unity, I started some tests in getting the latter to call functions from the Android API.
It was not straightforward.
The tutorial on Unity's site gave a number of techniques for calling Android's basic Java functionality from within Unity's C# context, through JNI and C/C++. Unfortunately, even after reading it through multiple times, I still felt that, while it gave a lot of background information, it wasn't as prescriptive as I was hoping.
Long story short: after patching that tutorial together with several dozen desperate and increasingly-confused online searches, I managed to gain access, in Unity, to the information polled from my Android device's wifi manager.
I present those steps here for truth, justice, and the American whey. Hope it's useful, and do call me out on the inevitable errors.
Calling the Android API from a Unity application
- Download and install the Android SDK. Learn it, love it.
- Start the Android SDK Manager, and install the SDKs you want. Make sure to also install the Android Support Library listed in "Extras."
- Download and install the Android NDK. Love it, learn it.
- In my own setup, I've got the above installed at C:\Android\android-sdk-(version) and C:\Android\android-ndk. Short paths simplify things. I'll be referring to them as ${ANDROID_SDK} and ${ANDROID_NDK}, respectively (Oh, and your Unity installation is probably something like "C:\Program Files\Unity", to which I will refer as ${UNITY}).
- Download and install Eclipse; make sure to get the C/C++ variant.
- Start Eclipse, and install the Android Eclipse plugin as per these instructions.
- Create your Android library; these are the Java functions running on the Android API side of things, returning device status &c. to..."the ether" (essentially, back to Unity). In my case, these are the functions accessing the device's wifi state. In Eclipse, go to New -> Project... -> Android Application Project.
- Give the app a name (what your device will visibly call it -- not really important to us), a project name (for use in Eclipse's workspace), and an Android package name (see those notes on nomenclature). MAKE SURE TO CHECK "Mark this project as a library.". I forgot this once, and couldn't figure out why Eclipse wasn't compiling a .jar library for me.
- Leave everything else as is and continue; accept whatever clipart is shown, create a blank activity, and finish the wizard.
- Navigate your computer to ${UNITY}\Editor\Data\PlaybackEngines\androidplayer\bin. You'll find classes.jar -- a Unity-to-Java helper package from the Unity folks themselves -- which you should copy to your Eclipse project's libs/ directory (that directory was created automatically with your Android project in Eclipse, and should be visible in the hierarchy on the left).
- In Eclipse, go to Properties -> Java Build Path -> Libraries, and add classes.jar to your library path.
- Eclipse generated some code for your Android activity. How very kind of it. It's called something like ProjectActivity.java. We'll have to make some changes though; most importantly, we no longer extend the Android "Activity" class -- but we do pass in an Android context. I have reasons. In your auto-generated MainActivity.java source code (you may have, and still can, rename it as you like), you can have the following (with these particular members specific only to my endeavour):
public class MainActivity { // does not "extend" anything private Context m_context; private WifiManager m_wifiMgr; // ---------------------------------------------------------- CONSTRUCTORS public MainActivity(Context c) { m_context = c; m_wifiMgr = (WifiManager)m_context.getSystemService(Context.WIFI_SERVICE); return; } // --------------------------------------------------------------- METHODS public boolean isWifiEnabled() { return m_wifiMgr.isWifiEnabled(); } }
- As you can see, I need the general Android context, if I am going to have access to the WifiManager class. The same is probably true for a lot of Android's *Manager classes.
- In Eclipse, make sure Project -> Build Automatically is checked. If so, you should have a ProjectName.jar file in your bin/ folder (again, this folder is auto-generated).
- In Eclipse, in your file hierarchy, make a folder called jni/ (it should be the same level as bin/, libs/, res/, &c.)
- Create a .cpp file in there. Call it anything you like; ProjectCppBridge.cpp is an option, as is Sheila.cpp.
- You'll also need an Android.mk file in the same directory, and maybe (but probably not) an Application.mk file. I'll admit my Android-Fu is a bit weak, so start your learning process here. Why not. My own Android.mk looks as follows:
include $(CLEAR_VARS) # override strip command to strip all symbols from output library; no need to ship with those.. # cmd-strip = $(TOOLCHAIN_PREFIX)strip $1 LOCAL_ARM_MODE := arm LOCAL_PATH := $(NDK_PROJECT_PATH) LOCAL_MODULE := libjavabridge LOCAL_CFLAGS := -Werror LOCAL_SRC_FILES := jni/YOUR_FILENAME_HERE.cpp LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)
And Application.mk:APP_OPTIM := release APP_ABI := armeabi APP_PLATFORM := android-8 APP_BUILD_SCRIPT := jni/Android.mk
- Now, the actual .cpp file, where you write all your JNI code, is going to be a bit complicated, so I'm going to just include it wholesale here.
- A few things to note. The JNI_OnLoad() function is called automatically; just use it to initialise things. Here, I get the running instance of Unity, find Unity's hook to the running application/activity, and use that as a context to pass to the MainActivity class (which, as shown above, requires a Context argument in its constructor).
- I also define a function, _cppbridge_isWifiEnabled(). This function is called from Unity, and then passes the call along to the Android API, and then returns a value back to Unity. Important to keep track of where all these calls are going, and to make sure all the names are referring to the right things.
- Download and install Cygwin.
You don't need any specific packages, just the command-line interface.You do need 'make,' so make sure that you search for it in the list of packages and install it. - Open Cygwin and navigate ("cd /cygdrive/c/Eclipse/workspace/...") to your Eclipse project directory (i.e., one above jni/).
- In Cygwin, run "${ANDROID_NDK}/ndk-build" (with the appropriate path typed in). This creates libs/armeabi/projectname.so in your Eclipse project directory.
- Now, finally, create your Unity project.
- In the Unity project's Assets/ folder, create a directory called Plugins/, and within Plugins/ create another directory called Android/.
- Copy ProjectName.jar (from your Eclipse project's bin/ directory) and projectname.so (from the libs/armeabi/ directory) into your new Assets/Plugins/Android/ directory.
- Copy ${UNITY}\Editor\Data\PlaybackEngines\androidplayer\AndroidManifest.xml into the same Assets/Plugins/Android/ directory.
- In the Unity script where you want to access the Android API, you must declare the following private references for every function you defined in your .cpp file:
[DllImport("your_lib_name")] private static extern bool _cppbridge_isWifiEnabled();
Note that "your_lib_name" is the name of the LOCAL_MODULE variable in your Android.mk file, without the "lib" prefix; or, it's the name of the .so file you generated with ndk-build, but without the "lib" prefix or ".so" suffix. Also, the extern reference refers to the exact name of the C/C++ function we defined in our .cpp file. - God, this blog.
- In Unity, go to File -> Build Settings..., and build your app for Android. You will end up with a .apk file, which you can then transfer to your device for installation and testing.
- A great way to debug your app is to fire up the DDMS. Plug your Android device into your PC via USB; open up a terminal (in Windows: Win-R, then type "cmd"); change to your ${ANDROID_SDK}/tools directory, and run ddms.bat; you should see a running list of all messages coming from your device. You can filter these in a number of ways: for instance, I prefaced all my error messages with "bagel," so I could just search for that string. Look at the __android_log_print() function in the .cpp file provided above to learn more about printing debug messages.
Right. I think that's just about complete, but feel free to give me a shout if something is missing/unclear/incorrect.
Now excuse me whilst I go rest my eyesies.
x