Bug in the Star Explorer Tutorial, or have I messed up?

What I was trying to do:

When running the completed version of the Star Fighter tutorial in the Corona Simulator, I was trying to move (drag) the ship on the screen immediately after pressing on the “Play” button, before the scene has totally faded in.

What I expected the result to be:

The ship to move

What happened instead:

Corona spits out the following error:

ERROR: Runtime error game.lua:159: attempt to perform arithmetic on field 'touchOffsetX' (a nil value) stack traceback: game.lua:159: in function '?' ?: in function \<?:190\>
  • What version of Corona SDK you I am running and which version of Windows
  • Corona Simulator 2018.3326 (2018.6.25)
  • Windows 10 Version 1803

 

I guess it has something to do with this:

local function dragShip( event ) local ship = event.target local phase = event.phase if ( "began" == phase ) then -- Set touch focus on the ship display.currentStage:setFocus( ship ) -- Store initial offset position ship.touchOffsetX = event.x - ship.x elseif ( "moved" == phase ) then -- Move the ship to the new touch position ship.x = event.x - ship.touchOffsetX elseif ( "ended" == phase or "cancelled" == phase ) then -- Release touch focus on the ship display.currentStage:setFocus( nil ) end return true -- Prevents touch propagation to underlying objects end

Here is the whole code for the game.lua:

[spoiler]

