Spamming buttons causes game to crash

Hi!

I’m close to finishing my first game with Corona but I’ve encountered some problems when using composer.

My game has a menu scene, highscores scene and settings scene. The buttons (text objects with tap listeners) in the different scenes are at the same locations. Normally everything works just fine but if I spam the buttons quite quickly one out of two things happen.

1: I get a runtime error, this error varies but usually it’s either a) says it’s trying to cancel a timer which doesn’t exist or b trying to remove an event listener which again doesn’t seem to exist.

2: The screen goes blank.

I’m guessing number 1 happens because the buttons get pressed and the function scene:hide gets called before the timers and event listeners has time to be created in the scene:create function.

Why number 2 happens I don’t know.

I’ve tried some different games made with Corona and some of them have this same issue, spamming buttons especially if they appear in the place where a previous button from the scene before was placed crashes or hangs the game.

During normal play this isn’t an issue since spamming the buttons isn’t something players would usually do. But I want my game to function as best as possible, so ideally I’d like to solve this.

Any help would be greatly appreciated

Hi,

A classic scenario.

I’ve previously been using boolean flags to control this.

I’ve also used an invisible fullscreen overlay that catches all to prevent this.

But lately I just removeEventListener on first press, then a second will not happen. Just remember to add it again at some point, if user is supposed to be able to press it again.

Happy coding!

I think the issue is your ‘touch’ event is leaking into the next scene.

This should be prevented by returning ‘true’ from the touch or tap listener, but either way you should be able to solve it by, simply waiting 1 frame to change scenes.

i.e. Instead of this:

local function touch( event ) if( event.phase == "ended" ) then composer.gotoScene( "scenes.someScene", { time = 500, effect = "crossFade", params = params } ) return true end return false end

do this:

local function touch( event ) if( event.phase == "ended" ) then -- Wait 1ms (which ends up being next frame) timer.performWithDelay( 1, function() composer.gotoScene( "scenes.someScene", { time = 500, effect = "crossFade", params = params } ) end ) return true end return false end

Thank you to both of you! This problem has been nagging on me for some time now but adding an invisible overlay to catch touches if the user touches too quickly worked! I couldn’t get the timer to work as was also suggested, although that’s probably because of my messy code…

Anyways thanks for the help, much appreciated!  :smiley:

Hi again,

Glad you got it solved.

Here’s some code snippet for that particular way showing how i did it.

I’m using a standard variable for transition speed which allows me to easily speed up or slow down the UI experience.

Taken out of full context, it is settings.trans and in this code its at 300ms

So lets say that my buttons all do a 300 ms animation when pressed, to avoid multiple pressed, the first thing that is called onPressed is:

lockdown()

which will block everything for 300ms

if the particluar function require more time to finish, lets say a press even and a screen fade out, i can do

lockdown(2)

which will block everything for 600ms

and if i dont know how long i need to block it for, lets say I am waiting for an async network callback, i can do

lockdown(true)

and in the async callback code when response has been received,

lockdown(false)

and all is available for the user again.

To keep it all in front, you either make dgBlocker your top layer displaygroup and/or use dgBlocker:toFront() each time you need it. I use the first alternative.

It works really well  :slight_smile:

local dcw=display.actualContentWidth local dch=display.actualContentHeight local settings={trans=300} local dgBlocker=display.newGroup()

local function blocker() local function callback(event) print("blocked "..os.time()) return true end local block=display.newRect(dcw\*0.5,dch\*0.5,dcw,dch) block:addEventListener("tap",callback) block:addEventListener("touch",callback) dgBlocker:insert(block) dgBlocker.isVisible=false end blocker()

local function lockdown(input) -- true = block -- false = unblock -- any number = number\*trans to block -- nothing defaults to 1\*trans to block local value=input or 1 local function callback() dgBlocker.isHitTestable=false end if value==true then dgBlocker.isHitTestable=true elseif value==false then dgBlocker.isHitTestable=false else dgBlocker.isHitTestable=true timer.performWithDelay(setting.trans\*value,callback) end end

Man this code editor is not my friend, sorry for the mess during posting :slight_smile:

Hi,

A classic scenario.

I’ve previously been using boolean flags to control this.

I’ve also used an invisible fullscreen overlay that catches all to prevent this.

But lately I just removeEventListener on first press, then a second will not happen. Just remember to add it again at some point, if user is supposed to be able to press it again.

Happy coding!

I think the issue is your ‘touch’ event is leaking into the next scene.

This should be prevented by returning ‘true’ from the touch or tap listener, but either way you should be able to solve it by, simply waiting 1 frame to change scenes.

i.e. Instead of this:

local function touch( event ) if( event.phase == "ended" ) then composer.gotoScene( "scenes.someScene", { time = 500, effect = "crossFade", params = params } ) return true end return false end

do this:

local function touch( event ) if( event.phase == "ended" ) then -- Wait 1ms (which ends up being next frame) timer.performWithDelay( 1, function() composer.gotoScene( "scenes.someScene", { time = 500, effect = "crossFade", params = params } ) end ) return true end return false end

Thank you to both of you! This problem has been nagging on me for some time now but adding an invisible overlay to catch touches if the user touches too quickly worked! I couldn’t get the timer to work as was also suggested, although that’s probably because of my messy code…

Anyways thanks for the help, much appreciated!  :smiley:

Hi again,

Glad you got it solved.

Here’s some code snippet for that particular way showing how i did it.

I’m using a standard variable for transition speed which allows me to easily speed up or slow down the UI experience.

Taken out of full context, it is settings.trans and in this code its at 300ms

So lets say that my buttons all do a 300 ms animation when pressed, to avoid multiple pressed, the first thing that is called onPressed is:

lockdown()

which will block everything for 300ms

if the particluar function require more time to finish, lets say a press even and a screen fade out, i can do

lockdown(2)

which will block everything for 600ms

and if i dont know how long i need to block it for, lets say I am waiting for an async network callback, i can do

lockdown(true)

and in the async callback code when response has been received,

lockdown(false)

and all is available for the user again.

To keep it all in front, you either make dgBlocker your top layer displaygroup and/or use dgBlocker:toFront() each time you need it. I use the first alternative.

It works really well  :slight_smile:

local dcw=display.actualContentWidth local dch=display.actualContentHeight local settings={trans=300} local dgBlocker=display.newGroup()

local function blocker() local function callback(event) print("blocked "..os.time()) return true end local block=display.newRect(dcw\*0.5,dch\*0.5,dcw,dch) block:addEventListener("tap",callback) block:addEventListener("touch",callback) dgBlocker:insert(block) dgBlocker.isVisible=false end blocker()

local function lockdown(input) -- true = block -- false = unblock -- any number = number\*trans to block -- nothing defaults to 1\*trans to block local value=input or 1 local function callback() dgBlocker.isHitTestable=false end if value==true then dgBlocker.isHitTestable=true elseif value==false then dgBlocker.isHitTestable=false else dgBlocker.isHitTestable=true timer.performWithDelay(setting.trans\*value,callback) end end

Man this code editor is not my friend, sorry for the mess during posting :slight_smile: