Corona – Tower Defense Game Experiments 11

So far we have the makings of a game but, there are more than a few that need to be taken care of. For example, the buttons do not show the energy cost. This makes it hard to play and test, since it’s hard to tell what the cost is for defenses.

Groups as layers again

The buttons that I had created earlier were simple shapes. To show both text and a shape we need to create a group and add both elements to the group. Groups in Corona have all of the common display properties available to other display objects.

To make this work out I created a group for each button and used this to replace the original shape I had used for the button. Then I added the original rounded rectangle to the group. Then created a new Text shape and added that to the new group also.

I need to keep a reference to the rounded rectangle shape and text field. So I added properties to the group to hold these.

From here I retooled the rest of the script so all of the properties that had been attached to the button shape were now attached to the button_group. I also inserted the button_group into the defense_button_array in place of the original button shape. I also added the button_group into the control_group.

To get the costs to chose up in the text field I needed to get the cost from the defense_type_array for the current defense type and set it as the text of button_text.

Last I needed to adjust the positions of all three objects: button_group, button, and button_text.

local function make_defense_buttons()
	for i = 1, #defense_type_array, 1 do
		local button_group = display.newGroup() -- Make a group to hold all button elements

		local button = display.newRoundedRect( 0, 0, 40, 40, 6 )
		local r = 255 * ( i / #defense_type_array )
		button:setFillColor( r, 0, 0 )
		button:setStrokeColor( 255, 255, 255 )
		defense_type_array[i].red = r 

		local button_text = display.newText( "0", 0, 0, native.systemFontBold, 14 ) -- make a text field
		button_text.text = defense_type_array[i].cost -- Display the cost
		button_text.x = 20	-- Set the position of the label
		button_text.y = 20	-- Set the position of the label

		button_group:insert( button ) 		-- Add button shape to group
		button_group:insert( button_text ) 	-- Add text to group

		button_group.shape = button -- add button shape as a property to group
		button_group.label = button_text -- add button text as property of group

		-- Assign these properties to button group
		button_group.index = i
		button_group.x = display.contentWidth - 45 -- adjust the position
		button_group.y = 40 + ( i * 50 )
		button_group:addEventListener( "touch", touch_defense_button )

		table.insert(defense_button_array, button_group ) -- Add button group to array
		control_group:insert( button_group ) -- add button group to control group
	end
end

The select_defense_button() function needs a little work also. This function needs to now set the stroke of the shape rather than the button. After the changes above the button will be a group rather than a shape.

local function select_defense_button()
	for i = 1, #defense_button_array, 1 do
		local button = defense_button_array[i]
		if button.index == current_defense_type then
			button.shape.strokeWidth = 3 -- set the stroke of the shape
		else
			button.shape.strokeWidth = 0 -- set the stroke of the shape
		end
	end
end

Showing disabled buttons

If a defense is not available to a player, due to not having enough energy to meet the cost, it should display as disabled. For this example I’ll make buttons that are disabled appear transparent. When a button is available it will be opaque.

Add a new function to handle this above touch_defense_button(). This new function will loop through each button in the defense_button_array and compare it’s cost to the current energy value and set it’s alpha accordingly.

-- *** Add a new function to enable and disable buttons based on energy available
local function enable_disable_buttons()
	for i = 1, #defense_button_array,1 do 
		local button = defense_button_array[i]
		local cost = button.cost
		if cost < energy then 
			button.enabled = true
			button.alpha = 1.0
		else 
			button.enabled = false
			button.alpha = 0.5
		end 
	end
end 

To set up the initial state for these buttons we should call on this function when the game begins. Place a call below make_defense_buttons().

enable_disable_buttons()

Last, when the energy value changes we need to update the buttons to show a new state. Find the energy_recharge() function and add a call to enable_disable_buttons() there.

local function energy_recharge()
	energy = energy + ENERGY_RECHARGE_RATE
	enable_disable_buttons() -- *** Update buttons 
	update_energy()
end>

This works here but might need to be rethought if the recharge rate were slower.

Here's a full listing of the code

-----------------------------------------------------------------------------------------
--
-- main.lua
--
-----------------------------------------------------------------------------------------


-- The goal of this example will be to turn enemies, defense and buttons into 
-- group objects that contain more than one element. 
-- The 


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 ALIEN_SPEED = 1000 / 20 
local ENERGY_RECHARGE_RATE = 1 
local ENERGY_TIMER_TIME = 150

local alien_timer
local energy_timer 
local energy = 0 
local current_defense_type = 1 
local energy_text 
local alien_target_y = ( TILE_SIZE + TILE_MARGIN ) * TILE_ROWS 

local defense_array = {} 	
local alien_array = {}		
local bullet_array = {}	

local defense_type_array = {
			{name="Standard",	rof=1000,	damage=1,	cost=50},
			{name="Rapid",		rof=800,	damage=1,	cost=75},
			{name="Heavy",		rof=2000,	damage=3,	cost=100},
			{name="Stun",		rof=1200,	damage=0.5,	cost=80}
							}
							
local defense_button_array = {} 

local game_group = display.newGroup()
local defense_group = display.newGroup() 	
local alien_group = display.newGroup()	
local tile_group = display.newGroup()
local control_group = display.newGroup() 

game_group:insert( tile_group )		
game_group:insert( defense_group )	
game_group:insert( alien_group )



local function select_defense_button()
	for i = 1, #defense_button_array, 1 do 
		local button = defense_button_array[i]
		if button.index == current_defense_type then 
			button.shape.strokeWidth = 3 -- set the stroke of the shape
		else
			button.shape.strokeWidth = 0 -- set the stroke of the shape
		end 
	end
end

-- *** Add a new function to enable and disable buttons based on energy available
local function enable_disable_buttons()
	for i = 1, #defense_button_array,1 do 
		local button = defense_button_array[i]
		local cost = button.cost
		if cost < energy then 
			button.enabled = true
			button.alpha = 1.0
		else 
			button.enabled = false
			button.alpha = 0.5
		end 
	end
end 

local function touch_defense_button( event )
	local button = event.target
	current_defense_type = button.index
	select_defense_button()
end

local function make_defense_buttons()
	for i = 1, #defense_type_array, 1 do 
		local button_group = display.newGroup() -- Make a group to hold all button elements
		
		local button = display.newRoundedRect( 0, 0, 40, 40, 6 )
		local r = 255 * ( i / #defense_type_array )
		button:setFillColor( r, 0, 0 )
		button:setStrokeColor( 255, 255, 255 )
		defense_type_array[i].red = r 
		
		button_group.cost = defense_type_array[i].cost -- *** add cost
		button_group.enabled = false -- *** add a property to mark this button as enabled
		
		local button_text = display.newText( "0", 0, 0, native.systemFontBold, 14 ) -- make a text field
		button_text.text = defense_type_array[i].cost -- Display the cost 
		button_text.x = 20	-- Set the position of the label
		button_text.y = 20	-- Set the position of the label
		
		button_group:insert( button ) 		-- Add button shape to group
		button_group:insert( button_text ) 	-- Add text to group
		
		button_group.shape = button -- add button shape as a property to group 
		button_group.label = button_text -- add button text as property of group
		
		-- Assign these properties to button group
		button_group.index = i
		button_group.x = display.contentWidth - 45 -- adjust the position
		button_group.y = 40 + ( i * 50 )
		button_group:addEventListener( "touch", touch_defense_button )
		
		table.insert(defense_button_array, button_group ) -- Add button group to array
		control_group:insert( button_group ) -- add button group to control group
	end 
end

make_defense_buttons()
select_defense_button()
enable_disable_buttons() -- *** Call this to initialize buttons


 
energy_text = display.newText( energy, 0, 0, native.systemFont, 16 )
control_group:insert( energy_text )
energy_text:setTextColor( 0, 255, 0 )
energy_text.x = 300
energy_text.y = 40

local function update_energy()
	energy_text.text = energy
end 

local function energy_recharge()
	energy = energy + ENERGY_RECHARGE_RATE
	enable_disable_buttons() -- *** Update buttons 
	update_energy()
end

energy_timer = timer.performWithDelay( ENERGY_TIMER_TIME, energy_recharge, -1 ) 


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} )
	return bullet 
