On collision timer scope problem, help

Let me explain first.  I create a ball, a goal and a bottomLine (if ball touch bottomLine game over)  in scene:create (composer)

local ball local goal local bottomLine --scene:create --create a ball ball = display.newCircle( centerX, centerY-300, 20 ) ball.fill = loads.ball\_1 ball.id = "ball" physics.addBody( ball, { density=0.8, friction=1.0, bounce=1.0, radius=20 } ) ball.strokeWidth = 1 ball:setStrokeColor( 0 ) sceneGroup:insert( ball ) ball.collision = onLocalCollision ball:addEventListener( "collision", ball ) --create a goal goal = display.newRect( centerX+50, centerY+180, 100, 15 ) goal:setFillColor( 24, 192, 130 ) goal.strokeWidth = 2 goal:setStrokeColor( 0 ) goal.id = "goal" physics.addBody( goal, "static" ) sceneGroup:insert( goal ) goal.collision = onLocalCollision goal:addEventListener( "collision", goal ) -- create a bottom line  bottomLine = display.newRect( centerX, center+400, 768, 30 )  bottomLine.id = "b"  physics.addBody( bottomLine, "static", { friction=1.0, bounce=0 } )  sceneGroup:insert( bottomLine )  bottomLine.collision = onLocalCollision  bottomLine:addEventListener( "collision", bottomLine )

Then at top in the composer area to code functions I put the “onLocalCollision()”.

--forward declarations local tmr --next level function local function nextLevel( event )   composer.gotoScene( "scenes.level\_2" ) end --collision function local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then event.contact.bounce = 0 ball.fill = loads.ball\_2 tmr = timer.performWithDelay( 3000, nextLevel ) elseif self.id == "ball" and event.other.id == "b" then composer.gotoScene( "scenes.reload" ) end end end

and in scene:hide “will” phase I check if the timer is nil, and if not, cancel and nil timer. I code a print statement for “tmr” to see in the console if I get a table or nil, and I get nil, yeah!!

-- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) if tmr ~= nil then timer.cancel( tmr ) tmr = nil end elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen print( tmr ) end end

but, in simulator, I go to level_2 if “ball” collide with “goal”, I go to reload and back to level_1 if “ball” collide with “b” (bottomLine), but if I’m playing with my ball in level_1 the timer activates on its own and takes me to level_2 although the “ball” does not collide with the “goal”. 

I know it is scope problem or something wrong with my timer.

Thanks in advance

DoDi

Your issue is most likely that don’t correctly cancel the timer, which results in the timer activating after 3 seconds.

Also, it doesn’t matter if “tmr” is nil as long as the timer has already been started. The timer will run its course anyway unless it is cancelled before “tmr” is set to nil. If “tmr” has been set to nil first, then you can no longer control the timer.

For example,
 

local function test() print("Beep!") end local timerTest = timer.performWithDelay( 1000, test, 3 ) -- timer.cancel( timerTest ) timerTest = nil

timerTest is set to nil immediately after it has been created, but the test function still runs three times unless the timer is cancelled (or paused).

do you properly “clean up” all physics objects at the end of each level, before going to next level?

there’s only one physics “world” - so a goal from the old level may still exist for the new ball to collide with

(or vice versa - the old ball may be colliding with a new goal, if you never removed it and its listener)

Thanks for your replys

@[font=‘MyriadPro-Regular’][/font]

XeduR @Spyric

[font=‘MyriadPro-Regular’][/font]

I try to eliminate timer.cancel() but I still get [font=Helvetica][/font]

the timer activates on its own. remember than I’m using composer. I’m confused right now.

[font=‘MyriadPro-Regular’][/font]

[font=Helvetica][/font]

@[font=‘MyriadPro-Regular’][/font]

davebollinger

[font=‘MyriadPro-Regular’][/font]

Yes, all my display objects are inserted in sceneGroup and in reload or level_2 the first thing I do it’s composer.remove(scene where I came from)

I have used timers to trigger simple functions in other apps (not games), like composer.gotoScene() in scene:show “did” phase. The problem here is that I don’t know how to use it within the “[font=Menlo][/font]
onLocalCollision” function. I can not understand how is the correct way to execute “[font=Menlo][/font]
timer** [font=Menlo][/font]**
.** [font=Menlo][/font]**
performWithDelay** [font=Menlo][/font]**
(****[font=Menlo][/font]
[font=Menlo][/font]
[font=Menlo][/font]
)” inside functions.

