Advertisement
Corona SDK

Corona SDK: Make a Word Drop Game

by

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.

gamescreen

1. New Project

Open the Corona Simulator and choose New Project.

CoronaSDK_BlackBox_15

On the next screen, apply the following settings.

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.

Related Posts