Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Corona SDK: Build a Monkey Defender

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

In this tutorial, we will be creating a game called Monkey Defender using the Corona SDK! This game will serve as a great foundation for a lot of different genres include defense style games. So, let's get started!


Project Overview

In this version of Monkey Defender, the player will have to defend the monkey by shooting monkey grenades at the incoming spaceships. Every time the player successfully hits an enemy spaceship, their score will increase by one. If they fail to hit the enemy spaceship before it reaches the monkey, they will lose one banana, or one life. When the player runs out of bananas, it's game over!

This game was built with the Corona SDK and here are some of the things you'll learn:

  • How to utilize Storyboard
  • How to use widgets
  • How to rotate objects based on touch
  • How to use Corona's Collision
  • How to build a full game with the Corona SDK

Tutorial Prerequisites

In order to use this tutorial, you’ll need to have the Corona SDK installed on your computer. If you do not have the SDK, head over to http://www.coronalabs.com to create a free account and download the free software.

To download the graphics that I used for the game, please download the source files attached to this post. The graphics for this game come from www.opengameart.org and www.vickiwenderlich.com. The background graphic comes from Sauer2 at Open Game Art and the rest of the graphics come Vicki Wenderlich. If you decide to publish this game with these graphics, please be sure to credit both artists.


1. Build Configuration

The first step to build our game, Monkey Defender, is to create a new file called build.settings and place it inside of your project folder. The build.settings file handles all of the build time properties inside of our app. For our game, we only need to worry about the orientation of the game, and we are only going to allow landscape mode.

settings = {
  orientation = {
    default = "landscapeRight",
    supported = { "landscapeRight", "landscapeLeft"}
  },
}

2. Runtime Configuration

Next, we will create another file called config.lua and place it within your project folder. The config.lua file handles all of our runtime configurations such as the height, width, scaling type, and frame rate. For our game, we are going to set up our app to be 320x480, to be in a letter box and to have 30 frames per second.

application = {
  content = {
    width = 320,
    height = 480,
    scale = "letterBox",
    fps = 30,
  },
}

3. Building main.lua

Now that we have our project configured, we can move forward with creating main.lua. The main.lua file is the starting point of every app built with the Corona SDK, so create a new file called main.lua and move it to your project folder. Inside of our main.lua file, we will hide the status bar, add some global variables, and use Corona's Storyboard feature to manage our scenes.

-- hide the status bar
display.setStatusBar( display.HiddenStatusBar )

-- Set up some global variables for our game
screenW, screenH, halfW, halfH = display.contentWidth, display.contentHeight, display.contentWidth*0.5, display.contentHeight*0.5

-- include the Corona "storyboard" module
local storyboard = require "storyboard"

-- load menu screen
storyboard.gotoScene( "menu" )

4. Building the Menu

With our main.lua file set up, we are going to move on to our menu. The menu for this game will be simple. It will display the title of the game and a button to start the game.

Step 1

To get started, create a new file called menu.lua and place the file in your project folder. The first addition to this file is to add a storyboard and the widget library. While the storyboard will allow us to easily manage our scene, the widget library will allow us to easily add common elements to our app. In this case, we will be using the widget library to add a button to our menu. We'll get to that later.

local storyboard = require( "storyboard" )
local scene = storyboard.newScene()

local widget = require "widget"

Step 2

After the requires, we will create our first function called scene:createScene(). This function will be called when the scene does not exist and is a perfect place for our game title and button.

-- Called when the scene's view does not exist:
function scene:createScene( event )
  local group = self.view
end

Step 3

Inside of our scene:createScene() function, we will create a new display object that will be used as our background. If you haven't already, make sure you download the source files for this tutorial and place all of the graphics inside a folder named images in your project.

The background display object will be centered on the screen and inserted into our group variable. By inserting the display object into our group variable, we are telling Corona that this object belongs to this scene. When we switch scenes, Corona will know to remove this object or hide it because we are no longer viewing the menu scene.

An in-depth look at storyboards is outside of the scope of this tutorial, but you can read more at the official documentation.

