Destroying and removing objects on collision- pitfalls

Howdy.  Apologies for the length of this, my first, post.  I’ve searched the forums for an answer, but all that’s led me to are multiple ways to tackle this problem, and I’m wondering which would be the most appropriate.

I’m currently building a game with several different, simple, characters.  These characters are active within the physics system, and when they collide with certain other terrain objects, they trigger an animation and then attempt to remove themselves from the game.  Since all these characters are the same size & shape, and only differ in their display graphic and the sound they play when they blow up, I created a simple class to produce them.

(I did this after reading about a handful of OOP approaches to Lua/Corona- I don’t need anything too complex, so I went with what seemed to be the simplest prescribed route).  So, the character file:

[lua]game_character = {}

function game_character:new(x,y, inType)
local theImage = “graphics/” … inType … “.png”
local game_character_instance = display.newImageRect( theImage, 22 , 22)
game_character_instance.x = x
game_character_instance.y = y
local puCollisionFilter = {categoryBits =4, maskBits = 31}
local puBodyElement = {filter=puCollisionFilter, radius=10, density=1, bounce=.6}

game_character_instance.physicsBody = puBodyElement
game_character_instance.isBullet = true
game_character_instance.isSleepingAllowed = false
game_character_instance.objectType = inType;
physics.addBody(game_character_instance, “dynamic”, game_character_instance.physicsBody)

function game_character_instance:destroy(inAudio)
print(self)
audio.play(inAudio)
self:removeSelf()
self = nil

end

return game_character_instance
end

return game_character

[/lua]

Notice the destroy method- I’ve stripped the animation code out of it, but that’s where I had that coded.

Now, in the main game file, I create a character instance like this:

[lua]

local game_character_A= game_character:new(display.contentCenterX-50,display.contentCenterY-50, “characterA”)

game_character_A.collision = onCollision

game_character_A:addEventListener(‘collision’, game_character_A)

[/lua]

And then in the method which is triggered by the collision:

[lua]

function onCollision(self, event)

    local theObj = event.other.objectType

    if (self.objectType ==‘characterA’) then

        if (theObj==“wall” and event.phase==“began”) then

                    local closureFxn = function()

                           self:destroy(wallHitSound)

                   end

                timer.performWithDelay( 1, closureFxn )

               end

    end

end

[/lua]

So, this works 85% of the time.  However, it often throws the following error (truncated for brevity):

character.lua:31: attempt to call method ‘removeSelf’ (a nil value)

message

stack traceback:

    ?: in function <?:218>

    [C]: in function ‘removeSelf’

  character.lua:31: in function ‘destroy’

  scene_game.lua:506: in function ‘_listener’

    ?: in function <?:141>

    ?: in function <?:218>

I notice that when this occurs, the table name of the character is printed twice to the log, so it looks like the collision continues after the object has been removed.

Now, I know that you’re not supposed to remove objects during a collision; however, some of the literature out there says that you can circumvent this by catching it only at the “began” phase, and then setting a delay to the call for removal.

Some questions:

  1. I noticed that in another slightly similar thread, the user was urged to check out the pre-collision event.  Would that make more sense for me?  Listen for pre-collision, then disable contact when the event is triggered, and then run the explosion animation and remove the character?

  2. Is the mildly OOP way I have this set up botched, or preventing this from running correctly?  Would it make more sense to include the removeSelf call right in the collision listener?

  3. Or, am I just wildly off base here, and not even close to the correct solution?

Any insight would be greatly appreciated.  This is my first large project with Lua- I’m coming from an actionscript 3/2 background, so I might not just be thinking “right” yet, especially in regards to scope and the way classes or event handling should be structured.

Thanks!

i think this problem is more to do with event driven code , rather than anything you are doing with OOP.

as you’ve noted you are getting more than one collision being fired, and that’s true with many events - you can have events firing repeatedly before you are done processing your intended event, and potentially in parallel.

i tend to add a flag to my object i’m trying to delete to indicate that i’m already processing this object.

so whenever the collision is fired  you set a flag

object.isDeleting = true 

but you do it inside an if that looks like

[lua]

if object.isDeleting == nil then

       object.isDeleting = true

– further code

timer.performWithDelay(1,function () object.removeSelf() end)

end

[/lua]

that way, any further events called for that object won’t get processed by the listener, they’ll be ignored.

so for your example code:

[lua]

  1. function onCollision(self, event)
  2.   if event.phase == “began” then  
  3. local theObj = event.other.objectType
  4.     
  5. if (self.objectType ==‘characterA’) then
  6.         if (theObj==“wall” and self.isDeleting == nil) then
  7.                    self.isDeleting == true
  8.   local closureFxn = function()
  9.                            self:destroy(wallHitSound)
  10.                    end
  11.                 timer.performWithDelay( 1, closureFxn )
  12.         end
  13.     end
  14. end
  15. end

[/lua]

I prefer to call display.remove(object) rather than object:removeSelf(), because display.remove() will check that the object is not nil first – preventing “interesting” things like this from happening :slight_smile:

Hi @mike21,

In terms of removing physics-based objects upon collision, Corona shouldn’t require a timer for this. There are some things which you must do “after a short time following a collision”, but simple removal is not one of them, at least not in any situation I’ve seen. Now, if for some reason you decide to do “more” to the object before it gets removed, like setting its body to inactive or moving it to a a new position, then you’ll need to do it upon a timer. But simple removal should be exactly that… it collides and then you remove it.

For your situation, I don’t recommend the precollision handler. That is more of a specialty technique that is useful for things like detecting the “event.contact” and handling it very specifically, i.e. for objects that may need to pass through one-way platforms as in a side-scroller. Beyond that, it’s not frequently needed, and it’s a fairly “noisy” handler since it may trigger a large number of events in that span before it collides with something.

Hope this helps,

Brent

Excellent- thanks for the input, guys- much appreciated!  So, I’ve implemented a solution which involves both suggestions.  And, that’s fixed my initial problem!  No more nil errors.   :slight_smile:

However, within the character file, I’ve revised the code to add in the pre-removal animation, which then calls the method to remove the asset from stage, and assign its reference a nil value.  When I run this, everything checks out… here’s the updated code:

[lua]function game_character_instance:reset()

            self.x = self.homeX

            self.y = self.homeY

            self:setLinearVelocity(0,0)

        end

        

        function game_character_instance:removeAsset()

          display.remove(game_character_instance)

            print("char - removeAsset: ", game_character_instance)

            pu_character_instance = nil

            print("char - after nil: ",game_character_instance)

        end

        

        function game_character_instance:destroy(inAudio)

             print("self - destroy: ", self)

            if (self.isDeleting== false) then

                self.isDeleting = true;

                audio.play(inAudio)

              

         self.fill.effect = “filter.polkaDots”

                self.fill.effect.numPixels = 0

                self.fill.effect.dotRadius = 0

                transition.to(self, {alpha=0, time=300})    

                transition.to(self.fill.effect,

                {numPixels = 7,

                dotRadius = .8,

                time = 300,

                transition = easing.outQuad,

                onComplete = self.removeAsset})

            end

       end[/lua]

And the prints return this

self - destroy:     table: 00EB3DD0    

char - removeAsset:     table: 00EB3DD0    

char - after nil:     nil    

Which is exactly what I want.  However, you’ll notice I added a “reset” method in there.  I wanted to create a way to recycle the level, so that when a timer ends, I can either reset the character if it still exists, or generate a new one if it’s been destroyed.

Within my game scene, I changed the code so that the character is now added via a method:

[lua]function addCharacterA()
local game_characterA = game_character:new(display.contentCenterX-50,display.contentCenterY-50, “game_characterA”)
game_characterA .collision = onCollision
game_characterA :addEventListener(‘collision’, game_characterA )
return game_characterA
end[/lua]

And in the scene initialization (using composer), I just add it like so:

[lua]

function scene:create( event )

          – blah blah blah

          game_characterA = addCharacterA()

end

[/lua]

And then a simplified version of my method to recycle the game looks like this:

[lua]

function initializeGame() 

    if (game_characterA == nil) then 

        game_characterA = addCharacterA()

    else

       game_characterA:reset()

    end

end

[/lua]

Which always throws an error- 

game_character.lua:34: attempt to call method ‘setLinearVelocity’ (a nil value)

message

stack traceback:

    ?: in function <?:218>

    [C]: in function ‘setLinearVelocity’

  game_character.lua:34: in function ‘reset’

  scene_game.lua:71: in function ‘initializeGame’

  scene_game.lua:111: in function ‘_listener’

    ?: in function <?:141>

    ?: in function <?:218>

And printing out confirms that it still has the value for the table as the original value (00EB3DD0, in this case) and not nil.

I strongly suspect that it’s my handling of scope that is screwing things up here.  Again, using composer, I have all variables and game characters defined as local variables, but I’m not declaring any of the functions as local (things seem to fall apart when I do that).  Does anything jump out here as being horribly wrong? 

Again, any help is greatly appreciated.  And thanks for lending a hand as I blunder onwards… :slight_smile:

Thanks!

Mike

i think this problem is more to do with event driven code , rather than anything you are doing with OOP.

as you’ve noted you are getting more than one collision being fired, and that’s true with many events - you can have events firing repeatedly before you are done processing your intended event, and potentially in parallel.

i tend to add a flag to my object i’m trying to delete to indicate that i’m already processing this object.

so whenever the collision is fired  you set a flag

object.isDeleting = true 

but you do it inside an if that looks like

[lua]

if object.isDeleting == nil then

       object.isDeleting = true

– further code

timer.performWithDelay(1,function () object.removeSelf() end)

end

[/lua]

