Monday, December 31, 2012

2012: the year of the crane.

The Chinese may call it the year of the dragon. However, I hereby proclaim 2012 to be the year of the crane. And I should say: specifically the year of the Little Crane. In the year 2012 alone, The Little Crane That Could was downloaded more than 5 million times. Thank you gamers!

2012 2011
iOS 3454K 1550K
Android 1656K -
Mac 81K -

Christmas day was by far the best day for Little Crane on iOS, where it hit 23K free downloads and 1.5K purchases of the premium version that day alone.

I can also report that this year 42K people have bought the Little Crane World Editor. This makes 2012 an amazing year, exceeding the goals I set a year ago. And for the sake of completeness: Hover Biker saw 315K downloads, and A Blocky Kind of Love saw 97K downloads.

Friday, December 21, 2012

ANR: Application Not Responding (keyDispatchingTimedOut)

This is a heads up for those Android developers that use the NDK (Native Development Kit) to write Android apps. If you are using the NDK, you are almost certainly using the glue code that Google provides in the android_native_app_glue.c file.

Chances are that in your Google Play Developer Console, you see reports of Application Not Responding (ANR keyDispatchingTimedOut.) For my app, I have 756 of these reports on an installed base of 1.5M downloads. Consulting stackoverflow or other developer groups, will invariably yield the advice not to block the main thread. However, it is easy to cause this ANR without blocking the main thread, if you are using the android_native_app_glue.c file in your project.

If two events are generated at exactly the same time, using different sources or devices, the app will freeze. You can easily produce this with a PS3 controller hooked up to your Android device and depress both analogue sticks at exactly the same time, or release them at exactly the same time. If you do this while running an NDK based app, the app will freeze and issue an ANR.

It took me a day of debugging to find a work around for this, but I am happy to report that the following change to the glue code will stop the issue from happening. What you need to do is get events from the queue repeatedly in a loop, instead of just handling a single event in process_input() function.

static void process_input(struct android_app* app, struct android_poll_source* source)
{
    AInputEvent* event = NULL;
    while ( AInputQueue_hasEvents( app->inputQueue ) )
    {
        if ( AInputQueue_getEvent( app->inputQueue, &event ) >= 0 )
        {
            int32_t handled = 0;
            uint32_t devid = AInputEvent_getDeviceId( event );
            uint32_t src   = AInputEvent_getSource( event );
            //LOGV("New input event: type=%d devid=%x src=%x\n", AInputEvent_getType(event), devid, src);
            int32_t predispatched = (AInputQueue_preDispatchEvent(app->inputQueue, event));
            if (app->onInputEvent != NULL && !predispatched) handled = app->onInputEvent(app, event);
            if (!predispatched) AInputQueue_finishEvent(app->inputQueue, event, handled);
        }
    }
}

I have reported the issue to Google.

Sunday, November 25, 2012

Unsafe Permissions

Where iOS gamers tend to pay for their gaming with a credit card, it seems that Android gamers prefer to pay with their privacy. Google has implemented warnings on unsafe permissions for the user to review before launching an app for the first time. Most users will click away the warnings without reading.

I am proud of the fact that my game does not require a single unsafe permission, as can be witnessed on its google play page:

Out of curiosity, I checked the permissions of the Top 100 free Android games on Google Play. It turns out that apart from my The Little Crane That Could, there was only one other game (Duck Hunt Mario) that did not require unsafe permissions. Those are pretty astonishing results, I would say. And it makes me even more proud that I managed to pull this off: a successful game generating a handsome revenue without compromises. I wish Google Play would open up a new category for safe apps, to promote safety in the ecosystem.

Sunday, November 18, 2012

A conversation with a user

I had an interesting conversation with a user of my game recently. Some background info: my game is a free game, and there is a 'BUY' button that lets you unlock extra levels in the game. If you press it, it will take you to the Google Play store, where it lists the price in your local currency, and describes the purchase.

For every 10000 downloads, I get one email from a user asking about the price of the in-app-purchase. I totally understand why they email me: they fear that pressing 'BUY' will immediately charge them without knowing the price in advance. I can relate to that, as mobile games have done sneakier things in the past. So I always respond to the email, and tell them the price is $2.89 or the equivalent in their local currency.

Now, customer is of course king, and should be treated in the best possible way. Somehow, my patience ran out on Randy, and this conversation ensued. Was I professional? Not as much as I could be. Was it funny? Yes.