I also try this:

--collision function local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then event.contact.bounce = 0 ball.fill = loads.ball\_2 --[[tmr = timer.performWithDelay( 3000, nextLevel ) tmr = nil]] self.tmr= timer.performWithDelay(3000,         function() self.tmr = nil; nextLevel() end, 1 ) elseif self.id == "ball" and event.other.id == "b" then composer.gotoScene( "scenes.reload" ) end elseif ( event.phase == "ended" ) then if( self.tmr) then            self.tmr = nil            timer.cancel(self.tmr) end         end end

and get the error of “table expected” for timer.cancel(self.tmr)

If I eliminate the timer no errors, and no timer activates on its own, but I need the timer cause I want the user see the image change on collision. 

cancel first, THEN nil the reference

[EDIT] should also test for nil (so you don’t overwrite a prior timer with a new one, or try to cancel ‘nothing’) as collision events can be noisy

@davebollinger 

anyway it keeps giving me error table expected.

the only way i only have the timer activates on its own only once I with this code;

---- ------------------------------------------------- -- Scene event functions -- --------------------------------------------------- local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then event.contact.bounce = 0 ball.fill = loads.ball\_2 tmr1 = timer.performWithDelay(3000, nextLevel) elseif self.id == "ball" and event.other.id == "b" then event.contact.bounce = 0 ball.fill = loads.ball\_3 tmr2 = timer.performWithDelay(3000, reloadLevel) end end end

-- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) if tmr1 ~= nil then timer.cancel( tmr1 ) tmr1 = nil end if tmr2 ~= nil then timer.cancel( tmr2 ) tmr2 = nil end end end

Could someone help me understand how to use the timers correctly?

Well, now I am a bit confused as well. When you say that your timer activates on its own, surely you mean that it activates only after you’ve called it?

I would recommend inserting a print command in your function right before you start your timer. This way you’ll know if the collision begins multiple times. 3 seconds is a long time and your collision event could start, end and start again before your first timer runs out.

 

local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then print("ball and goal collision began.") event.contact.bounce = 0 ball.fill = loads.ball\_2 tmr1 = timer.performWithDelay(3000, nextLevel) elseif self.id == "ball" and event.other.id == "b" then print("ball and goal b began.") event.contact.bounce = 0 ball.fill = loads.ball\_3 tmr2 = timer.performWithDelay(3000, reloadLevel) end end end

If you see either print twice, then there’s your problem. In instances like these, I usually create a gameover function that I would call where I’d manage scene changes, visual effects and all those sort of things. Anyway, if you see either print twice, you can just add some bit of code to ensure that the timers are only run once.

 

local gameover = false local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then print("ball and goal collision began.") if gameover == false then gameover = true event.contact.bounce = 0 ball.fill = loads.ball\_2 tmr1 = timer.performWithDelay(3000, nextLevel) end elseif self.id == "ball" and event.other.id == "b" then print("ball and goal b began.") if gameover == false then gameover = true event.contact.bounce = 0 ball.fill = loads.ball\_3 tmr2 = timer.performWithDelay(3000, reloadLevel) end end end end

@dodi_games, seeing as you’ve liked my previous post, I’m interested in knowing if that was your problem and if it is now fixed?

Yes, thanks for all your help, I’m doing some levels to see if it stays stable. The “print” worked for me and I was able to identify the replica, so I used the variable within the event. I want to get to level 10 and tell you. Thanks again!!!

Your issue is most likely that don’t correctly cancel the timer, which results in the timer activating after 3 seconds.

Also, it doesn’t matter if “tmr” is nil as long as the timer has already been started. The timer will run its course anyway unless it is cancelled before “tmr” is set to nil. If “tmr” has been set to nil first, then you can no longer control the timer.

For example,
 

local function test() print("Beep!") end local timerTest = timer.performWithDelay( 1000, test, 3 ) -- timer.cancel( timerTest ) timerTest = nil

timerTest is set to nil immediately after it has been created, but the test function still runs three times unless the timer is cancelled (or paused).

do you properly “clean up” all physics objects at the end of each level, before going to next level?

there’s only one physics “world” - so a goal from the old level may still exist for the new ball to collide with

(or vice versa - the old ball may be colliding with a new goal, if you never removed it and its listener)

Thanks for your replys

@[font=‘MyriadPro-Regular’][/font]

