Corona – Tower Defense Game Experiments 6.1

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 )

Leave a Reply

Your email address will not be published. Required fields are marked *