end

local function defense_defend( defense )
	for i = 1, #alien_array, 1 do 		
		local alien = alien_array[i]	
		if alien.col == defense.col then 
			local bullet = make_bullet( defense.x, defense.y ) 
			bullet.damage = defense.damage 
			bullet.defense_name = defense.defense_name 	
			break
		end 
	end 
end

local function remove_defense( defense )
	local index = table.indexOf( defense_array, defense ) 
	timer.cancel( defense.timer )	
	table.remove( defense_array, index )				  
	display.remove( defense )
end 

local function make_defense( x, y )
	local defense = display.newRect( 0, 0, 32, 32 )
	
	defense.rof 		 = defense_type_array[current_defense_type].rof
	defense.damage 		 = defense_type_array[current_defense_type].damage
	defense.defense_name = defense_type_array[current_defense_type].name
	defense.red 		 = defense_type_array[current_defense_type].red
	
	defense:setFillColor( defense.red, 0, 0 )
	defense_group:insert( defense ) 
	defense.x = x 
	defense.y = y
	table.insert( defense_array, defense ) 
	defense.timer = timer.performWithDelay( defense.rof, 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 cost = defense_type_array[current_defense_type].cost
				
		if energy >= cost then 
			energy = energy - cost 
			local defense = make_defense( tile_x, tile_y ) 
			defense.col = tile.col 
		end 						  
	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
		 	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 )
	alien.col = col
	alien.x = col * ( TILE_SIZE  + TILE_MARGIN ) 
	alien.y = 0
	alien.life = 5 
	
	local t = alien_target_y * ALIEN_SPEED
	alien.transition = transition.to( alien, {y=alien_target_y, time=t, onComplete=remove_alien} )
	alien_group:insert( alien ) 
	table.insert( alien_array, alien )
end 

local function hit_test( x, y, bounds )
	return x > bounds.xMin 
		and x < bounds.xMax 
		and y > bounds.yMin 
		and y < bounds.yMax
end 

local function hit_test_bounds( bounds1, bounds2 )
    return bounds1.xMin < bounds2.xMax
        and bounds1.xMax > bounds2.xMin
        and bounds1.yMin < bounds2.yMax
        and bounds1.yMax > bounds2.yMin
end

local function check_bullets()
	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 - bullet.damage 
					local defense_name = bullet.defense_name
					
					if defense_name == "Stun" then	
						transition.cancel( alien.transition ) 				
						local t = ( alien_target_y - alien.y ) * ALIEN_SPEED 
						
						alien.transition = transition.to( alien, { y=alien_target_y, 
																	time=t, 
																	delay=300,	
																	onComplete=remove_alien } )
					end 
					
				else
					remove_alien( alien )
				end 
				remove_bullet( bullet )
				break
			end 
		end 
	end 
end

local function check_enemies()
	for a = 1, #alien_array, 1 do 
		local alien = alien_array[a]
		for d = 1, #defense_array, 1 do 
			local defense = defense_array[d]
			if hit_test_bounds( alien.contentBounds, defense.contentBounds ) then 
				remove_defense( defense )	
				break
			end 
		end 
	end 
end

local function on_enterframe( event ) 
	check_bullets()
	check_enemies()
end

Runtime:addEventListener( "enterFrame", on_enterframe )

make_grid()

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 *