Writing your first Android game using the Corona SDK

by: Gary SimsJuly 13, 2015
829

The most popular category on the Google Play Store has always been Games. Although we all probably use key productivity apps like a web browser, an email client, and an instant messaging app, gaming still remains an important part of the mobile experience. So it is no surprise that many people who want to learn to develop for Android want to start by making a game. Also, let’s be honest, writing a game is a whole load more fun than developing a productivity app!

The official language of Android is Java and the official development environment is Android Studio. If you want to look into Java then I suggest our Java basics tutorial, and if you want to learn how to write an app using Android Studio then I suggest you check out our tutorial on writing your first Android app. However Java and Android studio aren’t the only ways to develop for Android. You can find an overview of the available languages and SDKs in our guide: I want to develop Android Apps – What languages should I learn?

corona-sim

One of the SDKs mentioned in the programming languages guide is Corona, a third party SDK designed primarily for writing games. Instead of Java, Corona uses Lua, a fast scripting language that is easy to learn yet powerful. However, Corona isn’t the only mobile gaming SDK that uses Lua, other well known examples include Cocos2d-XMarmalade, and Gideros.

Download and install

To get started with Corona you are going to need to download and install the SDK. Go to the Corona website and hit the download button. You will need to create an account (which is free) before you can download the kit. If you want to build an actual .apk file rather than just running your program in the emulator, you will need to install Java 7, however you won’t need to install the Android SDK. To install the Java 7 Development Kit go to Oracle’s website, look for the section called “Java SE Development Kit 7u79″ and download the version for your PC.

Once you have installed Corona you need to activate it. This is a one-time process, which is free. Start the Corona Simulator and agree to the license. Enter in the email address and password which you used for the download, and click Login.

Starting the project

From within the Corona Simulator click on “New Project.” Enter a name for your app in the “Application Name:” field and leave the rest of the settings at their defaults. Click “OK.”

corona-sim-new-project2

Three windows will now appear. The first two are the Corona Simulator and the Corona Simular Output. Corona will also open a file explorer window showing the files for your project.

The majority of the files (some 23 of them) in the project directory are for the application icon! The most important file for us right now is main.lua, as this is where we will write the code for our app.

Introduction to Lua

Before we get into writing the code, we need to make a whistle-stop tour of Lua. The Lua interpreter  (remember this is a scripting language, not a compiled language) is available for Windows, OS X, and Linux. However it is built-in to Corona, so at this time you don’t need to install anything extra. The easiest way to play with Lua is to use the online live demo.

You can find lots of good tutorials about Lua online and you should take a look at the Lua Reference ManualProgramming in LuaThe.Lua.Tutorial, and The Tutorials Point Lua Tutorial.

Here is a small Lua program which will show you some of the key features of Lua:

local function doubleIt(x)
    return x * 2
end

for i=1,10,1 do
    x = doubleIt(i)
    if(x == 10) then
        print("ten")
    else
        print(doubleIt(i))
    end
end

The code above shows three important Lua constructs: functions, loops, and if statements. The function doubleIt() is very simple, it just doubles the passed in parameter x.

The main code is a for loop from 1 to 10. It calls doubleIt() for each iteration. If the return value is 10 (i.e. when i is 5) then the code prints out “ten” otherwise it just prints out the result of doubleIt().

lua-online-live-demo-zoomed-16-9

If you have any coding experience then the example code should be easy enough to follow. If you are looking to learn some basic programming then I suggest you use some of the resources linked above to hone you skills.

Writing the game

Writing basic programs in Corona is simple. You only need concern yourself with one file, main.lua, and let Corona do all the heavy lifting. The game we are going to write is a simple “tap” game. A balloon or a bomb will fail down the screen. If the player taps on the balloon they score a point, they tap on a bomb then the score will divided by 2, as a penalty. To write the code you need to edit main.lua. You can do this in any text editor.

The Corona SDK has a built-in 2D physics engine, which makes building games very easy. The first step in writing the game is to initialize the physics engine:

local physics = require( "physics" )
physics.start()

The code is fairly self-explanatory. The module physics is loaded and initialized, it is assigned to the variable physics. To enable the engine physics.start() is called.

Next we create some helpful variables which will be useful not only for this simple game, but also for more complex games. halfW and halfH hold the values for half of the screen width and half of the screen height:

halfW = display.contentWidth*0.5
halfH = display.contentHeight*0.5

The display object is a pre-defined object which Corona makes globally available.

Now comes the first step that actually makes something happen on screen:

