Destroying Aliens with bullets
In the last post I created bullets that fire from a defense at advancing enemies. I still need to check if an enemy is hit, and if so reduce it’s life, and remove it if it has reached 0 life.
I see many example that would use physics for the collision detection for this. I feel that physics may be over kill for what I am doing here. it’s also my guess that using physics would add a lot of processing overhead. Physics also brings up other problems that need to be solved. All in all I physics, in this case will create more problems than it solves. So, I’m going to opt to use a hit test and manage bullets and alien enemies in arrays.
A few notes on code styles. The code is now getting longer and so I decided to make a few style changes to help me keep organized. Constants, these are variables that will not change value. These will be UPPER_CASE. Lua doesn’t have actual constants, I’m using upper case as a reminder.
Hit Test – Bullets vs Aliens
For each bullet fired, I will need to check if that bullet hits an enemy. To determine a hit take the x and y location of the bullet and check if it is inside the bounds of the enemy. The bounds of an object is represented by four values that define the edges of the rectangle surrounding the object. Use display.contentBounds to get the bounds object. This object will have properties of:
- xMin (defines the left edge)
- xMax (defines the right edge)
- yMin (defines the top edge)
- yMax (defines the bottom edge)
To determine if a point, an x and y pair, is within the bounds of an object use an if statement to compare the bounds properties and the x and y of the point. Check if the x value is greater than xMin and less than xMax, while the y value is greater than yMin and less than yMax. Something like this:
if x > bounds.xMin and x < bounds.xMax and y > bounds.yMin and y < bounds.yMax then ... HIT! ... end
Checking for a single hit is simple and straight forward. With multiple bullets and enemies on the screen at the same time the problem is a more complicated. For each bullet we need to check for a hit against each enemy. To organize this, all bullets need to be in an array and all enemies in another array. In the future I may want enemies to attack defenses so, I made an array to hold defenses also.
local defense_array = {}
local alien_array = {}
local bullet_array = {}
With the bullets in an array and the enemies in another array we can look at each bullet and perform a hit test against each enemy with that bullet.
Making Bullets
I used the same strategy for bullets that was used for other display elements tiles, aliens and defenses. I need a function to make bullets and another to remove them.
BULLET_SPEED
I used a transition to move the bullets up the screen. The time it takes a bullet to move up the screen will vary by the distance the bullet has to travel. To get the bullets to move at a consistent rate I created the variable BULLET_SPEED. This is a “constant”, that is it’s value will NOT change while the program runs. Corona and Lua do not support actual constants, but this is the intent for this variable, so I will use a style to mark it as such.
I set the speed in pixels per millisecond (1/1000 of a second). I decided on a speed of 400 pixels per second. In milliseconds this becomes 1000 / 400.
local BULLET_SPEED = 1000 / 400
To create transition that moves at this constant rate multiply the distance by BULLET_SPEED and set this value as the time of the transition.
Bullets will be black circles, for now.
Here are two functions to make and remove bullets.
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
To detect a hit I need to look at the position of each bullet and alien and apply a hit test each frame. I added an enterFrame event and handler. I also separated the hit test into it’s own function since it might be useful for other purposes in the future. Note the hit test returns true on a hit or false. This way you can place a hit test inside of an if statement, as in the code below:
local function hit_test( x, y, bounds ) if x > bounds.xMin and x < bounds.xMax and y > bounds.yMin and y < bounds.yMax then return true else return false end end local function on_enterframe( event ) 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 Runtime:addEventListener( "enterFrame", on_enterframe )
Here’s a full code listing so far.
-----------------------------------------------------------------------------------------
--
-- main.lua
--
-----------------------------------------------------------------------------------------
-- This example adds bullets fired by defense elements.
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 -- ms / distance in px
local defense_array = {} -- Make an array to hold the defenses
local alien_array = {} -- make an array to hold enemies.
local bullet_array = {} -- make an array for bullets
local game_group = display.newGroup()
local defense_group = display.newGroup() -- make a group to hold each class
local alien_group = display.newGroup() --
local tile_group = display.newGroup()
game_group:insert( tile_group ) -- Add each group to the main group
game_group:insert( defense_group )
game_group:insert( alien_group )
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 remove_defense( defense )
local index = table.indexOf( defense_array, defense ) -- get the index of this element
table.remove( defense_array, index ) -- delete this element from the array
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 ) -- Add defense to the proper group
defense.x = x
defense.y = y
table.insert( defense_array, defense ) -- Add this defense to the array
defense.timer = timer.performWithDelay( 1000, function() make_bullet( defense.x, defense.y ) 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
make_defense( tile_x, tile_y )
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.has_defense = false -- mark this tile as NOT having a defense when we begin.
tile:addEventListener( "touch", touch_tile )
tile_group:insert( tile ) -- Add tiles to the tile group
end
end
end
local function remove_alien( alien )
local index = table.indexOf( alien_array, alien ) -- Get the index
transition.cancel( alien.transition )
table.remove( alien_array, index ) -- Remove the element
display.remove( alien )
end
local function make_alien()
local alien = display.newRect( 0, 0, 32, 32 )
alien:setFillColor( 0, 200, 0 )
local row = math.random( 1, TILE_COLS )
alien.x = row * ( TILE_SIZE + TILE_MARGIN )
alien.y = 0
alien.life = 5 -- Assign each alien a life of 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 ) -- Add aliens to the alien group
table.insert( alien_array, alien )
end
local function hit_test( x, y, bounds )
if x > bounds.xMin and x < bounds.xMax and y > bounds.yMin and y < bounds.yMax then
return true
else
return false
end
end
local function on_enterframe( event )
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
Runtime:addEventListener( "enterFrame", on_enterframe )
make_grid()
local 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 )