Building a simple cross-platform game using Corona SDK

Building a simple cross-platform game using Corona SDK

Published 09. jan 2019 13:09 by Stein Ove Helset

In this tutorial, I'm going to show you how to build a simple game using Corona SDK. It's going to be cross-platform, so it will be working on both iOS and Android.

What are we building?

The goal of this tutorial is not to build the next big hit. I want to create an introduction to Corona SDK, which in my opinion is one of the best game engine out there for beginners.

The game we're building will be called SquareCatcher, and it will look like this.

Why choose Corona SDK?

In my opinion, Corona SDK is one of the best game engine out there for beginners. It's really easy to get started with, and you can build some amazing games using it. Corona SDK uses a scripting language called LUA. LUA has a really simple syntax and is also very good for beginners and experts.

Corona SDK is also free to use! You do not need to spend a cent to get started. You just download, install and start coding. You can even get a live preview of your game on both iOS and on Android.

Installing Corona SDK

To install Corona SDK, just head over to their website and click "Download" in the upper right corner. Unpack the downloaded file and follow the installation instructions.

Creating the project

As I said earlier in this tutorial, we're going to create a game called SquareCatcher. Open up Corona on your Mac or PC, and click "New project".

Tutorial - cross-platform game - Corona SDK 1

Fill in these details. The most important thing here is the name of the project, that the template is set to "Blank" and the orientation is set to "upright". When you click "Next" you need to find a folder where you want to create your projects. Call it "A Hacker's Day tutorials" or whatever you want.

Tutorial - cross-platform game - Corona SDK 2

When the project has been created, a simulator will automatically launch and it will look something like this.

Tutorial - cross-platform game - Corona SDK 3

Now we're ready for the fun part :D

What's inside the SquareCatcher folder?

If you open SquareCatcher in an editor, you'll see that the folder contain a couple of files. Here's an explanation of what they contains and do.

  • Images.xcassets - This is icons for iOS.
  • LaunchScreen.storyboardc - This is the launch screen for iOS
  • build.settings - This is the settings file for your project. Contains information about the orientation, permissions, which plugins to use and so on.
  • config.lua - This is also a "settings" file for your project. Contains information about resolution, hi-resolution graphics, FPS and how the app should be scaled.
  • Icon* - This is icon files used for Android.
  • main.lua - This is where all the actions goes in. You can either build the entire game using just this file or you can use this as a springboard where you send the user to a certain scene. For really small projects (like SquareCatcher) where the point is just to test stuff, putting all code in main.lua is good enough. When you start bigger projects, it just gets too messy if you put all of the code in one file.

Creating the game

We are going to start off simple by just adding all of our code into one file. Classes, scenes and so on is obsolete for this tutorial. These topics will be covered in a separate tutorial, or maybe a follow up on this tutorial where we rewrite this game. Instead of adding pretty graphics to this tutorial, I've decided to just use squares and other shapes which we can generate using Corona SDK.

Open main.lua. It should look something like this.

-----------------------------------------------------------------------------------------
--
-- main.lua
--
-----------------------------------------------------------------------------------------

-- Your code here

As you can see, it's completely empty. Just a comment about which file it is, and a comment stating where the code goes. Comments are an important part of every language. In lua, you add comments by adding to dashes in front of the text. As always when I write tutorials, I suggest that you change variable names, colors, texts and function names. This might make it easier to understand how everything is connected and if something fails, you should know why.

Remove this line "-- Your code here" and replace with this code. This is the code for our basket that will be placed on the bottom of the screen.

-- Variables
local width = display.contentWidth
local height = display.contentHeight
local center = display.contentCenterX
local hasStarted = true

-- Basket
local basket = display.newRect(center, height - 50, 80, 20)
basket.objType = "basket"
basket.fill = { 0, 1, 0 }

In the first lines, we set some variables we're going to use throughout the game. Width and height are the sizes of the screen. We need the center variable to make it easier to place objects on the center of the screen. The default anchor point of an object is at its center. So when you set the "x" value of an object to "center", it will be placed directly on the center of the screen. The last variable "hasStarted" is a boolean variable telling us if the game has started or what.

Underneath the variables, we define the basket. This will be the basket that catches the falling squares.