On Friday, November 16, 2012, Randy [REDACTED] wrote:
How much for the upgrade? The site don't say anything about that....free would be nice...lol Thanks for your time
On Nov 16, 2012 3:40 PM, "Bram Stolk" wrote:
$2.89
On Fri, Nov 16, 2012 at 11:32 PM, Randy [REDACTED] wrote:
How many levels?.. consistently updated??...
On Nov 17, 2012 1:03 AM, "Bram Stolk" wrote:
18 more levels
On Saturday, November 17, 2012, Randy [REDACTED] wrote:
Love the game.. was just wondering how often this game is updated/more levels added.. before I purchase Thanks
On Nov 17, 2012 12:19 PM, "Bram Stolk" wrote:
I do not expect new levels soon, just bug fixes for now.
On Sunday, November 18, 2012, Randy [REDACTED] wrote:
Cool game and time waster... but I can't see spendig that money (even tho minimal) when one don't update with new levels or added game play.. keep me posted as to when lvls or game play added and I'll be more than happy to purchase...thanks
On Nov 17, 2012 11:46 PM, "Bram Stolk" wrote:
Talk about time waster.... How much time do you think I have trying to convince you to spend that measly 2.89? Almost a 1000 people a day make the purchase, so I am not going to look up your email and send you a message just to get your money. Some people will never spend money, that is fine.
On Nov 18, 2012 12:22 AM, "Randy [REDACTED]" wrote:
I have plenty of money to spend.. and even more time .blogging and pasting what "proActive" man you are and your response to a simple question.. hell , I guess if you dont have time for your People or your program; choose a different career...don't hate.. a I Stated before, I have nothing better to do than to make you look foolish and post the negativity; which you exude upon me.. thanks..for keeping me from buying your program.. and many others, from making the same mistake..
On Nov 18, 2012 12:38 AM, "Randy [REDACTED]" wrote:
I am In Vancouver.. let me know where you at? Talk your smartass..

Friday, October 19, 2012

Useful software

This post is mainly a reminder for myself, and contains a list of essential software I use frequently. If I ever need to install a virgin Macintosh, I can refer to this.

  • graphviz so that Doxygen can use 'dot'.
  • doxygen so that I can make sense of large convoluted code bases.
  • inkscape my tool of choice for designing 2D artwork.
  • GIMP my tool of choice for WYSIWYG image manipulation.
  • NetPBM my tool of choice for command line image manipulation.
  • ImageMagick for when NetPBM doesn't cut it.
  • FFMpeg for video manipulation on the command line.
  • Grandperspective for finding those large files that clutter up your disk drive.
  • Kerkythea for state of the art rendering using Photon Maps.
  • Wings3D My overall favorite software. When I'm modelling in Wings3D I am happy.

Monday, July 23, 2012

Definitive Guide to Using Hyperdeck Shuttle II with Mac OSX

It took quite some experimentation and I was about to give up on this, but it looks I cracked it. I can now record compressed video with the BlackMagic Hyperdeck Shuttle II and use the video file with Mac OSX. I was already able to use the uncompressed video files from the device, but those are just too freaking huge to comfortably work with on a macbook air.

STEP1:
Make sure you are using the latest drivers and firmware as posted on the support page of Blackmagic design. I used the 3.0.2 version.

STEP2:
Set the device to record compressed files into a quicktime container.

STEP3:
Don't try to record output from your iPhone... I tried, and it did not work. My iPad2 and iPad3 HDMI signals were accepted just fine. You will need an adapter from Apple to hookup an iPad to a Hyperdeck Shuttle II.

STEP4:
BlackMagic wants you to install the DNxHD codecs from Avid's website. Frankly: don't bother. You can try, but they did not work for me. I would either get a black screen video with some audio (iPhone recording) or worse, I would have a crashing quicktime player (iPad recording). The crash I am seeing is this one:

