plants vs Zombies works on a strict grid with enemies advancing in rows, and defenses firing only on their row. Other tower defense games allow defenses to fire at any enemy within range. The last post tested an example that had defenses firing only within their row. In this example I will create defenses that fire at any enemy within range.
Get Distance to enemy
To get the distance to an enemy in pixels we can take the Pythagorean formula: a2 + b2 = c2. In Lua terms this might look like:
c = math.sqrt((a*a)+(b*b))
To make this more useful and easier to work with we can wrap it in a function that returns the distance between two points. The example below takes two sets of x and y values and returns the distance between these points.
local function get_distance( x1, y1, x2, y2 ) local dx = x1 - x2 local dy = y1 - y2 return math.sqrt( (dx*dx) + (dy*dy) ) -- Return the distance end
If you’re feeling Object Oriented, you could make a a point object and pass that to the function. Lua is not an Object Oriented language so we would need to use a table and factory function, but this has a very similar arrangement.
local function Point( x, y ) return {x=x, y=y} end local function get_distance( point_1, point_2 ) local dx = point_1.x - point_2.x local dy = point_1.y - point_2.y return math.sqrt( (dx*dx) + (dy*dy) ) -- Return the distance end
With this method, using either of the two functions above, a defense can make the decision to fire on an enemy when that enemy is within range.
Deciding when to fire
The defense_defend( defense ) function is called from the timer assigned to a defense. Here we need to examine each enemy and measure the distance between the enemy and the defense. If the range is less a specified range we have the defense fire at that enemy.
I set the range of the defense as a “constant” at the top of script for easy use.
local DEFENSE_RANGE = 250
Next I need to modify defense_defend(defense) to work with the get_distance(x1,y1,x2,y2) function above. I used the first function in this example. Here we loop through the alien_array and look at the distance to each alien. If there is an alien within range we fir on it and break the loop.
-- This new function will look at the board and find eligible targets local function defense_defend( defense ) for i = 1, #alien_array, 1 do -- Loop through alien_array local alien = alien_array[i] -- get the alien if get_distance( defense.x, defense.y, alien.x, alien.y ) < DEFENSE_RANGE then -- Check the distance make_bullet( defense.x, defense.y, alien.x, alien.y ) break end end end
Since get_distance(x1,y1,x2,y2) returns the distance as a number we can place this function call inside the if expression:
if get_distance( defense.x, defense.y, alien.x, alien.y ) < DEFENSE_RANGE then ...
I will also have to modify the make_bullet() function to allow it fire on a target anywhere on the screen. You’ll notice this function has been modified from make_bullet(x,y) to make_bullet(x1,y1,x2,y2).
Sending bullet toward the target
Using this method bullets will be fired across columns. This means I will have to animate both the x and the y of the bullet. In the previous versions I had only animated the y value of the bullet. In these versions bullets were animated to a y of 0, targeting the top edge of the screen.
To make this happen I need to modify the make_bullet( x, y ) function. Before the function only had to send a bullet to the top of the screen, in which case all that was needed was a starting position. Now it needs to know the position of the target also. So here we’ll modify make_bullet(x,y) to make_bullet( start_x, start_y, target_x, target_y ).
Since the distance to the target will always be different we’ll need to get the distance again and multiply by the constant BULLET_SPEED, thus making sure the bullets move at a consistent speed no matter what the distance.
Here’s a new version of the make_bullet() function
local function make_bullet( start_x, start_y, target_x, target_y ) local bullet = display.newCircle( 0, 0, 5 ) bullet:setFillColor( 0, 0, 0 ) bullet.x = start_x bullet.y = start_y table.insert( bullet_array, bullet ) -- The bullet speed and transition need to be retooled -- We will end up calling get_distance twice, we could optimize by passing the distance from defense_defend local d = get_distance( start_x, start_y, target_x, target_y ) -- Get the distance to the target local bt = d * BULLET_SPEED bullet.transition = transition.to( bullet, {y=target_y, x=target_x, time=bt, onComplete=remove_bullet} ) end
Following the flow of code here, the defense_defend() function calls on get_distance(). If an enemy is within the distance we move on to make_bullet(). Where make_bullet() calls on get_distance() again. There’s room for a small performance optimization here. We could pass the distance to make_bullet() and not have to do the math again. I’ll things as they are for now, and may be address this in a future update.
Here’s a full listing of the code for this example:
Continue reading Corona – Tower Defense Game Experiments 6.2