Bug Catcher - A Game Tutorial

Here is a new Tutorial that shows you how to create a simple yet entertaining game for kids, it is called the Bug Catcher, no it does not help you debug your apps...

The Game

The game is simple, you can tap the big frog in the middle of the pond, he will flick his tongue and catch the fly. You have a certain number of flies that can be spawned and each time you catch a fly successfully, the score increases.



The dependencies

SpriteLoq library - The graphics are made by Jake using Flash, so he had SWF files that he has converted using SpriteLoq

How do we do this

The game works on the concept of a central loop, the enterFrame that shall regulate all of the things, We can use timers, but that is not such a clean option in comparison to enterFrame

function onEnterFrame(event)
 --the heart beat of this app
 if counter >= tries and fliesOnScreen == 0 then
  print("Game Over")

  local text = display.newText("Game Over", 0,0, native.systemFontBold, 256)
  text:scale(0.5,0.5)
  text.x = screenW/2
  text.y = screenH/2
  text.alpha=0
  transition.to(text,{time=1000, alpha=1})
  
  Runtime:removeEventListener("enterFrame",onEnterFrame)  
 end
 
 
 if fliesOnScreen < maxFlies then
  timeBetweenFlies = timeBetweenFlies - 1
  if timeBetweenFlies==0 then 
   spawnFly()
   timeBetweenFlies = random(1,10)+20
  end
 end
 
 local k,v
 for k,v in pairs(flies)do
  local theFly = v
   theFly.x = theFly.x + theFly.speed
   if theFly.x > 1550 then
    theFly.canMove = false
    flies[k]=nil
    removeFly(v)
   end
 end
 
end


function spawnFly()
 if fliesOnScreen >= maxFlies then return end
 
 local theFly = _fly.new()
 if theFly == nil then return end
 
 theFly:play("aniFly fly")
 flies[theFly.ID] = theFly
 
 fliesOnScreen = fliesOnScreen + 1
 
 --print("Flies on screen", fliesOnScreen)
end

So what we do is first check if we have had as many flies spawned as we allocated for the level, this is held in the variable counter, which gets incremented everytime we spawn a fly.
if we have spawned as many flies as we need for the level, then we print Game Over to the console, you can add a director scene to either go to the next level or show the game over screen. We just show a message that says Game Over

Then we check if there are as many flies spawned as we allow on the screen, this is handled by the maxFlies variable. We set a variable time between the flies to be spawned using random and countDown to that before we spawn a fly. Then we iterate through every fly on screen and move it based on the speed we assigned to it, some flies will be faster, some will be slower.

local function catchFly(event)
 --Play the frog aniamtion
    frog:play("aniFrog eat")

 --Determine if the fly is at the position where the frogs tongue is...
 --1240 - 1280 is the window that we offer for the frog to catch the fly.
 
 local theFly = checkIfAnyFlyIsWithin(1240,1300)
 if theFly ~= nil then
  caughtFly(theFly)
 end
end

There is also an eventHandler that we attach to the frog sprite to handle taps, CatchFly, what we do in this function is call animate the frog flicking his tongue and then check if any fly is in the range of x1 and x2 co-ordinates. This returns the fly that is in that range, so we can then remove or manipulate that fly using the caughtFly function

local function checkIfAnyFlyIsWithin(x1, x2)
 local k,v
 for k,v in pairs(flies) do
  if v.x > x1 and v.x < x2 then
   return v
  end
 end
end
function caughtFly(thisFly)
 fliesCaught = fliesCaught + 1
 --update the score
 numberCount.text = string.format("%02d", fliesCaught)

 --and let us remove this fly
 removeFly(thisFly)
end
The caughtFly function increases the score by 1 and displays it on screen at the foot of the frog and then calls another function to remove that fly, removeFly
function removeFly(thisOne)
 thisOne:removeSelf()
    flies[thisOne.ID] = nil
 thisOne = nil
 --print("Fly Removed")
 fliesOnScreen = fliesOnScreen - 1
end
The removeFly function first removes the display item the fly animation, then it removes the item from the array and also updates that fliesOnScreen so we know how many flies are there on the screen.
function _fly.new()
 if tries < counter then return nil end
 
 local flyfactory = loqsprite.newFactory("fly")
 local aFly = flyfactory:newSpriteGroup()
 aFly.x = 200; aFly.y = 350
 aFly.ID = "Item_" .. counter
 aFly.speed = random(1,6)+3
 aFly.canMove = true
 
 counter= counter + 1
 return aFly
end
When we spawn a new fly, what we are doing is just creating a new fly and setting the variables to it, this is best handled outside in its own self, it returns nil if we have created as many flies as we allow for the level, otherwise it returns a valid fly.

The full code

display.setStatusBar( display.HiddenStatusBar ) -- HIDE STATUS BAR

--Math Functions
local random = math.random

--requires Loq Sprite
local loqsprite = require('loq_sprite')