local composer = require( "composer" ) local scene = composer.newScene() -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- local physics = require( "physics" ) physics.start() physics.setGravity( 0, 0 ) -- Configure image sheet local sheetOptions = { frames = { { -- 1) asteroid 1 x = 0, y = 0, width = 102, height = 85 }, { -- 2) asteroid 2 x = 0, y = 85, width = 90, height = 83 }, { -- 3) asteroid 3 x = 0, y = 168, width = 100, height = 97 }, { -- 4) ship x = 0, y = 265, width = 98, height = 79 }, { -- 5) laser x = 98, y = 265, width = 14, height = 40 }, } } local objectSheet = graphics.newImageSheet( "gameObjects.png", sheetOptions ) -- Initialize variables local lives = 3 local score = 0 local died = false local asteroidsTable = {} local ship local gameLoopTimer local livesText local scoreText local backGroup local mainGroup local uiGroup local explosionSound local fireSound local musicTrack local function updateText() livesText.text = "Lives: " .. lives scoreText.text = "Score: " .. score end local function createAsteroid() local randomAsteroidnr = math.random( 3 ) if ( randomAsteroidNr == 1 ) then randomAsteroidHeight = 102 randomAsteroidWidth = 85 elseif ( randomAsteroidNr == 2 ) then randomAsteroidHeight = 90 randomAsteroidWidth = 83 elseif ( randomAsteroidNr == 3 ) then randomAsteroidHeight = 100 randomAsteroidWidth = 97 end local newAsteroid = display.newImageRect( mainGroup, objectSheet, randomAsteroidnr, randomAsteroidheight, randomAsteroidwidth ) table.insert( asteroidsTable, newAsteroid ) physics.addBody( newAsteroid, "dynamic", { radius=40, bounce=0.8 } ) newAsteroid.myName = "asteroid" local whereFrom = math.random( 3 ) if ( whereFrom == 1 ) then -- From the left newAsteroid.x = -60 newAsteroid.y = math.random( 500 ) newAsteroid:setLinearVelocity( math.random( 40,120 ), math.random( 20,60 ) ) elseif ( whereFrom == 2 ) then -- From the top newAsteroid.x = math.random( display.contentWidth ) newAsteroid.y = -60 newAsteroid:setLinearVelocity( math.random( -40,40 ), math.random( 40,120 ) ) elseif ( whereFrom == 3 ) then -- From the right newAsteroid.x = display.contentWidth + 60 newAsteroid.y = math.random( 500 ) newAsteroid:setLinearVelocity( math.random( -120,-40 ), math.random( 20,60 ) ) end newAsteroid:applyTorque( math.random( -6,6 ) ) end local function fireLaser() -- Play fire sound! audio.play( fireSound ) local newLaser = display.newImageRect( mainGroup, objectSheet, 5, 14, 40 ) physics.addBody( newLaser, "dynamic", { isSensor=true } ) newLaser.isBullet = true newLaser.myName = "laser" newLaser.x = ship.x newLaser.y = ship.y newLaser:toBack() transition.to( newLaser, { y=-40, time=500, onComplete = function() display.remove( newLaser ) end } ) end local function dragShip( event ) local ship = event.target local phase = event.phase if ( "began" == phase ) then -- Set touch focus on the ship display.currentStage:setFocus( ship ) -- Store initial offset position ship.touchOffsetX = event.x - ship.x elseif ( "moved" == phase ) then -- Move the ship to the new touch position ship.x = event.x - ship.touchOffsetX elseif ( "ended" == phase or "cancelled" == phase ) then -- Release touch focus on the ship display.currentStage:setFocus( nil ) end return true -- Prevents touch propagation to underlying objects end local function gameLoop() -- Create new asteroid createAsteroid() -- Remove asteroids which have drifted off screen for i = #asteroidsTable, 1, -1 do local thisAsteroid = asteroidsTable[i] if ( thisAsteroid.x \< -100 or thisAsteroid.x \> display.contentWidth + 100 or thisAsteroid.y \< -100 or thisAsteroid.y \> display.contentHeight + 100 ) then display.remove( thisAsteroid ) table.remove( asteroidsTable, i ) end end end local function restoreShip() ship.isBodyActive = false ship.x = display.contentCenterX ship.y = display.contentHeight - 100 -- Fade in the ship transition.to( ship, { alpha=1, time=4000, onComplete = function() ship.isBodyActive = true died = false end } ) end local function endGame() composer.setVariable( "finalScore", score ) composer.gotoScene( "highscores", { time=800, effect="crossFade" } ) end local function onCollision( event ) if ( event.phase == "began" ) then local obj1 = event.object1 local obj2 = event.object2 if ( ( obj1.myName == "laser" and obj2.myName == "asteroid" ) or ( obj1.myName == "asteroid" and obj2.myName == "laser" ) ) then -- Remove both the laser and asteroid display.remove( obj1 ) display.remove( obj2 ) -- Play explosion sound! audio.play( explosionSound ) for i = #asteroidsTable, 1, -1 do if ( asteroidsTable[i] == obj1 or asteroidsTable[i] == obj2 ) then table.remove( asteroidsTable, i ) break end end -- Increase score score = score + 100 scoreText.text = "Score: " .. score elseif ( ( obj1.myName == "ship" and obj2.myName == "asteroid" ) or ( obj1.myName == "asteroid" and obj2.myName == "ship" ) ) then if ( died == false ) then died = true -- Play explosion sound! audio.play( explosionSound ) -- Update lives lives = lives - 1 livesText.text = "Lives: " .. lives if ( lives == 0 ) then display.remove( ship ) timer.performWithDelay( 2000, endGame ) else ship.alpha = 0 timer.performWithDelay( 1000, restoreShip ) end end end end end -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create() function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen physics.pause() -- Temporarily pause the physics engine -- Set up display groups backGroup = display.newGroup() -- Display group for the background image sceneGroup:insert( backGroup ) -- Insert into the scene's view group mainGroup = display.newGroup() -- Display group for the ship, asteroids, lasers, etc. sceneGroup:insert( mainGroup ) -- Insert into the scene's view group uiGroup = display.newGroup() -- Display group for UI objects like the score sceneGroup:insert( uiGroup ) -- Insert into the scene's view group -- Load the background local background = display.newImageRect( backGroup, "background.png", 800, 1400 ) background.x = display.contentCenterX background.y = display.contentCenterY ship = display.newImageRect( mainGroup, objectSheet, 4, 98, 79 ) ship.x = display.contentCenterX ship.y = display.contentHeight - 100 physics.addBody( ship, { radius=30, isSensor=true } ) ship.myName = "ship" -- Display lives and score livesText = display.newText( uiGroup, "Lives: " .. lives, 200, 80, native.systemFont, 36 ) scoreText = display.newText( uiGroup, "Score: " .. score, 400, 80, native.systemFont, 36 ) ship:addEventListener( "tap", fireLaser ) ship:addEventListener( "touch", dragShip ) explosionSound = audio.loadSound( "audio/splat.wav" ) fireSound = audio.loadSound( "audio/fart1.wav" ) musicTrack = audio.loadStream( "audio/game.wav") end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen physics.start() Runtime:addEventListener( "collision", onCollision ) gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 ) -- Start the music! audio.play( musicTrack, { channel=1, loops=-1 } ) 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) timer.cancel( gameLoopTimer ) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen Runtime:removeEventListener( "collision", onCollision ) physics.pause() -- Stop the music! audio.stop( 1 ) composer.removeScene( "game" ) end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view -- Dispose audio! audio.dispose( explosionSound ) audio.dispose( fireSound ) audio.dispose( musicTrack ) end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene

[/spoiler]

P.S. I tried to use the lua tags for pasting in lua code, but I couldn’t get it to work correctly - it didn’t show the indentations or line numbers when I preview the post.

Thanks for any help!

What is game.lua line 159?  I suspect it’s

ship.x = event.x - ship.touchOffsetX

which tells me that ship.touchOffsetX didn’t get initialized properly. The question is why? 

Perhaps adding some prints to make sure you’re getting a “began” phase might be useful.

Rob

Edit #2:

OK this fixed it:

local function dragShip( event ) local ship = event.target local phase = event.phase if ( "began" == phase ) then -- Set touch focus on the ship display.currentStage:setFocus( ship ) ship.isFocus = true -- Store initial offset position ship.touchOffsetX = event.x - ship.x elseif ( ship.isFocus ) then if ( "moved" == phase ) then -- Move the ship to the new touch position ship.x = event.x - ship.touchOffsetX elseif ( "ended" == phase or "cancelled" == phase ) then -- Release touch focus on the ship display.currentStage:setFocus( nil ) end end return true -- Prevents touch propagation to underlying objects end

Edit: i think i have some idea of maybe what is happening:

The error occurs when you start the touch as the scene is fading in, and continue moving the same touch after the scene has completed the transition.

as the scene is transitioning in, the “began” phase does not register - you cannot move the ship for some reason. I’m not sure why.

But that’s the phase where touchOffsetX is defined. so it doesn’t get defined.

So then you’re able to move or end the touch once the scene has transitioned, without beginning the touch, so a touchOffsetX is not defined… which it needs, so there’s an error.

I also tested it with the source files from the tutorial, and the same thing happens.

Thanks Rob!

Yes that is exactly the correct line :slight_smile:

The thing is, if I wait for the scene to “fade in” completely, there is no problem - it’s only an error if I try to move the ship a fraction of a second after pressing the Play button, as the scene is still fading in.

I made some prints:

local function dragShip( event ) local ship = event.target local phase = event.phase if ( "began" == phase ) then -- Set touch focus on the ship display.currentStage:setFocus( ship ) -- Store initial offset position ship.touchOffsetX = event.x - ship.x print(ship.touchOffsetX) print(phase) elseif ( "moved" == phase ) then -- Move the ship to the new touch position ship.x = event.x - ship.touchOffsetX print(ship.x) print(phase) elseif ( "ended" == phase or "cancelled" == phase ) then -- Release touch focus on the ship display.currentStage:setFocus( nil ) print(phase) end return true -- Prevents touch propagation to underlying objects end

and the began, moved, and ended phases seem to happen as expected, as long as the scene has completely faded in.

19:35:45.431 began 19:35:45.805 279.46664428711 19:35:45.805 moved 19:35:46.025 ended 19:35:48.035 3.0517578125e-005 19:35:48.035 began 19:35:48.563 281.59997558594 19:35:48.563 moved 19:35:48.905 ended

But the same error happens if I try to move the ship while it’s still loading in the scene. Perhaps it has something to do with this?:

function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen physics.start() Runtime:addEventListener( "collision", onCollision ) gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 ) -- Start the music! audio.play( musicTrack, { channel=1, loops=-1 } ) end end

What is game.lua line 159?  I suspect it’s

ship.x = event.x - ship.touchOffsetX

which tells me that ship.touchOffsetX didn’t get initialized properly. The question is why? 

Perhaps adding some prints to make sure you’re getting a “began” phase might be useful.

Rob

Edit #2:

OK this fixed it:

local function dragShip( event ) local ship = event.target local phase = event.phase if ( "began" == phase ) then -- Set touch focus on the ship display.currentStage:setFocus( ship ) ship.isFocus = true -- Store initial offset position ship.touchOffsetX = event.x - ship.x elseif ( ship.isFocus ) then if ( "moved" == phase ) then -- Move the ship to the new touch position ship.x = event.x - ship.touchOffsetX elseif ( "ended" == phase or "cancelled" == phase ) then -- Release touch focus on the ship display.currentStage:setFocus( nil ) end end return true -- Prevents touch propagation to underlying objects end

Edit: i think i have some idea of maybe what is happening:

The error occurs when you start the touch as the scene is fading in, and continue moving the same touch after the scene has completed the transition.

as the scene is transitioning in, the “began” phase does not register - you cannot move the ship for some reason. I’m not sure why.

But that’s the phase where touchOffsetX is defined. so it doesn’t get defined.

So then you’re able to move or end the touch once the scene has transitioned, without beginning the touch, so a touchOffsetX is not defined… which it needs, so there’s an error.

I also tested it with the source files from the tutorial, and the same thing happens.

Thanks Rob!

Yes that is exactly the correct line :slight_smile:

The thing is, if I wait for the scene to “fade in” completely, there is no problem - it’s only an error if I try to move the ship a fraction of a second after pressing the Play button, as the scene is still fading in.

I made some prints:

local function dragShip( event ) local ship = event.target local phase = event.phase if ( "began" == phase ) then -- Set touch focus on the ship display.currentStage:setFocus( ship ) -- Store initial offset position ship.touchOffsetX = event.x - ship.x print(ship.touchOffsetX) print(phase) elseif ( "moved" == phase ) then -- Move the ship to the new touch position ship.x = event.x - ship.touchOffsetX print(ship.x) print(phase) elseif ( "ended" == phase or "cancelled" == phase ) then -- Release touch focus on the ship display.currentStage:setFocus( nil ) print(phase) end return true -- Prevents touch propagation to underlying objects end

and the began, moved, and ended phases seem to happen as expected, as long as the scene has completely faded in.

19:35:45.431 began 19:35:45.805 279.46664428711 19:35:45.805 moved 19:35:46.025 ended 19:35:48.035 3.0517578125e-005 19:35:48.035 began 19:35:48.563 281.59997558594 19:35:48.563 moved 19:35:48.905 ended

But the same error happens if I try to move the ship while it’s still loading in the scene. Perhaps it has something to do with this?:

function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen physics.start() Runtime:addEventListener( "collision", onCollision ) gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 ) -- Start the music! audio.play( musicTrack, { channel=1, loops=-1 } ) end end