-- Insert a background into the game
local background = display.newImageRect("images/background.png", 480, 320)
  background.x = halfW
  background.y = halfH
  group:insert(background)

Step 4

After our background object, we will place a text object that will display the title of our game. This text object will be centered on the screen and inserted into the group variable.

-- Insert the game title
local gameTitle = display.newText("Space Monkey",0,0,native.systemFontBold,32)
  gameTitle.x = halfW
  gameTitle.y = halfH - 80
  group:insert(gameTitle)

Step 5

Our last addition to the function scene:createScene() will be a button widget. This widget will allow players to start the game. Before we can add the widget, we need to create a function that will handle the touch event.

When the button is touched, the following function will be called. This function will send players to the game scene, which we will be creating later.

-- This function will only be fired when the widget playBtn is touched.
local function onPlayBtnRelease()
  storyboard.gotoScene( "game", "fade", 500 )
  return true
end

Step 6

After the onPlayBtnRelease function, we will then add the button to the menu scene. We add the button by using widget.newButton with a couple of parameters. The label property will set the text of our button and the onRelease property will tell our app which function to fire when it's touched. Then, we will place the button in the center of the screen and insert it into the group variable. The playBtn will be the last piece added to the scene:createScene() function.

-- Create a widget button that will let the player start the game
local playBtn = widget.newButton
{
  label="Play Now",
  onRelease = onPlayBtnRelease
}

playBtn.x = halfW
playBtn.y = halfH
group:insert( playBtn )

Step 7

Right after the scene:createScene() function, we are going to add the scene:enterScene() function. This function will be called after the scene is created and it's on the screen.

function scene:enterScene(event)
  local group = self.view

  if(storyboard.getPrevious() ~= nil) then
    storyboard.purgeScene(storyboard.getPrevious())
    storyboard.removeScene(storyboard.getPrevious())
  end
end

Step 8

To wrap up our menu.lua file, we will add two event listeners and return the scene variable. This is an important step because the two event listeners will fire the appropriate functions and return the scene variable to signify the end of the file.

scene:addEventListener("createScene", scene)
scene:addEventListener("enterScene", scene)

return scene

5. Game Logic

By now, we've completed the configuration, the main.lua file and the menu.lua file. Next, we are going to create the logic for our game.

Step 1

The game logic will be held inside a file called game.lua. To get started, create a new file called game.lua and place it inside your project folder. Our first addition to the new file is to require Corona's Storyboard.

local storyboard = require( "storyboard" )
local scene = storyboard.newScene()

Step 2

Next, we will add physics to our game. We will use physics to make collision detection between the bullets and the enemy ships easier. Right after the physics capability is added, we will pause it so it doesn't interfere with the creation of the scene.

local physics = require "physics"
physics.start(); physics.pause()

Step 3

After the physics, we will predefine the variables for our game.

local background, monkey, bullet, txt_score
local tmr_createBadGuy
local lives = {}
local badGuy = {}
local badGuyCounter = 1
local score = 0

Step 4

The next set of variables will be used as settings for our game. Feel free to change the speeds of the game or increase the number of lives for the player.

local numberOfLives = 3
local bulletSpeed = 0.35
local badGuyMovementSpeed = 1500
local badGuyCreationSpeed = 1000

Step 5

Now we will create our scene:createScene function.

-- Called when the scene's view does not exist:
function scene:createScene( event )
local group = self.view

Step 6

Next, we will create a function that will be fired when the background display object is touched, and this function will contain the bulk of our game logic. When fired, we will rotate the monkey towards the touch event and fire a bullet towards the same touch event.

First, let's create the touched function and create a conditional statement so our logic only runs during the begin phase.

function touched(event)
  if(event.phase == "began") then

Within the if-then statement, we use the math library to determine the angle between the monkey and the touch event. This is accomplished by using a combination of math.deg and math.atan2 to find where the monkey needs to be rotated. After the rotation degree is found, we rotate the monkey to the appropriate position.

  angle = math.deg(math.atan2((event.y-monkey.y),(event.x-monkey.x)))
  monkey.rotation = angle + 90

