One aspect that will make the game more interesting is the addition of different types of enemies.
Let’s refer to Plants vs Zombies as our model. Here all of the enemies are zombies but there are different types with different capabilities. Besides the standard there are zombies that move faster, zombies that withstand more damage, zombies that jump over a defense, and more.
The variety of enemies adds a lot to strategies that players can employ, and creativity that developers can employ when creating levels.
Enemy features
Enemies can have as may features as you can imagine. Here’s a few ideas that fit into the game as I have been envisioning it so far.
- speed – Some enemies move faster than others
- Toughness – some enemies take more damage to destroy than others
- Special – some enemies may have special features or attacks
To keep track of the enemy types I created an array similar to the array used for organizing defense types. Each enemy type will be described in a table with properties:
- name – name of feature
- speed – sets speed in px per second
- life – sets the starting life value for this enemy type
- green – color of enemy type
The last property, green, is just used for this example. Later we will have graphics to replace the shapes.
All of the enemy type tables will be held in an array for easy access and organization.
-- Add an array to manage enemy types
local alien_type_array = {
{ name="normal", speed=ALIEN_SPEED, life=5, green=200 }, -- Normal enemy
{ name="fast", speed=ALIEN_SPEED / 2, life=2, green=255 }, -- Fast enemy weaker
{ name="slow", speed=ALIEN_SPEED * 2, life=10, green=165 }} -- slow enemy tougher
make_alien()
You might make different enemy types based on different criteria or different situations. In this example to keep things simple I will make the choice of enemy type random. Each time an enemy is created I will choose a random type and configure the new enemy based on the information in the tables created above.
-- Modify this function to select different types of aliens
local function make_alien()
local alien_type = alien_type_array[ math.random( 1, #alien_type_array ) ] -- Choose a random alien type
local alien = display.newRect( 0, 0, 32, 32 )
alien.alien_type = alien_type -- Save the type
alien:setFillColor( 0, alien_type.green, 0 ) -- get the green value
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 -- Get the life foe this type
alien.speed = alien_type.speed -- Save the speed
local t = alien_target_y * alien_type.speed -- Get the speed for this type
alien.transition = transition.to( alien, {y=alien_target_y, time=t, onComplete=remove_alien} )
alien_group:insert( alien )
table.insert( alien_array, alien )
end
Here we’re creating the enemies as before. The difference this time is that property values are set to starting values based on values found in the tables in the alien_type_array.
Last, since the speed can be set after an alien gets hit with a stun weapon, we should make sure the alien moves at it’s types speed after it is hit with a stun weapon. Look at 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
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 -- Use this aliens speed here
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
Here’s the full listing
-----------------------------------------------------------------------------------------
--
-- main.lua
--
-----------------------------------------------------------------------------------------
-- This example creates an array of enemy types and chooses a random enemy type
-- for each new enemy. Enemies features: life, speed, and color.
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}
}
-- Add an array to manage enemy types
local alien_type_array = {
{ name="normal", speed=ALIEN_SPEED, life=5, green=200 }, -- Normal enemy
{ name="fast", speed=ALIEN_SPEED / 2, life=2, green=255 }, -- Fast enemy weaker
{ name="slow", speed=ALIEN_SPEED * 2, life=10, green=165 }} -- slow enemy tougher
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 = 20
button_group:insert( button )
button_group:insert( button_text )
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 )
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
-- Modify this function to select different types of aliens
local function make_alien()
local alien_type = alien_type_array[ math.random( 1, #alien_type_array ) ] -- Choose a random alien type
local alien = display.newRect( 0, 0, 32, 32 )
alien.alien_type = alien_type -- Save the type
alien:setFillColor( 0, alien_type.green, 0 ) -- get the green value
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 -- Get the life foe this type
alien.speed = alien_type.speed -- Save the speed
local t = alien_target_y * alien_type.speed -- Get the speed for this type
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 -- Use this aliens speed here
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 )