Giving the player the ability to place different types of defenses adds interest to the game. It opens up different types of strategies and different types of game play.
Looking at Plants vs Zombies you will see a wide variety of defense types. Here’s a list of the types of plants available: http://plantsvszombies.wikia.com/wiki/Gallery_of_Plants
Defenses can have a variety of features. Core ideas might be:
- Damage – how much damage the defense applies to an enemy
- Rate of fire – how often the defense fires
- Special effect – A special ability only applied to this type of defense
- Cost – The energy cost to place the defense
Really you could boil the list down cost vs power. This is the essential formula that creates balance and inspires game play. All of the features, except cost, are a measure of power for a defense. Cost is the balancing factor. Energy is collected over time, gives players the choice to build fewer more expensive defenses, or more less powerful defenses, or build a mix of each.
The key factor to making the game playable is a balance of cost vs power. Adding a mix of special features add a lot of interest to the game, provided the special features work well in cost vs power balance. As the programmer you can make anything happen. You can make the most powerful weapons cost very little. But this makes for an uninteresting playing experience. As a game design this is where you your real work. Finding the right balance of cost vs power for each element while providing some interesting effects.
In the example here I’ll keep the features simple. each defense will have a rate of fire, damage, and a cost.
Rate of fire will set the interval, in milliseconds, between shots for a defense.
Damage will determine how much subtracted from an enemies life property with each hit.
Cost sets the amount of energy that is paid to create this type of defense.
I’ve also added one special feature. One type of defense will also momentarily stop an approaching alien.
defense_type_array
This array will hold a description of each defense type. With properties holding values for each of the features above. Here’s an example:
{name="Standard", rof=1000, damage=1, cost=50}
This is standard defense, it all of the features of the original defense. It fires once every 1000 ms, it subtracts 1 from the life of an enemy with each hit, and costs 50 energy to create.
The name property will be used for special abilities.
I created an array of these tables named defense_type_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}
}
There will be one button for each of items in this array.
We need to be able to identify each defense placed on the board. So far I haven’t used any graphics, so there isn’t a picture for each, yet. For now I will match the color of the button to the color of the defense. When the buttons are created I generate a color for each. As a temporary measure I will put the color in the defense type tables for easy reference.
In the make_defense_button() function add the following:
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 )
-- For this example I'll put the red values in each of these tables
-- this way we can color each of the defense elements to match the buttons.
defense_type_array[i].red = r
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
touch_tile()
Touching a tile creates a new defense. This is also where we check the available energy and pay the cost for the defense.
local function touch_tile( event ) -- Check the type of defense
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 -- Get the cost for this defense type
if energy >= cost then -- Use cost here
energy = energy - cost -- and here
local defense = make_defense( tile_x, tile_y )
defense.col = tile.col
end
end
end
make_defense()
Each defense will now have a type and each will have different abilities. Each defense should keep track of it’s features: roy, damage and defense_name.
As an alternative, each defense could hold a reference to the table describing the defense features. Here I chose the first option. If there were many defense features the second option might be better.
local function make_defense( x, y )
local defense = display.newRect( 0, 0, 32, 32 )
-- Get all of the properties describing this weapon and store them in the new defense
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
Here I just transferred the features across and set them as properties of the defense that was created. Notice that I also got the red property and used this to set the color of the defense. The defense should now match the color of button. Later when images are added we can dump the color.
make_bullet()
Bullets created earlier were all the same. Now we want them to have unique properties described by the defense that fired them. The make_bullet() function doesn’t have a reference to the defense. But, it is called from the defense_defend() function that does have a reference to the defense. To share information between the two I’ll have make_bullet() return a reference to the bullet.
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 -- better return the bullet so the defense_defend() function can work with it
end
defense_defend()
The defense_defend() function creates bullets. Each bullet will need to know it’s damage value. To get the damage we need to have a reference to the defense. Here we get the bullet that was returned and assign it the damage and the name of the defense type that fired it. The name is important since I’ll use it for special effects.
Damage is easy, instead of subtracting 1 from alien.life. Here we replace this with bullet.damage. In this way each bullet can subtract it’s own damage value.
Next we need to check the defense_name. If the name is “Stun” I need to stop the enemy, and then start again after a short delay. Since the movement is handled by a transition we need to cancel the previous transition. Then add a new transition with a delay. Here I used the ALIEN_SPEED constant to make sure the enemy is moving at the same speed.
To get the speed right I needed to know the distance. To get the remaining distance to move the enemy I subtracted the current y from the alien_target_y value.
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 ) -- Get the bullet
bullet.damage = defense.damage -- assign a damage value to the bullet
bullet.defense_name = defense.defense_name -- get the name of the defense
break
end
end
end
make_alien()
I want to add a weapon with a special feature. One of the weapons will “Stun” an enemy. This will have the effect of stopping the enemy momentarily. To make this effect happen the current transition will have to be removed from the enemy and replaced with a new transition. The new transition will have a delay.
The challenge here is making the enemy continue moving at the same speed to the same end point as before it was stopped. To make this happen I need to know the end point, and know the rate of movement.
The end point for enemies was calculated in the make_alien() function previously. To make things more organized and efficient I defined a variable to hold the ending y value for enemies.
local alien_target_y = ( TILE_SIZE + TILE_MARGIN ) * TILE_ROWS -- Ending y value for aliens
This would need to be defined after TILE_SIZE, TILE_MARGIN and TILE_ROWS are defined. This value will probably not change, so it could have be declared as a constant I suppose.
Enemies will also need a rate of movement in px per sec. I defined this at the top as a constant.
local ALIEN_SPEED = 1000 / 20 -- Assign an alien speed 20px per sec
Multiplying this by the distance will give us the time in ms that enemies will move at a rate of 20 px per sec.
Now we need to retool the make_alien function() to make use of these new variables.
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
-- Change the alien movement
local t = alien_target_y * ALIEN_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
Since enemies start at y of 0. The distance then move is alien_target_y. Multiply this by the ALIEN_SPEED and we get the time in ms for the transition. Notice that I removed some of the other code that was here from the previous example.
check_bullet()
This function checks for bullets hitting enemies. We need to add two new features here: apply damage for specific bullet/defense types, and any special features for any specific bullet type. In this example only the “Stun” type defense will have a special effect.
The
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 -- Use the damage value of the bullet
local defense_name = bullet.defense_name -- Get the name of the defense
-- Check the defense name and apply special stuff
if defense_name == "Stun" then
transition.cancel( alien.transition ) -- Cancel the current transition
local t = ( alien_target_y - alien.y ) * ALIEN_SPEED -- Calculate a new speed
-- Add a new transition with a delay
alien.transition = transition.to( alien, { y=alien_target_y,
time=t,
delay=300, -- Delay
onComplete=remove_alien } )
end
else
remove_alien( alien )
end
remove_bullet( bullet )
break
end
end
end
end
Here’s a listing of the entire code used so far.
Continue reading Corona – Tower Defense Game Experiments 10