Since this function is going to fire a bullet, we have to create the bullet as a display object. Once it's created, we will make the physics object so it can respond to collisions with the enemies.

  bullet = display.newImageRect("images/grenade_red.png",12,16)
  bullet.x = halfW
  bullet.y = halfH
  bullet.name = "bullet"
  physics.addBody( bullet, "dynamic", { isSensor=true, radius=screenH*.025} )
  group:insert(bullet)

Now that we have our bullet, we need to find out where to send it. To do this, we first determine whether we need to send the bullet to the left or right and then use the y = mx + b formula to determine the y location. Our last bit of math is to find the distance between the two points so we can determine how fast to project the bullet.

  -- Find out if we need to fire the bullet to the left or right
  local farX = screenW*2

  if(event.xStart >= screenW/2)then
    farX = screenW*2
  else
    farX = screenW-(screenW*2)
  end

  -- Use y = mx + b to find the Y position
  local slope = ((event.yStart-screenH/2)/(event.xStart-screenW/2))
  local yInt = event.yStart - (slope*event.xStart)
  local farY = (slope*farX)+yInt

  -- Get the distance from the bullet to bullet destination
  local xfactor = farX-bullet.x
  local yfactor = farY-bullet.y
  local distance = math.sqrt((xfactor*xfactor) + (yfactor*yfactor))

Now that we know the distance and the x/y coordinates of the bullet destination, we can send our bullet to the destination using transition.to. We'll also need to include a couple of end statements to wrap up the touched function.

  bullet.trans = transition.to(bullet, { time=distance/bulletSpeed, y=farY, x=farX, onComplete=nil})
  end
end

Step 7

After all that math, the next few steps are simple. We will add a background to our game (we will attach an event listener to the background later), the monkey, and a text object to display the score.

-- Create a background for our game
background = display.newImageRect("images/background.png", 480, 320)
  background.x = halfW
  background.y = halfH
  background:setFillColor( 128 )
  group:insert(background)

-- Place our monkey in the center of screen
monkey = display.newImageRect("images/spacemonkey-01.png",30,40)
  monkey.x = halfW
  monkey.y = halfH
  group:insert(monkey)

-- Create a text object for our score
txt_score = display.newText("Score: "..score,0,0,native.systemFont,22)
  txt_score.x = 430
  group:insert(txt_score)

We will also insert three bananas to represent three player lives in the top right of the screen.

-- Insert our lives, but show them as bananas
for i=1,numberOfLives do
  lives[i] = display.newImageRect("images/banana.png",45,34)
  lives[i].x = i*40-20
  lives[i].y = 18
  group:insert(lives[i])
end

Step 8

Next, we will create a function that will send bad guys toward our player. We are going to call this function createBadGuy and we will first determine which direction to send the bad guy from.

-- This function will create our bad guy
function createBadGuy()

  -- Determine the enemies starting position
  local startingPosition = math.random(1,4)

  if(startingPosition == 1) then
    -- Send bad guy from left side of the screen
    startingX = -10
    startingY = math.random(0,screenH)
  elseif(startingPosition == 2) then
    -- Send bad guy from right side of the screen
    startingX = screenW + 10
    startingY = math.random(0,screenH)
  elseif(startingPosition == 3) then
    -- Send bad guy from the top of the screen
    startingX = math.random(0,screenW)
    startingY = -10
  else
    -- Send bad guy from the bototm of the screen
    startingX = math.random(0,screenW)
    startingY = screenH + 10
end

After we have determined the direction that our bad guy will come from, we will then create a new display object for our bad guy and turn the bad guy into a physics object. We’ll use physics to detect collisions later on.

-- Start the bad guy according to starting position
badGuy[badGuyCounter] = display.newImageRect("images/alien_1.png",34,34)
  badGuy[badGuyCounter].x = startingX
  badGuy[badGuyCounter].y = startingY
  physics.addBody( badGuy[badGuyCounter], "dynamic", { isSensor=true, radius=17} )
  badGuy[badGuyCounter].name = "badGuy"
  group:insert(badGuy[badGuyCounter])

