Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat
Deleting an instance within a collision function - Gideros Forum

Deleting an instance within a collision function

BramanikBramanik Member
edited August 2012 in General questions
I'm currently using the Gideros SVG Level Builder (documentation) along with Box2D and I'm having problems with setActive and removing a body from the scene. I did solve the setActive problem by constantly checking a bool on every frame, although I'm still not able to delete the instance.

Here is a simplified version of my function:
local function onCollision(event)
	if gameActive then
		local fixtureA = event.fixtureA
		local fixtureB = event.fixtureB
		local bodyA = fixtureA:getBody()
		local bodyB = fixtureB:getBody()
 
		local inst, other
 
		if (bodyA.info.material == "wood") then
			inst = bodyA
			other = bodyB
		elseif (bodyB.info.material == "wood") then
			inst = bodyB
			other = bodyA
		end	
 
		if inst ~= nil then
			if (other.info.material == "woodHit") then
				player:setLinearVelocity(5, 5);
			elseif (other.info.material == "woodCount") then
				del = true
				delItem = inst
			end
		end
	end
end
 
del = false
delItem = nil
local function onEnterFrame()
	if del == true then
		print ("Item Deleted")
		delItem:setActive(false)
		--The next 2 lines are used in the documentation, although in a "touch" listener function
		local parent = delItem:getParent() --This line has the error
		parent.destroyBody(delItem)
 
		del = false
		delItem = nil
	end
end
 
level.world:addEventListener(Event.POST_SOLVE, onItemCollision)
stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
As I said, setActive now works, although I am unable to remove the instance and get this error: attempt to call method 'getParent' (a nil value)
I think this is because it is getting a body from a fixture, and not the actual item instance in its entirety, although I'm not sure. The same getParent method works in the example included in the library inside a touch function. I posted on the level builder forums, although its been a couple of weeks and no response, so I thought I'd try my luck here to see if anyone can help me out.

Thanks.