XeduR @Spyric

[font=‘MyriadPro-Regular’][/font]

I try to eliminate timer.cancel() but I still get [font=Helvetica][/font]

the timer activates on its own. remember than I’m using composer. I’m confused right now.

[font=‘MyriadPro-Regular’][/font]

[font=Helvetica][/font]

@[font=‘MyriadPro-Regular’][/font]

davebollinger

[font=‘MyriadPro-Regular’][/font]

Yes, all my display objects are inserted in sceneGroup and in reload or level_2 the first thing I do it’s composer.remove(scene where I came from)

I have used timers to trigger simple functions in other apps (not games), like composer.gotoScene() in scene:show “did” phase. The problem here is that I don’t know how to use it within the “[font=Menlo][/font]
onLocalCollision” function. I can not understand how is the correct way to execute “[font=Menlo][/font]
timer** [font=Menlo][/font]**
.** [font=Menlo][/font]**
performWithDelay** [font=Menlo][/font]**
(****[font=Menlo][/font]
[font=Menlo][/font]
[font=Menlo][/font]
)” inside functions.

I also try this:

--collision function local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then event.contact.bounce = 0 ball.fill = loads.ball\_2 --[[tmr = timer.performWithDelay( 3000, nextLevel ) tmr = nil]] self.tmr= timer.performWithDelay(3000,         function() self.tmr = nil; nextLevel() end, 1 ) elseif self.id == "ball" and event.other.id == "b" then composer.gotoScene( "scenes.reload" ) end elseif ( event.phase == "ended" ) then if( self.tmr) then            self.tmr = nil            timer.cancel(self.tmr) end         end end

and get the error of “table expected” for timer.cancel(self.tmr)

If I eliminate the timer no errors, and no timer activates on its own, but I need the timer cause I want the user see the image change on collision. 

cancel first, THEN nil the reference

[EDIT] should also test for nil (so you don’t overwrite a prior timer with a new one, or try to cancel ‘nothing’) as collision events can be noisy

@davebollinger 

anyway it keeps giving me error table expected.

the only way i only have the timer activates on its own only once I with this code;

---- ------------------------------------------------- -- Scene event functions -- --------------------------------------------------- local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then event.contact.bounce = 0 ball.fill = loads.ball\_2 tmr1 = timer.performWithDelay(3000, nextLevel) elseif self.id == "ball" and event.other.id == "b" then event.contact.bounce = 0 ball.fill = loads.ball\_3 tmr2 = timer.performWithDelay(3000, reloadLevel) end end end

-- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) if tmr1 ~= nil then timer.cancel( tmr1 ) tmr1 = nil end if tmr2 ~= nil then timer.cancel( tmr2 ) tmr2 = nil end end end

Could someone help me understand how to use the timers correctly?

Well, now I am a bit confused as well. When you say that your timer activates on its own, surely you mean that it activates only after you’ve called it?

I would recommend inserting a print command in your function right before you start your timer. This way you’ll know if the collision begins multiple times. 3 seconds is a long time and your collision event could start, end and start again before your first timer runs out.

 

local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then print("ball and goal collision began.") event.contact.bounce = 0 ball.fill = loads.ball\_2 tmr1 = timer.performWithDelay(3000, nextLevel) elseif self.id == "ball" and event.other.id == "b" then print("ball and goal b began.") event.contact.bounce = 0 ball.fill = loads.ball\_3 tmr2 = timer.performWithDelay(3000, reloadLevel) end end end

If you see either print twice, then there’s your problem. In instances like these, I usually create a gameover function that I would call where I’d manage scene changes, visual effects and all those sort of things. Anyway, if you see either print twice, you can just add some bit of code to ensure that the timers are only run once.

 

local gameover = false local function onLocalCollision( self, event ) if ( event.phase == "began" ) then if self.id == "ball" and event.other.id == "goal" then print("ball and goal collision began.") if gameover == false then gameover = true event.contact.bounce = 0 ball.fill = loads.ball\_2 tmr1 = timer.performWithDelay(3000, nextLevel) end elseif self.id == "ball" and event.other.id == "b" then print("ball and goal b began.") if gameover == false then gameover = true event.contact.bounce = 0 ball.fill = loads.ball\_3 tmr2 = timer.performWithDelay(3000, reloadLevel) end end end end

@dodi_games, seeing as you’ve liked my previous post, I’m interested in knowing if that was your problem and if it is now fixed?