In this three-part series, I will be showing you how to create a game inspired by the popular seventies game, Space Invaders. Along the way, you'll learn about Corona's scene management functionality, timers, moving a character, the built-in physics engine, and how to use modules to emulate classes in the Lua programming language.
1. New Project
Open the Corona Simulator, click New Project, and configure the project as shown below.
Select a location to save your project and click OK. This will create a folder with a number of icons and three files that are important to us, main.lua, config.lua, and build.settings.
We'll take a look at each file in the next few steps.
2. Build Settings
The build.settings file is responsible for the build time properties of the project.
Open this file, remove its contents, and populate it with the following configuration.
settings = { orientation = { default ="portrait", supported = { "portrait" }, }, }
In build.settings, we are setting the default orientation and restricting the application
to only support a portrait orientation. You can learn which other settings you can include in
build.settings by exploring the Corona documentation.
3. Application Configuration
The config.lua file handles the application's configuration. As we did with build.settings,
open this file, remove its contents, and add the following configuration.
application = { content = { width = 768, height = 1024, scale = "letterbox", fps = 30, } }
This sets the default width and height of the screen, uses letterbox to scale the images,
and sets the frame rate to 30. Visit the Corona documentation to learn more about the other properties you can set in config.lua.
4. Entry Point
The main.lua file is the file that the application loads first and uses to bootstrap the application. We will be using main.lua to set a few default settings for the application and use the Composer library to load the first screen.
If you're not familiar with Corona's Composer library, then I recommend giving the
documentation a quick read. In short, Composer is the built-in solution to scene (screen) creation and management in Corona. The library provides developers with an easy way to create and transition between individual scenes.
The newer Composer module replaces the older and now deprecated StoryBoard module. A migration guide is available to help convert your old projects over to use Composer.
5. Hide Status Bar
We don't want the status bar showing in our application. Add the following code snippet to main.lua to hide the status bar.
display.setStatusBar(display.HiddenStatusBar)
6. Set Default Anchor Points
To set the default anchor or registration points add the following code block to main.lua.
display.setDefault( "anchorX", 0.5) display.setDefault( "anchorY", 0.5)
The anchorX
and anchorY
properties specify where you want the registration point of your display objects to be. Note that the value ranges from 0.0 to 1.0. For example, if you'd want the registration point to be the top left of the display object, then you'd set both properties to 0.0.
7. Seed Random Generator
Our game will be using Lua's math.random
function to generate random numbers. To make sure that the numbers are truly random each time the application runs, you must provide a seed value. If you don't provide a seed value, the application will generate the same randomness every time.
A good seed value is Lua's os.time
function since it will be different each time the
application is run. Add the following code snippet to main.lua.
math.randomseed( os.time() )
8. Avoiding Globals
When using Corona, and specifically the Lua programming language, one way to have access to variables application-wide is to use global variables.The way you declare a global variable is by leaving off the keyword local
in front of the variable declaration.
For example, the following code block declares two variables. The first one is a local variable that would only be available in the code block it is defined in. The second one is a global variable that is available anywhere in the application.
local iamalocalvariable = "local" iamaglobalvariable = "global"
It is generally considered bad practice to use global variables. The most prevalent reason is to avoid naming conflicts, that is, having two variables with the same name. We can solve this problem by using modules. Create a new Lua file, name it gamedata.lua, and add the following code to it.
M = {} return M
We simply create a table and return it. To utilize this, we use Lua's require
method. Add the following to main.lua.
local gameData = require( "gamedata" )
We can then add keys to gameData
, which will be the faux global variables. Take a look at the following example.
gameData.invaderNum = 1 -- Used to keep track of the Level we are on gameData.maxLevels = 3 -- Max number of Levels the game will have gameData.rowsOfInvaders = 4 -- How many rows of Invaders to create
Whenever we want to access these variables, all we have to do is use the require
function to load gamedata.lua. Every time you load a module using Lua's require
function, it adds the moduleto a package.loaded
table. If you load a module, the package.loaded
table is checked first to see if the module is already loaded. If it is, then it uses the cached module instead of loading it again.
9. Require Composer
Before we can use the Composer module, we must first require it. Add the following to main.lua.
local composer = require( "composer" )
10. Load the Start Scene
Add the following code snippet to main.lua. This will make the application go to the scene named start, which is also a Lua file, start.lua. You don't need to append the file extension when calling the gotoScene
function.
composer.gotoScene( "start" )
11. Start Scene
Create a new Lua file named start.lua in the project's main directory. This will be a composer file, which means we need to require the Composer module and create a composer scene. Add the following snippet to start.lua.
local composer = require( "composer" ) local scene = composer.newScene() return scene
The call to newScene
makes start.lua part of composer's scene hierarchy. This means that it becomes a screen within the game, which we can call composer methods on.
From here on out, the code added to start.lua should be placed above the return
statement.
11. Local Variables
The following are the local variables we will need for the start scene.
local startButton -- used to start the game local pulsatingText = require("pulsatingtext") -- A module providing a pulsating text effect local starFieldGenerator = require("starfieldgenerator") -- A module that generates the starFieldGenerator local starGenerator -- An instance of the starFieldGenerator
It's important to understand that local variables in the main chunk only get called once,
when the scene is loaded for the first time. When navigating through the composer scenes, for example, by invoking methods like gotoScence
, the local variables will already be initialized.
This is important to remember if you want the local variables to be reinitialized when
navigating back to a particular scene. The easiest way to do this is to remove the scene from the composer hierarchy by calling the removeScence
method. The next time you navigate to that scene, it will be automatically reloaded. That's the approach we'll be taking in this tutorial.
ThepulsatingText
and starFieldGenerator
are two custom modules we will create to add class-like functionality to the project. Create two new files in your project folder named pulsatingtext.lua and starfieldgenerator.lua.
12. Storyboard Events
If you've taken the time to read the documentation on Composer, which I linked to earlier,
you will have noticed the documentation includes a template that contains every possible
composer event. The comments are very useful as they indicate which events to leverage for initializing assets, timers, etc. We are interested in the scene:create
, scene:show
, and scene:hide
methods for this tutorial.
Step 1: scene:create
Add the following code snippet to start.lua.
function scene:create( event ) local group = self.view startButton = display.newImage("new_game_btn.png",display.contentCenterX,display.contentCenterY+100) group:insert(startButton) end
This method is called when the scene's view doesn't exist yet. This is where you should initialize the display objects and add them to the scene. The group
variable is pointing to self.view
, which is a GroupObject
for the entire scene.
We create the startButton
by using the Display
object's newImage
method, which takes as its parameters the path to the image and the x
and y
values for the image's position on screen.
Step 2: scene:show
Composer's scene:show
method has two phases. The will phase is called when the scene is still off-screen, but is about to come on-screen. The did phase is called when the scene is on-screen. This is where you want to add code to make the scene come alive, start timers, add event listeners, play audio, etc.
In this tutorial we are only interested in the did phase. Add the following code snippet to start.lua.
function scene:show( event ) local phase = event.phase local previousScene = composer.getSceneName( "previous" ) if(previousScene~=nil) then composer.removeScene(previousScene) end if ( phase == "did" ) then startButton:addEventListener("tap",startGame) end end
We declare a local variable phase
, which we use to check which phase the show
method is in. Since we will be coming back to this scene later in the game, we check to see if there is a previous scene and, if so, remove it. We add a tap listener to the startButton
that calls the startGame
function.
Step 3: scene:hide
Composer's scene:hide
method also has two phases. The will phase is called when the scene is on-screen, but is about to go off-screen. Here you will want to stop any timers, remove event listeners, stop audio, etc. The did phase is called once the scene has gone off-screen.
In this tutorial, we are only interested in the will phase in which we remove the tap listener from the startButton
.
function scene:hide( event ) local phase = event.phase if ( phase == "will" ) then startButton:removeEventListener("tap",startGame) end end
16. Start Game
The startGame
function is called when the user taps the startButton
. In this function, we invoke the gotoScene
composer method, which will take us to the gamelevel scene.
function startGame() composer.gotoScene("gamelevel") end
17. Game Level Scene
Create a new file named gamelevel.lua and add the following code to it. This should look familiar. We are creating a new scene and returning it.
local composer = require("composer") local scene = composer.newScene() return scene
18. Add Scene Listeners
We need to add scene listeners for the create
, show
, and hide
methods. Add the following code to start.lua.
scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene )
19. Test Progress
If you test the game now, you should see a black screen with a button you can tap. Tapping the button should take you to the gamelevel scene, which is now just a blank screen.
Conclusion
This brings this part of the series to a close. In the next part, we will start implementing the game's gameplay. Thanks for reading and see you in the second part of this series.