My initial idea was to start at a beginner level and explain from the very beginning how to make games for iOS and Android.  However, after starting to write, I realised that I began duplicating a lot of information that is already out there.

So instead, I will be linking other resources with the basic background knowledge for the concept we are talking about - a sort of background reading for beginners - and then explain on a more intermediate level some of the tricks that can help you use these concepts most effectively.  I hope that way this tutorial will be more useful for both beginners and experts alike.

What you'll find here are a series of sections covering various topics that I'm often asked about, that you can read independently of the others.

Twitter: @Base2solutions
IRC: / #Corona


  1. Walkabout
  2. Corona
  3. Dynamic Image Resolution?
  4. Error Checking
  5. Functions
  6. Transitions
  7. Android Manifest Customisation
  8. Groups - removing images effectively [Coming Soon]
  9. Garbage Collection [Coming Soon]
  10. Preloading and performance tricks [Coming Soon]
  11. Performance Tricks 2 [Coming Soon]
  12. Suspend/Resume handling [Coming Soon]
  13. Optimising file sizes and music [Coming Soon]

1 - Walkabout

I recently created my first iOS and Android game called 'Walkabout'.  The whole game was made in my spare time in about 3 weeks.  I hope to pass on the tricks that I used to deal with the various problems that arose during the development of Walkabout.

In these tutorials I will explain concepts in relation to Walkabout and so I suggest you download the free version for either iOS or android.  The Lite version is totally free, uses no special permissions and has no Ads.

App Store

Android Market:

2 - Corona

Ordinarily, if you wanted to develop for iOS you would need to learn a language called Objective-C.  This isn't a very easy language for beginners to pick up. Similarly for Android development you would need to learn Java.

So, if you decided to make a game, and wanted to release it for both platforms you would need to learn both languages (!).

Fortunately there is a much easier option.  These days there are many SDK's on the market called 'App or Game engines'.  These have a bunch of functions that help you do the common tasks you need for applications and games.  In addition they use a simpler language that's a lot easier to pick up.

My tutorial will use one such game engine called 'Corona'.  It is a free download, and you can use it as long as you like.  The code you write can be simulated on a 'virtual' phone on the screen.  Corona isn't totally free though.  If you decide you want to release your game on the App store or Android market you will need to buy a license.

I encourage you to try your hand at writing games with the free version, and if you decide this is for you, and manage to make a game you want to release, you can buy the license at that point.  Corona is available for both Windows and Mac OS X.

You can download Corona here:

If you are a total beginner to Corona, there are some good getting started guides here that you should read first:

3 - Dynamic Image Resolution?


One of the first problems you'll encounter with developing on mobile devices is the varying screen sizes (Resolution) and aspect ratios (Ratio between the height and width of a screen).  Your game may look amazing on an iPhone 3G, but end up looking stretched, or even worse leave blank areas on an iPad or Android Tablet.

Corona offers a function called 'Dynamic Image Resolution'.  This works together with Content Scaling to handle different devices screen sizes.  You can read more about it at these two very good blog posts from Ansca.

And the documentation can be found here:


So, we know all about the various content scaling modes Corona offers, namely:

These work well in a lot of situations, but what if you don't want letterbox borders on your backgrounds AND don't want content off screen AND don't want to stretch your entire game? 

This is exactly the problem I had in Walkabout.  If you try the game on different devices you'll quickly see that the in game graphics don't end up skewed or stretched, yet the title images fill the entire screen.  Here are some screen shots of Walkabout on iPhone, iPad and Nexus.

The solution you'll find in the Ansca blog entry is to oversize your images so that they bleed into the letterbox regions.  The 'magic' image size makes an assumption about the aspect ratios and screen sizes of currently available devices.
With the number of new devices released on Android, particularly tablets, I wanted a more universal solution.

In Walkabout, rather than use dynamic image scaling with the "@2" type notation in the config.lua, I calculate the screen scale in code, and use this to setup a ImageSuffix variable.

The background image from the title screen you can see above is saved in three sizes as follows:

Platform Target
iPhone (480 by 320 Screens)
Nexus/Droid (8xx by 480 Screens)
iPad, Tablets (1024 by xxx Screens)

