Why am I always having trouble managing game objects in arrays?

Working on a game I have several arrays keeping track of various objects. During the course of the game objects get added and removed. It seems with Lua I often run into errors where I think I’ve removed an element but the program still thinks there’s something there.

Using a for loop I work through the arrays backwards. I use table.remove() to remove elements.

Can you throw up your object creation, array addition and loop code to evaluate?

Thanks for the reply. I’ll try and put something together. I have everything distributed across several Lua files so it’s hard to show it as a simple example. 

In general I use a Lua file to manage each type of object, defense, enemies etc. This file will also have an array to store it’s objects. I’ll use a function to make objects, and add them to display groups and the array etc. Then I’ll have a function to remove objects, this function might also do other things to clean up. 

Here’s an example of a file that manages alien sprites. This file creates, stores the sprites in an array, and moves them on an update call from a enterFrame handler in another file. 

----------------------------------------------------------------------------------------- local M = {} ----------------------------------------------------------------------------------------- local grid = require( "grid" ) ----------------------------------------------------------------------------------------- local sprite\_sheet = graphics.newImageSheet( "Alien-All.png", {width=34, height=34, numFrames=80} ) ----------------------------------------------------------------------------------------- local alien\_type\_array = { {name="blue", speed=0.1, hits=5, damage=0.1, options={frames={1,2,3,2,1,4,5,6,7,6,5,4,11,12,13,14,13,12,11,4,8,9,10,9,8}, time=2000} }, {name="red", speed=1.0, hits=3, damage=0.1, options={frames={16,17,18,19,18,17,16,21,16,25}, time=1000} }, {name="green", speed=0.7, hits=4, damage=0.1, options={frames={31,32,33,34,35,36,37,38,39,40,41,42,43,44,45}, time=1000} }, {name="black", speed=0.3, hits=7, damage=0.1, options={frames={51,52,53,54,55,54,53,52,51,56,57,56,51,58,59,58,51}, time=2000} }, {name="pink", speed=3.0, hits=2, damage=0.1, options={frames={66,67,68,69,70,69,68,67,66,71,66,72,66,73,66,74,75,76,75,74}, time=2400} } } ----------------------------------------------------------------------------------------- local alien\_array = {} local alien\_view local alien\_timer local rows, cols = grid.get\_rows\_cols() local tile\_size = grid.get\_tile\_size() local end\_y = display.contentHeight -- + 17 local destroyed\_sound ----------------------------------------------------------------------------------------- local function make() local n = math.random( #alien\_type\_array ) -- local n = 1 local alien = display.newSprite( sprite\_sheet, alien\_type\_array[n].options ) alien:play() alien\_view:insert( alien ) alien.speed = alien\_type\_array[n].speed alien.name = alien\_type\_array[n].name alien.hits = alien\_type\_array[n].hits alien.damage = alien\_type\_array[n].damage alien.grid = nil alien.x = math.random( cols ) \* tile\_size -9 alien.y = -16 function alien:remove() display.remove( self ) end function alien:hit( damage ) self.hits = self.hits - damage if self.hits \<= 0 then audio.play( destroyed\_sound ) self:remove() return true end return false end -- Not yet implemented... function alien:move() self.y = self.y + self.speed if self.y \> end\_y then return true -- Yes remove this it's moved off the screen end return false -- No don't remove it hasn't moved off the screen end alien\_array[#alien\_array+1] = alien end ----------------------------------------------------------------------------------------- local function set\_view( view ) alien\_view = view end M.set\_view = set\_view -- Alien update each frame -- local function update() for i = #alien\_array, 1, -1 do local alien = alien\_array[i] if alien ~= nil then -- check tile -- local tile = grid.point\_in\_tile( alien ) local tile = grid.alien\_in\_tile( alien ) if tile ~= nil and tile.has\_defense then -- munch print( alien.name, "eating defense", tile.defense ) tile.defense:hit( alien.damage ) else -- move alien if alien:move() then -- Move and check if this has moved off screen -- print( "remove:", alien, i, #alien\_array ) table.remove( alien\_array, i ) display.remove( alien ) -- print( "confirm:", alien, i, #alien\_array ) end end end end end M.update = update ------------------------------ local function start\_timer( delay, count ) alien\_timer = timer.performWithDelay( delay, make, count ) end M.start\_timer = start\_timer local function stop\_timer() timer.cancel( alien\_timer ) end M.stop\_timer = stop\_timer local function get\_array() return alien\_array end M.get\_array = get\_array local function get\_types() return alien\_type\_array end M.get\_types = get\_types local function clear\_aliens() for i = 1, #alien\_array do display.remove( table.remove( alien\_array ) ) end end M.clear\_aliens = clear\_aliens local function build() destroyed\_sound = audio.loadSound( "sound/alien-destroyed.wav" ) return true end M.build = build local function set\_speed\_hits\_by\_name( name, speed, hits ) for i = 1, #alien\_type\_array do if alien\_type\_array[i].name == name then alien\_type\_array[i].speed = speed alien\_type\_array[i].hits = hits break end end end M.set\_speed\_hits\_by\_name = set\_speed\_hits\_by\_name local function destroy() stop\_timer() clear\_aliens() end M.destroy = destroy ----------------------------------------------------------------------------------------- return M

Maybe I missed it, but I don’t see your function to iterate backwards, removing the objects. Here’s a thread discussing the various merits of removing objects from tables:

http://forums.coronalabs.com/topic/34186-removing-display-object-from-table/

Is your table removal code above, and I just missed it? If not, if you could post that, that would be great!

Here’s another module that handles collisions. This has an update function called each frame. It grabs an array of bullets and aliens and does a simple point in rectangle type hit test. On a collision aliens are sent a :hit(damage) which returns true if they should be removed. :hit(damage) removes the alien display object, while the object is removed from the array here. 

----------------------------------------------------------------------------------------- local M = {} ----------------------------------------------------------------------------------------- local alien = require( "alien" ) local bullet\_manager = require( "bullet\_manager" ) local sprite\_sheet = graphics.newImageSheet( "explosion-4.png", require("explosion-4").getSheetOptions() ) local explosion\_type\_array = { {start=1, count=13}, {start=14, count=13} } local collision\_view ----------------------------------------------------------------------------------------- local function make\_explosion( x, y, explosion\_type ) local explosion = display.newSprite( sprite\_sheet, explosion\_type\_array[explosion\_type] ) collision\_view:insert( explosion ) explosion.x = x explosion.y = y explosion:play() explosion:addEventListener( "sprite", function( event ) if event.phase == "loop" then display.remove( event.target ) end end ) end local function hit\_test( bullet, alien ) local x, y = bullet.x, bullet.y local bounds = alien.contentBounds local l, t, r, b = bounds.xMin, bounds.yMin, bounds.xMax, bounds.yMax if x \> l and x \< r and y \> t and y \< b then return true end return false end ----------------------------------------------------------------------------------------- local function set\_view( view ) collision\_view = view end M.set\_view = set\_view local function update() local alien\_array = alien.get\_array() local bullet\_array = bullet\_manager.get\_array() for a = #alien\_array, 1, -1 do local alien = alien\_array[a] for b = #bullet\_array, 1, -1 do local bullet = bullet\_array[b] if hit\_test( bullet, alien ) then if alien:hit( bullet.damage ) then table.remove( alien\_array, a ) make\_explosion( bullet.x, bullet.y, 2 ) else make\_explosion( bullet.x, bullet.y, 1 ) end table.remove( bullet\_array, b ) display.remove( bullet ) end end end end M.update = update ----------------------------------------------------------------------------------------- return M

Did you already test this by putting print statements before and after you called the “local alien_array = alien.get_array()”  and the table.remove( alien_array, a ) calls? I ask to confirm that you are seeing the array being called correctly, and to confirm that the table is actually being emptied. 

FWIW, my game’s AI necessitated the removal of objects (bullets, enemies, powerups) and while I did insert stuff into tables, I didn’t have enough time/inclination to figure out how to efficiently remove items from tables. I basically just bulldogged it and removed/nilled the table itself. I’m not seeing any memory leaks with this method, but I am fully aware it’s not the most elegant hack in the world.

Thanks again for the reply. Actually what I have here works, after an hour of banging my head on the desk, followed by a round of face palms. Even though I have this is worked out here, this is the most common problem that comes back around every time. And it seems like a task thats required for almost every game. I keep thinking there must be a standard fool proof system. Maybe some design pattern that boils this down to an elegant reliable solution. 

What I don’t like about my method is that I have to pass the arrays from two modules into a third which adds and removes things. I suppose I could have used table.remove( t, table.indexof( t, item) ). I guess I was thinking this would add another layer of looping through the table, it seemed more computationally efficient to remove the item where I already had it’s index. 

I could have sworn that I read somewhere, that using table.remove() was not an efficient way of removing things from tables. I can’t find it now, so I guess I imagined it in a fever dream. 

Glad to hear you had this worked out. Your solution seems as valid as any other, and if you’re not encountering memory leaks and/or Runtime errors, you should be golden. 

Can you throw up your object creation, array addition and loop code to evaluate?

Thanks for the reply. I’ll try and put something together. I have everything distributed across several Lua files so it’s hard to show it as a simple example. 

In general I use a Lua file to manage each type of object, defense, enemies etc. This file will also have an array to store it’s objects. I’ll use a function to make objects, and add them to display groups and the array etc. Then I’ll have a function to remove objects, this function might also do other things to clean up. 

Here’s an example of a file that manages alien sprites. This file creates, stores the sprites in an array, and moves them on an update call from a enterFrame handler in another file. 

----------------------------------------------------------------------------------------- local M = {} ----------------------------------------------------------------------------------------- local grid = require( "grid" ) ----------------------------------------------------------------------------------------- local sprite\_sheet = graphics.newImageSheet( "Alien-All.png", {width=34, height=34, numFrames=80} ) ----------------------------------------------------------------------------------------- local alien\_type\_array = { {name="blue", speed=0.1, hits=5, damage=0.1, options={frames={1,2,3,2,1,4,5,6,7,6,5,4,11,12,13,14,13,12,11,4,8,9,10,9,8}, time=2000} }, {name="red", speed=1.0, hits=3, damage=0.1, options={frames={16,17,18,19,18,17,16,21,16,25}, time=1000} }, {name="green", speed=0.7, hits=4, damage=0.1, options={frames={31,32,33,34,35,36,37,38,39,40,41,42,43,44,45}, time=1000} }, {name="black", speed=0.3, hits=7, damage=0.1, options={frames={51,52,53,54,55,54,53,52,51,56,57,56,51,58,59,58,51}, time=2000} }, {name="pink", speed=3.0, hits=2, damage=0.1, options={frames={66,67,68,69,70,69,68,67,66,71,66,72,66,73,66,74,75,76,75,74}, time=2400} } } ----------------------------------------------------------------------------------------- local alien\_array = {} local alien\_view local alien\_timer local rows, cols = grid.get\_rows\_cols() local tile\_size = grid.get\_tile\_size() local end\_y = display.contentHeight -- + 17 local destroyed\_sound ----------------------------------------------------------------------------------------- local function make() local n = math.random( #alien\_type\_array ) -- local n = 1 local alien = display.newSprite( sprite\_sheet, alien\_type\_array[n].options ) alien:play() alien\_view:insert( alien ) alien.speed = alien\_type\_array[n].speed alien.name = alien\_type\_array[n].name alien.hits = alien\_type\_array[n].hits alien.damage = alien\_type\_array[n].damage alien.grid = nil alien.x = math.random( cols ) \* tile\_size -9 alien.y = -16 function alien:remove() display.remove( self ) end function alien:hit( damage ) self.hits = self.hits - damage if self.hits \<= 0 then audio.play( destroyed\_sound ) self:remove() return true end return false end -- Not yet implemented... function alien:move() self.y = self.y + self.speed if self.y \> end\_y then return true -- Yes remove this it's moved off the screen end return false -- No don't remove it hasn't moved off the screen end alien\_array[#alien\_array+1] = alien end ----------------------------------------------------------------------------------------- local function set\_view( view ) alien\_view = view end M.set\_view = set\_view -- Alien update each frame -- local function update() for i = #alien\_array, 1, -1 do local alien = alien\_array[i] if alien ~= nil then -- check tile -- local tile = grid.point\_in\_tile( alien ) local tile = grid.alien\_in\_tile( alien ) if tile ~= nil and tile.has\_defense then -- munch print( alien.name, "eating defense", tile.defense ) tile.defense:hit( alien.damage ) else -- move alien if alien:move() then -- Move and check if this has moved off screen -- print( "remove:", alien, i, #alien\_array ) table.remove( alien\_array, i ) display.remove( alien ) -- print( "confirm:", alien, i, #alien\_array ) end end end end end M.update = update ------------------------------ local function start\_timer( delay, count ) alien\_timer = timer.performWithDelay( delay, make, count ) end M.start\_timer = start\_timer local function stop\_timer() timer.cancel( alien\_timer ) end M.stop\_timer = stop\_timer local function get\_array() return alien\_array end M.get\_array = get\_array local function get\_types() return alien\_type\_array end M.get\_types = get\_types local function clear\_aliens() for i = 1, #alien\_array do display.remove( table.remove( alien\_array ) ) end end M.clear\_aliens = clear\_aliens local function build() destroyed\_sound = audio.loadSound( "sound/alien-destroyed.wav" ) return true end M.build = build local function set\_speed\_hits\_by\_name( name, speed, hits ) for i = 1, #alien\_type\_array do if alien\_type\_array[i].name == name then alien\_type\_array[i].speed = speed alien\_type\_array[i].hits = hits break end end end M.set\_speed\_hits\_by\_name = set\_speed\_hits\_by\_name local function destroy() stop\_timer() clear\_aliens() end M.destroy = destroy ----------------------------------------------------------------------------------------- return M

Maybe I missed it, but I don’t see your function to iterate backwards, removing the objects. Here’s a thread discussing the various merits of removing objects from tables:

http://forums.coronalabs.com/topic/34186-removing-display-object-from-table/

Is your table removal code above, and I just missed it? If not, if you could post that, that would be great!

Here’s another module that handles collisions. This has an update function called each frame. It grabs an array of bullets and aliens and does a simple point in rectangle type hit test. On a collision aliens are sent a :hit(damage) which returns true if they should be removed. :hit(damage) removes the alien display object, while the object is removed from the array here. 

----------------------------------------------------------------------------------------- local M = {} ----------------------------------------------------------------------------------------- local alien = require( "alien" ) local bullet\_manager = require( "bullet\_manager" ) local sprite\_sheet = graphics.newImageSheet( "explosion-4.png", require("explosion-4").getSheetOptions() ) local explosion\_type\_array = { {start=1, count=13}, {start=14, count=13} } local collision\_view ----------------------------------------------------------------------------------------- local function make\_explosion( x, y, explosion\_type ) local explosion = display.newSprite( sprite\_sheet, explosion\_type\_array[explosion\_type] ) collision\_view:insert( explosion ) explosion.x = x explosion.y = y explosion:play() explosion:addEventListener( "sprite", function( event ) if event.phase == "loop" then display.remove( event.target ) end end ) end local function hit\_test( bullet, alien ) local x, y = bullet.x, bullet.y local bounds = alien.contentBounds local l, t, r, b = bounds.xMin, bounds.yMin, bounds.xMax, bounds.yMax if x \> l and x \< r and y \> t and y \< b then return true end return false end ----------------------------------------------------------------------------------------- local function set\_view( view ) collision\_view = view end M.set\_view = set\_view local function update() local alien\_array = alien.get\_array() local bullet\_array = bullet\_manager.get\_array() for a = #alien\_array, 1, -1 do local alien = alien\_array[a] for b = #bullet\_array, 1, -1 do local bullet = bullet\_array[b] if hit\_test( bullet, alien ) then if alien:hit( bullet.damage ) then table.remove( alien\_array, a ) make\_explosion( bullet.x, bullet.y, 2 ) else make\_explosion( bullet.x, bullet.y, 1 ) end table.remove( bullet\_array, b ) display.remove( bullet ) end end end end M.update = update ----------------------------------------------------------------------------------------- return M

Did you already test this by putting print statements before and after you called the “local alien_array = alien.get_array()”  and the table.remove( alien_array, a ) calls? I ask to confirm that you are seeing the array being called correctly, and to confirm that the table is actually being emptied. 

FWIW, my game’s AI necessitated the removal of objects (bullets, enemies, powerups) and while I did insert stuff into tables, I didn’t have enough time/inclination to figure out how to efficiently remove items from tables. I basically just bulldogged it and removed/nilled the table itself. I’m not seeing any memory leaks with this method, but I am fully aware it’s not the most elegant hack in the world.

Thanks again for the reply. Actually what I have here works, after an hour of banging my head on the desk, followed by a round of face palms. Even though I have this is worked out here, this is the most common problem that comes back around every time. And it seems like a task thats required for almost every game. I keep thinking there must be a standard fool proof system. Maybe some design pattern that boils this down to an elegant reliable solution. 

What I don’t like about my method is that I have to pass the arrays from two modules into a third which adds and removes things. I suppose I could have used table.remove( t, table.indexof( t, item) ). I guess I was thinking this would add another layer of looping through the table, it seemed more computationally efficient to remove the item where I already had it’s index. 

I could have sworn that I read somewhere, that using table.remove() was not an efficient way of removing things from tables. I can’t find it now, so I guess I imagined it in a fever dream. 

Glad to hear you had this worked out. Your solution seems as valid as any other, and if you’re not encountering memory leaks and/or Runtime errors, you should be golden.