--forward Declaration
local caughtFly, removeFly, spawnFly, catchFly
local onEnterFrame


--Variable we need for the game
local i
local fliesCaught = 0
local flies = {}
local fly
local maxFlies = 10
local fliesOnScreen = 0
local _fly={}
local counter = 0
local timeBetweenFlies = 1
local tries = 10

local screenW = display.contentWidth
local screenH = display.contentHeight


--Start creating the Elements on screen
--The background
local BG = display.newImageRect("background.png", 1024, 768)
BG.x = screenW/2;  BG.y = screenH/2 

--The Frog
local ffactory = loqsprite.newFactory("frog")
local frog = ffactory:newSpriteGroup()
frog.x = 640 ; frog.y = 470

--The Score Text
local numberCount = display.newText(string.format("%02d", 0), 0, 0, native.systemFontBold, 40)
numberCount.x = frog.x; numberCount.y = frog.y

function caughtFly(thisFly)
 fliesCaught = fliesCaught + 1
 --update the score
 numberCount.text = string.format("%02d", fliesCaught)

 --and let us remove this fly
 removeFly(thisFly)
end

function removeFly(thisOne)
 thisOne:removeSelf()
    flies[thisOne.ID] = nil
 thisOne = nil
 --print("Fly Removed")
 fliesOnScreen = fliesOnScreen - 1
end


function spawnFly()
 if fliesOnScreen >= maxFlies then return end
 
 local theFly = _fly.new()
 if theFly == nil then return end
 
 theFly:play("aniFly fly")
 flies[theFly.ID] = theFly
 
 fliesOnScreen = fliesOnScreen + 1
 
 --print("Flies on screen", fliesOnScreen)
end

local function checkIfAnyFlyIsWithin(x1, x2)
 local k,v
 for k,v in pairs(flies) do
  if v.x > x1 and v.x < x2 then
   return v
  end
 end
end

local function catchFly(event)
 --Play the frog aniamtion
    frog:play("aniFrog eat")

 --Determine if the fly is at the position where the frogs tongue is...
 --1240 - 1280 is the window that we offer for the frog to catch the fly.
 
 local theFly = checkIfAnyFlyIsWithin(1240,1300)
 if theFly ~= nil then
  caughtFly(theFly)
 end
end

function onEnterFrame(event)
 --the heart beat of this app
 if counter >= tries and fliesOnScreen == 0 then
  print("Game Over")

  local text = display.newText("Game Over", 0,0, native.systemFontBold, 256)
  text:scale(0.5,0.5)
  text.x = screenW/2
  text.y = screenH/2
  text.alpha=0
  transition.to(text,{time=1000, alpha=1})
  
  Runtime:removeEventListener("enterFrame",onEnterFrame)  
 end
 
 
 if fliesOnScreen < maxFlies then
  timeBetweenFlies = timeBetweenFlies - 1
  if timeBetweenFlies==0 then 
   spawnFly()
   timeBetweenFlies = random(1,10)+20
  end
 end
 
 local k,v
 for k,v in pairs(flies)do
  local theFly = v
   theFly.x = theFly.x + theFly.speed
   if theFly.x > 1550 then
    theFly.canMove = false
    flies[k]=nil
    removeFly(v)
   end
 end
 
end

Runtime:addEventListener("enterFrame",onEnterFrame)
frog:addEventListener("tap", catchFly)

function _fly.new()
 if tries < counter then return nil end
 
 local flyfactory = loqsprite.newFactory("fly")
 local aFly = flyfactory:newSpriteGroup()
 aFly.x = 200; aFly.y = 350
 aFly.ID = "Item_" .. counter
 aFly.speed = random(1,6)+3
 aFly.canMove = true
 
 counter= counter + 1
 return aFly
end

the project in ZIP format

COMING SOON... once this hits about 500 reads
As one anonymous reader left a note that by the time this post would reach 500 hits the reader would have forgotten about this, it made me wonder and...to make it worth everyones time, including mine. There will be no resources available for this project. The code is up there, the graphics are courtesy of Jake, who has requested that they not be distributed. So you can use whatever graphics you want instead. I can give you the dimensions of the images if that would help.

Credits and background

I'd like to thank Jake Scarano for the lovely Artwork and the idea of this game. He has kindly provided the permission to use this for the tutorial. I would also like to tell you of the interesting fact on how this tutorial came into being. Generally I eyeball and dry run most of the code on the forums, and hence when there is a long listing, I disregard that post, that's too much effort and a lot of redundancy. In some cases there is hardly any code provided which also makes it difficult to resolve.

So while I was fixing the spelling errors (trying to be a spelling Nazi :( ) I understood what Jake was trying to do and he agreed for this to become a tutorial post. So, though the wrong reasons, it worked out quite well.. Thanks again Jake...

Comments

  1. This is an AWESOME tutorial! It covers some key things one onEnterFrame events!

    ReplyDelete

Post a Comment

Popular Posts