Urgent Help Needed: Storyboard not removing scene in the App Store version of the App

Hey guys,

My game just became available in the App Store.

https://itunes.apple.com/app/heroes-vs.-mechs-tower-defense/id767652185

However, there’s a pretty major issue. When you go to another scene, the previous scene is not getting removed! This does not happen when I build it for my device. I have all my display objects inserted into self.view when I create the scene, so I doubt that is the problem.

Has anyone had a problem where the distribution version of the game builds differently from the developer version? I rebuilt the app and submitted it as an update via iTunes Connect, hoping that will magically solve the issue. However, since I can’t recreate the problem at all when I build it for my device, I didn’t make any changes to the code. I’m using version 2013.2076 (2013.07.15).

In case it is a code issue, here is the code for the main menu scene. Note that although I required the widget class, I did not use widgets (just used text), so I don’t think it’s problem of not removing the widgets when the scene is exited.

[lua]


– menu.lua


local storyboard = require( “storyboard” )

local scene = storyboard.newScene()

– include Corona’s “widget” library

local widget = require “widget”

local ads = require “ads”


local function onNewGameBtnRelease(self, event) 

    if event.phase == “ended” then

        storyboard.gotoScene( “Menu Files.mapSelection”, “fade”, 300 )

    end

end

local function onUpgradesBtnRelease(self, event)

    if event.phase == “ended” then

        storyboard.gotoScene( “Menu Files.upgrades”, “fade”, 300 )

    end

end


– BEGINNING OF YOUR IMPLEMENTATION

– 

– NOTE: Code outside of listener functions (below) will only be executed once,

–         unless storyboard.removeScene() is called.

– 


local emperor

– Called when the scene’s view does not exist:

