Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
When destroying a body, game says it is already destroyed. — Gideros Forum

When destroying a body, game says it is already destroyed.

ZizanymanZizanyman Member
edited July 2017 in General questions
I have this piece of code to destroy an object if it goes below a certain point. This code runs every frame in the "onEnterFrame" function.
 for i, val in pairs(self.balls) do
	if val:getY() > 500 then
		self.world:destroyBody(val.body)
	end
end
However, whenever the object goes below that point, an error comes up saying:
Scenes/game.lua:74: Body is already destroyed.
I tried adding code that checks if val.body is nil, and doesn't destroy it if so, but the error message still came up. Does anybody know why this is happening?

Comments

  • antixantix Member
    edited July 2017
    Maybe you could add another variable to your balls and check that before attempting removal.
     for i, ball in pairs(self.balls) do
        if ball.alive then
            if ball:getY() > 500 then
                ball.alive = false
                self.world:destroyBody(ball.body)
            end
        end
    end
  • Thanks for the suggestion. When I tried this, I thought that it would work, but for some reason, it still doesn't. I'm really confused.
  • piepie Member
    edited July 2017
    I had this too: I am not sure to remember how, but I think that I fixed it referring directly to self.balls[i] and/or skipping some frames (nobody would notice if you do this every 3/5 frames instead of each one)
  • HubertRonaldHubertRonald Member
    edited July 2017
    Hi @Zizanyman, also another solution is
    for _, obj in pairs(self.balls) do
     if obj:getY() > 500 then
         Timer.delayedCall(0.5, function() --because world is locked
             obj.body:setActive(false)
            obj:setVisible(false)
        end)
     end
    end
    And re-use this body in another moment for example "a box" only destroy fixture here a simple idea:
    for _, obj in pairs(self.balls) do
      if not obj.body:isActive() then
       Timer.delayedCall(0.5, function()
         obj.body:destroyFixture(obj.body.fixture)
         obj:setTexture(Texture.new("myBox",true))
     
     
         local poly = b2.PolygonShape.new()
         poly:setAsBox(obj:getWidth(), obj:getHeight())
     
         local fixture = body:createFixture{shape = poly, density = 1.0, friction = 0.1, restitution = 0.2}
         obj.body.fixture = fixture -- you can have a setup function for your physic
     
         obj.body:setActive(true)
         obj:setVisible(true)
         break --if you don't need more 
      end)
     end
    end
    It save a lot memory because it works like a sprite pool

  • HubertRonaldHubertRonald Member
    edited July 2017
    Yes I forgot it
    http://docs.giderosmobile.com/reference/physics/b2World/destroyBody#b2.World:destroyBody

    So another solution if you are not going to need the object anymore is:
    for _, obj in pairs(self.balls) do
     if obj:getY() > 500 then
       obj:removeFromParent() --because on function() yourClass:onEnterFrame "loop for" takes num child on scene
       Timer.delayedCall(0.5, function() --because world is locked
         self.world:destroyBody(obj.body)
         obj.body = nil -- this line is optional
       end)
     end
    end
    I think everything will depend on what you want to do


  • Thank you for the feedback, but no matter which method I try, I keep getting error messages. However, I changed around the game a bit so that I don't have to destroy the ball once it goes under a certain level. Thank you all for the feedback anyway though, even though it didn't work in my case it may help others who are having a similar issue!
  • Hi @Zizanyman
    It's good you have found your own solution, but just out of curiosity is using your own script or, are you using Box2dEasy or any? If you are using your own script, how are you definied running the world (EnterFrame)? for future references.
  • I was using Box2d for this game.
  • john26john26 Maintainer
    edited July 2017
    It's not obvious from your code that val.body is actually a body. It seems val is a Sprite but Sprites do not have bodies by default in Gideros. So when you create the body you need to create your own field body and set it equal to the box2d body.

    Could you perhaps write a short, complete project which illustrates the problem and gives the error.

    In Nebula Retro, I do the same thing: destroy crates when they fall off the screen (ie I destroy both the Sprite and the body), I didn't get an error, see here for how I did it:

    https://github.com/john-blackburn/nebularetro/blob/master/main.lua#L1079

    This function is called on ENTER_FRAME as well.

    The .body field has been set up earlier (manually by me, it is not a standard Sprite field)
  • Every ball object had a box2d body attached to it. I was able to access it using val.body in other parts of the script, it just didn't work when destroying it for some reason.
  • hgy29hgy29 Maintainer
    Maybe a silly remark, but do you actually remove your object from the balls array at same point ? If not then your object will be destroyed again and again each frame

    Likes: antix, john26

    +1 -1 (+2 / -0 )Share on Facebook
  • I tried adding code that set self.balls[i] to nil, and it still didn't work.
  • Hi @Zizanyman as said @hgy29 you must remove object from the balls array because "nil" doesn't work try the next:
    local l={1,2,3,4,5,6}
    --case 1
    for k, v in pairs(l) do print (v); if v==4 then l[k] = nil end end
    --case 2
    for k, v in pairs(l) do print(k,v) end
     
    -- case 3
    table.remove(l,4)
    for k, v in pairs(l) do print(k,v) end
     
    --Output case 1
    --1,2,3,4,5,6
     
    --[[ Output case 2
    1	1
    2	2
    3	3
    5	5
    6	6
    number four is not appear but index of number 6 is six and not five
    ]]
     
    --[[Output case 3
    1	1
    2	2
    3	3
    4	5
    5	6
    nil was remove of table "l" and six has index five
    ]]
  • antixantix Member
    edited July 2017
    By starting at the end of the table and reversing to the beginning of the table is the best method.
    local objects = {
      {x = 0, y = 0, },
      {x = 0, y = 500, },
      {x = 0, y = 0, },
    }
     
    for i = #objects, 1, -1 do -- reverse through table
      local remove = table.remove
      local object = objects[i]
      if object.y >= 500 then
        remove(objects, i) -- removes object
      end
    end
    In this example the second object will be removed, whilst maintaining the sanity of the table.

    This is the way (IMHO) to work with tables of game objects.. pairs is slow.
  • HubertRonaldHubertRonald Member
    edited July 2017
    "Yep, that's right" @antix also I've used reversing in collision when I must destroy object but currently I'm not using it because I'm reusing my objects.
  • antixantix Member
    @HubertRonald, ahh okay. You can always keep two tables (active and inactive) and move them between tables as required :)
Sign In or Register to comment.