The basket is just a simple rectangle. "newRect" will create one for us and we first insert the x value (center), then the y value (height - 50 = 50 from the bottom of the screen), then the width (80) and at last the height (20). We set the basket to be objType = "basket". Later when we add collision testing, this will make it easier to check which objects are colliding. Instead of a black box, we set a fill value ({ 0, 1, 0 }). This will make the box green. If you want you can change the values of those three numbers (from 0.0, to 1.0).

Physics

We are going to need physics in order to make the squares fall and to make it as easy as possible to detect collision between the squares and the baskets. Under this line "local squares = {}" add the following code.

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

What these lines do is to include the "physics" library from Corona SDK and start the physics engine. Let's proceed and add some physics to the basket and watch it fall.

basket.fill = { 0, 1, 0 }

physics.addBody( basket, "dynamic" )

When you've added that line (physics...) underneath the code where we added the basket, you can open the game in the simulator again. Now you'll see that the basket will start falling. But we want the basket to stay in one place, so change "dynamic" to "static". Static bodies are not impacted by gravity, but they can detect collision with other objects.

The ground
We should add one more static body to the screen, so we can detect when squares aren't picked up by the basket. So let's add a "ground" element. Add this code underneath the physics code for the basket.

-- Ground
local ground = display.newRect(center, height, width, 10)
ground.objType = "ground"
ground.fill = { 1, 0, 0 }

physics.addBody( ground, "static" )

This ground element will be added centered and to the bottom of the screen. The width is set to be as wide as the screen, and the height is 10. We set the objType to be "ground", so the collision testing will be easier. Since the ground is an object we don't want to hit, we'll make it red. When the object is added, we'll make a physics body from it and set it to be "static".

If you run the game in the simulator now, you'll see the green basket and a red ground underneath it. Now that this is finished, we can proceed to make it possible to move the basket.

Moving the basket

To move the basket back and forth, we're going to use touch. So when you drag your finger on the screen, the basket will slide from left to right. Under the line were the physics body for the basket is added, we can this line too. This will add a touch listener to the basket.

-- Touch listener
local function touchAction( event )
 
    basket.x = event.x
end
basket:addEventListener( "touch", touchAction )

On the last line, we attach an event listener to the basket object. The event we want to listen to is "touch", and this should call a function called touchAction. Four lines up, we create the touchAction function. Inside this function, we set the x position for the basket to be the events x position. The events x position is where you put the finger on the screen.

If you run the game again now, you will be able to touch the basket and move it back and forth on the screen.

Score counter

In order to make this a game, we'll need a score counter. At the bottom of the list of variables on the top of main.lua, add these to variables.

local score = 0
local scoreLabel = ""

The first variable "score", will contain an integer value and the second variable "scoreLabel" will be the label showing on the screen. Let's show the label on the screen. This code can be placed before we add the touch listener.

-- Score label
scoreLabel = display.newText(score, center, 50, native.systemFont, 15)

This will add a text label to the screen. The first parameter "score" is the number of squares you've picked up, the label is placed on the center of the screen and 50 from the top. We'll just use a default font and set it to be size 15. Well, this was easy. Let's proceed and make the functionality we need to add squares and make them fall from the top.

Add falling squares

Let's add this functionality underneath the score label.

-- Add squares
local function addSquare()

    if hasStarted then
        local square = display.newRect(center, -50, 20, 20)
        square.objType = "square"
        square.fill = { 0, 0, 1 }

        physics.addBody( square, "dynamic" )
    end
end
timer.performWithDelay(1000, addSquare, 0)

The first line is where we define the function for adding a square. This will be called every second. Under the function, we'll add a timer. This timer will call the addSquare function every 1000ms. The last parameter "0", is the number of iterations. If this is set to 1000, it will be an infinite loop.

Inside the function, we're first checking that the game has actually started. If it has, it will add a square to the center of the screen, 50pts over the screen and make it 20pts by 20pts. We'll set the square to be blue and make it a dynamic physics body.

If you run the game now, you should see that there will be falling blue squares from the top of the screen. Instead of just falling from the center, we can make the x value random. So replace "center" with "math.random(40, width-40)". This way the center of the square can be anything from 40pts from the left side to 40pts from the right side.

Collision detection

We are going to need two collision testers. One for the basket and one for the ground. Let's begin by adding the one we need for the basket. Add this code above the addSquare function.