function scene:createScene( event )

    local group = self.view

    local menuMusic = audio.loadStream(“Sounds/Future Gladiator.mp3”)

    audio.play(menuMusic, {channel=1, loops=-1})

    audio.setVolume(0.3, {channel=1})

    local function adListener( event )

    local msg = event.response

        if event.isError then

            – Failed to receive an ad, we print the error message returned from the library.

            print(msg)

        end

    end

    – ads.init( “iads”, “767652185”, adListener )

    ads.init(“admob”, “a152964d667eaa6”, adlistener)

    ads.show( “banner”, { x=0, y=0 } )

    local sheetData = { width=32, height=32, numFrames=8 }

    local sheet = graphics.newImageSheet( “Tower Defense Graphics/Heroes/Emperor.png”, sheetData )

    

    local sequenceData = {

        { name=“up”, frames={1,2}, time=300},

        { name=“down”, frames={3,4}, time=300},

        { name=“left”, frames={5,6}, time=300},

        { name=“right”, frames={7,8}, time=300},

    }

    emperor = display.newSprite(sheet, sequenceData)

    group:insert(emperor)

    emperor:setSequence( “right” )

    emperor:play()

    emperor.direction = “right”

    emperor.x, emperor.y = -20, 300

    emperor.speech = display.newText("", 0, emperor.y - 70, 100, 100, “Minecraftia”, 8)

    emperor.speech.isVisible = false

    group:insert(emperor.speech)

    emperor.touches = 0

    function emperor:fade(teleportSprite)

        teleportSprite:pause()

        self.speech:removeSelf()

        self:removeSelf()

        transition.to(teleportSprite, {time = 1000, alpha=0, onComplete = function() teleportSprite:removeSelf() end})

    end

    function emperor:teleport()

        local sheetData = { width=20, height=20, numFrames=10 }

        local sheet = graphics.newImageSheet( “Tower Defense Graphics/Abilities/Teleport.png”, sheetData )

        

        local sequenceData = {

            { name=“teleport”, start=1, count=10, time=625}

        }

        local teleportSprite = display.newSprite(sheet, sequenceData)

        teleportSprite:scale(2, 2)

        teleportSprite.x, teleportSprite.y = self.x, self.y

        teleportSprite:play()

        timer.performWithDelay(580, function() emperor:fade(teleportSprite) end)

    end

    function emperor:touch(event)

        if event.phase == “began” then

            self.touches = self.touches + 1

            self.speech.isVisible = true

            if self.touches == 1 then

                self.speech.text = “Get yer hands off me! I AM THE EMPEROR!”

            elseif self.touches == 2 then

                self.speech.text = “If you really want to touch something, click the ad and support the developer.”

            elseif self.touches == 3 then

                self.speech.text = “Seriously, cut it out.”

            elseif self.touches == 4 then

                self.speech.text = “I’m warning you, one more time and you’re toast.”

            elseif self.touches == 5 then

                self.speech.text = “That’s it. I’m leaving. Have fun on map 10. *smirk*”

                Runtime:removeEventListener(“enterFrame”, self)

                self:pause()

                timer.performWithDelay(2000, function() emperor:teleport() end)

            end

        end

    end

    emperor:addEventListener(“touch”, emperor)

    function emperor:enterFrame(event)

        if self.x == 20 then

            self:setSequence(“right”)

            self.direction = “right”

            self:play()

            self.x = 21

        elseif self.x < display.contentWidth -20 and self.direction == “right” then

            self.x = self.x + 1

        elseif self.x == display.contentWidth - 20 then

            self:setSequence(“left”)

            self.direction = “left”

            self:play()

            self.x = display.contentWidth - 21

        elseif self.x < display.contentWidth -20 and self.direction == “left” then

            self.x = self.x - 1

        end

        if self.x > display.contentWidth/2 then

            self.speech.x = self.x - 70

        else

            self.speech.x = self.x + 70

        end

    end

    Runtime:addEventListener(“enterFrame”, emperor)

    

    – create/position logo/title image on upper-half of the screen

    local titleLogo = display.newText(“HEROES vs MECHS”, 0, 0, “Minecraftia”, 37)

    titleLogo:setReferencePoint( display.CenterReferencePoint )

    titleLogo.x = display.contentWidth * 0.5

    titleLogo.y = 100

    

    newGameBtn = display.newText(“New Game”, 0, 0, “Minecraftia”, 25)

    newGameBtn:setReferencePoint( display.CenterReferencePoint )

    newGameBtn.x = display.contentWidth * 0.5

    – newGameBtn.y = 135

    newGameBtn.y = 160

    

    newGameBtn.touch = onNewGameBtnRelease

    newGameBtn:addEventListener(“touch”, newGameBtn)

    

    – resumeGameBtn = display.newText(“Resume”, 0, 0, “Minecraftia”, 25)

    – resumeGameBtn:setReferencePoint( display.CenterReferencePoint )

    – resumeGameBtn.x = display.contentWidth * 0.5

    – resumeGameBtn.y = 190

    

    – resumeGameBtn.touch = onResumeGameBtnRelease

    – resumeGameBtn:addEventListener(“touch”, resumeGameBtn)

    upgradesBtn = display.newText(“Upgrades”, 0, 0, “Minecraftia”, 25)

    upgradesBtn:setReferencePoint( display.CenterReferencePoint )

    upgradesBtn.x = display.contentWidth * 0.5

    – upgradesBtn.y = 245

    upgradesBtn.y = 205

    

    upgradesBtn.touch = onUpgradesBtnRelease

    upgradesBtn:addEventListener(“touch”, upgradesBtn)

    – all display objects must be inserted into group

    group:insert( titleLogo )

    group:insert( newGameBtn )

    – group:insert( resumeGameBtn )

    group:insert( upgradesBtn )

end

– Called immediately after scene has moved onscreen:

function scene:enterScene( event )

    local group = self.view

    ads.show( “banner”, { x=0, y=0 } )

    

    – INSERT code here (e.g. start timers, load audio, start listeners, etc.)

    

end

