Bullets
In the last couple posts I modeled some simple ideas for creating a Tower Defense game like Plants vs Zombies. So far I have a tiled background, enemies that advance down the screen, and the ability to add a defense at any particular tile.
At this point the defense and the enemies still do not interact. The next step is to give the defense elements the ability to fire bullets at the advancing enemies.
Bullet Factory
Throughout the process I have been using a system based on “factory functions” to create new objects. I like this method it keeps me organized. I’ll use it again to create bullets. Though Lua is not an OOP language factory functions have some the feeling of using new to create an instance.
The bullets will be simple circles. I will use a transition.to() to move the bullets up the screen.
The make_bullet() function takes the starting x and y position of the bullet.
local function remove_bullet( bullet ) display.remove( bullet ) end local function make_bullet( x, y ) local bullet = display.newCircle( 0, 0, 5 ) bullet:setFillColor( 0, 0, 0 ) bullet.x = x bullet.y = y bullet.transition = transition.to( bullet, {y=0, time=1000, onComplete=remove_bullet} ) end
Calling functions from timer.perfromWithDelay()
Now it’s time to send the bullets up the screen. To make this happen I’ll use a timer. Each defense element will need a timer. I’m adding a reference to each timer as it is created, to the defense element the timer is created with. This way if a defense element is destroyed in the future that element will be able to cancel it’s timer.
defense.timer = timer.performWithDelay( 1000, function() make_bullet( defense.x, defense.y ) end, -1 )
The second parameter is a function, that is called with each timer event. We need to call make_bullet(x,y), this function requires the x and y location of the bullet. We can’t write this as:
-- WRONG!
defense.timer = timer.performWithDelay( 1000, make_bullet( defense.x, defense.y ), -1 )
Here we would be invoking make_bullet( defense.x, defense.y ) at the time we created he timer. The effect would be one bullet fired, rather than firing a bullet with each timer event. The timer in this case would act on what was RETURNED from make_bullet(), which would be nil.
In the correct format above I wrapped the call to make_bullet( defense.x, defense.y ) in a function. This is interesting because defense is a local variable and lost when make_defense() ends. The handler function defined inside the timer function encloses the variable defenseĀ (defense.x and defense.y) effectively preserving the values they had at the time that this function was defined.
Since this project has gone through a few changes, I’ll post the full source code I have been using up to this point for reference.
----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- display.setStatusBar( display.HiddenStatusBar ) local tile_rows = 9 local tile_cols = 5 local tile_size = 48 local tile_margin = 1 local defense_array = {} -- Make an array to hold the defenses local alien_array = {} -- make an array to hold enemies. local game_group = display.newGroup() local defense_group = display.newGroup() -- make a group to hold each class local alien_group = display.newGroup() -- local tile_group = display.newGroup() game_group:insert( tile_group ) -- Add each group to the main group game_group:insert( defense_group ) game_group:insert( alien_group ) local function remove_bullet( bullet ) display.remove( bullet ) end local function make_bullet( x, y ) local bullet = display.newCircle( 0, 0, 5 ) bullet:setFillColor( 0, 0, 0 ) bullet.x = x bullet.y = y bullet.transition = transition.to( bullet, {y=0, time=1000, onComplete=remove_bullet} ) end local function remove_defense( defense ) local index = table.indexOf( defense_array, defense ) -- get the index of this element table.remove( defense_array, index ) -- delete this element from the array display.remove( defense ) end local function make_defense( x, y ) local defense = display.newRect( 0, 0, 32, 32 ) defense:setFillColor( 200, 0, 0 ) defense_group:insert( defense ) -- Add defense to the proper group defense.x = x defense.y = y table.insert( defense_array, defense ) -- Add this defense to the array defense.timer = timer.performWithDelay( 1000, function() make_bullet( defense.x, defense.y ) end, -1 ) return defense end local function touch_tile( event ) local phase = event.phase if phase == "began" then local tile = event.target local tile_x = tile.x local tile_y = tile.y make_defense( tile_x, tile_y ) end end local function make_grid() for row = 1, tile_rows, 1 do for col = 1, tile_cols, 1 do local tile = display.newRect( 0, 0, tile_size, tile_size ) tile.x = ( tile_size + tile_margin ) * col tile.y = ( tile_size + tile_margin ) * row tile.has_defense = false -- mark this tile as NOT having a defense when we begin. tile:addEventListener( "touch", touch_tile ) tile_group:insert( tile ) -- Add tiles to the tile group end end end local function remove_alien( alien ) local index = table.indexOf( alien ) -- Get the index table.remove( alien_array, index ) -- Remove the element display.remove( alien ) end local function make_alien() local alien = display.newRect( 0, 0, 32, 32 ) alien:setFillColor( 0, 200, 0 ) local row = math.random( 1, tile_cols ) alien.x = row * ( tile_size + tile_margin ) alien.y = 0 alien.life = 5 -- Assign each alien a life of 5. local target_y = ( tile_size + tile_margin ) * tile_rows local t = ( tile_rows + 1 ) * 2000 alien.transition = transition.to( alien, {y=target_y, time=t, onComplete=remove_alien} ) alien_group:insert( alien ) -- Add aliens to the alien group table.insert( alien_array, alien ) end make_grid() -- make_alien() local alien_timer = timer.performWithDelay( 5300, make_alien, -1) local memory_text = display.newText( "Hello", 5, 5, native.systemFont, 16 ) memory_text:setTextColor( 255, 0, 0 ) memory_text.x = display.contentCenterX local monitorMem = function() collectgarbage() local textMem = system.getInfo( "textureMemoryUsed" ) / 1000000 memory_text.text = "Mem:"..collectgarbage("count") .. " tex:".. textMem end Runtime:addEventListener( "enterFrame", monitorMem )
You’ll see at the end I have a small block of code for displaying memory usage. You should see the numbers go up to a point and they stop at some point. This shows that objects are getting cleared out of memory as they are removed. We’ll keep track of this, and remove it in the final version.