-- Collision - basket
local function basketCollide( self, event )

    if ( event.other.objType == "square" ) then
        if ( event.phase == "ended" ) then
            local function removeBody()         
                physics.removeBody(event.other)

                event.other:removeSelf()
                event.other = nil
            end

            transition.to( event.other, { time=50, width=0, height=0, onComplete=removeBody })

            if hasStarted then
                score = score + 1
                scoreLabel.text = score
            end
        end
    end
end
basket.collision = basketCollide
basket:addEventListener( "collision" )

A couple of new concepts are introduced here, but I'll explain what everything here does. First, we add the function for testing the basket collision. This will accept two parameters containing information about the collision. Underneath the function, we connect the basket's collision event to this function. We also need to attach an eventListener to the basket. This event listener will listen for "collision".

Inside the function, we check if the object the basket is colliding with is a "square". We want to do something once the collision has ended. We create a new function inside there to remove the physics body. We can't remove this directly because the body still lives inside the physics world because of the collision.

transition.to is Coronas animation library. We pass in the "event.other" object, this is the square we collided with. During 50ms, the width and height should decrease to 0 and when the animation is complete, we want to call the "removeBody" function where we remove the physics body. We're also removing it from the screen and set it to nil.

The other collision handler for the ground looks like this. You can add this code underneath the other collision handler.

-- Collision - ground
local function groundCollide( self, event )

    if ( event.other.objType == "square" ) then
        if ( event.phase == "ended" ) then
            local function removeBody()         
                physics.removeBody(event.other)

                event.other:removeSelf()
                event.other = nil
            end

            transition.to( event.other, { time=50, width=50, height=50, alpha=0, onComplete=removeBody })

            scoreLabel.text = score .. " - Game over"
            hasStarted = false
        end
    end
end
ground.collision = groundCollide
ground:addEventListener( "collision" )

This code should be familiar to you know. It's basically the same as the other tester, but the transition/animation is a little bit different. Instead of imploding, it's now "exploding".
Also, instead of increasing the score. We change the scoreLabel to include the score and also say "Game over".

Restarting the game

Wow, we have already reached the last part of this tutorial. In this part, we'll show a restart button on the screen when you die, and also create the necessary functionality for resetting the game. Add this function above the two collision handlers.

-- Reset game
local function resetGame(event)
    event.target:removeEventListener("tap", resetGame)
    event.target:removeSelf()
    event.target = nil
    
    hasStarted = true
    score = 0
    scoreLabel.text = "0"
end

Inside this function, we first get the "button object" from the event. We need to remove the event listener, remove it from the screen and set it to nil. Once the button has been removed, we reset three variables so the game can restart.

The last thing we need to do now is to show the restart button when you die. Scroll down to the groundCollide function and add the bold lines below.

-- Collision - ground
    local function groundCollide( self, event )

        if ( event.other.objType == "square" ) then
            if ( event.phase == "ended" ) then
                local function removeBody()         
                    physics.removeBody(event.other)

                    event.other:removeSelf()
                    event.other = nil
                end

                transition.to( event.other, { time=50, width=50, height=50, alpha=0, onComplete=removeBody })

                scoreLabel.text = score .. " - Game over"
                hasStarted = false

                --

                local resetButton = display.newText("Reset game", center, 100, native.systemFont, 20)
                resetButton:addEventListener("tap", resetGame)
            end
        end
    end
    ground.collision = groundCollide
    ground:addEventListener( "collision" )

The new code here will add a "button" to the screen. Center it and set it 100pts from the top. We're adding a "tap" listener to it, so we know when a user taps it.

What's next?

Now that you have a playable game, the sky is the limit for you :-)
You can either keep building on this game or start building your own. The most important thing to remember is to not aim too high. You're never going to build the next GTA V or Call of duty for your self. These are games that take years and thousands of people to build.

Continue by creating many and small games. Build a lot of prototypes and if you really like one of the prototypes, then keep building it until you can release it. It's also important to try releasing a few games. Releasing a game on Google Play doesn't cost any more than $25 I think.

Once you've first released a game, you will feel that your stress level should be decreasing. It feels good to release a game.

Good luck, and remember to leave a comment here if you've got a question or want to tell me about your game!

Share this post

Comments

No comments yet

Add comment