Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Physics not quite resetting

edited February 2015 in Questions Posts: 2,020

I'm having a strange issue where box2D isn't resetting properly when you start the next level within the game (ie a "warm reset" within the programme I'm writing, I'm not talking about restarting the code). I've not had any problems with resetting the physics system in my previous projects, and I'm following the same method here, iterate through the table holding the bodies, destroy() each one, then nil the table, and then build the system again.

What happens with this project though, is in the first frame or two of the new game, I get a handful of collision events, say between five and twenty events, for bodies that don't exist anymore. I need to check more, but it also seems that one or two of the new bodies have inherited the linear velocity of their predecessors. There is no evidence of "zombie" bodies though, no lingering rigid shapes in the field, as far as I can tell. It's only an issue that affects the first frame or two. As each level starts with a "ready?" delay, I disable the collision checking for the duration of the delay, so the junk collisions aren't so important. I'm worried that it could portend further problems down the line and I'm wondering what the cause could be.

There are a couple of things that I'm doing differently from previous projects, so they could be a culprit, but I haven't been able to completely get rid of the problem.

  1. On "game over" I use physics:pause(). I want a freeze frame so the player can clearly see the game-ending event. I've experimented with using physics:resume() at various points, such as just before I destroy all the bodies, just after I destroy them, just as the new game is about to start etc. I've also tried removing the pause, resume calls altogether, but I still got the junk collisions.

  2. The second new process compared to what I've done before, is indexing the table holding the bodies by the body itself, an idea I "borrowed" from @Ignatz 's 2D sidescrolling platform game. It's really handy for collisions, as you can call something like objects[contact.bodyA]:collide(contact) from the collision routine (....until you get collisions being triggered for bodies that don't exist anymore upon the first frame or two of the restart)

  3. The events handler is also indexed in part by the body address. So there are at least three places pointing to the body. All of these tables get nilled when the game restarts though.

Could any of these things be keeping the bodies alive somehow?

I've read other threads about issues with resetting the physics, and tried things suggested there like running the entire destroy, create new bodies loop twice in a row, calling collectgarbage() after the tables have been destroyed, etc, but to no avail.

As I say, at the moment I have a delay as the physics starts, and that mostly deals with the issue, but it feels like a bit of a "bandaid" solution

