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
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).
@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\ Tap 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 ```
@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.
@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 \
x =",a.x,"y =",a.y)
a:destroy()
print("DESTROY")
print("after destroy \
x =",a.x,"y =",a.y)
count=1
print("END of touched")
end
end
@dave1707 Thank you for solving this conundrum, and for the proof, that’s incredibly helpful to know!