Tower Defense type game: spawning and moving enemies

Scratch the previous post.

I have successfully detected which tank to remove at what time but I’m having issues removing the display object.

If you look at my code, I tell corona to remove the physics body, which it does fine, but when it tries to remove the display object itself, it does so successfully, but then other functions that reference it error out the app because the aTank[i].x value or whatever no longer exists since I removed it. Can any one help specifically with that part?

[code]


– level1.lua


local storyboard = require( “storyboard” )
local scene = storyboard.newScene()

– include Corona’s “physics” library
local physics = require “physics”
physics.start()
physics.setGravity( 0, 0)
physics.setDrawMode(“hybrid”)

– forward declarations and other locals
local screenW, screenH, halfW, halfH = display.contentWidth, display.contentHeight, display.contentWidth*0.5, display.contentHeight*0.5

local tanks = {}
local tickCnt = 0
local TOTAL_TANKS = 2
local tankCnt = 1

function AddCommas( number, maxPos )

local s = tostring( number )
local len = string.len( s )

if len > maxPos then
– Add comma to the string
local s2 = string.sub( s, -maxPos )
local s1 = string.sub( s, 1, len - maxPos )
s = (s1 … “,” … s2)
end

maxPos = maxPos - 3 – next comma position

if maxPos > 0 then
return AddCommas( s, maxPos )
else
return s
end

end

function tickCntFunct()
if tickCnt <= 2000 then
tickCnt = tickCnt + 25
print(“tick count”, tickCnt)
else
tickCnt = 0
end
end
timer.performWithDelay(10, tickCntFunct, 0)

function moveTank(aTank)
– you can move the tank, as well as other actions here for each
– tank as it’s reference is sent in as a parameter
local mSpeed = 150
rSpeed = 6

if aTank.isAlive == true and aTank.isKilled == false then
print("aTank.x = ", aTank.x)
print("aTank.y = ", aTank.y)
print("tank rotation = ", aTank.rotation)

local disP1 = math.sqrt((math.abs(aTank.x - pointer1.x))^2 + (math.abs(aTank.y - pointer1.y))^2)
local disP2 = math.sqrt((math.abs(aTank.x - pointer2.x))^2 + (math.abs(aTank.y - pointer2.y))^2)
local disP3 = math.sqrt((math.abs(aTank.x - pointer3.x))^2 + (math.abs(aTank.y - pointer3.y))^2)
local disP4 = math.sqrt((math.abs(aTank.x - pointer4.x))^2 + (math.abs(aTank.y - pointer4.y))^2)

removeTanknumber = 0

if aTank.x < 0 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
if aTank.rotation < 180 and disP1 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation + rSpeed
end
if aTank.rotation >= 180 and disP1 < 1 then
aTank.rotation = 180
aTank:setLinearVelocity( 0, mSpeed)
end
if aTank.rotation > 90 and disP2 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation - rSpeed
end
if aTank.rotation <= 90 and disP2 < 1 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
if aTank.rotation > 0 and disP3 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation - rSpeed
end
if aTank.rotation <= 0 and disP3 < 1 then
aTank.rotation = 0
aTank:setLinearVelocity( 0, -1*mSpeed)
end
if aTank.rotation < 90 and disP4 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation + rSpeed
end
if aTank.rotation >= 90 and disP4 < 1 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
end
end
timer.performWithDelay(1, moveTank, 0)


– BEGINNING OF YOUR IMPLEMENTATION

– NOTE: Code outside of listener functions (below) will only be executed once,
– unless storyboard.removeScene() is called.


– Called when the scene’s view does not exist:
function scene:createScene( event )
local group = self.view

pointersGroup = display.newGroup()
mapGroup = display.newGroup()

– create a grey rectangle as the backdrop
map = display.newImage( “map1.png” )
map:setReferencePoint(display.TopLeftReferencePoint)
map.x = 0
map.y = 0

spawner = display.newImage(“pointer.png”)
spawner:setReferencePoint(display.CenterReferencePoint)
spawner.y = 210
spawner.x = -40

pointer1 = display.newImage(“pointer.png”)
pointer1:setReferencePoint(display.CenterReferencePoint)
pointer1.x = 210
pointer1.y = 210

pointer2 = display.newImage(“pointer.png”)
pointer2:setReferencePoint(display.CenterReferencePoint)
pointer2.x = 210
pointer2.y = 390