– Called when scene is about to move offscreen:

function scene:exitScene( event )

    local group = self.view

    Runtime:removeEventListener(“enterFrame”, emperor)

    ads.hide()

    storyboard.purgeScene(storyboard.getCurrentSceneName())

    

    – INSERT code here (e.g. stop timers, remove listenets, unload sounds, etc.)

    

end

– If scene’s view is removed, scene:destroyScene() will be called just prior to:

function scene:destroyScene( event )

    local group = self.view

    

end


– END OF YOUR IMPLEMENTATION


– “createScene” event is dispatched if scene’s view does not exist

scene:addEventListener( “createScene”, scene )

– “enterScene” event is dispatched whenever scene transition has finished

scene:addEventListener( “enterScene”, scene )

– “exitScene” event is dispatched whenever before next scene’s transition begins

scene:addEventListener( “exitScene”, scene )

– “destroyScene” event is dispatched before view is unloaded, which can be

– automatically unloaded in low memory situations, or explicitly via a call to

– storyboard.purgeScene() or storyboard.removeScene().

scene:addEventListener( “destroyScene”, scene )


return scene

[/lua]

Just right off the bat, there are quite a few global variables in the code posted above. Did you forward declare the emperor object somewhere that isn’t listed?

Just to confirm the problem here, you are building for development, and not encountering an issue. You build for distribution, and on the SAME DEVICE, the issue appears. Is that accurate?

PS: Also, I see up there that you have a (what looks like) global variable for your “emperor” object, and also a Runtime listener for an “emperor” function, and a touch listener for the global “emperor” object. While this might not be causing this issue, you are certain to run into problems if this is taking place across your app.

Yes, on the same device the issue appears. Another person has experienced this issue when downloading my app, so I know there is an issue with the app store version.

The emperor is not global, I have a forward declaration on line 35. This is so I can remove the enterFrame Runtime listener in exitScene(), on line 196. Could you clarify what problems you are expecting this to cause? I need to have a Runtime listener on emperor because displayObjects can’t add the enterFrame listener on themselves. However, the touch listener is added by the “emperor” on itself.

My bad, didn’t see that forward declaration. I just meant that global variables could potentially cause problems later in the game, not to mention some memory leak problems. I had the same issue so I just wanted to warn. I should have known you had caught it. 

I don’t have my iDevice on me so I can’t test this out, but it looks normal to me. Also to confirm, you tried the distribution build on a device? Did you try running it with the XCode console window to confirm no errors are being thrown?

Wait so you mean I can install my distribution build through XCode? How do I do this? The XCode organizer does not let me add distribution provisioning profiles to my device, so when I try to install a distribution build, it just says a valid provisioning profile was not found.

Just to make it clear, when I say that my distribution build is wonky, I mean the version of the app that I download from the App Store.

Here is the link on how to run the XCode simulator on Macs to evaluate error logging:

http://www.coronalabs.com/blog/2013/07/09/tutorial-basic-debugging/

As far as not being able to put your distribution build onto your device from XCode, I’m not sure where you’re going wrong there, but I can do it with my setup. I followed the below link when I set up my configuration and it works perfectly:

http://developer.coronalabs.com/forum/2012/01/10/ios-walkthrough-testing-device-0

Oh you mean to create an ad hoc profile. Okay, I just tested it now and I can confirm that the bug appears when I build for my device using a distribution profile. Without changing a line of code, it works fine when I build for development. And nothing strange is showing up in the console.

Edit: I think the issue may have to do with admob. Investigating now.

Curiouser and curiouser. I’d suggest manually niling the objects when you leave the scene and see if that allows them to disappear?

My mistake, it turns out there is an error showing up in the console.  Looks like a gotoscene error. Removing admob doesn’t affect it, even though some admob warnings show up.

