With one type of defense the only choice a player has is where to place a defense. Providing a choice of defense types gives players a chance to try different strategies. Plants vs Zombies provides a wide range of defense types each with a range of effects and cost.
As a first step to getting different types of defenses to work I need to have an interface to choose the type of defense to place. I don’t want to get into a long discussion what the best type of interface might be. The goal for this post will be to make a simple set of buttons that will act like a radio button group. Tapping one button will select it and deselect the currently selected button. Only one button can be selected at a time.
This idea could be applied to many other projects!
The buttons
The buttons will be rounded rectangles filled with a color. I’ll use a white stroke to show which is selected.
List types of defenses
The types of defense will be stored in an array. Knowing how many different types of defenses are available will tell us how many buttons to make.
For now the types of defenses can be anything. Later I will look at the features of each defense type and we can store that information. For now this array can store anything as long as there one element for each type. in the example I used the letters: “A”, “B”, “C” etc.
Keeping track of the current defense type
I added a new variable: current_defense_type. This variable holds the index of the current defense type in the defense_type_array. For example if the current defense type is “B”, then current_defense_type would be 2.
local current_defense_type = 1 -- marks the current defense type, matches the index of the defense type in the defense_type_array
List of buttons
The buttons will also be stored in an array. This is essential for keeping track of the buttons. Having an array of the buttons will make it easy to select and deselect any of the buttons in the array.
local defense_type_array = {"A","B","C","D"} -- Make an array to keep track of defense buttons
local defense_button_array = {} -- use an array to keep track of buttons
The functions
I used three functions make_defense_buttons(), touch_defense_button(), and select_defense_button().
make_defense_buttons()
This function makes the defense buttons. It makes one button for each item in the defense_type_array. Each button is given a different shade of red, and a white stroke. The buttons are also assigned the property index. This property is the index of the defense type in the defense_type_array that the button corresponds to. I also added a touch event to each button that is handled by touch_defense_button().
touch_defense_button()
This function gets the index from the button and sets current_defense_type to this value. Then it calls select_defense_button().
select_defense_button()
This function highlights the current button by setting the stroke width to 3 for that button and the stroke width of the other buttons to 0.
-- Select the current button 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.strokeWidth = 3 else button.strokeWidth = 0 end end end -- Handle button events local function touch_defense_button( event ) local button = event.target current_defense_type = button.index select_defense_button() end -- Add a function to create defense buttons local function make_defense_buttons() for i = 1, #defense_type_array, 1 do local button = display.newRoundedRect( 0, 0, 40, 40, 6 ) local r = 255 * ( i / #defense_type_array ) button.index = i button:setFillColor( r, 0, 0 ) button:setStrokeColor( 255, 255, 255 ) button.x = display.contentWidth - 26 button.y = 40 + ( i * 50 ) button:addEventListener( "touch", touch_defense_button ) table.insert(defense_button_array, button ) control_group:insert( button ) end end -- Call the function to make the buttons make_defense_buttons() -- Select the current defense type select_defense_button()
Here’s a full listing of the code so far:
-----------------------------------------------------------------------------------------
--
-- main.lua
--
-----------------------------------------------------------------------------------------
-- This example will an interface for creating different types of defenses
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 ENERGY_RECHARGE_RATE = 1
local DEFENSE_ENERGY_COST = 50
local ENERGY_TIMER_TIME = 150
local alien_timer
local energy_timer
local energy = 0
local current_defense_type = 1 -- marks the current defense type, matches the index of the defense type in the defense_type_array
local energy_text
local defense_array = {}
local alien_array = {}
local bullet_array = {}
local defense_type_array = {"A","B","C","D"} -- Make an array to keep track of defense buttons
local defense_button_array = {} -- use an array to keep track of buttons
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 )
-- Select the current button
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.strokeWidth = 3
else
button.strokeWidth = 0
end
end
end
-- Handle button events
local function touch_defense_button( event )
local button = event.target
current_defense_type = button.index
select_defense_button()
end
-- Add a function to create defense buttons
local function make_defense_buttons()
for i = 1, #defense_type_array, 1 do
local button = display.newRoundedRect( 0, 0, 40, 40, 6 )
local r = 255 * ( i / #defense_type_array )
button.index = i
button:setFillColor( r, 0, 0 )
button:setStrokeColor( 255, 255, 255 )
button.x = display.contentWidth - 26
button.y = 40 + ( i * 50 )
button:addEventListener( "touch", touch_defense_button )
table.insert(defense_button_array, button )
control_group:insert( button )
end
end
-- Call the function to make the buttons
make_defense_buttons()
-- Select the current defense type
select_defense_button()
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
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} )
end
local function defense_defend( defense )
for i = 1, #alien_array, 1 do
local alien = alien_array[i]
if alien.col == defense.col then
make_bullet( defense.x, defense.y )
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:setFillColor( 200, 0, 0 )
defense_group:insert( defense )
defense.x = x
defense.y = y
table.insert( defense_array, defense )
defense.timer = timer.performWithDelay( 1000, 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
if energy >= DEFENSE_ENERGY_COST then
energy = energy - DEFENSE_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 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 )
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 - 1
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 )