local bkg = display.newImage( "night_sky.png", halfW, halfH )

As well as properties like contentHeight and contentWidth, the display object also has lots of useful functions. The newImage() function reads an image file (in this case a .png) and displays it on the screen. Display objects are rendered in layers, so since this is the first image we are putting on the screen then it will always be the background (unless the code explicitly does something to change that). The parameters halfW and halfH tell Corona to place the image in the middle.

At this point you can run the code in the emulator and see the background image.  If you save the file then the emulator will notice that the file has changed and offer to relaunch. If that doesn’t happen then use File->Relaunch.

Since the user will score points for tapping on balloons, we need to initialize a score variable and display the score on the screen:

score = 0
scoreText = display.newText(score, halfW, 10)

The score will be kept in the imaginatively named variable score, and scoreText is the object which displays the score. Like newImage(), newText() put something on the screen, in this case text. Since scoreText is a global variable then we can change the text at any point. But we will get to that soon.

You can relaunch the emulator and see the score of 0 display towards the top of the screen.

Left: Just the background. Right: Background and score.

Left: Just the background. Right: Background and score.

Now comes something a bit more tricky, but don’t worry I will explain it line by line:

local function balloonTouched(event)
    if ( event.phase == "began" ) then
	Runtime:removeEventListener( "enterFrame", event.self )
        event.target:removeSelf()
	score = score + 1
	scoreText.text = score
    end
end

The code above defines a function called balloonTouched() which will be called every time a balloon is tapped. We haven’t yet told Corona to call this function every time the balloon is tapped, that will come later, but when we do this is the function that gets called.

Tap or touch events have several stages, many to support dragging. The user puts their finger on an object, this is the “began” phase. If they slide their finger in any direction, that is the “moved” phase. When the user lifts their finger from the screen, that is the “ended” phase.

The first line of balloonTouched() checks we are in the “began” phase. We want to remove the balloon and increment the score as soon as posible. If the function is called again for other phases like “ended” then the function does nothing.

Inside the if statement are four lines of code. Let’s deal with the last two first, as they are simpler. score = score + 1 just increments the score by one and scoreText.text = score changes the score text on the screen to reflect the new score. Remember how I said that scoreText was global and could be accessed anywhere, well that is what we do here.

Now for the first two lines. Once a balloon or bomb falls of the bottom of the screen it still exists in the app’s memory, it is just that you can’t see it. As the game progresses the number of these off-screen objects will steadily increase. Therefore we need to have a mechanism which deletes objects once they are out of sight. We do that in a function called offscreen, which we haven’t written yet. That function will be called once per frame during the game. Once a balloon has been tapped then we need to delete it and remove the call that checks if the balloon has gone offscreen.

The line event.target:removeSelf() deletes the balloon. When a touch event occurs one of the parameters of the listener function is the event parameter. It tells the function about the event and what type of event it is, e.g. event.phase. It also tells us which balloon was tapped, event.target. The removeSelf() function does what it says it does, it deletes the object (in this case a balloon).

The line before that removes the “enterframe” listener, which is the function that is called every frame to see if the balloon has fallen off the bottom of the screen. We will look at that in more detail when we come to write the offscreen listener function.

So, to recap. balloonTouched() checks that this is the beginning of the touch sequence. It then removes the “enterframe” listener, which is the function that is called every frame to see if the balloon has fallen off the bottom of the screen. It then deletes the balloon, increments the score and displays the new score.

That was for balloons, now we need something similar for bombs:

local function bombTouched(event)
    if ( event.phase == "began" ) then
        Runtime:removeEventListener( "enterFrame", event.self )
        event.target:removeSelf()
        score = math.floor(score * 0.5)
        scoreText.text = score
    end
end

As you can see the code is very similar with the exception that rather than incrementing the score, the score is multiplied by 0.5 (i.e. divided by 2). The math.floor() function rounds down the score to the nearest integer. So if the player had a score of 3 and tapped a bomb then the new score would be 1, and not 1.5.

I mentioned the offscreen() function earlier. This function will be called every frame to check if an object has gone off screen. Here is the code:

local function offscreen(self, event)
	if(self.y == nil) then
		return
	end
	if(self.y > display.contentHeight + 50) then
		Runtime:removeEventListener( "enterFrame", self )
		self:removeSelf()
	end
end

In computing there is a situation known as a race condition. This is where two things are going to happen but one might happen first, or sometimes the other might happen first. It is a race. Some race conditions are unseen because one thing always seems to happen first, but they can cause interesting bugs in that one day, under the right conditions, the other thing happens first and then the system breaks!