that way, any further events called for that object won’t get processed by the listener, they’ll be ignored.

so for your example code:

[lua]

  1. function onCollision(self, event)
  2.   if event.phase == “began” then  
  3. local theObj = event.other.objectType
  4.     
  5. if (self.objectType ==‘characterA’) then
  6.         if (theObj==“wall” and self.isDeleting == nil) then
  7.                    self.isDeleting == true
  8.   local closureFxn = function()
  9.                            self:destroy(wallHitSound)
  10.                    end
  11.                 timer.performWithDelay( 1, closureFxn )
  12.         end
  13.     end
  14. end
  15. end

[/lua]

I prefer to call display.remove(object) rather than object:removeSelf(), because display.remove() will check that the object is not nil first – preventing “interesting” things like this from happening :slight_smile:

Hi @mike21,

In terms of removing physics-based objects upon collision, Corona shouldn’t require a timer for this. There are some things which you must do “after a short time following a collision”, but simple removal is not one of them, at least not in any situation I’ve seen. Now, if for some reason you decide to do “more” to the object before it gets removed, like setting its body to inactive or moving it to a a new position, then you’ll need to do it upon a timer. But simple removal should be exactly that… it collides and then you remove it.

For your situation, I don’t recommend the precollision handler. That is more of a specialty technique that is useful for things like detecting the “event.contact” and handling it very specifically, i.e. for objects that may need to pass through one-way platforms as in a side-scroller. Beyond that, it’s not frequently needed, and it’s a fairly “noisy” handler since it may trigger a large number of events in that span before it collides with something.

Hope this helps,

Brent

Excellent- thanks for the input, guys- much appreciated!  So, I’ve implemented a solution which involves both suggestions.  And, that’s fixed my initial problem!  No more nil errors.   :slight_smile:

However, within the character file, I’ve revised the code to add in the pre-removal animation, which then calls the method to remove the asset from stage, and assign its reference a nil value.  When I run this, everything checks out… here’s the updated code:

[lua]function game_character_instance:reset()

            self.x = self.homeX

            self.y = self.homeY

            self:setLinearVelocity(0,0)

        end

        

        function game_character_instance:removeAsset()

          display.remove(game_character_instance)

            print("char - removeAsset: ", game_character_instance)

            pu_character_instance = nil

            print("char - after nil: ",game_character_instance)

        end

        

        function game_character_instance:destroy(inAudio)

             print("self - destroy: ", self)

            if (self.isDeleting== false) then

                self.isDeleting = true;

                audio.play(inAudio)

              

         self.fill.effect = “filter.polkaDots”

                self.fill.effect.numPixels = 0

                self.fill.effect.dotRadius = 0

                transition.to(self, {alpha=0, time=300})    

                transition.to(self.fill.effect,

                {numPixels = 7,

                dotRadius = .8,

                time = 300,

                transition = easing.outQuad,

                onComplete = self.removeAsset})

            end

       end[/lua]

And the prints return this

self - destroy:     table: 00EB3DD0    

char - removeAsset:     table: 00EB3DD0    

char - after nil:     nil    

Which is exactly what I want.  However, you’ll notice I added a “reset” method in there.  I wanted to create a way to recycle the level, so that when a timer ends, I can either reset the character if it still exists, or generate a new one if it’s been destroyed.

Within my game scene, I changed the code so that the character is now added via a method:

[lua]function addCharacterA()
local game_characterA = game_character:new(display.contentCenterX-50,display.contentCenterY-50, “game_characterA”)
game_characterA .collision = onCollision
game_characterA :addEventListener(‘collision’, game_characterA )
return game_characterA
end[/lua]

And in the scene initialization (using composer), I just add it like so:

[lua]

function scene:create( event )

          – blah blah blah

          game_characterA = addCharacterA()

end

[/lua]

And then a simplified version of my method to recycle the game looks like this:

[lua]

function initializeGame() 

    if (game_characterA == nil) then 

        game_characterA = addCharacterA()

    else

       game_characterA:reset()

    end

end

[/lua]

Which always throws an error- 

game_character.lua:34: attempt to call method ‘setLinearVelocity’ (a nil value)

message

stack traceback:

    ?: in function <?:218>

    [C]: in function ‘setLinearVelocity’

  game_character.lua:34: in function ‘reset’

  scene_game.lua:71: in function ‘initializeGame’

  scene_game.lua:111: in function ‘_listener’

    ?: in function <?:141>

    ?: in function <?:218>

And printing out confirms that it still has the value for the table as the original value (00EB3DD0, in this case) and not nil.

I strongly suspect that it’s my handling of scope that is screwing things up here.  Again, using composer, I have all variables and game characters defined as local variables, but I’m not declaring any of the functions as local (things seem to fall apart when I do that).  Does anything jump out here as being horribly wrong? 

Again, any help is greatly appreciated.  And thanks for lending a hand as I blunder onwards… :slight_smile:

Thanks!

Mike