Things that take time
At the core, Rocket Mail is a very simple game. I made the early prototype (Doomsray) in less than a day. There are no complicated physics, no advanced graphics, and very little in the way of performance requirements. Of course there’s a long way to go from a prototype to a finished game, but even after the game looked, felt and sounded polished, I still found that I needed several weeks to get it ready for beta testing.
Why, you ask? It turns out, since the “core” of the game is so simple, it’s the “edge” of it that takes up the majority of the time. Interfacing with a variety of APIs, dealing with different hardware, and figuring out all the things that should be easy but somehow aren’t.
Analytics
Actually, adding the Google Analytics library and wrapping it to work with LibGDX was pretty straightforward. Mostly now it’s a matter of understanding how the Analytics dashboard actually works. For example, I’ve added some custom dimensions to my metrics to track player levels and scores, but I’ve no idea how to make them appear in on the dashboard.
Ads
Adding interstitial ads wasn’t so hard, although I was plagued by some concurrency issues initially (why don’t people document on which thread your callbacks run?). The main difficulty is that you have to preload an ad before showing it. So either I skip showing the ad if it isn’t loaded yet, or I block the user from continuing until the ad is loaded. Of course I chose the former, because I don’t hate my players that much ;)
In-app purchases
For those who don’t like ads, there’s a one-time IAP to disable them. Integrating in-app purchases into an Android app involves connecting to a billing service on the device, querying for available products, and sending buy intents. All the communication happens in JSON format, which is a pleasure to work with in Java – not. Most lines of code here are for handling all the edge cases and error cases.
But the hardest part is actually security: when someone purchases something, ideally you have to store the order ID on your own server so it can’t be easily hacked or copied. (I don’t even fully understand how it works myself.) Since in this case the purchase is not critical, and there are easier ways to rip me off (ad blockers), I decided not to bother.
Compass and sensors
Virtually every Android device has a magnetometer, i.e. a compass. The same
goes for the accelerometer. You would think there’s just a function
getCompassBearing
that you call to get a bearing between 0 and 360 degrees,
but alas, it’s not that simple.
I had things working beautifully on two of my test
devices using the
TYPE_ROTATION_VECTOR
sensor
which gives a rotation in a “world” coordinate system, when it occurred to me
to test on my tablet as well. There, it seemed to have some kind of gimbal lock
issue: half of the compass circle was inaccessible, the other half inaccurate.
I read up on documentation and found that you need to manually compensate for a
device’s “natural orientation” by rotating your coordinate system, but that
wasn’t it.
But there are plenty of other badly documented sensor types, so I decided to
try a combination of TYPE_MAGNETIC_FIELD
and TYPE_GRAVITY
, passing the
results to
[getRotationMatrix
](http://developer.android.com/reference/android/hardware/SensorManager.html#getRotationMatrix(float[], float[], float[], float[]))
to make sense out of them. This worked on all three devices, but it was very
jittery. So I added some smoothing (a simple IIR low-pass filter) and life was
OK. Then I sent the game to some friends for beta testing, and two of them
reported that the compass either didn’t work properly or didn’t work at all.
Location
The Android location APIs are relatively nice and straightforward. However, they are also limited. My app just asks for the “coarse location” permission: I neither need nor want to know down to the metre where you are. However, this location is determined using cell towers and wifi networks, and not all people have that feature enabled (since it comes with also uploading such data to Google). However, if it’s disabled, there’s no way for me to fall back to GPS, because I don’t have permission for that!
Enter the new “fused” location API of Google Play Services. It solves all this, but at the price of having to deal with Google Play Services. Much like with in-app purchases, you have to establish a network-like connection which can drop at any time, handle all sorts of errors and edge cases, and have no way to test whether you did it all correctly.
Besides, including the Play Services location client library apparently pulls in the client library for Maps as well, even though I’m not using it. Not really a problem, except that the Maps library sneakily adds an extra permission to your manifest, leaving people wondering why my game requests access to their “Photos/Media/Files”. Overruling that permission request is easy but scary…
ProGuard mappings
Using ProGuard to minify your code and remove unneeded code from your APK (see
also: Play Services Maps library) is easy, since it comes preconfigured with a
LibGDX project. However, since ProGuard obfuscates all your class and function
names, any crash report you receive via the Play Store console will be
unreadable without the generated mapping.txt
file. So it’s a no-brainer that
you want to hold onto this file very carefully, version it, and check it into
source control.
Sadly the standard build system does none of this for you. It just overwrites
the mapping.txt
upon every release build. So I had to figure out how to
instruct Gradle to make a copy. In a Makefile, I would just add a line: cp path/to/mapping.txt mappings/mapping-$(VERSION).txt
. Eventually I figured out
the equivalent Gradle incantation, but actually convincing Gradle to run this
every build was yet another story. But I think it works now.
The bright side
Most of these things I have to do only once, and then I can reuse the solutions and my experience between projects. I’m definitely learning. So actually all this is exactly according to plan!
There don’t seem to be libraries that abstract away the hassle, probably because they wouldn’t be able to without also cutting out a lot of the power of the APIs that they wrap. But if I get tired of copying and pasting code between projects, maybe I’ll see if I can package it up nicely. On the other hand: packaging and distributing a library is yet another of these ratholes…