There is a race condition in this simple game because two things can happen very close to each other: a balloon being tapped and the offscreen() function being called to see if the balloon has gone off the screen. The result is that the code to delete the balloon can be called and then the offscreen() function is called (which happens like 30 times per second). To get around this odd sequence of events the offscreen() function needs to check if the y value of the object is nil (null) or not. If it is nil then it means that the object has been deleted already, so move along, these aren’t the droids we are looking for.

If the object is still in play, then check its position, if it has gone 50 pixels off the screen then delete it and remove the listener so that the offscreen() function won’t be called again for this object. The code to make sure that offscreen() is called every frame is part of the next section of code.

The whole premise of this game is that new balloons or bombs will continue to drop down the screen. Therefore we need a function which will create either a new balloon or a new bomb:

local function addNewBalloonOrBomb()
	local startX = math.random(display.contentWidth*0.1,display.contentWidth*0.9)
	if(math.random(1,5)==1) then
		-- BOMB!
		local bomb = display.newImage( "bomb.png", startX, -300)
		physics.addBody( bomb )
		bomb.enterFrame = offscreen
		Runtime:addEventListener( "enterFrame", bomb )
		bomb:addEventListener( "touch", bombTouched )
	else
		-- Balloon
		local balloon = display.newImage( "red_balloon.png", startX, -300)
		physics.addBody( balloon )
		balloon.enterFrame = offscreen
		Runtime:addEventListener( "enterFrame", balloon )
		balloon:addEventListener( "touch", balloonTouched )
	end
end

The first line of the function decides where the balloon will drop from on the x plane.  If the balloon or bomb always dropped in the middle, that won’t be very interesting! So startX is a random number between 10 percent and 90 percent of the screen width.

Next a random number is picked between 1 and 5. If the number is 1 then a bomb will be dropped. If it 2, 3, 4 or 5 then a balloon will be dropped. This means that bombs will be dropped around 20 percent of the time.

The bomb and balloon code are quite similar. First the image (either a bomb or a balloon) is displayed using newImage(). Its x position is that of startX while its y position is set to -300, i.e. off the top of the screen. The reason for that is that we want the object to fall from outside the screen area into the visible area and then off the bottom. Since we are using the 2D physics engine it is good to give the object a bit of an initial distance to fall, so it can gain some speed.

The call to physics.addBody() takes the image loaded by newImage() and turns it into an object in the physics engine. This is very powerful. Any image file can be made into a body which responds to gravity and collisions just by calling physics.addBody().

The last three lines of the bomb or balloon code set up the listeners. Setting the enterFrame property tells Corona which function to call every frame and the call to Runtime:addEventListener() sets it up. Lastly the call to balloon:addEventListener() tells Corona which function to call if the bomb or balloon is touched.

And now the game is almost complete. We just need two more lines of code:

addNewBalloonOrBomb()
timer.performWithDelay( 500, addNewBalloonOrBomb, 0 )

The first line makes the very first bomb or balloon fall by explicitly calling addNewBalloonOrBomb(). The second line sets up a timer which will call addNewBalloonOrBomb() every half a second (500 milliseconds). This means that a new balloon or bomb will fall every half a second.

corona-sim-balloon-or-bomb-2-shots

You can now run the game in the emulator.

Here is the complete listing for main.lua, the full project source code for this game can be found here on GitHub.

-----------------------------------------------------------------------------------------
--
-- Falling balloon and bomb game
-- Written by Gary Sims for Android Authority
--
-----------------------------------------------------------------------------------------

-- Start the physics engine
local physics = require( "physics" )
physics.start()

-- Calculate half the screen width and height
halfW = display.contentWidth*0.5
halfH = display.contentHeight*0.5

-- Set the background
local bkg = display.newImage( "night_sky.png", halfW, halfH )

-- Score
score = 0
scoreText = display.newText(score, halfW, 10)

-- Called when the balloon is tapped by the player
-- Increase score by 1
local function balloonTouched(event)
    if ( event.phase == "began" ) then
        Runtime:removeEventListener( "enterFrame", event.self )
        event.target:removeSelf()
        score = score + 1
        scoreText.text = score
    end
end

-- Called when the bomb is tapped by the player
-- Half the score as a penalty
local function bombTouched(event)
    if ( event.phase == "began" ) then
        Runtime:removeEventListener( "enterFrame", event.self )
        event.target:removeSelf()
        score = math.floor(score * 0.5)
        scoreText.text = score
    end
