Removing bodies Box2D

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
Fraek
Prole
Posts: 1
Joined: Tue Oct 22, 2013 12:57 pm

Removing bodies Box2D

Post by Fraek »

Dear People,

struggling for a while now I'm hoping someone can help me out with Box2D physics.
The problem is it is trying to use a body that is destroyed. Regarding safe deletion of bodies I'm trying to use a queu and a tick counter.

Code: Select all

bodyQueu = {}
function bodyQueu.add(givenBody)
  if bodyID == nil then bodyID = 1 end
  bodyQueu[bodyID] = {}
  bodyQueu[bodyID].body = givenBody
  bodyQueu[bodyID].tick = bodyQueu.tick            --This adds the current number the ticker is at
  bodyID = bodyID + 1
end

Code: Select all

function bodyQueu.update(dt)
--Only do the following if there is anything in the Queu to be deleted
  if(#bodyQueu ~= nil) then
   --Loop through the list in the Queu
    for bodyID = 1, #bodyQueu do
      --If the tick the body was added is not equal to the current tick (so its not used by the world atm?)
      if(bodyQueu[bodyID].tick ~= bodyQueu.tick) then
        --If the body still excists and isnt allready deleted and is not used by the world then remove it and remove it from the Queu table
        if(bodyQueu[bodyID] ~= nil and world:isLocked() == false) then
          bodyQueu[bodyID].body:destroy()
          table.remove(bodyQueu, bodyID)
        end 
        
      end
    end
  end

--This makes the ticker never go over 100 
  if(bodyQueu.tick >= 100) then
    bodyQueu.tick = 0
  else
    bodyQueu.tick = bodyQueu.tick + 1
  end
end

Code: Select all

--The ticker starts at 0
function love.load()
bodyQueu.tick = 0
end
The idea behind it is that it updates the world. The enitity''asteroid'' (circle) triggers bodyQueu.add(bullet[bulletID].body) correctly on collision via Callback.
So in the collision callback beginContact it adds the bullet to the bodyQueu, ready for deletion. If you would instantly remove the body via bullet[bulletID]:body:Destroy() it would crash the game as it requires the object for drawing (its still in use). The current tick (per example 72) gets added to the body. Every update the ticker updates by 1, when its 73 it is allowed to delete the body. It first updates the bodyQueu (see if something is there for deletion) then it starts with the world calculations.

Code: Select all

function love.update(dt) 
  --Dont ever ever go faster then 60 calculations per second NOTE: Not frames per second!
  local MAX_DT = 1/60;
  if(dt > MAX_DT) then    
    dt = MAX_DT
  end
    
  --Updates the physics world
  bodyQueu.update(dt)
  world:update(dt)
end
Does anyone knows what is going wrong, cause it keeps saying the body is in use when Im trying to delete it! Ive read about the workaround by removing the shape from all contact categories (https://love2d.org/wiki/Remove_Workaround). But this seemed like a cleaner way....
Is the workaround the only possibility or am I missing something?

Thanks in advance
NOTE: I know it is Queue yet Im a lazy typer....
Attachments
collision.love
Current Progress
(431.61 KiB) Downloaded 117 times
User avatar
Boolsheet
Inner party member
Posts: 780
Joined: Wed Dec 29, 2010 4:57 am
Location: Switzerland

Re: Removing bodies Box2D

Post by Boolsheet »

If you call Body:destroy, then that body gets removed from the Box2D world and the LÖVE Body object is now useless to you. It throws an error to make it very obvious that you tried accessing this useless object.

Instead of just adding the Box2D body to the BodyQueu table, consider adding the whole table of that bullet. This way you can set a flag that the body is now invalid and the other parts that still want to access the body can check for that flag and safely skip it.

For example add the table, not just the body in asteroid.lua.

Code: Select all

bodyQueu.add(bullet[bulletID])
Then tag it as dead when you destroy it.

Code: Select all

bodyQueu[bodyID].body.body:destroy()
bodyQueu[bodyID].body.dead = true
The code in bullet.draw can then check if it still exists.

Code: Select all

if not bullet[bulletID].dead then
Some other issues:

Code: Select all

--Dont ever ever go faster then 60 calculations per second NOTE: Not frames per second!
local MAX_DT = 1/60;
if(dt > MAX_DT) then    
	dt = MAX_DT
end
This is invalid. All this will do is step 1/60 seconds in the simulation regardless of the actual time passed in real time. If you want a fixed time step, you have to add a timer that accumulates dt and if the timer is over your limit (1/60 or whatever) you do a time step in the simulation and subtract that from the timer. A fixed time step has additional issues because what you display on the screen will probably not be at those time points and it may jitter a bit.

Code: Select all

-- Untested example. Just to convey the concept.
local timer = 0
local step = 1/60
function love.update(dt)
    timer = timer + dt
    if timer < step then
        return
    end
    timer = timer - step
    world:update(step)
end
Be very careful when you use table.remove. This function modifies the table and you probably didn't account for that. Here's an example of the issue: http://codepad.org/WEiJSz8t . It wants to delete 3 and 4, but deletes 3 and 5 because the first delete moves the table up. One way to solve this is to iterate backwards over the table, but this won't suit your bodyID counting scheme. The length operator # only works reliable for sequences (tables that have values starting at the integer key 1 and go up to a key with integer N without any nil holes in between).
Shallow indentations.
Post Reply

Who is online

Users browsing this forum: DTmg, Google [Bot] and 4 guests