pointer3 = display.newImage(“pointer.png”)
pointer3:setReferencePoint(display.CenterReferencePoint)
pointer3.x = 510
pointer3.y = 390

pointer4 = display.newImage(“pointer.png”)
pointer4:setReferencePoint(display.CenterReferencePoint)
pointer4.x = 510
pointer4.y = 90

sideBlock = display.newImage(“side_block.png”)
physics.addBody( sideBlock, “static”, { friction=0.5, bounce=0.3 } )
sideBlock:setReferencePoint(display.CenterReferencePoint)
sideBlock.x = screenW - 100
sideBlock.y = screenH/2

– all display objects must be inserted into group

pointersGroup:insert( spawner )
pointersGroup:insert( pointer1 )
pointersGroup:insert( pointer2 )
pointersGroup:insert( pointer3 )
pointersGroup:insert( pointer4 )
pointersGroup:insert( sideBlock )
mapGroup:insert( map )
group:insert( pointersGroup )
group:insert( mapGroup )

end

– Called immediately after scene has moved onscreen:
function scene:enterScene( event )
local group = self.view

for i = 1, TOTAL_TANKS do
– create 5 tanks, place them off screen, set their status to isAlive and not isMoving
table.insert(tanks, display.newImage(“tank.png”))
print("memory (all spawned): “, AddCommas( system.getInfo(“textureMemoryUsed”), 9 ) … " bytes” )
tanks[i]:setReferencePoint(display.CenterReferencePoint)
tanks[i]:scale(0.75, 0.75)
tanks[i].x = spawner.x
tanks[i].y = spawner.y
tanks[i].isAlive = true
tanks[i].isMoving = false
tanks[i].isKilled = false
end

local function gameLoop(event)

– normally should not have loops inside the game loop, because
– if there is too much going on during each frame call it can cause issues
– but for moving only 5 tanks, this is how you can do it.
– each tank will call the same function above (moveTank)

– have a variable that you would check here, to see if ‘so many ticks or seconds’
– has passed, and if so, set the isMoving to true for the next tank … will just use
– a simple incremented variable ‘tickCnt’

if tickCnt > 2000 and tankCnt <= TOTAL_TANKS then
physics.addBody( tanks[tankCnt], { density=3.0, friction=0.5, bounce=0.3 } )
tanks[tankCnt].isMoving = true
print("tankCnt= ", tankCnt)
tankCnt = tankCnt + 1
print("memory (moving): “, AddCommas( system.getInfo(“textureMemoryUsed”), 9 ) … " bytes” )
end

for i=1, TOTAL_TANKS do
if tanks[i].isMoving == true and tanks[i].isAlive == true and tanks[i].isKilled == false then
moveTank(tanks[i])
end
end

for i = 1, TOTAL_TANKS do
if tanks[i].x >= 40 and tanks[i].isKilled == false then
tanks[i].isKilled = true
tanks[i].isMoving = false
tanks[i].isAlive = false
physics.removeBody( tanks[i] )
display.remove(tanks[i])
tanks[i] = nil
end
end

end

Runtime:addEventListener(“enterFrame”, gameLoop)

physics.start()

end

– Called when scene is about to move offscreen:
function scene:exitScene( event )
local group = self.view

physics.stop()

end

– If scene’s view is removed, scene:destroyScene() will be called just prior to:
function scene:destroyScene( event )
local group = self.view

package.loaded[physics] = nil
physics = nil
end


– END OF YOUR IMPLEMENTATION

– “createScene” event is dispatched if scene’s view does not exist
scene:addEventListener( “createScene”, scene )

– “enterScene” event is dispatched whenever scene transition has finished
scene:addEventListener( “enterScene”, scene )

– “exitScene” event is dispatched whenever before next scene’s transition begins
scene:addEventListener( “exitScene”, scene )

– “destroyScene” event is dispatched before view is unloaded, which can be
– automatically unloaded in low memory situations, or explicitly via a call to
– storyboard.purgeScene() or storyboard.removeScene().
scene:addEventListener( “destroyScene”, scene )


return scene [import]uid: 118482 topic_id: 26218 reply_id: 107835[/import]