Thread 3 Crashed:: Dispatch queue: com.apple.coremedia.playbackboss
0   com.apple.audio.toolbox.AudioToolbox 0x00007fff841595a7 _AT_AudioUnitUninitialize + 55
1   com.apple.audio.toolbox.AudioToolbox 0x00007fff841ed819 MixerChannel::Uninitialize() + 27
2   com.apple.audio.toolbox.AudioToolbox 0x00007fff841ede5e SubmixGraph::ConnectToDestination(bool, CAStreamBasicDescription const&, CAAudioChannelLayout*) + 184
3   com.apple.audio.toolbox.AudioToolbox 0x00007fff841e7120 MasterMixer::ConnectSubgraph(SubmixGraph*) + 686
4   com.apple.audio.toolbox.AudioToolbox 0x00007fff841e7415 SubmixGraph::ConnectInputChannel(bool, MixerChannel*, bool) + 657
5   com.apple.audio.toolbox.AudioToolbox 0x00007fff841e7826 AQMEDevice::AddRunningClient(AQIONodeClient&, bool) + 262
6   com.apple.audio.toolbox.AudioToolbox 0x00007fff841cbaf5 AudioQueueObject::StartRunning(AQIONode*) + 51
7   com.apple.audio.toolbox.AudioToolbox 0x00007fff841c47e3 AudioQueueObject::_Start(XAudioTimeStamp const&) + 637
8   com.apple.audio.toolbox.AudioToolbox 0x00007fff841c494a AudioQueueObject::Start(XAudioTimeStamp const&) + 18
9   com.apple.audio.toolbox.AudioToolbox 0x00007fff841dd8cb AQServer_Start + 64
10  com.apple.audio.toolbox.AudioToolbox 0x00007fff841e0e88 AudioQueueStart + 183
11  com.apple.MediaToolbox         0x00007fff877497d8 FigAudioQueueStart + 551
12  com.apple.MediaToolbox         0x00007fff877a33b3 0x7fff87728000 + 504755
13  com.apple.MediaToolbox         0x00007fff8779ae4e 0x7fff87728000 + 470606
14  com.apple.MediaToolbox         0x00007fff8779e2e0 0x7fff87728000 + 484064
15  com.apple.MediaToolbox         0x00007fff8779e508 0x7fff87728000 + 484616
16  com.apple.MediaToolbox         0x00007fff8779e75a 0x7fff87728000 + 485210
17  libdispatch.dylib              0x00007fff8dd77a86 _dispatch_call_block_and_release + 18
18  libdispatch.dylib              0x00007fff8dd792d6 _dispatch_queue_drain + 264
19  libdispatch.dylib              0x00007fff8dd79132 _dispatch_queue_invoke + 54
20  libdispatch.dylib              0x00007fff8dd7892c _dispatch_worker_thread2 + 198
21  libsystem_c.dylib              0x00007fff8825b3da _pthread_wqthread + 316
22  libsystem_c.dylib              0x00007fff8825cb85 start_wqthread + 13

STEP5:
FFMPEG to the rescue! Instead of trying to get DNxHD and Quicktime to play nice together, Open Source will save us, hooray! Note that some companies charge 500 euros for video conversion software, but ffmpeg will do the trick. You need to make sure you have a very recent version, as DNxHD support was only recently added. Get the ffmpeg binary from the ffmpegmac.net site.

STEP6:
Convert the DNxHD file to something more useful using the following command line:

$ ffmpeg -i Capture0002.mov -an output.mp4
Note that this invokation strips the audio, if you want to keep it, you will have to consult the ffmpeg manual on how to do that. The ffmpeg tool recognizes the iPad2 stream with the following properties:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'Capture0002.mov':
  Metadata:
    creation_time   : 2012-07-23 22:12:33
  Duration: 00:00:13.38, start: 0.000000, bitrate: 238657 kb/s
    Stream #0:0(eng): Video: dnxhd (AVdn / 0x6E645641), yuv422p10le, 1280x720, 220200 kb/s, 60 fps, 60 tbr, 6k tbn, 6k tbc
    Metadata:
      creation_time   : 2012-07-23 22:12:33
      handler_name    : Apple Alias Data Handler
    Stream #0:1(eng): Audio: pcm_s24le (lpcm / 0x6D63706C), 48000 Hz, 16 channels, s32, 18432 kb/s
    Metadata:
      creation_time   : 2012-07-23 22:12:33
      handler_name    : Apple Alias Data Handler

Friday, July 13, 2012

Beware of automatically downscaled retina images


The iOS development environment facilitates image content loading for retina and non-retina devices with a clever naming scheme. If an iOS app is running on a retina device, and is instructed to load an image named foo.png, it will actually attempt to load the foo@2x.png file if it exists. By providing both foo.png and foo@2x.png images, both classes of devices are supported.

This convenience offered by UIKit goes one step further: if you do not provide the regular resolution version foo.png but only the retina version foo@2x.png then non-retina devices will load the high res version and automatically downscale a factor 2 so that it can be used. If you are tempted by just providing retina versions of your images, and skip the regular versions, like I was, you are selling your app short. It turns out that UIKit does a horrible job at downscaling, and the image will look considerably worse than a version that you pre-scaled yourself.

To illustrate the effect, see below how the original is downscaled by UIKit, and how it is downsampled by my authoring tool inkscape. A big difference, I would say.

The bottom line: if you care for your iPad2 and iPhone3GS Users, don't skimp on images. Provide both the foo.png and the foo@2x.png files.


The retina version of the graphic, viewed at 1:1 zoom.


The image automatically downscaled by UIKit from the retina version, viewed at 2:1 zoom.


The image, exported at non-retina resolution by inkscape, viewed at 2:1 zoom.

