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 Oases about seeking refuge from wifi hotspots in the environment. I built this on a Windows PC (my laptop), since it seemed the quickest way for me to bang out the network code in 48 hours.

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

  1. Download and install the Android SDK. Learn it, love it.
  2. Start the Android SDK Manager, and install the SDKs you want. Make sure to also install the Android Support Library listed in "Extras."
  3. Download and install the Android NDK. Love it, learn it.
  4. 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}).
  5. Download and install Eclipse; make sure to get the C/C++ variant.
  6. Start Eclipse, and install the Android Eclipse plugin as per these instructions.
  7. 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.
  8. 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.
  9. Leave everything else as is and continue; accept whatever clipart is shown, create a blank activity, and finish the wizard.
  10. 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).
  11. In Eclipse, go to Properties -> Java Build Path -> Libraries, and add classes.jar to your library path.
  12. 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();
    
      }	
    
    }
  13. 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.
  14. 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).
  15. In Eclipse, in your file hierarchy, make a folder called jni/ (it should be the same level as bin/, libs/, res/, &c.)
  16. Create a .cpp file in there. Call it anything you like; ProjectCppBridge.cpp is an option, as is Sheila.cpp.
  17. 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
    
    
  18. 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.
  19. 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).
  20. 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.
  21. 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.
  22. Open Cygwin and navigate ("cd /cygdrive/c/Eclipse/workspace/...") to your Eclipse project directory (i.e., one above jni/).
  23. In Cygwin, run "${ANDROID_NDK}/ndk-build" (with the appropriate path typed in). This creates libs/armeabi/projectname.so in your Eclipse project directory.
  24. Now, finally, create your Unity project.
  25. In the Unity project's Assets/ folder, create a directory called Plugins/, and within Plugins/ create another directory called Android/.
  26. 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.
  27. Copy ${UNITY}\Editor\Data\PlaybackEngines\androidplayer\AndroidManifest.xml into the same Assets/Plugins/Android/ directory.
  28. 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.
  29. God, this blog.
  30. 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.
  31. 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.

Blinking Totoro animated gif


x