In the game initialisation code, a check is made against the content scaling Corona is currently doing:


     scale = tonumber(string.format("%." .. (1) .. "f", 1/display.contentScaleY))
    if scale == 1 then
        ImageSuffix = "-480"
    elseif scale == 1.5 then
        ImageSuffix = "-800"
        ImageSuffix = "-1024"

Checking this in code allows you to make decisions per graphic rather than in the entire game.  As a side note you can find the actual number of pixels the device has by doing:

     scaleX = tonumber(string.format("%." .. (1) .. "f", 1/display.contentScaleX))
    scaleY = tonumber(string.format("%." .. (1) .. "f", 1/display.contentScaleY))
    pixelsX = scaleX * display.contentWidth
    pixelsY = scaleY * display.contentHeight

By using scale = letterbox in the config.lua you can decide programatically if and how you want to stretch the image:


     letterbox = display.newImageRect( "Title" .. ImageSuffix .. ".jpg",
        display.contentWidth, display.contentHeight)

     zoomEvenX = display.newImageRect( "Title" .. ImageSuffix .. ".jpg",
        display.contentWidth - display.screenOriginX*2,
        display.contentHeight * (1 -
display.screenOriginX*2 / display.contentWidth)

     zoomEvenY = display.newImageRect( "Title" .. ImageSuffix .. ".jpg",
        display.contentWidth * (
1 - display.screenOriginY*2 / display.contentHeight,
        display.contentHeight - display.screenOriginY*2)

     zoomStretch = display.newImageRect( "Title" .. ImageSuffix .. ".jpg",
        display.contentWidth - display.screenOriginX*2,
        display.contentHeight - display.screenOriginY*2)

NOTE: These are for landscape orientation.

4 - Error Checking

The biggest annoyance and pitfall is basic errors caused by typos.  In Lua, unlike other languages, you don't need to declare variables.

If you use a variable without declaring it, it becomes a global.  This can cause uncaught errors if you have a typo.  Consider the following:


    local levelComplete = true

   if LevelComplete == true then
     print("you won")

Notice the typo, 'LevelComplete' with a capital 'L'.  Lua/Corona won't catch this and instead make 'LevelComplete' a new global, and of course it will never be set to true, and so you could never win this game!

To get around the problem you can add this code to the beginning of your main.lua to catch these issues.  This will prevent ALL globals from being declared, and so would catch the above by displaying an error.


    function declare (name, initval)

      rawset(_G, name, initval or false)


   setmetatable(_G, {

   __newindex = function(_ENV, var, val)

     if var ~= "tableDict" then

       error(("attempt to set undeclared global\"%s\""):format(tostring(var)), 2)


       rawset(_ENV, var, val)


   __index = function(_ENV, var, val)

     if var ~= "tableDict" then

       error(("attempt to read undeclared global\"%s\""):format(tostring(var)), 2)

       rawset(_ENV, var, val)


if you do want to use a Global then you need to use declare as below:



Here Levels and BonusLevels are two globals.  You should use this for debugging and in your final build comment them out.  I personally leave a multi line comment --[[  and ]]-- around my debugs so I can quickly comment them in and out.  Remember comment out the whole block, even the declares as they will automatically become globals.

Another good debugging technique is to use print("error") etc in your code.  I'm sure you are familiar with that already, but in case you didn't know these prints also show in logcat on Android and the xcode organiser's Console window on the device.  On iOS you should turn off the output buffer if your messages are lagging:



Again leave this in the big block at the top of your file and comment it out for the final release build!

One last error checking mechanism is xpcall.  This is a lua function that calls the passed function in protected mode.  It will catch any errors in that function and call the second function that you pass it.  For example:


    xpcall(LoadLevel, ErrorHandler)

If an error occurs in LoadLevel it will call ErrorHandler() to handle it.  This is good to handle strange errors like with partially written suspend/resume/save files.  In Walkabout, the calls to my file load functions all use xpcall.  If an error is detected the save file is deleted and recreated.  It's an easy short cut, rather than having to implement complex save file checking mechanisms.

However when debugging it will also catch those errors and you won't get an error message in the Corona log window!  To override it for debugging purpose just add this to the top of your code:


    local function xpcall (f, a)


Again leave this in the debug block at the top of your file and comment it all in one go for release builds.

DON'T be lazy and use xpcall for functions that you know have an error.  Spend the time to debug the function properly.  You'll be much better off in the long run!

5 - Functions

In most Corona docs I see functions being defined in this format:



    local function hello (a)
      print("hello" .. a)

The above code would actually give you an error, as the function 'hello' isn't defined until after it is called.  You could re-arrange the code so that the call to 'hello' was after the function definition, but this isn't always convenient.  It cant get a little complicated trying to find an order that works when you have functions calling other functions which in turn call other functions.

Instead it's better to get into the habit of defining functions like this:


    -- My Functions
    local hello

    -- Main code

    hello = function (a)
      print("hello" .. a)

Define variables for all the functions you will use at the top of your main.lua, and then don't worry about the order you define them.  You can then group functions together in a more logical way - one that fits with sections of your game.  e.g.. Title functions, In Game functions, End Level functions etc.

Another situation which led me to some bad habits was dealing with functions like performWithDelay.  These expect a function pointer, and so cannot handle functions with parameters:


    -- My Functions
    local hello

    hello = function (a)
      print("hello" .. a)

    -- Main code
    timer.performWithDelay(500, hello("bob"))  -- ERROR

    timer.performWithDelay(500, hello)  -- Valid, but how to pass "bob" as a parameter?

The solution is quite simple:


    -- My Functions
    local hello
    local closure

    hello = function (a)
      print("hello" .. a)

    closure = function()

    -- Main code
    timer.performWithDelay(500, closure)

This works, but if we wanted to pass "Tom" next time, we would have to create another closure function.  It's not a great solution.  Fortunately Lua lets you define functions anywhere, even in the middle of performWithDelay:


    -- My Functions
    local hello

    hello = function (a)
      print("hello" .. a)

    -- Main code
    timer.performWithDelay(500, function() hello("bob") end)

This is a much more elegant solution, and doesn't leave a bunch of single use functions laying around in your code.  This concept becomes even more fun when working with transitions.....

6 - Transitions

It's quite typical to use a lot of transitions in your game/app.  A good example is in Walkabout on the level selector.

Each level select square transitions into the main screen.  It's tempting to just add one transition per square in the loop that you used to create them, but this is a performance hog!

Instead add them all to a layer and transition the layer instead.

Another pitfall is where you have multiple transitions at the same time, and then do something once they complete.  For example scene changing.  In Walkabout going from the Main Screen to the level selector the 'Walkabout' title transitions off the screen, as does the 'Play' icon and the sound icons.  The background also alpha's to zero.

Again it's very tempting to do this:

CODE (Walkabout, {time = 200, alpha = 0 x = 100}) (background, {time = 200, alpha = 0, x = 200}) (playicon, {time = 200, alpha = 0, x = 300}) (volumeicon, {time = 200, alpha = 0, x = 400})

    timer.performWithDelay(200, LoadLevelScreen)

In this case you can't put them all into one layer as each transition is different.  But, you are relying on all the transitions to have finished at time = 200 so that your level screen can load.  Technically this should work fine, but in the real world you can end up with odd glitches when a transition didn't actually complete exactly on time.  Worse you might remove an object before it's actually totally off screen.

A better way is to use onComplete:


  local WaitingFor

  local LoadLevel = function()
WaitingFor = WaitingFor - 1
      if WaitingFor == 0 then LoadLevelScreen end

WaitingFor = 4  -- Waiting for 4 transitions (Walkabout, {time = 200, alpha = 0, x = 100, onComplete = LoadLevel}) (background, {time = 200, alpha = 0, x = 200
, onComplete = LoadLevel}) (playicon, {time = 200, alpha = 0, x = 300
, onComplete = LoadLevel}) (volumeicon, {time = 200, alpha = 0, x = 400
, onComplete = LoadLevel})

  timer.performWithDelay(200, LoadLevelScreen)

This way you can be totally sure the next function is only called once all transitions are finished.


  -- OK (Title, {time = 200, alpha = 0}) (Level1, {delay = 200, time = 200, alpha = 1})
  timer.performWithDelay(400, AddLevelListeners)

  -- Better (Title, {time = 200, alpha = 0, onComplete = function() (Level1, {time = 200, alpha = 1, onComplete = function()


7 - Android Manifest Customisation

A about a year or two ago, I bought a Samsung Galaxy I-7500, which was a 528MHz Qualcomm based Android phone with very similar hardware to the HTC Hero.  The only problem was that it only had 192MB of RAM, and came with OS 1.5 installed.

Myself and a group of people on Androidforums started an effort to root it and tweak it to deal with the low levels of RAM.  This later led to a merge with separate French and German groups trying to achieve the same thing.  They joined my channel #i7500 on freenode and we began working on porting 2.1, 2.2 and now 2.3 to this underpowered little device!

Since then I had bought a Nexus 1, and all my on-device testing for Walkabout was done on that.  Once Walkabout was looking good enough for me to show other people,  a few of my friends on the i7500 channel ran a test version of my game, and to my horror the performance was terrible!  I spent a lot of time making sure that Walkabout ran well on these old slow devices, and eventually succeeded.  I will explain the methods I used in later chapters.

Anyway, after all the optimising, my game was complete!  I uploaded it to the Android Market and happily announced it to all my friends in #I7500.  Great, mass excitement in the channel, everyone stopped what they were doing, started screaming out of their windows - well not quite - but lets say a lot of my friends rushed to the Android market to download the free version of my game.

Disaster - my game did not appear for them on the Android Market.  Some were running the latest official 1.6 OS from Samsung, but most were running one of our 2.1 or 2.2 custom builds.  How could this be?  I had built for iOS 1.6.......

So many Android versions, so many Corona versions...

The first obvious reason why an application may not show in the Android Market for a certain device is that it's built for a newer Android version.  As I'm sure you all know, Ansca have dropped support for Android OS versions below 2.2.

A lot of people ask on the Corona IRC channel, and the Corona forums whether many people still use version 2.1 and below.  Here are some real world statistics based on sales and downloads of Walkabout:

Android Version
Percentage share of downloads
Android 2.2
Android 2.1
Android 2.3.3
Android 2.3
Android 1.6
Android 2.0.1
Android 1.5

I don't know about you but I'm not ready to drop the 24.7% of my customers who are still using 2.1!  So what options are there to support Android 2.1 and below?  There are many different Corona versions, and although Corona build 319 and below support 2.1, they all have some trade-offs. 

Corona 243, and the Touch bug 1...

Corona build 243 is a previous release build.  You can still download it from here:

On some devices, the touch screen driver sends move events to Android in a specific way.  This together with the way that Corona handles these events means that a touch event is fired every frame.  This bug was discovered after 243 was released.  Therefore some very old devices like the G1, and possibly many others exhibit very very slow performance.  When I say slow, I mean 1 to 5 fps, where a similarly powered device may show 30fps.  If you use Build 243, your app will be unusable on these devices.  Unfortunately I don't have a full list of devices affected, but there are several posts about it in the forums.

Worse, ALL Motorola based Android phones will crash if you touch the screen excessively.  If you use this version you should make a note to say Motorola Android phones are not supported.

Corona 268, and the Touch bug 2...

Corona Build 268, is the previous release build.  You can still download it from here:

Ansca attempted to fix the bug in 243 and all looked good.  I used this version to release Walkabout back on 1st March 2011.  I quickly started receiving complaints and 1 star ratings from various users.

Some further research showed that the problem on the G1 etc was fixed, however, the fix had caused the same problem on different devices.  Worst of all these devices were:

Samsung Galaxy S
Nexus S
ALL Motorola Android phones

These are some of the most popular Android phones out there, and worse the most powerful.  There is no ambiguity that it's your apps fault and not the power of the phone.  Chaos ensued as I received email after email, and poor rating after poor rating!

Corona 319 - A daily build, a brighter day...

Corona Build 319, is a daily build only available to subscribers.  If you are a subscriber and logged in, you can download it from here:

This version fixed the touch bug, and I have no reported problems on any phones or firmware versions

Corona 484 - The latest Release, a step backwards?

The latest release version for Corona contains various enhancements which make huge performance gains.  However they all rely on arm7 support, which is generally only available on the newest devices.  Ansca have also removed Android 2.1 support, and some modules for arm6.  This means that many old devices will no longer be able to run builds made with this version, even if they have Android 2.2.

Additionally there is a bug that crashes HoneyComb based tablets.  I would advise you to think very carefully about whether you need the speed enhancements in this version, and if you are willing to trade all those users for it.

Another bug - idletimer...

I will explain handling suspend and resume in a later chapter, but it's worth mentioning here.  If your device sleeps due to inactivity with the screen, the app will 'suspend'.  If you do not specifically add code to handle this and resume, your game will simply restart when the user wakes the phone.  One way to get around having to handle this situation is to prevent the phone from sleeping while your game is running, you can use the following api:


   system.setIdleTimer( false )  -- disable (turn off) the idle timer

However, a bug in Corona versions prior to daily 310 mean it does not work.  So if you want to use this API you must use daily 310, or above (e.g. Release 484.


So which version should you use?  If you are a subscriber it is definitely daily build 319.  If not, then it depends.  See the summary below:

Corona Version
G1 & Old phones
Nexus S & new phones
Idlestimer & Suspend
Android 2.1 & below
Release 243
Touch Bug 1
(except Motorola)
IdleTimer bug
Release 268
Touch Bug 2
IdleTimer bug OK
Daily 319
Release 484
No Support

Moving on swiftly....

So, to continue my story, I had already built Walkabout for version 2.0.1, and yet it still did not appear on the Android Market for my friends in the #i7500 channel.  The reason? - Corona predefines a few requirements in the AndroidManifest.xml file that is included inside your final build.  In addition it misses out others!

The AndroidManifest.xml is similar to the info.plist in iOS, and defines the minimum OS, minimum hardware, screen size and permissions required by the application - such as Internet for example.  The AndroidManifest.xml is compressed and encoded into the apk and cannot be edited directly.

However with a bit of work and the amazing Apk Manager tool from #xda-developers you can edit the AndroidManifest.  Is it worth the hassle I hear you ask?  Well here are the benefits:
I have edited the tool to incorporate the new Honeycomb frameworks.  You will need the following two files:

Unfortunately this only works on a PC at the moment.  The OS X version of apk manager has issues with Corona built apk files.  I will update this section when the issue is resolved.

1) Download and extract the apk_manager zip file onto your desktop

2) Open a command prompt and type 'java' (without the quotes) and press enter.  If you receive command not found, then go to the following link and download the JAVA JDK before proceeding.

3) Open the Apk manager folder, and place your Corona build apk file in the folder place-here-for-modding.

4) Double click script.bat, and ignore the error about adb not being found.  Press menu option 22 and then 1 to select your apk.  Then press option 9 to extract your apk.

5) ONLY ONCE.  Browse to c:\users\<yourusername>\apktool\frameworks\resource and replace the 1.apk with the one you downloaded above.

6) You fill find your extracted apk in the projects folder of apk manager.  Edit your AndroidManifest in a text editor and save the changes (More on how below).  Then select option 11 in apk manager to compile your new APK.  Press 'n' to 'Is this a system apk'

7) You will find a new apk in the place-here-for-modding folder that has the same name as your original apk but 'unsigned' prepended to it.  Transfer this back over to your Mac.

8) Sign the apk with your key.  You will need to use the keystore you generated for Corona and the alias you used.  If you don't remember your alias, you can build for Android in Corona, select your key and the alias will be in the second drop down.  Here is an example of how I sign Walkabout  (That is all one line):


   jarsigner -verbose -keystore ~/Desktop/Walkabout_Submission/base2-release-key.keystore ~/Desktop   
      /unsignedWalkaboutLite.apk  base2_release

9) Finally zipalaign the apk - Corona doesn't do this for some reason (?!).  You can read more about zipaligning here:


   /android-sdk-mac_86/tools/zipalign -v 4 ~/Desktop/unsignedWalkaboutLite.apk ~/Desktop/WalkaboutLite.apk