Comments

  • IgnatzIgnatz Mod
    edited February 2015 Posts: 5,396

    @yojimbo2000 - I might start debugging by giving all the physics objects a name, via the info property, then when you get phantom collisions, you can print the names of the phantoms and at least see which they are.

    PS I realise that pausing physics isn't the main problem, but you could avoid the pause (in the interests of keeping things simple) when the level finishes, by redrawing the last frame to an image in memory, and spriting that as the frozen ending. Then you can delete all the objects behind the scenes.

  • dave1707dave1707 Mod
    Posts: 7,200

    @yojimbo2000 Are you destroying the objects in the table in reverse order. If you destroy an object in a table and remove that object from the table, the table shifts down and an object gets skipped each time. By doing it in reverse order, there is no shifting of the table.

  • IgnatzIgnatz Mod
    Posts: 5,396

    @yojimbo2000, @dave1707 - I would do it more simply - loop through the table just destroying physics objects, then clear the table in one go with tableName={}. Then it doesn't matter what order you use.

  • Posts: 1,595

    @yojimbo2000 I ran in to a similar problem in my game, where ghost objects would linger round. A lot of trial and error got me through it, if you like I could show you my strategy for setting up and removing entity tables properly in a big game. Without seeing code there is no positive assumption I can make.

  • IgnatzIgnatz Mod
    edited February 2015 Posts: 5,396

    I still say - first find out exactly which objects are zombies, then look at the way you are destroying them, and think about any other variables that may reference them.

  • dave1707dave1707 Mod
    Posts: 7,200

    @Ignatz Clearing the whole table works if the objects that are being destroyed are the only things in the table. Sometimes, a table can hold multiple objects and only certain objects in the table will be destroyed and removed. In that case, traversing the table in reverse order is the easiest way to delete and remove selected objects.

  • IgnatzIgnatz Mod
    Posts: 5,396

    @dave1707 - I don't disagree, there are lots of way to do these things

  • dave1707dave1707 Mod
    Posts: 7,200

    @Ignatz I agree. Without seeing the original code, we're just guessing.

  • edited February 2015 Posts: 2,020

    Here's the reset code, fairly standard I think.
    @Ignatz , yeah each body has an id string as its info. I get, say five to twenty junk collisions in the first frame or two of the restart. But other than that there aren't zombie bodies or anything (it seems almost as if every collision from the previous game is replayed, all at once, in one frame).

    Thinking about it, this is the first time I've used the game.bodies[contact.bodyA]:collide(contact) method of processing collisions, where each contact is tied to its iteration in the class. Maybe box2D always does this and I just haven't noticed before.

     
       physics.resume() --to see if this is causing junk collision data in first frame of restart
     
        for i,v in pairs(game.bodies) do --table indexed by body memory address, therefore pairs
            v.body:destroy()
        end
        for i,v in ipairs(game.wall) do --except walls, indexed sequentially, therefore ipairs
            v:destroy()
        end
     
        game.events={} --these five lines are redundant, as game variable, and its arrays...
        game.bodies={}
        game.wall={}
        game=nil
        collectgarbage() 
     
        game=Game()  --...are reset here anyway
        game:initObjects()  --reboot game
  • IgnatzIgnatz Mod
    Posts: 5,396

    @yojimbo2000 - when you write this

    for i,v in pairs(game.bodies) do 
        v.body:destroy()
    end
    

    is v a physics object? If it is, then you should destroy it with v:destroy() not v.body:destroy()

  • Posts: 2,020

    @Ignatz , no, with the game.bodies table self.body is the physics object, as there are all sorts of other variables associated with each iteration (a mesh, etc). With the game.wall table though, the self is the body, so it's just v:destroy(). Here's how each body is defined in its class:

        local b=physics.body(POLYGON, vec2(-ww,-ww),vec2(-ww,ww),vec2(ww,ww),vec2(ww,-ww))
        b.position=self.pos
        b.interpolate=true
        b.info=self.id
        self.body=b
        game.bodies[b]=self --indexed by body, useful for physics collisions
    

    the last line is the technique I took from your 2D sidescroller.

  • Posts: 2,020

    For the game.bodies table, the command could even be i:destroy() as the index is also a pointer to the body.

  • Posts: 2,020

    This is slightly off-topic, but I was wondering, why is the body.info tag necessary? I've noticed that myBody behaves like any other table, that you can attach whatever arbitrary fields you want to it, myBody.foo="bar" or whatever, and that those user defined fields get passed to the collision routine, so you could query contact.bodyA.foo. So why a special info tag?

  • IgnatzIgnatz Mod
    Posts: 5,396

    Convenience, I guess.

    btw, you can't use i:destroy(), it would have to be game.bodies[i]:destroy(), because i is not a pointer, but a table key.

  • Posts: 2,020

    @Ignatz But in this case the table key is the pointer. It was set as game.bodies[b]=self where b is the defined body. So the key is equal to (part of) the value, and in a pairs loop either key or value can be used (in this case either key or value.body).

  • IgnatzIgnatz Mod
    Posts: 5,396

    Ah, yes

  • edited February 2015 Posts: 2,020

    Great idea by the way, making the key the body memory address, thank you for that! Makes collision handling a breeze (from about frame 3 onwards....)

  • IgnatzIgnatz Mod
    Posts: 5,396

    can you create a toy example that replicates the problem, then we have a chance of solving it with you

  • Posts: 2,020

    I think I've just worked it out! My brain must have been frozen.

    I haven't had a chance to test this hypothesis yet, but I think that this is what is happening. The physics body is created, and will start triggering collisions, as soon as the call to physics.body is made inside the class's init() routine. But at this point, in the middle of the class init() function, there is not yet a self for that body, because the self is still in the process of being created (it doesn't come into being until the end of the class init routine. To prove this: if you query objects[#objects] from within a class init routine, where objects is the table holding instances of that class, it will return the previously created instance, not the current self which is still under construction). So in my system collision events begin being triggered, but game.bodies[contact.bodyA]:collide(contact) in the collide function doesn't work in frame 1, because game.bodies[contact.bodyA] doesn't come into existence until frame 2.

    So the solutions would be: first, just before the bodies are created in the init() routine, do some rough collision detection (not necessarily physics based, as the body in question doesn't exist yet, just based on a calculation of "is distance greater than radius1+radius2", and if so, move the coordinates of the new body slightly so it isn't overlapping with anything).

    But actually, my current "band aid" solution, of not calling the collision routine at all in frame 1, is probably the simplest fail-safe that will catch all occurrences.

    I apologise if my previous posts implied that there was a fault with Box2D, when (as is almost always the case) the mistake is the user's own.

  • Posts: 2,020

    Although.... that still doesn't explain why it works flawlessly the first time that you play the game, and only becomes a problem from the second run-through onwards..... that's a bit of a head-scratcher

  • IgnatzIgnatz Mod
    Posts: 5,396

    AFAIK, if you create the physics objects in setup, box2D won't update any of them until setup is done, and I think it updates just before draw runs. So you won't get collisions starting in the middle of a class init function.

    I still think the best way to solve it is to cut the code down to the absolute minimum that creates the problem, because this isolates potential problem areas (and lets the rest of us help you).

  • edited February 2015 Posts: 2,020

    @Ignatz you're right, I've been working on a minimal working example, and learned a lot! The solution, I think, is not to destroy all the bodies immediately before you create them all again, to leave a cycle or two inbetween destruction and recreation. The code below is very stable. But if you comment out the destruction loop in GameOver:init(), and uncomment the later destruction loop in GameOver:touched(), it becomes very unstable, crashing the whole of Codea. But I'd be very interested if other people can recreate this instability by moving the destruction loop to different parts of the code.

     
    --# Main
    -- Collision Manager MWE, game reset stress test
     
    displayMode(OVERLAY)
     
    function setup()
        font("DINAlternate-Bold")
        fill(31, 31, 95, 255)
        fontSize(30)
        textMode(CENTER)
        textAlign(CENTER)
        centre=vec2(WIDTH*0.5,HEIGHT*0.5)
        game=Game(math.random(30,100))
        delay=1 --number of frames to wait before calling collision routine. if you set this to 0, ie no delay at all, generally the programme is stable, and only falls over if you tap really fast (and it is unlikely you'd want to restart that quickly)
        print ("test stability by tapping the screen to reset the game at increasing speed")
    end
     
    function draw()
        sprite("SpaceCute:Background", centre.x, centre.y, WIDTH, HEIGHT)
        scene:draw()
        frameCount = frameCount + 1
    end
     
    function collide(contact)
      if frameCount>=delay then scene:collide(contact) end
    end
     
    function touched(touch)
       if frameCount>=delay and touch.state==ENDED then scene:touched(touch) end
    end
     
    --# Game
    Game = class()
     
    mask={
    wall=1,
    ball=2}
     
    function Game:init(number)
        print (number.." bodies")
     
        walls={}
        local p={vec2(0,0), vec2(0,HEIGHT), vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)}
        for a=1, 4 do
            if a==4 then b=1 else b=a+1 end
            local w=physics.body(EDGE, p[a], p[b])
            w.categories={mask.wall}
            walls[a]=w 
        end
     
        bodies={}  
        bodiesMesh=mesh()
        bodiesMesh.texture=readImage("Platformer Art:Coin")
        local diff=(WIDTH-50)/number
        for i=1,number do
            Body(i*diff, math.random(100, HEIGHT-100))
        end
        frameCount = 0
        scene=self
    end
     
    function Game:draw()
        for i,v in pairs(bodies) do --hash table, therefore pairs
            v:draw()
        end
        bodiesMesh:draw()
    end
     
    function Game:touched()
        GameOver()
    end
     
    function Game:collide(contact)
        local bodA, bodB, ball = contact.bodyA, contact.bodyB
        if bodA.categories[1]==mask.ball then ball=bodA
        elseif bodB.categories[1]==mask.ball then ball=bodB
        end
        if ball and bodies[ball] then
            bodies[ball]:collide(contact)
        else
            print("ERROR: no self for this body yet") --you'll only see this if you set delay to zero and tap really fast
        end
    end
     
    --# GameOver
    GameOver = class()
     
    function GameOver:init()
        scene=self
        frameCount = 0
        for i,v in pairs(bodies) do --hash table, therefore pairs
            v.body:destroy()
        end
        for i,v in ipairs(walls) do --sequential array therefore ipairs
            v:destroy()
        end       
    end
     
    function GameOver:draw()
        bodiesMesh:draw()
        text("GAME OVER\nTap anywhere to restart", centre.x, centre.y)
    end
     
    function GameOver:collide()
      --  print ("You should only see this if delay is totally turned off")
    end
     
    function GameOver:touched(touch)
        --[[ --killing the bodies here seems to create instability. If you restart enough times Codea crashes. It seems to be best to at least have a draw cycle or two in between killing all the bodies and setting up the new ones.
        for i,v in pairs(bodies) do --hash table, therefore pairs
            v.body:destroy()
        end
        for i,v in ipairs(walls) do --sequential array therefore ipairs
            v:destroy()
        end   
          ]]
        bodiesMesh:clear()   
        game=Game(math.random(30,100))
    end
     
    --# Body
    Body = class()
     
    function Body:init(x,y)
        local d=math.random(50,100)
        local b=physics.body(CIRCLE, d*0.5)
        b.x,b.y=x,y
        b.interpolate=true
        b.bullet=true
        b.restitution=1
        b.categories={mask.ball}
        local c=color(math.random(255), math.random(255), math.random(255))
        local r=bodiesMesh:addRect(x,y,d,d)
        bodiesMesh:setRectColor(r, c)
        self.body=b
        self.rect=r
        self.color=c
        self.diameter=d
        self.seed=math.random(16416)
        bodies[b]=self --key set to part of value
    end
     
    function Body:draw()
        bodiesMesh:setRect(self.rect, self.body.x, self.body.y, self.diameter, self.diameter, math.rad(self.body.angle))
    end
     
    function Body:collide(contact)
        if contact.state==BEGAN then
            bodiesMesh:setRectColor(self.rect, self.color.r, self.color.g, self.color.b, 100)
            sound(SOUND_BLIT, self.seed, contact.normalImpulse)
        else
            bodiesMesh:setRectColor(self.rect, self.color.r, self.color.g, self.color.b, 255)
        end
    end
  • dave1707dave1707 Mod
    Posts: 7,200

    @yojimbo2000 I tried your example. I commented out the destroy in GameOver:init and uncommented the destroy in GameOver:touched and it crashed Codea like you said. Here's why. Even though you destroyed the bodies in GameOver:touched, you have to exit that function before Codea actually destroyes the bodies. You can display information about a body after it's been destroyed as long as you haven't exited the function. But once you exit the function, the information will be nil. I guess Codea eventually crashes because you're creating bodies before it has a chance to destroy bodies.

  • dave1707dave1707 Mod
    edited February 2015 Posts: 7,200

    @yojimbo2000 Here a small example showing that information is retained after a destroy until the function is exited.


    function setup() a=physics.body(CIRCLE,20) a.x=WIDTH/2 a.y=HEIGHT/2 a.type=STATIC count=0 end function draw() background(40, 40, 50) fill(255) text("tap screen to destroy circle",WIDTH/2,HEIGHT/2-100) if count==0 then ellipse(a.x,a.y,40) end if count>0 and count<2 then count=count+1 print("in draw function after touched") print("x =",a.x,"y =",a.y) end end function touched(t) if t.state==BEGAN then print("START of touched") print("before destroy \nx =",a.x,"y =",a.y) a:destroy() print("DESTROY") print("after destroy \nx =",a.x,"y =",a.y) count=1 print("END of touched") end end
  • Posts: 2,020

    @dave1707 Thank you for solving this conundrum, and for the proof, that's incredibly helpful to know!

Sign In or Register to comment.