Then, we will use Corona’s transition.to to move the bad guy towards the center of the screen. Once the transition is done and the enemy has not been hit, we will remove the bad guy and subtract one life from the player.

If the player’s lives have reached 0 or less, we will stop sending the bad guys and remove the player’s ability to send more bullets. We will also show two text objects to signify that the game is over and let the player return to the menu.

badGuy[badGuyCounter].trans = transition.to(badGuy[badGuyCounter], {
  time=badGuyMovementSpeed, x=halfW, y=halfH,
  onComplete = function (self)
  self.parent:remove(self);
  self = nil;

  -- Since the bad guy has reached the monkey, we will want to remove a banana
  display.remove(lives[numberOfLives])
  numberOfLives = numberOfLives - 1

  -- If the numbers of lives reaches 0 or less, it's game over!
  if(numberOfLives <= 0) then
    timer.cancel(tmr_createBadGuy)
    background:removeEventListener("touch", touched)

    local txt_gameover = display.newText("Game Over!",0,0,native.systemFont,32)
      txt_gameover.x = halfW
      txt_gameover.y = screenH * 0.3
      group:insert(txt_gameover)

      local function onGameOverTouch(event)
      if(event.phase == "began") then
        storyboard.gotoScene("menu")
      end
  end

  local txt_gameover = display.newText("Return To Menu",0,0,native.systemFont,32)
    txt_gameover.x = halfW
    txt_gameover.y = screenH * 0.7
    txt_gameover:addEventListener("touch",onGameOverTouch)
    group:insert(txt_gameover)
  end
end; })

badGuyCounter = badGuyCounter + 1
end

Step 9

The last function inside of scene:createScene is for collision detection. When a bullet and bad guy collide, this function will be trigged and the following will happen:

  • Add 1 to the player score and update the score text object.
  • Set the alpha of both objects to 0.
  • Cancel the transition on each object so it does not interfere with the removal process.
  • Finally, we will create a timer that will wait 1 millisecond before removing both objects. It’s always a bad idea to remove display objects in the middle of collision detection.
function onCollision( event )
  if(event.object1.name == "badGuy" and event.object2.name == "bullet" or  event.object1.name == "bullet" and event.object2.name == "badGuy") then

    -- Update the score
    score = score + 1
    txt_score.text = "Score: "..score

    -- Make the objects invisible
    event.object1.alpha = 0
    event.object2.alpha = 0

    -- Cancel the transitions on the object
    transition.cancel(event.object1.trans)
    transition.cancel(event.object2.trans)

    -- Then remove the object after 1 cycle. Never remove display objects in the middle of collision detection.
    local function removeObjects()
      display.remove(event.object1)
      display.remove(event.object2)
    end

  timer.performWithDelay(1, removeObjects, 1)

  end
  end
end

Step 10

After the scene:createScene, we will then write our enter scene function. This function is fired after the scene is created and moved on to the screen. Inside the enter scene function, we will start all of the functions that we wrote in the create scene function.

function scene:enterScene( event )
  local group = self.view

  -- Actually start the game!
  physics.start()

  -- Start sending the bad guys
  tmr_createBadGuy = timer.performWithDelay(badGuyCreationSpeed, createBadGuy, 0)

  -- Start listening for the background touch event to fire the bullets
  background:addEventListener("touch", touched)

  -- Start the listener to remove bad guys and bullets when they collide
  Runtime:addEventListener( "collision", onCollision )
End

Step 11

We are almost done! The last piece of our code is to add the scene event listeners and return the variable scene. The event listeners will trigger our functions and the return statement lets Corona know we are done with this scene.

scene:addEventListener( "createScene", scene )
scene:addEventListener( "enterScene", scene )

return scene

Conclusion

I hope you enjoyed this tutorial on creating a Monkey Defender game with the Corona SDK! We covered a lot of topics ranging from storyboards to geometric formulas! If you have questions or comments, please leave them below and thank you for reading.

Advertisement