Dec 12 13:10:12 William-Kwans-iPad Heroesvs.MechsTowerDefense[270] <Warning>: Runtime error

    ?:0: attempt to index field ‘?’ (a nil value)

    stack traceback:

        [C]: ?

        ?: in function <?:66>

        ?: in function ‘dispatchEvent’

        ?: in function ‘gotoScene’

        ?: in function <?:20>

        ?: in function <?:218>

Dec 12 13:10:12 William-Kwans-iPad Heroesvs.MechsTowerDefense[270] <Warning>: Runtime error

    

    stack traceback:

        [C]: ?

        ?: in function <?:66>

        ?: in function ‘dispatchEvent’

        ?: in function ‘gotoScene’

        ?: in function <?:20>

You’ve got yourself a filename error. Having spaces and periods in filenames usually throws errors when you get to a device, but everything works normally on a simulator. Weird that you weren’t getting errors when you installed with a development build. I’d suggest taking the spaces, uppercase letters and periods out of the actual lua modules and the text in your scenes, and try it out then.

So I changed all my lua files to lowercase with no spaces and periods and all my folders containing lua files to lowercase with no spaces and periods. Still getting the same error.

This is an example of the code causing the error:

storyboard.gotoScene( “menufiles.mapselection”, “fade”, 300 )

You still have a period in the filename up there. Did you try taking this out, and making sure it is identical to the lua filename itself?

mapselection is inside the menufiles folder. If I use a “/” instead of a period, Corona tells me to put a period in. I am going to try putting all the lua files in the main directory.

Snap, my bad. I’ve never stuck my lua modules in anything but the main root folder. I just tested the period with my stuff and it works fine on the simulator, but I haven’t tested it on device. Let me know how it turns out.

Huh still not working. And the funny thing is that there is one scene that seems to work fine.

So I’m currently looking at this scene which causes the error when I try to go to it.

[lua]


– mapselection.lua


local storyboard = require( “storyboard” )

local scene = storyboard.newScene()

– include Corona’s “widget” library

local widget = require “widget”


local difficultyLevel

local NUM_MAPS = 10

local maps = {}

local mapTitle

local MAP_NAMES = {“Foliage”, “Arctic Forest”, “Bleak Desert”, “Hideout”, “Barriers”, “Martian Hell”, “Glacial Fortress”, “The Bridge”, “Ancient Ruins”, “Enemy Territory”}

local currentMap = 1

local function onMapLeftBtnRelease(self, event)

    maps[currentMap].isVisible = false

    if currentMap == 1 then

        currentMap = NUM_MAPS

    else

        currentMap = currentMap - 1

    end

    maps[currentMap].isVisible = true

    mapTitle.text = MAP_NAMES[currentMap]

end

local function onMapRightBtnRelease(self, event)

    maps[currentMap].isVisible = false

    if currentMap == NUM_MAPS then

        currentMap = 1

    else

        currentMap = currentMap + 1

    end

    maps[currentMap].isVisible = true

    mapTitle.text = MAP_NAMES[currentMap]

end

local function onStartBtnRelease(self, event) 

    if event.phase == “ended” then

        --fade does not work, maybe because of mte

        storyboard.gotoScene( “game”, {effect=“fade”, time=300, params={level=currentMap, difficulty=difficultyLevel.text}} )

    end

end

local function onBackBtnRelease(self, event)

    if event.phase == “ended” then

        storyboard.gotoScene( “menu”, “fade”, 300 )

    end

end

local difficultyStepper

local mapStepper


– BEGINNING OF YOUR IMPLEMENTATION

– 

– NOTE: Code outside of listener functions (below) will only be executed once,

–         unless storyboard.removeScene() is called.

– 


– Called when the scene’s view does not exist:

