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.
Comments