A while ago I posted the following tweet to my twitter account:
#windupknight on Android includes 7575 lines of Java. iOS version has 7005 lines of Objective-C. Both have 12000 lines of C#.
This generated a lot of responses, mostly asking about what the Java and Objective-C code is for. Today I’ll explain how I structured the OS-native code for Wind-up Knight, and what I learned in the process.
We absolutely love Unity to death, but one thing it’s terrible at is 2D UI. Specifically, 2D UI that is based on atlas textures and that can automatically resize itself to different screen sizes and aspect ratios is a gaping hole in Unity’s otherwise formidable mobile game engine feature set. All the 2D stuff we do in Wind-up Knight is code I wrote from scratch (or borrowed from the net); there are basically no tools for 2D-on-mobile and so you have to make your own. Font rendering is particularly problematic. Unity takes the old-school game engine approach of rendering all fonts with font textures, so if you want to support arbitrary UTF8 text, you’re in for a hard time (or some ugly fonts).
Early in the project I decided to rely on native OS APIs to do 2D UI rendering in order to get around this weakness in Unity. It’s pretty easy to knock up some simple views and render them on top of Unity in both iOS and Android; in fact, both systems use view hierarchies that are so similar that I was able to copy and paste some code between platforms (and languages) with only minor syntactical changes. My plan was to draw anything requiring text with the native OS API, and to draw everything else with Unity. This would also make certain complex-but-common UI controls (like scrolling lists) easier to implement, as both operating systems provide them right out of the box. We knew that we wanted to localize the game into Japanese (and maybe Korean and Chinese as well), so having a robust way to render UTF8 text was key.
To get native code support to work you need to follow a few steps. On Android, I created a project outside of the Unity folder and then wrote a shell script that would package the generated .class files up as a jar and move it, along with the other project resources, into Unity’s Plugins/Android folder. The workflow was to edit Java outside of Unity, compile it, build the Jar, and then rebuild the binary from Unity.
On iOS, the process is reversed: Unity outputs an Xcode project, so you put your native code files in the Plugins folder (or elsewhere; there’s some trial and error, and maybe a few shell scripts involved) and execute a Unity build first. Then you can work on the generated project in Xcode to add Objective-C code, and finally compile the final binary from Xcode like any other project.
In theory, using native code for UI drawing this is a great idea. Unity can bind itself to native code (via the [DllImport("__Internal")] declaration on iOS and via JNI on Android) and call down into it pretty easily. On the native side you have tools and debuggers made for designing interfaces, and you can leverage all that stuff this way. Plus both systems have a solution for different size screens and things like that.
In practice, it turned out to be a poor decision. First, we ended up having quite a lot of non-trivial UI (even though Wind-up Knight is a pretty simple game, interface-wise), which required me to write those 7000 lines of native code twice in two different languages (and if we ever want to port the game elsewhere, I’ll have to write it again).
Second, the tools provided by Apple and Google are not artist-friendly tools. They both require intimate knowledge of how the UI subsystems that run the OS work under the hood. Therefore, the only person who can author UI with this setup is the engineer (that’s moi). Unfortunately, engineers are rarely artists; though I worked from mockups generated by people who understand art and did my best to replicate them, the UI in Wind-up Knight is missing much of the polish that we put into the rest of the game. Our artists, Mike and Casey, didn’t have access to the UI authoring environment and couldn’t get in there to work their magic.
Third, as the game got larger our Unity rebuilds got slower and slower. This is especially problematic for Android because we need to do a Unity rebuild to test every change. With 52 levels it takes Unity a few minutes to build and package our game as an apk, so even a one-line Java change requires a lot of waiting to verify. It would be nice if Unity had an incremental build system for this kind of stuff.
Fourth, and perhaps most importantly, maintaining a few thousand lines of code in two different languages is costly and error-prone. Porting Wind-up Knight from Android to iOS was trivial at first, but as our UI grew more complex the amount of work it required to mirror in Objective-C became significant. In the end, between the two platforms, I wrote more UI code than game code (!!), and though that code was not complex, it was quite time consuming. And the result was a serviceable but far from fantastic UI.
One thing about this plan that did go well was our plans for localization. Thanks to OS native text rendering, implementing Japanese text was pretty trivial. There are still some issues (word wrapping rules for Japanese suck on both platforms), but for the most part it worked exactly the way I had planned it. Japan is our #2 market worldwide, and at this moment it’s #1 for iOS; I suspect that success has a lot to do with our Japanese localization.
Future Robot Invader games will definitely leverage platform APIs, but probably not for UI. There are other things we do in Java and Objective-C, such as in-app billing integration, GameCenter calls, and Android Notifications. Those kinds of things are quick and easy to do in the OS-native programming environment, and really don’t belong in Unity code anyway. But for the next project, my plan is to spend some significant time building a proper UI system within Unity (or perhaps buying one of the available 3rd party libraries).