In this tutorial, I’ll teach you how to create a Word Drop game using the Corona SDK. We’ll build the game from start to finish so I encourage you to follow along. In this tutorial, we will work with timers, physics, and implement our own touch controls. Let’s get started.
Introduction
The objective of the game is to create words from the lettered balls. If the balls cross the bar at the top, the game is over. You have three bombs, which you can use to remove five balls so it is key to use them sparingly. Every ten seconds, more balls drop from the top of the screen. Take a look at the screenshot to get an idea of the game.
1. New Project
Open the Corona Simulator and choose New Project.
On the next screen, apply the following settings.
Click Next and choose Open in Editor. This will open Main.lua
in your text editor of choice.
2. Project Configuration
Open Config.lua and replace the file’s contents with the configuration shown below. This will set the project’s default width, height, scale, and FPS (frames per second). The letterBox
scale setting means that the application will scale up in both directions as uniformly as possible. If necessary, however, the game will be letterboxed.
application = { content = { width = 320, height = 480, scale = "letterBox", fps = 30, } }
3. Hiding the Status Bar
To prevent the status bar from being shown at the top of the screen, add the following snippet to Main.lua
.
display.setStatusBar(display.HiddenStatusBar)
4. Local Variables
In the following code snippet, you can see a listing of the variables that we’ll be using in this game. Read the comments get an idea of each variable’s responsibility.
local gameBackground = display.newImage("background.png",true) local theWord = "" -- the word the user spells local theWordText -- shows the word local isNewGame = true local allBallsGroup -- group to hold all the balls local wordTable = {} -- will hold the words from the text file local chosenBalls = {} -- holds the balls the user has chosen local instructionsText -- shows instructions local countDownText -- shows the time local numBallsToGenerate = 10 -- how many balls to generate local allBalls = {} -- holds all the balls that have been generated local theBombs = {} -- holds references to the bomb image local generateBallTimer -- timer for generating balls local clockTimer -- timer for the clock local gameTime = 10 -- how many seconds before new balls drop
5. Setting Up the Physics
With the following snippet, we require and start the physics engine. We set the gravity high to make sure that the balls drop fast.
local physics = require("physics") physics.start(true) physics.setGravity(0,50)
6. setup
In setup
, we seed the random generator to ensure math.random
generates a random number when we need one.
function setup() math.randomseed(os.time()) end
Call setup
immediately after its declaration.
setup()
7. setupGameAssets
In the next few steps, we will stub out a number of functions, which we will implement a bit later in this tutorial.
In setupGameAssets
, the assets of the game are set up.
function setupGameAssets() end
8. newGame
The newGame
function is responsible for starting a new game.
function newGame() end
9. setupGameBoundaries
In setupGameBoundaries
, the boundaries for the balls at the left, right, and bottom of the screens are set.
function setupGameBoundaries() end
10. setupButtons
The setupButtons
function sets up the buttons.
function setupButtons() end
11. setupTextFields
In setupTextFields
, the text fields are set up.
function setupTextFields() end
12. setupBombs
This function sets up the images for the bombs.
function setupBombs() end
13. setBombsVisible
By invoking setBombsVisible
, the bombs are set to be visible.
function setBombsVisible() end
14. setBombsInvisible
This functions needs no explaining. Right?
function setBombsInvisible() end
15. generateBalls
The generateBalls
function is responsible for generating new balls at regular time intervals.
function generateBalls() end
16. startTimer
startTimer
starts the timer.
function startTimer() end
17. doCountDown
In doCountDown
, the game time is decremented.
function doCountdown() end
18. createBall
The createBall
function is responsible for creating one ball. It takes one argument, the letter the ball contains.
function createBall(createVowel) end
19. checkGameOver
In checkGameOver
, we verify if the game is over, that is, if the wall of balls has crossed the bar at the top of the screen.
function checkGameOver() end
20. checkWord
In checkWord
, we verify if the word the user spelled is valid.
function checkWord() end
21. resetWord
The resetWord
function is invoked when the player cancels a submission.
function resetWord() end
22. createVowelBalls
The createVowelBalls
function ensures that some of the balls contain a vowel. It takes one parameter, the number of balls that should contain a vowel.
function createVowelBalls(number) end
23. formString
This function creates a string from the letters on the balls that the user has chosen.
function formString(e) end
24. explode
The explode
function is invoked when the player uses a bomb. It removes five balls from the game.
function explode(e) end
25. removeBall
The removeBall
function chooses a random ball to remove from the game.
function removeBall() end
26. readTextFile
The readTextFile
function is used to read a text file and store the words it contains in a table for use in the game.
function readTextFile() end
27. Progress Check
Double-check that you’ve created stub implementations for the above functions before moving on. In the next few steps, we’ll implement each function.
28. Implementing readTextFile
In readTextFile
, we read the text file from the resource directory and store each word in wordTable
. We make use of string.sub
to trim whitespace from the end of each word.
local path = system.pathForFile( "wordlist.txt", system.ResourceDirectory) local file = io.open( path, "r" ) for line in file:lines() do line = string.sub(line, 1, #line - 1) table.insert(wordTable,line) end io.close( file ) file = nil
readTextFile
is invoked in setupGameAssets
as shown below. We’ll be updating setupGameAssets
a few more time later in this tutorial.
function setupGameAssets() readTextFile() end
29. Implementing setupGameBoundaries
In setupGameBoundaries
, the boundaries for the game are defined. lineLeft
and lineRight
are the right and left boundaries, whereas groundLine
is the bottom of the screen, the ground so to speak. These are used by the physics engine and will prevent the balls from moving outside of the game area. We set them to not be visible because we do not need to see them. The reason we’ve used -29
, is because, the radius of the balls is 29
and the physics system uses the center of objects when testing for collision.
local groundLine = display.newRect(0, 380,display.contentWidth, 2) local lineLeft = display.newRect(-29,0,2,display.contentHeight) local lineRight = display.newRect(display.contentWidth-29,0,2,display.contentHeight) physics.addBody(groundLine, 'static',{bounce=0,friction=0}) physics.addBody(lineLeft, 'static',{bounce=0,friction=0}) physics.addBody(lineRight,'static',{bounce=0,friction=0}) groundLine.isVisible = false lineLeft.isVisible = false lineRight.isVisible = false
Just like readTextFile
, setupGameBoundaries
is invoked in setupGameAssets
.
function setupGameAssets() readTextFile() setupGameBoundaries() end
30. Implementing setupButtons
Implement setupButtons
as shown below and invoke the function in setupGameAssets
.
local goButton = display.newImage("goButton.png",260,420) goButton:addEventListener('tap', checkWord) local stopButton = display.newImage("stopButton.png",5,430) stopButton:addEventListener('tap',resetWord) local bar = display.newImage("bar.png",0,100)
function setupGameAssets() readTextFile() setupGameBoundaries() setupButtons() end
31. Implementing setupTextFields
Implement setupTextFields
as shown below and invoke the function in setupGameAssets
.
countDownText = display.newText(gameTime,290,10,native.systemFontBold,20) countDownText:setTextColor("#000000") theWordText = display.newText("",60,437,native.systemFontBold,25) theWordText:setTextColor("#000000") instructionsText = display.newText("",0,0,native.systemFontBold,25) instructionsText.x = display.contentWidth/2 instructionsText.y = display.contentHeight/2 instructionsText:setTextColor("#000000")
function setupGameAssets() readTextFile() setupGameBoundaries() setupButtons() setupTextFields() end
32. Implementing setupBombs
Implement setupBombs
as shown below and invoke the function in setupGameAssets
. In setupBombs
, we generate three bomb images. By storing the images in a table, we can reference them without having to declare three separate image variables.
for i=1, 3 do local tempBomb = display.newImage("bomb.png") tempBomb.width = 30 tempBomb.height = 30 tempBomb.x = 33 * i tempBomb.y = 20 tempBomb:addEventListener('tap', explode) table.insert(theBombs,tempBomb) end
function setupGameAssets() readTextFile() setupGameBoundaries() setupButtons() setupTextFields() setupBombs() end
33. Finishing setupGameAssets
Finalize the implementation of setupGameAssets
by adding the snippet shown below. It initializes the group for the balls.
allBallsGroup = display.newGroup();
34. Finishing setup
With the setupGameAssets
function ready to use, we can invoke it in the setup
function as shown below.
function setup() math.randomseed(os.time()) setupGameAssets() end
35. Implementing createBall
We have two tables, one for the alphabet and one only for vowels as we want to ensure that some of the balls contain vowels. We then generate random values for the ballType
and ballSize
variables. The value of ballType
ranges from 1
to 4
, whereas the value of ballSize
ranges from 1
to 2
. By using these variables, we get a ball color and set its radius. The letterText
uses the random letter we generated and sets its x
and y
to be the same as the ball. We then insert both the letter and the ball in a group so they appear as one element in the game. Next, we generate a random x
position for the ball and place it at -40
for the y
position. We add physics to the ball to make sure it falls from the top of the screen to the bottom. Give it name
and letter
keys, add a tap event, and insert it into the allBalls
table as well as the allBallsGroup
table. The latter enables us to work with all the balls currently in the game.
local var alphabetArray = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"} local vowels = {"A","E","I","O","U"} local ballType = math.random(4) local ballSize = math.random(2) local letterIndex local letter if(createVowel == true) then letterIndex = math.random(#vowels) letter = vowels[letterIndex] else letterIndex = math.random(#alphabetArray); letter = alphabetArray[letterIndex] end local ballGroup = display.newGroup(); local ball local ballRadius if(ballType == 1) then ball = display.newImage("greenBall.png") elseif(ballType == 2) then ball = display.newImage("brownBall.png") elseif(ballType == 3) then ball = display.newImage("pinkBall.png") else ball = display.newImage("redBall.png") end if(ballSize == 1)then ball.width = 48 ball.height = 48 ballRadius = 24 else ball.width = 58 ball.height = 58 ballRadius = 29 end local letterText = display.newText( letter, 0,0, native.systemFontBold, 25 ); letterText:setTextColor(0,0, 0) letterText.x = ball.x letterText.y = ball.y ballGroup:insert(ball) ballGroup:insert(letterText) ballGroup.x = math.random(ballRadius,display.contentWidth-ballRadius*2) ballGroup.y= - 40 physics.addBody(ballGroup, 'dynamic',{friction = 0,bounce = 0,radius = ballRadius}) ballGroup.name = "ball" ballGroup.letter = letter ballGroup:addEventListener('tap',formString) table.insert(allBalls,ballGroup) allBallsGroup:insert(ballGroup)
If you invoke this function in setup
, you should see one ball being created and fall from the top of the screen to the bottom. Don’t forget to remove the call form the setup
method when you’re done testing.
36. Implementing generateBalls
In generateBalls
, we invoke checkGameOver
after 1500 milliseconds, which gives the balls enough time to fall past the bar. If it’s a new game, we need to generate ten balls, otherwise we generate four balls of which at least one contains a vowel. We’ll explore the implementation of createVowelBalls
shortly. If you invoke generateBalls
in setup
, you should see ten balls being generated.
function generateBalls() timer.performWithDelay(1500,checkGameOver) if(isNewGame == true) then numBallsToGenerate = 10 else numBallsToGenerate = 4 createVowelBalls(1) end generateBallTimer = timer.performWithDelay( 50, createBall, numBallsToGenerate) end
37. Implementing createVowelBalls
All this function does is invoke createBall
as many times as the value of number
that was passed to the function. We pass true
as the parameter, which means createBall
will generate a ball containing a vowel.
for i=1, number do createBall(true) end
38. Implementing removeBall
This function chooses a random ball from the allBalls
table and removes it. This function is invoked by the explode
function, which we’ll implement in a few moments.
local randomIndex = math.random(#allBalls) local tempBall = allBalls[randomIndex] tempBall:removeSelf() tempBall = nil table.remove(allBalls,randomIndex)
39. Implementing setBombsVisible
In setBombsVisible
, we loop through the bombs and setting them to visible.
for i=1, #theBombs do theBombs[i].isVisible = true end
40. Implementing setBombsInvisible
In this function, we do the exact opposite as we did in setBombsVisible
.
for i=1, #theBombs do theBombs[i].isVisible = false end
41. Implementing explode
In explode
, we check whether allBalls
contains less than five balls. If less than five balls are present, we remove all balls, otherwise we only remove five balls.
local thisSprite = e.target thisSprite.isVisible = false local randomIndex local randomBall if(#allBalls < 5) then for i=1, #allBalls do removeBall() end else for i=1, 5 do removeBall() end end
42. Implementing formString
In formString
, we form a word every time the user clicks a ball. Remember that each ball has a letter
key added to it. We check whether the chosenBalls
table doesn’t contain the ball they tapped on. If it doesn’t, we insert the ball into the chosenBalls
table, tack the letter onto the end of theWord
variable, and show it in the text field. If the ball was already chosen and added to chosenBalls
, we don’t add it to chosenBalls
and print a message to the console instead. You can already test our game by tapping some balls and seeing the word appear in the text field.
local thisSprite = e.target local theLetter = thisSprite.letter if(table.indexOf(chosenBalls,thisSprite) == nil) then table.insert(chosenBalls,thisSprite) theWord = theWord .. theLetter theWordText.text = theWord theWordText.x = display.contentWidth/2 else print("already chose that ball") end
43. Implementing resetWord
instructionsText.text = ""; theWord = '' theWordText.text = "" chosenBalls = {}
This function resets the current word and clears out the chosenBalls
table. If you test the game, you can click the cancel button to clear out the text field.
44.Implementing checkWord
In checkWord
, we check if the length of theWord
is less than or equal to one. If it is, we return from the function. We need to make sure the player has chosen a word with a minimum of two letters. We then need to check if theWord
matches a word from the wordTable
. If it doesn’t, we set the instructionsText
to NOT A WORD and show it to the player. If it does, we loop through the chosenBalls
table and remove each ball from the game.
if(#theWord <= 1) then return; end local lowerCaseWord = string.lower(theWord) local tempBall if(table.indexOf(wordTable,lowerCaseWord) == nil) then instructionsText.text = "NOT A WORD!" instructionsText:toFront() else for i=1, #chosenBalls do table.remove(allBalls,table.indexOf(allBalls,chosenBalls[i])) chosenBalls[i]:removeSelf() chosenBalls[i] = nil theWord = "" theWordText.text = "" end chosenBalls = {} end
45. Implementing doCountDown
In doCountDown
, we grab the number from the text field, decrement it, and check if it is equal to zero. If it is, we call generateBalls
, reset it, and call startTimer
, which in turn invokes doCountDown.
local currentTime = countDownText.text currentTime = currentTime -1 countDownText.text = currentTime if(currentTime == 0) then generateBalls() countDownText.text = gameTime startTimer() end
46. Implmenting startTimer
The implementation of startTimer
is simple. We call doCountdown
every second and repeat this as many times as the value of gameTime
.
clockTimer = timer.performWithDelay(1000,doCountdown,gameTime)
47. Implementing newGame
To start a new game, the variables we declared earlier are reset and startTimer
is invoked to start the game.
isNewGame = true chosenBalls = {} allBalls = {} theWord = "" theWordText.text = "" instructionsText.text = "" countDownText.text = gameTime; createVowelBalls(2) generateBalls() setBombsVisible() startTimer() isNewGame = false
48. Implementing checkGameOver
In this function, we loop through the allBalls
table and check if the y
value of any of the balls is greater than 100. If it is, one or more balls are crossing the bar at the top and the game is over. If the game is over, the balls are removed from the ballGroup
table. The timers are canceled to make the bombs invisible and newGame
is invoked after three seconds.
local gameOver = false; for i=1,#allBalls do if(allBalls[i].y < (100 - allBalls[i].height))then gameOver = true break; end end if(gameOver) then for i=allBallsGroup.numChildren,1,-1 do local child = allBallsGroup[i] child:removeSelf() child = nil end timer.cancel(generateBallTimer) timer.cancel(clockTimer) instructionsText.text = "GameOver" instructionsText:toFront() setBombsInvisible() timer.performWithDelay(3000,newGame)
49. Finishing the Game
All that is left for us to do is calling newGame
in the setup
function.
function setup() math.randomseed(os.time()) setupGameAssets() newGame() end
Conclusion
In this tutorial, we created a creative word game. Feel free to experiment with the code we’ve written to see how it affects the game. I hope you found this tutorial helpful.