Corona – Tower Defense Game Experiments 4

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.

Leave a Reply

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