Comments

  • ar2rsawseenar2rsawseen Maintainer
    Accepted Answer
    Hello,
    in which variable do you store the world instance? Because deleteing box2d object should be something like:
    world:destroyBody(delItem)
    There is no method to get a world instance from a box2d body and there is no getParent method in body object. Only Sprite hierarchy has that, which is a completely different concept, representing the visual aspect.

    Likes: Bramanik

    +1 -1 (+1 / -0 ) Share on Facebook
  • Alright I've implemented that as:
    level.world:destroyBody(delItem)
    Although now I get an error of:
    Body is already destroyed.
  • It seems that you are trying to destroy it two times somewhere.

    How your code looks now, can you show it?
  • BramanikBramanik Member
    edited August 2012
    Here are the whole functions for the collision, onEnterFrame and init
    local function onItemCollision(event)
    	if gameActive then
    		local fixtureA = event.fixtureA
    		local fixtureB = event.fixtureB
     
    		local bodyA = fixtureA:getBody()
    		local bodyB = fixtureB:getBody()
     
    		local itemInst, other
     
    		if (bodyA.info.material == "mitem1") then
     
    			itemInst = bodyA
    			other = bodyB
    		elseif (bodyB.info.material == "mitem1") then
     
    			itemInst = bodyB
    			other = bodyA
    		end	
     
    		if itemInst ~= nil then
    			itemInst.vx, itemInst.vy = itemInst:getLinearVelocity()
    			--Item goes LEFT
    			if (other.info.material == "beltL" or other.info.material == "beltLendL" or other.info.material == "beltRendL" or other.info.material == "pipeHitL") then
    				print("Belt Left")
    				--Set LEFT velocity/force here
    				itemInst:setLinearVelocity(-5, itemInst.vy);
    			--Item goes RIGHT
    			elseif (other.info.material == "beltR" or other.info.material == "beltLendR" or other.info.material == "beltRendR" or other.info.material == "pipeHitR") then
    				print("Belt Right")
    				--Set RIGHT velocity/force here
    				itemInst:setLinearVelocity(5, itemInst.vy);
    			--Item goes UP
    			elseif (other.info.material == "beltU") then
    				print("Belt Up")
    				--Set UP velocity/force here
    			elseif (other.info.material == "dispenserCount") then
    				del = true
    				delItem = itemInst
    			elseif (other.info.material == "dispenserHit") then
     
    			end
    		end
    	end
    end
     
    del = false
    delItem = nil
    local function onEnterFrame()
    	if del == true then
    		print ("Item Deleted")
    		delItem:setActive(false)
    		level.world:destroyBody(delItem)
     
    		del = false
    		delItem = nil
    	end
    end
     
    function gameScene:init()
    	if level ~= nil then
    		svg:cleanUp(true)
    		level = nil
    	end
     
    	stage:addChild(sceneGroup)
     
    	backGButton = Button.new("<-", backGButtonClicked, 60, 60)
    	backGButton:setPosition(0, (application:getDeviceWidth() - backGButton:getWidth()))
     
    	level = svg:new("january01")
    	--level = svg:new("january01", {spriteSheet = "january", defaultSpriteSheet = "january"})
     
    	levelGroup = level.group
     
    	reorderLayers()
    	initiateObjects()
     
    	-- Touch the screen do add a new body
    	stage:addEventListener(Event.MOUSE_UP, addItem1)
     
    	level.world:addEventListener(Event.POST_SOLVE, onItemCollision)
     
    	stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    end
    I only have destroyBody in one place in this class, and I've searched for "destroyBody" in the SVG builder code and didn't come up with anything. The only place in the SVG builder code I could think that it would remove bodies is in the cleanUp function, although that shouldn't be called unless I'm returning to menu, which isn't the case.

    The whole error code is:
    .\slb-svgparser.lua:590: Body is already destroyed.

    The error is coming up inside another onEnterFrame function inside the SVG builder and the line is trying to reference the body (which is obviously now destroyed).
  • zvardinzvardin Member
    edited August 2012
    Strange, I tried modifying the physics example of collisions to be similar to your code. The ground object can have its body destroyed fine, but as soon as box1 tries to get destroyed I also get an error saying the body is already destroyed.

    [Edit] Nevermind, I realized it was because after I deleted there was the loop that went through the actors. So, one thing learned is you may get the error that the body is already destroyed if trying to act on it (other than a delete command). So my suspicion is either in your code somewhere else or in the SVG code it is trying to loop through a list of actors maybe and although you destroy the body, something else tries to act on the body elsewhere.
  • Yeh the SVG code has another onEnterFrame function that is obviously being used after my own onEnterFrame function and then referencing the body which then results in the error because its just been destroyed.

    I tried a bool check (using del) on the final if before the SVG tries to set position and rotation of the body, although that just made it jittery and still came up with the same error eventually anyway.

    I'm not sure if it increments the ID of an item after its cloned in the SVG builder, although I vaguely remember testing it and they all had the cloned instances ID. Maybe if I increment the ID on creating the instance, then checked that ID against the bodies inside the items layer on my level file, then it might not leave a reference for the onEnterFrame function to screw up. I don't think I can reach the custom.id or just id of an instance from the fixture or body though.
  • Hm, the only thing that makes sense is somewhere the body is stored as a reference. In the physics example and other examples I've seen it seems common to store the body as the index, but looking at the SVG documentation it seems they use the identifiers and not the body object itself, so when you set delItem to nil, I would think that should solve the issue.

    Are you doing anything else anywhere storing references to the bodies? (like how you keep other references to level.group etc for convenience?)
  • ar2rsawseenar2rsawseen Maintainer
    edited August 2012
    I think you are right. SVG probably stores reference to all created physical bodies. And once you destroy one, it still tries to use it. You need to reference SVG editor documentation, to see how to properly remove body. I believe, they should have implement a method which removes specific body, or at least removes its reference.

    For example found something like this:

    You can also disable physics behaviour in a body by adding the noPhysics attribute to it.

    Although I don't know if it's used on init or can be applied to existing bodies
  • Hm... assuming there's no other internal references made you may be able to do something like:
    local mt = {__mode = "v"}
    setmetatable(level.bodies, mt)
    setmetatable(levelGroup, mt)
    This way assuming if delItem is the only current reference to the body, then they should get dropped from the corresponding SVG tables (may have to manually call collectgarbage()).

    Otherwise you could manually remove references in your function like so:
    local function onEnterFrame()
    	if del == true then
    		print ("Item Deleted")
    		delItem:setActive(false)
    		level.world:destroyBody(delItem)
                    level.bodies[delItem.name] = nil -- may need to remove from the group table also
    		del = false
    		delItem = nil
    	end
    end
    There may be better ways, just throwing some ideas out there with the little I know, but hopefully this leads to answer if nothing else :)
  • BramanikBramanik Member
    edited August 2012
    Using the metatables way, I get errors from every single function call trying to reference the Sprites but coming up with a table instead.

    Using the second solution I get:
    sceneGame.lua:351: table index is nil (on the "level.bodies[delItem.name] = nil" line)

    I'm thinking that I can't access any of the XML properties because the delItem is the body and not the actual instance, otherwise I could use the getParent method that is used in the example.

    Maybe an additional check inside the SVG's onEnterFrame to check if it is the same body then delete the reference and/or body there?


    EDIT: I tried adding an extra if inside the SVG's instance table/body in onEnterFrame and then used my del bool and delItem body to compare against the same body in the onEnterFrame and then remove the reference from the parent (ie. subchild). (If you are looking in the slb-svgparser class, the first if starts on line 585, I added the existing code just for relevance purposes)
    if subchild ~= nil then
    	if subchild.physicsEnabled then
    		local body = subchild.body
    		if body ~= nil then
    			if del and body == delItem then
    				level.world:destroyBody(delItem)
    				subchild = nil
    				delItem = nil
    				del = false
    				print("Item Deleted")
    			else
    				subchild:setPosition(body:getPosition())
    				subchild:setRotation(body:getAngle() * 180 / math.pi)
    			end
    		end
    	end						
    end
    I still get the error:
    .\slb-svgparser.lua:597: Body is already destroyed.
  • Worked it out with the help of @Caroline

    Here is the edited onEnterFrame function within the SVG code (starts on slb-svgparser.lua line 575) for anyone interested in the solution. This solution will only delete 1 body/table per frame even if there are more bodies to be deleted on that frame. You can just use a table to store multiple bodies if you wanted to implement that though.
    onEnterFrame = function()
    	world:step(frameStep, SETTINGS.DEFAULT_VELOCITY_ITERATIONS, SETTINGS.DEFAULT_POSITION_ITERATIONS)
     
    	removeIndex = 0
    	for i = 1, groupMain:getNumChildren() do
    		local child = groupMain:getChildAt(i)
     
    		if child:getNumChildren() ~= nil then
    			for j = 1, child:getNumChildren() do
    				local subchild = child:getChildAt(j)
     
    				if subchild ~= nil then
    					if subchild.physicsEnabled then
    						local body = subchild.body
     
    						if body ~= nil then
    							if body == delItem then
    								level.world:destroyBody(delItem)
    								removeIndex = j
    								delItem = nil
    								print("Item Deleted")
    							else
    								subchild:setPosition(body:getPosition())
    								subchild:setRotation(body:getAngle() * 180 / math.pi)
    							end
    						end
    					end						
    				end
    			end
    			--Child must be removed here otherwise results in an index out of bounds error
    			--if there are more children AFTER the deleted child
    			if removeIndex > 0 then
    				child:removeChildAt(removeIndex)
    				removeIndex = 0
    			end
    		end			
    	end
    end
    Thankyou for the help @ar2rsawseen and @zvardin :)
Sign In or Register to comment.