Monday, June 25, 2012

Bram's upcoming indie game

I've been working on my next indie game. It uses a voxel art style, and like the little crane that could, it features a top notch physics simulation. Here are some screenshots of the game. Note: the new project has not been named yet.


On the farm.


Visiting the queen.


At the picnic.

Monday, May 21, 2012

De Nederlandsche taal, het redden niet waard?

Zittend in mijn badkuip overpeinsde ik de betekenis van het woordje weer. Dit woordje heeft een verscheidenheid aan betekenissen, geillustreerd met de zin:

Wegens het stormachtige weer moest de brandweer vandaag weer uitrukken.

Dan doen de oosterburen het toch een stuk preciezer in de Duitse taal. Met mijn VWO Duits (en Google) maak ik er het volgende van:

Wegen des stürmischen Wetters musste die Feuerwehr heute wieder ausrücken.

Waarom is de Nederlandse taal zo slordig? Mijn vermoeden is dat deze termen niet altijd op één hoop werden gegooid, getuige het oud Nederlandsche woord onweder en de term wederom. Door de eeuwen heen zijn de termen gedegenereerd tot één enkel woord. Door al die jaren heen hebben de Nederlanders het niet zou nauw genomen met hun taal, dus waarom zouden we dat in de 21e eeuw wel moeten doen? Van mij mogen dus de colleges aan de Nederlandse universiteiten best in het Engels, Duits of zelfs Kantonees (toekomstgericht!) worden gegeven. Het redden van de taal hadden we 500 jaar eerder maar moeten doen, nu is het denk ik de redding niet waard.

Thursday, May 17, 2012

Building a brand

I had this printed at Make Vancouver, so I can build my brand. I will be wearing this during Apple's World Wide Developer Conference, so I can get some exposure for the fine game that is 'the little crane that could'. It is a full colour digital print. I was considering a silk screen print, but that requires reducing the number of colours. I am still toying with the idea of getting an embroidered jacket to go with it.

Tuesday, March 27, 2012

Making good progress on the World Editor

I am making good progress on the world editor for the little crane that could. I am creating a stand alone companion app for the game with which you can build your own levels. The levels are stored in iCloud, so that you can play them on any iOS device you own. You can also send your designs to friends via email. Here is a screen shot of the game menu that shows a user created level in the list.

The world editor and the game are both supporting the retina display of the new iPad. Man, oh man, that 2048x1536 resolution sure looks marvellous, and is ideally suited for level editing.

Tuesday, February 21, 2012

Skycrane

My game the little crane that could is still going strong, so I keep releasing updates for it. Lately, it has been very popular in Japan, not sure why. A lot of Japanese downloads, but the conversion rate in Japanese market is pretty low. Anyway, the next update will feature something special: a Skycrane.

Just like I did for the crane on wheels, I am taking the high road: a proper full blown physics simulation. This means that the forces of lift actually work on the rotor blades, where force leads to acceleration leads to velocity leads to movement. Also, the force that spins the main rotor causes the body of the helicopter to counter rotate, so I apply an anti-torque force with the tail rotor. Note that the tail rotor is not yet visualized in the image. Also missing are wheel struts and some sort of winch with hook that will enable the player to grab objects.

After implementing this elaborate and accurate simulation, I found out that controlling the helicopter was neigh impossible. It was simply too hard to steer due to oscillations and spiralling out of control. That is why I added two PID controllers that sit between the cyclic controller (joystick) and the control surfaces. They translate the player's intent into steering signals, 180 times per second. With PID control you can fix overshoots, oscillations and steady state errors by carefully tuning the proportional, integral and derivative constants. Now the Skycrane flies like a dream: it has nice, yet realistic handling characteristics.

Monday, January 2, 2012

Walk in the park

Because mommy had a day off today, we were able to make a little walk in the park with Annelies this morning. Annelies enjoyed it very much, and loved playing with doggies and getting attention from the women. Nobody can resist her when she puts on her best smile and start giggling. Annelies is excited about her grandparents visit at the end of this month. We all look forward to seeing them again.

Sunday, January 1, 2012

A 1000 soldiers

My explorations of Android Development are starting to find shape. Here is a screenshot from my tablet device showing a lot of the little buggers on the march. Now I have to find a good gameplay mechanic to make directing a small army fun for the player. In my younger years (early 90s) I used to play xbattle on the University's Sun workstations. That was a lot of fun, so it would be an excellent starting point. And I think that a touch interface is a much better fit for an xbattle game than a mouse interface is, so that should give me an edge. The performance is pretty good on the Acer Iconia, despite the modest Tegra2 specifications that don't even include Neon SIMD support.