## Corona – Tower Defense Game Experiments 14

Here’s a few thoughts on setting up waves of enemies. Plants vs Zombies makes waves of zombies and puts the waves to good use. Game play gets much more exciting after the message “A huge wave of Zombies is approaching.”

The game requires that you either a starting reserve of energy or you have to start with a fewer enemies. After building your defense for a while it becomes very easy to take on the enemies. This means that a larger wave of enemies is required to make the game more interesting at this point.

There are a lot approaches you could take to creating wave of enemies. I’m going to define a wave of enemies by describing the wave in terms of time between enemies, number of enemies in wave, and pool of enemies.

Time between enemies will be the amount of time between the creation of each new enemy.

Number of enemies in wave, will be the count of total enemies in this wave.

Pool of enemies will be a listing of the types of enemies in the wave. This is the most abstract idea here. I’m going to use the pool as a list of possible enemy types that might appear in the wave. During the wave each new enemy created will be a random type chosen from the pool. The pool will be an array. By adding the same type more than once the pool can weighted.

Each wave will be initiated by a timer.

Use three variables to keep track of the wave. One and array the other an integer setting the index of the current wave in the array. The last variable will keep track of a timer that tracks the wave.

```local wave_timer -- add a timer to hold the wave
local alien_wave_index = 1 -- Use this to keep track of the current wave
-- Add an array to hold a wave of enemies
local alien_wave_array = {
{ time=2300, 		-- Time between enemies
count=10, 			-- Number of enemies in wave
pool={1,1,1,2,3}, 	-- Number and type of enemies in wave
delay=3000
} ,

{ time=1500, 		-- Time between enemies
count=20, 			-- Number of enemies in wave
pool={1,2,3}, 		-- Number and type of enemies in wave
delay=2000
}
} ```

The array above holds a list of tables. Each of these tables describes a wave of enemies. Notice each table has properties of time, count, pool, and delay.

• time – sets the time in ms between enemies during wave
• count – total number of enemies in wave
• pool – an array of alien types represented in wave
• delay – time to wait after wave before starting the net wave

I added the extra delay property here as thought it might be useful to control the flow of things.

Next I needed two functions to control the waves of enemies. The make_wave() function sets up timers to run the wave. Here I create two timers. One called make_alien(). This timer takes the time and count from the current wave table. The second timer is set to total length of the current wave and is used to start the next wave.

I need to include a forward declaration here for next_wav, since this function calls make_wave and make wave calls it.

```-- Need a forward declaration
local next_wave

-- Add this function to mange creating enemies that attack in waves.
local function make_wave()
local wave_table = alien_wave_array[ alien_wave_index ]
local wave_time = wave_table.time
local wave_count = wave_table.count
local delay_after_wave = wave_table.delay

alien_timer = timer.performWithDelay( wave_time, make_alien, wave_count )
local time_til_next_wave = ( wave_time * wave_count ) + delay_after_wave
wave_timer = timer.performWithDelay( time_til_next_wave, next_wave, 1 )
end

-- Called at the end of each wave
function next_wave()
alien_wave_index = alien_wave_index + 1
if alien_wave_index > #alien_wave_array then
print( "ALL WAVES COMPLETE" )
else
print( "next wave" )
make_wave()
end
end

make_wave()```

Here’s a full listing of the code in this example:

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

-- Waves of Enemies

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 wave_timer -- add a timer to hold the wave
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 alien_wave_index = 1 -- Use this to keep track of the current wave

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 }}

-- Add an array to hold a wave of enemies
local alien_wave_array = {
{ time=2300, 		-- Time between enemies
count=10, 			-- Number of enemies in wave
pool={1,1,1,2,3}, 	-- Number and type of enemies in wave
delay=3000
} ,

{ time=1500, 		-- Time between enemies
count=20, 			-- Number of enemies in wave
pool={1,2,3}, 		-- Number and type of enemies in wave
delay=2000
}
}

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 )
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_wave_pool = alien_wave_array[ alien_wave_index ].pool -- get the current pool of alien types
local alien_type_index = alien_wave_pool[ math.random( 1, #alien_wave_pool ) ] 	-- Get a random type index
local alien_type = alien_type_array[ alien_type_index ]			-- Get a random alien type

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()

-- Need a forward declaration
local next_wave

-- Add this function to mange creating enemies that attack in waves.
local function make_wave()
local wave_table = alien_wave_array[ alien_wave_index ]
local wave_time = wave_table.time
local wave_count = wave_table.count
local delay_after_wave = wave_table.delay

alien_timer = timer.performWithDelay( wave_time, make_alien, wave_count )
local time_til_next_wave = ( wave_time * wave_count ) + delay_after_wave
wave_timer = timer.performWithDelay( time_til_next_wave, next_wave, 1 )
end

-- Called at the end of each wave
function next_wave()
alien_wave_index = alien_wave_index + 1
if alien_wave_index > #alien_wave_array then
print( "ALL WAVES COMPLETE" )
else
print( "next wave" )
make_wave()
end
end

make_wave()

-------------------------------------------------------------------------------------
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 )
```
• DHennrich
• October 11th, 2012

Can you provide the download of this amazing tutorial?? It will be helpful.

Thanks ALOT for your tutorial, it’s all I was looking for

• admin
• October 11th, 2012

There is nothing to download. The tutorial contains the complete code. Create a new project, copy and paste the code listing from the tutorial into main.lua.

• DHennrich
• October 11th, 2012

Oooh just like that? it’s simple hahaha

again Thanks alot for your tutorial!

• DHennrich
• November 8th, 2012

Hey, I’m trying to make your TD example in horizontal

I already change all parameters to able TD to horizontal but I’m getting an issue with bullets speed.

If they’re closer to x = 1024, more slower the bullet go, don’t know how to solve this…. can you give me a little help here, please?

local BULLET_SPEED = 1000 / 400
local bt = x * BULLET_SPEED

table.insert( bullet_array, bullet )
bullet.transition = transition.to( bullet, { x = 1024, time = bt, rotation = 360, onComplete = remove_bullet} )

I note that in your sample the bullet have the same speed in any position
please help T.T

thanks

1. No trackbacks yet.