Ya I see what you are saying. I edited my previous post because I fixed one issue but a new one arose. Its in my last post before this one ^. Thanks! [import]uid: 118482 topic_id: 26218 reply_id: 107837[/import]

Not sure exactly what your problem is. Seems like you need to remove the tank from the tanks array not just set it to nil. Setting it to nil and the next time you loop past the nil entries.

Why not:
[lua]for i = 1, TOTAL_TANKS do
local tank = tanks[i] – get a reference to object
if tank.x >= 40 and tank.isKilled == false then
tank.isKilled = true
tank.isMoving = false
tank.isAlive = false
– physics.removeBody( tank )
tank:removeSelf() – Does this also remove the physics body?
tanks[i] = nil
end
end[/lua]

I have had problems deleting on item from an array while you are iterating through the array. You might need to clean up the array after, or may be just test for nil:

[lua]… if tank ~= nil then …[/lua] [import]uid: 98652 topic_id: 26218 reply_id: 107838[/import]

Change this:

table.insert(tanks, display.newImage("tank.png"))   

To:

tanks[i] = display.newImage("tank.png")  

And then to remove them:

tanks[i]:removeSelf() [import]uid: 23649 topic_id: 26218 reply_id: 107847[/import]

Here’s an idea that might work for you. Here each box is assigned it’s position in the array as index. When it comes time to remove boxes from the array the boxes know their index making it easy to remove them.

[lua]local box_array = {}
local function tap_box( event )
local box = event.target
local index = event.target.index

box:removeSelf()
table.remove( box_array, index )

