Defensive Firing Habits
This time the goal will be to control the firing habits of defense elements. There are several factors that can be used to control the firing habits of defenses.
Distance to enemies
The distance to an enemy can be set for defense. This gives the option choosing defenses with greater or lesser range.
Column position
In Plants vs Zombies defenses only fire at enemies in the same column and distance is not a factor. Though, this could be combined with distance for more variation.
Firing angle
In the original experiments the defenses only fire straight up the screen. There’s nothing to stop us from having defenses fire at an enemy in any column, except a few lines of code.
Firing at enemies in same column
In this experiment we’ll make the defenses fire only when there is an enemy in their column. To make this happen defenses and enemies will have to know their column number.
make_alien()
When enemies are created I choose a random column and position the enemy in that column. At this time we need to add a property variable to the enemy and set it to the column number. The place for this to happen is make_alien().
NOTE: I realized when I created the earlier version I used the name row, when I setting the column! So I changed this variable name to col.
local function make_alien() local alien = display.newRect( 0, 0, 32, 32 ) alien:setFillColor( 0, 200, 0 ) local col = math.random( 1, TILE_COLS ) -- Oops! I had previously used row instead of col here! alien.col = col -- Assign this alien his col position alien.x = col * ( TILE_SIZE + TILE_MARGIN ) alien.y = 0 alien.life = 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 ) table.insert( alien_array, alien ) end
make_grid()
When creating defense elements we tap on a tile. The tile positions the new defense at the tile’s location. Since defense will no need to know their column number, the tile will also need to know it’s column number so it can pass it along to the defense that is created. Tiles are created in the make_grid() function. Here I added one line of code assigning a new property variable, col, to each tile.
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.col = col -- Tiles need to know col
tile.has_defense = false
tile:addEventListener( "touch", touch_tile )
tile_group:insert( tile )
end
end
end
touch_tile()
Touching a tile creates a defense. Touch events on tiles are handled in the touch_tile() handler. Here we need to get the column number from the tile and assign it to the new defense that is created. Earlier I had just made a defense with getting a reference to the defense. Note that the make_defense() function does return the new defense created! I had ignored the return value previously. We’ll use it now. Here we get a reference to the new defense and assign it a new property variable.
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
local defense = make_defense( tile_x, tile_y ) -- Get a reference to the new defense defense.col = tile.col -- assign the col to the new defense
end
end
make_defense()
Next we need defenses to think before they shoot. Each defense will need to look at all of the enemies on the screen and check their col agains the col of the defense. If cols match the defense fires, others not. To do this I added an intermediate function call from the timer, rather than call make_bullet() directly from the timer.
The new function, defense_defend( defense ), takes the defense as a parameter.
The defense timer is created when the defense is created, in make_defense(). Here’s an updated version of make_defense():
local function make_defense( x, y ) local defense = display.newRect( 0, 0, 32, 32 ) defense:setFillColor( 200, 0, 0 ) defense_group:insert( defense ) defense.x = x defense.y = y table.insert( defense_array, defense ) -- Change the timer handler to call defense_defend( defense ) defense.timer = timer.performWithDelay( 1000, function() defense_defend( defense ) end, -1 ) return defense end
defene_defend()
The defense_defend( defense ) function needs to examine all of the enemies and check their col. Here I will use a loop to look at each of the enemies in the alien_array. Since defenses can only fire once, if we find a target we break the loop, and stop checking for new enemies.
-- This new function will look at the board and find eligible targets local function defense_defend( defense ) for i = 1, #alien_array, 1 do -- Loop through alien_array local alien = alien_array[i] -- get the alien if alien.col == defense.col then -- check column make_bullet( defense.x, defense.y ) break end end end
Time to test! If everything is working correctly defense should now only fire when there is an enemy in their column.
Here’s a listing the entire code for this version:
----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- -- Experiments controlling the firing habits of defense elements -- This time defense elements only fire at enemies in their row display.setStatusBar( display.HiddenStatusBar ) local TILE_ROWS = 9 local TILE_COLS = 5 local TILE_SIZE = 48 local TILE_MARGIN = 1 local BULLET_SPEED = 1000 / 400 local defense_array = {} local alien_array = {} local bullet_array = {} local game_group = display.newGroup() local defense_group = display.newGroup() local alien_group = display.newGroup() local tile_group = display.newGroup() game_group:insert( tile_group ) game_group:insert( defense_group ) game_group:insert( alien_group ) local function remove_bullet( bullet ) local index = table.indexOf( bullet_array, bullet ) transition.cancel( bullet.transition ) table.remove( bullet_array, index ) 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 table.insert( bullet_array, bullet ) local bt = y * BULLET_SPEED bullet.transition = transition.to( bullet, {y=0, time=bt, onComplete=remove_bullet} ) end -- This new function will look at the board and find eligible targets local function defense_defend( defense ) for i = 1, #alien_array, 1 do -- Loop through alien_array local alien = alien_array[i] -- get the alien if alien.col == defense.col then -- check column make_bullet( defense.x, defense.y ) break end end end local function remove_defense( defense ) local index = table.indexOf( defense_array, defense ) table.remove( defense_array, index ) 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 ) defense.x = x defense.y = y table.insert( defense_array, defense ) -- Change the timer handler to call defense_defend( defense ) defense.timer = timer.performWithDelay( 1000, function() defense_defend( defense ) 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 local defense = make_defense( tile_x, tile_y ) -- Get a reference to the new defense defense.col = tile.col -- assign the col to the new defense 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.col = col -- Tiles need to know col tile.has_defense = false tile:addEventListener( "touch", touch_tile ) tile_group:insert( tile ) end end end local function remove_alien( alien ) local index = table.indexOf( alien_array, alien ) transition.cancel( alien.transition ) table.remove( alien_array, index ) display.remove( alien ) end local function make_alien() local alien = display.newRect( 0, 0, 32, 32 ) alien:setFillColor( 0, 200, 0 ) local col = math.random( 1, TILE_COLS ) -- Oops! I had previously used row instead of col here! alien.col = col -- Assign this alien his col position alien.x = col * ( TILE_SIZE + TILE_MARGIN ) alien.y = 0 alien.life = 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 ) table.insert( alien_array, alien ) end local function hit_test( x, y, bounds ) if x > bounds.xMin and x < bounds.xMax and y > bounds.yMin and y < bounds.yMax then return true else return false end end local function on_enterframe( event ) for b = 1, #bullet_array, 1 do local bullet = bullet_array[b] if b > #bullet_array then return end for a = 1, #alien_array, 1 do local alien = alien_array[a] if hit_test( bullet.x, bullet.y, alien.contentBounds ) then if alien.life > 0 then alien.life = alien.life - 1 else remove_alien( alien ) end remove_bullet( bullet ) break end end end end Runtime:addEventListener( "enterFrame", on_enterframe ) make_grid() 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 )