Corona – Tower Defense Game Experiments 13

This series of posts has been going on for some time. I’m up to 13! I’m not done with the game yet, but it does have some interesting features.

In this post I’d like to add some “explosions”. What I really want is a small animated effect to show that an enemy has been hit by a bullet. For simplicity I’ll stick with the shapes and use a circle for the explosion. This could be replaced in the future.

The strategy here will be the same as was used with everything else, use factory function to create objects, and another function to remove the object when the explosion is finished.

Explosions will be animated with a transition animating the width and height. The onComplete will be used to remove the explosion when the animation finished.

-- Use this function to remove explosions
local function remove_explosion( explosion )
	display.remove( explosion )
end

-- Use this function to make explosions
local function make_explosion( x, y )
	local explosion = display.newCircle( 0, 0, 1 )
	explosion.x = x 
	explosion.y = y
	explosion:setFillColor( 0, 0, 255, 66 )
	explosion.strokeWidth = 3
	explosion:setStrokeColor( 0, 0, 255, 128 )
	alien_group:insert( explosion )
	transition.to( explosion, {width=20, height=20, time=500, onComplete=remove_explosion} )
end 

Above are the two functions. These are pretty simple. I made the explosions blue to separate them from the enemies (green), and the defenses (red).

The explosion is added to the alien_group. This keeps them inside the game_group, since alien_group is a child of game_group, and should keep new explosions on top of existing enemies.

All that’s need now is to call make_explosion( x, y ) and pass the x and y location where the explosion should occur. Bullets hit enemies in the check_bullets() function.

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 
					make_explosion( alien.x, alien.y ) -- Add an explosion on a hit
					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

Find the comment in the function above. Here you can see a call to make_explosion( alien.x, alien.y ). This creates a new explosion at the location of the enemy. You might have also placed the explosion at the location of the bullet instead.

Here’s full listing:

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

-- This example added a small explosion when a bullet hits an enemy

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 alien_type_array = {
			{ name="normal", speed=ALIEN_SPEED, 	life=5, green=200 }, 
			{ name="fast", 	 speed=ALIEN_SPEED / 2, life=2, green=255 },
			{ name="slow",   speed=ALIEN_SPEED * 2, life=10, green=165 }}
			
										
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 
		else
			button.shape.strokeWidth = 0 
		end 
	end
end


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()
		
		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 
		button_group.enabled = false 
		
		local button_text = display.newText( "0", 0, 0, native.systemFontBold, 14 ) 
		button_text.text = defense_type_array[i].cost 
		button_text.x = 20	
		button_text.y = 30
		
		-- Add a name label to each button
		local button_name_text = display.newText( "", 0, 0, native.systemFont, 12 )
		button_name_text.text = defense_type_array[i].name
		button_name_text.x = 20
		button_name_text.y = 10
		
		button_group:insert( button ) 		
		button_group:insert( button_text ) 	
		button_group:insert( button_name_text ) -- insert button into group
		
		button_group.shape = button  
		button_group.label = button_text 
		
		button_group.index = i
		button_group.x = display.contentWidth - 45 
		button_group.y = 40 + ( i * 50 )
		button_group:addEventListener( "touch", touch_defense_button )
		
		table.insert(defense_button_array, button_group ) 
		control_group:insert( button_group ) 
	end 
end

make_defense_buttons()
select_defense_button()
enable_disable_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_energy()
end

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


-- Use this function to remove explosions
local function remove_explosion( explosion )
	display.remove( explosion )
end

-- Use this function to make explosions
local function make_explosion( x, y )
	local explosion = display.newCircle( 0, 0, 1 )
	explosion.x = x 
	explosion.y = y
	explosion:setFillColor( 0, 0, 255, 66 )
	explosion.strokeWidth = 3
	explosion:setStrokeColor( 0, 0, 255, 128 )
	alien_group:insert( explosion )
	transition.to( explosion, {width=20, height=20, time=500, onComplete=remove_explosion} )
end 


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_type = alien_type_array[ math.random( 1, #alien_type_array ) ]
	
	local alien = display.newRect( 0, 0, 32, 32 )
	alien.alien_type = alien_type					
	alien:setFillColor( 0, alien_type.green, 0 )	
	local col = math.random( 1, TILE_COLS )
	alien.col = col
	alien.x = col * ( TILE_SIZE  + TILE_MARGIN ) 
	alien.y = 0
	alien.life = alien_type.life
	
	local t = alien_target_y * alien_type.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 
					make_explosion( alien.x, alien.y ) -- Add an explosion on a hit
					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 = math.round( system.getInfo( "textureMemoryUsed" ) / 1000000 )
	memory_text.text = "Mem:"..math.round( collectgarbage("count")) .. " tex:".. textMem
end

Runtime:addEventListener( "enterFrame", monitorMem )

Leave a Reply

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