function scene:createScene( event )

    local group = self.view

    

    --some buttons need to be smooth

    display.setDefault( “magTextureFilter”, “linear” )

    display.setDefault( “minTextureFilter”, “linear” )

    

    local backBtn = display.newText(“Back”, 0, 0, “Minecraftia”, 25)

    backBtn:setReferencePoint( display.CenterReferencePoint )

    backBtn.x = 70

    backBtn.y = display.contentHeight - 20

    

    backBtn.touch = onBackBtnRelease

    backBtn:addEventListener(“touch”, backBtn)

    

    local startBtn = display.newText(“Start”, 0, 0, “Minecraftia”, 25)

    startBtn:setReferencePoint( display.CenterReferencePoint )

    startBtn.x = display.contentWidth - 70

    startBtn.y = display.contentHeight - 20

    

    startBtn.touch = onStartBtnRelease

    startBtn:addEventListener(“touch”, startBtn)

    

    

    local difficultyText = display.newText(“Difficulty”, 0, 0, “Minecraftia”, 25)

    difficultyText:setReferencePoint( display.CenterReferencePoint )

    difficultyText.x = display.contentWidth * 0.5 - 90

    difficultyText.y = 25

    

    difficultyLevel = display.newText(“1”, 0, 0, “Minecraftia”, 25)

    difficultyLevel:setReferencePoint( display.CenterReferencePoint )

    difficultyLevel.x = display.contentWidth * 0.5 + 160

    difficultyLevel.y = 25

    

    – 

    

    local stepperSheet = graphics.newImageSheet(“Tower Defense Graphics/Menu Buttons/Stepper.png”, 

        {

            width=200,

            height=100,

            numFrames=5,

            sheetContentWidth=1000,

            sheetContentHeight=100

        })

    

    difficultyStepper = widget.newStepper({

        left=display.contentWidth * 0.5 - 40, top=-20,

        width=200, height=100,

        sheet=stepperSheet,

        defaultFrame=1,

        noMinusFrame=2,

        noPlusFrame=3,

        minusActiveFrame=4,

        plusActiveFrame=5,

        initialValue=1,

        minimumValue=1,

        maximumValue=5,

        onPress=function(event) difficultyLevel.text = event.value end

        })

    difficultyStepper:scale(0.5, 0.5)

    

    mapTitle = display.newText(MAP_NAMES[currentMap], 0, 0, “Minecraftia”, 25)

    mapTitle:setReferencePoint( display.CenterReferencePoint )

    mapTitle.x = display.contentWidth * 0.5

    mapTitle.y = 70

    

    

    local buttonSheet = graphics.newImageSheet(“Tower Defense Graphics/Menu Buttons/MapButton.png”,

    {

        width=103,

        height=100,

        numFrames=4,

        sheetContentWidth=412,

        sheetContentHeight=100

    })

    local mapLeftBtn = widget.newButton

    {

        left=0, top=130,

        width=103, height=100,

        sheet=buttonSheet,

        defaultFrame=1,

        overFrame=2,

        onRelease=onMapLeftBtnRelease

    }

    

    mapLeftBtn:scale(0.5,0.5)

    

    local mapRightBtn = widget.newButton

    {

        left=display.contentWidth - 100, top=130,

        width=103, height=100,

        sheet=buttonSheet,

        defaultFrame=3,

        overFrame=4,

        onRelease=onMapRightBtnRelease

    }

    

    mapRightBtn:scale(0.5,0.5)

    

    for i=1,NUM_MAPS do

        --replace 1 with i when previews done

        maps[i] = display.newImageRect(“Tower Defense Graphics/Map Previews/Map” … i … “.png”, 280, 180)

        maps[i]:setReferencePoint( display.CenterReferencePoint )

        maps[i].x = display.contentWidth * 0.5

        maps[i].y = 190

        maps[i].isVisible = false

        group:insert(maps[i])

    end

    

    maps[currentMap].isVisible = true

    

    – all display objects must be inserted into group

    group:insert( backBtn )

    group:insert( startBtn )

    group:insert( difficultyText )

    group:insert( difficultyLevel )

    group:insert( difficultyStepper )

    group:insert( mapTitle )

    group:insert( mapLeftBtn )

    group:insert( mapRightBtn )

    display.setDefault( “magTextureFilter”, “nearest” )

    display.setDefault( “minTextureFilter”, “nearest” )

    