You're final apk will be on your desktop enjoy!

Changes you say?

So, what changes can and should you make I hear you ask?  Firstly I recommend you build with Daily 319 for 2.0.1 and you will end up with an Android Manifest similar to the following:


<?xml version="1.0" encoding="UTF-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="com.base2.walkaboutdemo"
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-feature android:glEsVersion="65537" />
    <application android:theme="@android:style/Theme.NoTitleBar" android:label="@string/app_name" android:icon="@drawable/icon">
        <activity android:label="@string/app_name" android:name=".MyCoronaActivity" android:launchMode="singleInstance" android:screenOrientation="landscape">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
    <uses-sdk android:minSdkVersion="6" />

Lets go through the changes one by one:

Apps 2 SD

- Only Android 2.2 supports installing to the SD card to save internal space.  Usually this is only possible if you build for 2.2.  If you want to support older Android versions you lose the option to install to SD, or do you?

In this line:

<manifest android:versionCode="1" android:versionName="1.0" package="com.base2.walkaboutdemo"

Add the section in red:

<manifest android:versionCode="1" android:versionName="1.0" android:installLocation="auto" package="com.base2.walkaboutdemo" xmlns:android="">


Corona automatically adds Vibrate permissions, and an OpenGL 1.1 requirement.  Some older phones (the trusty i7500 for example) only supported 1.0 on launch, and although it was updated to 1.1, it was never specified properly.  In fact even the old OS with only 1.0 support works fine with Corona apps.  The 1.1 requirement is unnecessary, and just narrows your user base.  Also the vibrate permissions isn't used in my app - by removing it, I have a nice 'This app doesn't use any special permissions' when a user installs the app!

Delete these two lines:

    <uses-permission android:name="android.permission.VIBRATE" />

    <uses-feature android:glEsVersion="65537" />

Small Screens and Large Screens!  Honeycomb anyone?

By default the SDK version you built against defines the default settings for small and large screen support.  This means by default qvga devices will not be able to run your app.

Additionally, targeting an OS version below 9 will NOT enable xlarge screens support for new Honeycomb tablets like the xoom.  So lets fix that, shall we!

Add the 2 lines in RED after the minSDKVersion line:

    <uses-sdk android:minSdkVersion="6" />
    <uses-sdk android:targetSdkVersion="11" />
    <supports-screens android:anyDensity="true" android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true"/>

This will enable support for ALL screen types and densities - as well as honeycomb support.  This only works with the modded framework file 1.apk you downloaded earlier.

Smaller file size?

Apk manager uses the maximum compression rate possible, which should reduce your final apk size.  The zip align process will also make the app more efficient on device.

Remember you MUST sign your apk after the process.  If you want to just try this out, you can use the 'sign' option in APK manager.  This will use a test key - DON'T RELEASE With this key (!), only use it for testing if you're lazy to sign every version until release.

Is it worth it?

Of course you want your app visible to as many users on the Android mMarket as possible.  However if your app barely runs on the latest phones, it may not be a good idea to enable things like small screen support, or to build for very old OS's.  I chose 2.0.1 as my minimum, as I don't think very old phones would be able to handle the graphics in my game.  Also even Google shows that <= 1.6 has less than 5% users, so it's not a huge sacrifice.

Be careful, enabling ALL versions and screen types, just for the heck of it may lead to 1 star reviews where the users don't have the the good sense to realise their phone is just incapable of running your game!

Having said that, with a bit of work, I transformed the performance from < 5fps to a playable 20 fps on the galaxy, and opened up a whole new set of users.  Image how many HTC hero users there are out there for example!

Also, there is a HUGE opportunity to target Xoom and other HoneyComb users.  The number of apps and games they see as HoneyComb compatible is very small, and typically apps not designed for HoneyComb and their very high res screen open up in a smaller window.  With Dynamic image scaling and the AndroidManifest tricks I've shown you here, you can fully support these devices TODAY!


If you would like to show your support, please download the Free version of Walkabout on Android or iOS and leave me an honest rating!  Every rating helps achieve presence on the app store.  Just search 'Walkabout' in either App Store or Android Market.

If you're looking for help or support you can find me on at #corona and of course #i7500

Thanks for listening!