end

-- Delete objects which has fallen off the bottom of the screen
local function offscreen(self, event)
    if(self.y == nil) then
        return
    end
    if(self.y > display.contentHeight + 50) then
        Runtime:removeEventListener( "enterFrame", self )
        self:removeSelf()
    end
end

-- Add a new falling balloon or bomb
local function addNewBalloonOrBomb()
    -- You can find red_ballon.png and bomb.png in the GitHub repo
    local startX = math.random(display.contentWidth*0.1,display.contentWidth*0.9)
    if(math.random(1,5)==1) then
        -- BOMB!
        local bomb = display.newImage( "bomb.png", startX, -300)
        physics.addBody( bomb )
        bomb.enterFrame = offscreen
        Runtime:addEventListener( "enterFrame", bomb )
        bomb:addEventListener( "touch", bombTouched )
    else
        -- Balloon
        local balloon = display.newImage( "red_balloon.png", startX, -300)
        physics.addBody( balloon )
        balloon.enterFrame = offscreen
        Runtime:addEventListener( "enterFrame", balloon )
        balloon:addEventListener( "touch", balloonTouched )
    end
end

-- Add a new balloon or bomb now
addNewBalloonOrBomb()

-- Keep adding a new balloon or bomb every 0.5 seconds
timer.performWithDelay( 500, addNewBalloonOrBomb, 0 )

Next steps

The next step is to play the game on a real Android device. To build an .apk file click on File->Build for Android… and fill out the fields. The result will be a .apk file which you can copy onto your device and then install. You will need to ensure that you have configured your device to allow installation of app from unknown sources. Amazon has some good documentation on this as you also need to set this to install the Amazon Appstore. Corona also has a guide on how to sign, build, and test your app on Android devices.

balloon-or-bomb-in-hand

With the game successfully installed on your device the next thing to do is improve the game. For example, why not try adding a “pop” or “bang” sound everything a balloon or bomb is tapped. Corona has an API for that: media.playEventSound().

Or why not trying adding a third type of object, say a super boost which doubles the current score, or how about some background music?

Wrap-up

Writing games with Corona is quite straight forward because the SDK handles things like OpenGL and it includes a built-in 2D physics engine. Also Lua is easy to learn and shouldn’t be hard for anyone with even the minimum of programming experience. The Coronalabs website has lots of documentation including lots of guides and tutorials.

In less than 100 lines of code we have a working game. OK, it isn’t going to win any prizes, however it shows the power and flexibility of the Corona SDK.



Subscribe to our Android Dev Weekly Newsletter


Once a week we send you all the latest Android developer news & articles.


  • Toma Hawkee

    Got the error message main.lua:63 ERROR: table expected. IF this is a function call, you might have used ‘.’ instead of ‘:’
    stack traceback:
    [C]: in function ‘add Body’
    main.lua:63: in function ‘addNewBallonOrBomb’
    main.lua:78 in main chunck

    with your code

    • Since the error says: in function ‘add Body’ then I think you might have a copy and paste error as the function should be addBody, one word, not “add Body” with a space.

    • Also make sure you have downloaded the .png image file from GitHub, it won’t work without those and it will give you an error like the one you are seeing.

  • Speaking as a very active member of the Corona community, I want to point out that Corona forums are among the most affable and supportive on the entire innertubes. We’re always happy to provide assistance, so feel free to post there if you’re having trouble getting started:

    https://forums.coronalabs.com/

  • Praveen Kumar

    Got the error message main.lua:63 ERROR: table expected. IF this is a function call, you might have used ‘.’ instead of ‘:’
    stack traceback:
    [C]: in function ‘addBody’
    main.lua:63: in function ‘addNewBallonOrBomb’
    main.lua:78 in main chunk

    • It won’t work unless you download the .png files from GitHub (i.e. the images for the balloon and bomb).

      • Praveen Kumar

        Thanks :)

  • Guwab

    Worked perfectly, however tried to do something else with the game. Instead of objects falling I made them appear randomly on the map without the physics as an unmovable objects, and also tried to change the offscreen condition with a delayed condition. All I did was change the code like this however now it returns errors after you touch the object since I guess it still checks for it in the offscreen function. Any way in fixing this?

    Changed the:
    if(self.y > display.contentHeight + 50) then
    Runtime:removeEventListener( “enterFrame”, self )
    self:removeSelf()

    with:
    timer.performWithDelay( 800, function() self:removeSelf() end)