end

– Called immediately after scene has moved onscreen:

function scene:enterScene( event )

    local group = self.view

    

    – INSERT code here (e.g. start timers, load audio, start listeners, etc.)

    

end

– Called when scene is about to move offscreen:

function scene:exitScene( event )

    local group = self.view

    storyboard.purgeScene(storyboard.getCurrentSceneName())

    

    – INSERT code here (e.g. stop timers, remove listenets, unload sounds, etc.)

    

end

– If scene’s view is removed, scene:destroyScene() will be called just prior to:

function scene:destroyScene( event )

    local group = self.view

    if difficultyStepper then

        difficultyStepper:removeSelf()

    end

    if mapStepper then

        mapStepper:removeSelf()

    end

    

end


– END OF YOUR IMPLEMENTATION


– “createScene” event is dispatched if scene’s view does not exist

scene:addEventListener( “createScene”, scene )

– “enterScene” event is dispatched whenever scene transition has finished

scene:addEventListener( “enterScene”, scene )

– “exitScene” event is dispatched whenever before next scene’s transition begins

scene:addEventListener( “exitScene”, scene )

– “destroyScene” event is dispatched before view is unloaded, which can be

– automatically unloaded in low memory situations, or explicitly via a call to

– storyboard.purgeScene() or storyboard.removeScene().

scene:addEventListener( “destroyScene”, scene )


return scene

[/lua]

Took me half the day but I fixed the problem. The problem was with one pesky png file. I was loading a bunch of images here:

  1.     for i=1,NUM_MAPS do
  2.         --replace 1 with i when previews done
  3.         maps[i] = display.newImageRect(“Tower Defense Graphics/Map Previews/Map” … i … “.png”, 280, 180)
  4.         maps[i]:setReferencePoint( display.CenterReferencePoint )
  5.         maps[i].x = display.contentWidth * 0.5
  6.         maps[i].y = 190
  7.         maps[i].isVisible = false
  8.         group:insert(maps[i])
  9.     end

 

And I found that the error was removed if I commented out this code. So I looked at the 10 png files I was loading and it struck me as odd that one of them was 500kb while the rest of them were <100kb. These images are images of maps that I created in Tiled Map Editor, and saved using “Save as image”. So I resaved this unusually large image and now it works.

 

Really appreciate your help. Now I realize why all lua files are in lowercase :blink:  

Not a problem a’tall. If it’s any consolation, I’m going to be checking your game out when I get home.

I’m a big fan of those sprite assets. I saw them over at opengameart.org and wanted to implement them very badly!

Are you planning on releasing on Android?

Yep those assets are great, I came up with the idea for the game after I saw them. Will release on Android when I upgrade to Pro, but that might be awhile…wanna try out unity first.

Just right off the bat, there are quite a few global variables in the code posted above. Did you forward declare the emperor object somewhere that isn’t listed?

Just to confirm the problem here, you are building for development, and not encountering an issue. You build for distribution, and on the SAME DEVICE, the issue appears. Is that accurate?

PS: Also, I see up there that you have a (what looks like) global variable for your “emperor” object, and also a Runtime listener for an “emperor” function, and a touch listener for the global “emperor” object. While this might not be causing this issue, you are certain to run into problems if this is taking place across your app.

Yes, on the same device the issue appears. Another person has experienced this issue when downloading my app, so I know there is an issue with the app store version.

The emperor is not global, I have a forward declaration on line 35. This is so I can remove the enterFrame Runtime listener in exitScene(), on line 196. Could you clarify what problems you are expecting this to cause? I need to have a Runtime listener on emperor because displayObjects can’t add the enterFrame listener on themselves. However, the touch listener is added by the “emperor” on itself.