print( #box_array )
end
for i = 1, 10, 1 do
local box = display.newRect( 0, 0, 32, 32 )
box.x = math.random( 50, 270 )
box.y = math.random( 50, 430 )

box.index = i

box_array[i] = box

box:addEventListener( “tap”, tap_box )
end[/lua]
[import]uid: 98652 topic_id: 26218 reply_id: 107887[/import]

You can’t call local box that many times, it will only reference it’s local self in each function, not the the global (local) one you want for the scene.

Call local box right after local box_array = {}.
[import]uid: 23649 topic_id: 26218 reply_id: 107918[/import]

@jeremyapplebaum12 Not sure what you are referring to, but the example code I posted works just fine.

Box is local to the for loop then added to the array.

When a box is tapped I assigned box to a new local variable inside the tap handler, where it is local to that handler. [import]uid: 98652 topic_id: 26218 reply_id: 107932[/import]

Whoops, didn’t notice this line:

box_array[i] = box

Sorry about that. [import]uid: 23649 topic_id: 26218 reply_id: 107987[/import]

Hey guys well I am now able to remove the display objects along with its physics body succesfully. I have not tried to add a menu which i can go back and forth between using a play and menu button each in their respective scene. I can “play” what I have succesfully with no glitches, but i don’t think I cleaned up all the tank, pointer and map display objects entirely.

When I first start the game (ctrl - r) the memory reading in the menu is about 1.2 million bytes. When I get to my level1 scene the memory fluctuates between 3.5 mill and 5.9 mill bytes. However if I go back to the menu the memory reading is 5.9 mill bytes!!

In my destroyScene storyboard function I thought I had removed everything properly? What am I doing wrong?

[code]



– level1.lua


local storyboard = require( “storyboard” )
local scene = storyboard.newScene()
local widget = require “widget”

– include Corona’s “physics” library
local physics = require “physics”
physics.start()
physics.setGravity( 0, 0)
physics.setDrawMode(“hybrid”)

– forward declarations and other locals
local screenW, screenH, halfW, halfH = display.contentWidth, display.contentHeight, display.contentWidth*0.5, display.contentHeight*0.5

local tanks = {}
local tickCnt = 0
local TOTAL_TANKS = 2
local tankCnt = 1

function menuExit ()
storyboard.gotoScene( “menu”, “fade”, 100 )
end

function AddCommas( number, maxPos )

local s = tostring( number )
local len = string.len( s )

if len > maxPos then
– Add comma to the string
local s2 = string.sub( s, -maxPos )
local s1 = string.sub( s, 1, len - maxPos )
s = (s1 … “,” … s2)
end

maxPos = maxPos - 3 – next comma position

if maxPos > 0 then
return AddCommas( s, maxPos )
else
return s
end

end

function tickCntFunct()
if tickCnt <= 2000 then
tickCnt = tickCnt + 25
print(“tick count”, tickCnt)
else
tickCnt = 0
end
end
timer.performWithDelay(10, tickCntFunct, 0)

function moveTank(aTank)
– you can move the tank, as well as other actions here for each
– tank as it’s reference is sent in as a parameter
local mSpeed = 150
rSpeed = 6

if aTank.isAlive == true and aTank.isKilled == false then
print("aTank.x = ", aTank.x)
print("aTank.y = ", aTank.y)
print("tank rotation = ", aTank.rotation)

local disP1 = math.sqrt((math.abs(aTank.x - pointer1.x))^2 + (math.abs(aTank.y - pointer1.y))^2)
local disP2 = math.sqrt((math.abs(aTank.x - pointer2.x))^2 + (math.abs(aTank.y - pointer2.y))^2)
local disP3 = math.sqrt((math.abs(aTank.x - pointer3.x))^2 + (math.abs(aTank.y - pointer3.y))^2)
local disP4 = math.sqrt((math.abs(aTank.x - pointer4.x))^2 + (math.abs(aTank.y - pointer4.y))^2)

removeTanknumber = 0

if aTank.x < 0 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
if aTank.rotation < 180 and disP1 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation + rSpeed
end
if aTank.rotation >= 180 and disP1 < 1 then
aTank.rotation = 180
aTank:setLinearVelocity( 0, mSpeed)
end
if aTank.rotation > 90 and disP2 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation - rSpeed
end
if aTank.rotation <= 90 and disP2 < 1 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
if aTank.rotation > 0 and disP3 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation - rSpeed
end
if aTank.rotation <= 0 and disP3 < 1 then
aTank.rotation = 0
aTank:setLinearVelocity( 0, -1*mSpeed)
end
if aTank.rotation < 90 and disP4 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation + rSpeed
end
if aTank.rotation >= 90 and disP4 < 1 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
end
end
timer.performWithDelay(1, moveTank, 0)


– BEGINNING OF YOUR IMPLEMENTATION

– NOTE: Code outside of listener functions (below) will only be executed once,
– unless storyboard.removeScene() is called.


– Called when the scene’s view does not exist:
function scene:createScene( event )
local group = self.view

pointersGroup = display.newGroup()
mapGroup = display.newGroup()
HUDGroup = display.newGroup()

– create a grey rectangle as the backdrop
map = display.newImage( “map1.png” )
map:setReferencePoint(display.TopLeftReferencePoint)
map.x = 0
map.y = 0

spawner = display.newImage(“pointer.png”)
spawner:setReferencePoint(display.CenterReferencePoint)
spawner.y = 210
spawner.x = -40

pointer1 = display.newImage(“pointer.png”)
pointer1:setReferencePoint(display.CenterReferencePoint)
pointer1.x = 210
pointer1.y = 210

pointer2 = display.newImage(“pointer.png”)
pointer2:setReferencePoint(display.CenterReferencePoint)
pointer2.x = 210
pointer2.y = 390

pointer3 = display.newImage(“pointer.png”)
pointer3:setReferencePoint(display.CenterReferencePoint)
pointer3.x = 510
pointer3.y = 390

pointer4 = display.newImage(“pointer.png”)
pointer4:setReferencePoint(display.CenterReferencePoint)
pointer4.x = 510
pointer4.y = 90

menuButton = widget.newButton{

default=“menu_button.png”,
over=“menu_button.png”,
width=100, height=50,
onRelease = menuExit – event listener function
}
menuButton.view:setReferencePoint( display.CenterReferencePoint )
menuButton.view.x = screenW - screenW/10
menuButton.view.y = screenH - screenH/10

– all display objects must be inserted into group

pointersGroup:insert( spawner )
pointersGroup:insert( pointer1 )
pointersGroup:insert( pointer2 )
pointersGroup:insert( pointer3 )
pointersGroup:insert( pointer4 )
HUDGroup:insert( menuButton.view )
mapGroup:insert( map )
group:insert( pointersGroup )
group:insert( mapGroup )
group:insert( HUDGroup )

end

– Called immediately after scene has moved onscreen:
function scene:enterScene( event )
local group = self.view

for i = 1, TOTAL_TANKS do
– create 5 tanks, place them off screen, set their status to isAlive and not isMoving
tanks[i] = display.newImage(“tank.png”)
print("memory (all spawned): “, AddCommas( system.getInfo(“textureMemoryUsed”), 9 ) … " bytes” )
tanks[i]:setReferencePoint(display.CenterReferencePoint)
tanks[i]:scale(0.75, 0.75)
tanks[i].x = spawner.x
tanks[i].y = spawner.y
tanks[i].isAlive = true
tanks[i].isMoving = false
tanks[i].isKilled = false
end

local function gameLoop(event)

– normally should not have loops inside the game loop, because
– if there is too much going on during each frame call it can cause issues
– but for moving only 5 tanks, this is how you can do it.
– each tank will call the same function above (moveTank)

– have a variable that you would check here, to see if ‘so many ticks or seconds’
– has passed, and if so, set the isMoving to true for the next tank … will just use
– a simple incremented variable ‘tickCnt’

if tickCnt > 2000 and tankCnt <= TOTAL_TANKS then
physics.addBody( tanks[tankCnt], { density=3.0, friction=0.5, bounce=0.3 } )
tanks[tankCnt].isMoving = true
print("tankCnt= ", tankCnt)
tankCnt = tankCnt + 1
print("memory (moving): “, AddCommas( system.getInfo(“textureMemoryUsed”), 9 ) … " bytes” )
end

for i = 1, TOTAL_TANKS do
if tanks[i] ~= nil then
if tanks[i].x >= 200 and tanks[i].isKilled == false then
print("memory (before removal): “, AddCommas( system.getInfo(“textureMemoryUsed”), 9 ) … " bytes” )
tanks[i].isKilled = true
tanks[i].isMoving = false
tanks[i].isAlive = false
physics.removeBody( tanks[i] )
tanks[i]:removeSelf()
tanks[i] = nil
print("memory (removed): “, AddCommas( system.getInfo(“textureMemoryUsed”), 9 ) … " bytes” )
end
end
end

for i=1, TOTAL_TANKS do
if tanks[i] ~= nil then
if tanks[i].isMoving == true and tanks[i].isAlive == true and tanks[i].isKilled == false then
moveTank(tanks[i])
end
end
end

end

Runtime:addEventListener(“enterFrame”, gameLoop)

physics.start()

end

– Called when scene is about to move offscreen:
function scene:exitScene( event )
local group = self.view

physics.stop()

end

– If scene’s view is removed, scene:destroyScene() will be called just prior to:
function scene:destroyScene( event )
local group = self.view

if menuButton then
menuButton:removeSelf() – widgets must be manually removed
menuButton = nil
end

spawner:removeSelf()
spawner = nil
pointer1:removeSelf()
pointer1 = nil
pointer2:removeSelf()
pointer2 = nil
pointer3:removeSelf()
pointer3 = nil
pointer4:removeSelf()
pointer4 = nil
map:removeSelf()
map = nil

package.loaded[physics] = nil
physics = nil
end


– END OF YOUR IMPLEMENTATION

– “createScene” event is dispatched if scene’s view does not exist
scene:addEventListener( “createScene”, scene )

– “enterScene” event is dispatched whenever scene transition has finished
scene:addEventListener( “enterScene”, scene )

– “exitScene” event is dispatched whenever before next scene’s transition begins
scene:addEventListener( “exitScene”, scene )

– “destroyScene” event is dispatched before view is unloaded, which can be
– automatically unloaded in low memory situations, or explicitly via a call to
– storyboard.purgeScene() or storyboard.removeScene().
scene:addEventListener( “destroyScene”, scene )


return scene [import]uid: 118482 topic_id: 26218 reply_id: 108014[/import]

Anyone? [import]uid: 118482 topic_id: 26218 reply_id: 108189[/import]

Hmmm, I’m seeing at least 2 timers which are declared outside of any local function, with the third parameter as “0” (meaning they run forever until stopped). You don’t stop these timers on scene exit, but you should.

I’m also seeing several non-local functions… the first 4?.. So, they are global, meaning they stick in memory forever. Storyboard can’t clean them up. Storyboard is a great scene management option, but you still must adhere to strict Lua scope and cleanup practices to prevent memory leaks.

Hope this helps. :slight_smile:

Brent Sorrentino
Ignis Design [import]uid: 9747 topic_id: 26218 reply_id: 108199[/import]