Chapter 5 — 转换为 Composer(Converting to Composer)

In the last chapter, we learned the basics of scene management. In this chapter, we’re going to convert the original game file, now main_original.lua, into our game.lua scene.

  • Scene Structure
  • Accessible Code
  • Creating the Scene
  • Showing the Scene
  • Hiding the Scene
  • Scene Cleanup
  • Extra Credit
  • Chapter Concepts

Scene Structure

As you learned in the previous chapter, there are dedicated places in a Composer-enabled Lua file to put different aspects of your program. In our original version of Star Explorer, we had the luxury of writing our code in a linear order, for example creating an object, positioning it on the screen, potentially adding a physical body, linking up event listeners, and then moving on to the next item.

Composer requires that you think a little differently. Using it correctly requires that you consider the scene life cycle functions — scene:create(), scene:show(), scene:hide(), and scene:destroy() — and that you run commands depending on whether the scene is on screen or off screen.

Consider this concept like a movie scene: if the director is transitioning into a scene — fading in, panning the camera to a point, etc. — the actors in the scene usually won’t begin acting until the scene is “ready” and focused. The same approach applies to Composer scenes. For instance, we already added commands to spawn asteroids and put them in motion, but in this game.lua scene we’re about to create, those commands will only run once the scene is fully on screen.

Accessible Code

Let’s get started! The first point of attack is the scene-accessible area of the file.

-

Make a copy of the standard scene-template.lua file, included with this chapter’s source files.

-

Rename this copy to game.lua and place it within your StarExplorer project folder.

-

Open both main_original.lua and game.lua in separate editor windows/tabs. You will be copying several blocks of code from main_original.lua to game.lua, so it’s convenient to have both Lua files open simultaneously.

Physics Setup

Since this game will obviously still utilize the physics engine, there’s no reason to defer that setup until later. Copy your physics setup commands from main_original.lua and paste them into the scene-accessible space of game.lua, immediately following initialization of the scene:

  1. local composer = require( "composer" )
  2. local scene = composer.newScene()
  3. -- -----------------------------------------------------------------------------------
  4. -- Code outside of the scene event functions below will only be executed ONCE unless
  5. -- the scene is removed entirely (not recycled) via "composer.removeScene()"
  6. -- -----------------------------------------------------------------------------------
  7. local physics = require( "physics" )
  8. physics.start()
  9. physics.setGravity( 0, 0 )

Note

Previously, we followed these commands with the math.randomseed() command. However, you may recall that we re-stated that command in our modified main.lua file, so there’s no reason to copy it over to game.lua.

Image Sheet

Next we’ll need the image sheet configuration. Let’s paste that just below the physics commands:

  1. local physics = require( "physics" )
  2. physics.start()
  3. physics.setGravity( 0, 0 )
  4. -- Configure image sheet
  5. local sheetOptions =
  6. {
  7. frames =
  8. {
  9. { -- 1) asteroid 1
  10. x = 0,
  11. y = 0,
  12. width = 102,
  13. height = 85
  14. },
  15. { -- 2) asteroid 2
  16. x = 0,
  17. y = 85,
  18. width = 90,
  19. height = 83
  20. },
  21. { -- 3) asteroid 3
  22. x = 0,
  23. y = 168,
  24. width = 100,
  25. height = 97
  26. },
  27. { -- 4) ship
  28. x = 0,
  29. y = 265,
  30. width = 98,
  31. height = 79
  32. },
  33. { -- 5) laser
  34. x = 98,
  35. y = 265,
  36. width = 14,
  37. height = 40
  38. },
  39. }
  40. }
  41. local objectSheet = graphics.newImageSheet( "gameObjects.png", sheetOptions )

Initial Variables

Following the image sheet setup, paste in the following localized variables from main_original.lua:

  1. local objectSheet = graphics.newImageSheet( "gameObjects.png", sheetOptions )
  2. -- Initialize variables
  3. local lives = 3
  4. local score = 0
  5. local died = false
  6. local asteroidsTable = {}
  7. local ship
  8. local gameLoopTimer
  9. local livesText
  10. local scoreText

Display Groups

In our original version, we created three display groups for sorting and layering our game objects: backGroup, mainGroup and uiGroup. We are still going to use them, but a few small modifications will be necessary since we’re using Composer.

In the last chapter, you learned how to insert scene objects into the scene’s view group (sceneGroup). Now, an important concept to understand is that display groups can actually be inserted into other display groups! As such, we can maintain the three display groups from the original game and instill them into the scene’s view group.

To facilitate this, we will defer the actual creation of our three groups until we create the scene. However, we still need to define the variables now using the forward declaration method that you learned about earlier. So, instead of associating each variable with a display.newGroup(), just leave them undefined for the moment:

  1. local ship
  2. local gameLoopTimer
  3. local livesText
  4. local scoreText
  5. local backGroup
  6. local mainGroup
  7. local uiGroup

Note

In our original version, we created the background, ship, lives text, and score text immediately following initialization of the display groups. Now, because we’re only creating local references for the display groups, we must defer these actions until later, inside the scene:create() function.

Game Functions

As we continue past this point in main_original.lua, we come to the local functions which power our game’s core functionality. Basically, these can be copied over directly into your game.lua file, pasted directly below the variables we just defined.

First, copy over the updateText() function:

  1. local backGroup
  2. local mainGroup
  3. local uiGroup
  4. local function updateText()
  5. livesText.text = "Lives: " .. lives
  6. scoreText.text = "Score: " .. score
  7. end

Follow this with the createAsteroid() function:

  1. local function updateText()
  2. livesText.text = "Lives: " .. lives
  3. scoreText.text = "Score: " .. score
  4. end
  5. local function createAsteroid()
  6. local newAsteroid = display.newImageRect( mainGroup, objectSheet, 1, 102, 85 )
  7. table.insert( asteroidsTable, newAsteroid )
  8. physics.addBody( newAsteroid, "dynamic", { radius=40, bounce=0.8 } )
  9. newAsteroid.myName = "asteroid"
  10. local whereFrom = math.random( 3 )
  11. if ( whereFrom == 1 ) then
  12. -- From the left
  13. newAsteroid.x = -60
  14. newAsteroid.y = math.random( 500 )
  15. newAsteroid:setLinearVelocity( math.random( 40,120 ), math.random( 20,60 ) )
  16. elseif ( whereFrom == 2 ) then
  17. -- From the top
  18. newAsteroid.x = math.random( display.contentWidth )
  19. newAsteroid.y = -60
  20. newAsteroid:setLinearVelocity( math.random( -40,40 ), math.random( 40,120 ) )
  21. elseif ( whereFrom == 3 ) then
  22. -- From the right
  23. newAsteroid.x = display.contentWidth + 60
  24. newAsteroid.y = math.random( 500 )
  25. newAsteroid:setLinearVelocity( math.random( -120,-40 ), math.random( 20,60 ) )
  26. end
  27. newAsteroid:applyTorque( math.random( -6,6 ) )
  28. end

The next function, fireLaser(), should follow:

  1. newAsteroid:applyTorque( math.random( -6,6 ) )
  2. end
  3. local function fireLaser()
  4. local newLaser = display.newImageRect( mainGroup, objectSheet, 5, 14, 40 )
  5. physics.addBody( newLaser, "dynamic", { isSensor=true } )
  6. newLaser.isBullet = true
  7. newLaser.myName = "laser"
  8. newLaser.x = ship.x
  9. newLaser.y = ship.y
  10. newLaser:toBack()
  11. transition.to( newLaser, { y=-40, time=500,
  12. onComplete = function() display.remove( newLaser ) end
  13. } )
  14. end

Important

Now observe the line directly following the fireLaser() function in main_original.lua:

  1. ship:addEventListener( "tap", fireLaser )

This creates the event listener function for ship, but we haven’t created ship yet! Thus, we must defer this command until later, following creation of the actual ship object.

As you copy/paste in the next few functions, skip the commands which immediately follow them. Specifically, omit the following lines when you’re copying over code from main_original.lua to game.lua:

  1. ship:addEventListener( "tap", fireLaser )
  1. ship:addEventListener( "touch", dragShip )
  1. gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )
  1. Runtime:addEventListener( "collision", onCollision )

Essentially, the remainder of your scene-accessible space should be populated as follows:

  1. local function dragShip( event )
  2. local ship = event.target
  3. local phase = event.phase
  4. if ( "began" == phase ) then
  5. -- Set touch focus on the ship
  6. display.currentStage:setFocus( ship )
  7. -- Store initial offset position
  8. ship.touchOffsetX = event.x - ship.x
  9. elseif ( "moved" == phase ) then
  10. -- Move the ship to the new touch position
  11. ship.x = event.x - ship.touchOffsetX
  12. elseif ( "ended" == phase or "cancelled" == phase ) then
  13. -- Release touch focus on the ship
  14. display.currentStage:setFocus( nil )
  15. end
  16. return true -- Prevents touch propagation to underlying objects
  17. end
  18. local function gameLoop()
  19. -- Create new asteroid
  20. createAsteroid()
  21. -- Remove asteroids which have drifted off screen
  22. for i = #asteroidsTable, 1, -1 do
  23. local thisAsteroid = asteroidsTable[i]
  24. if ( thisAsteroid.x < -100 or
  25. thisAsteroid.x > display.contentWidth + 100 or
  26. thisAsteroid.y < -100 or
  27. thisAsteroid.y > display.contentHeight + 100 )
  28. then
  29. display.remove( thisAsteroid )
  30. table.remove( asteroidsTable, i )
  31. end
  32. end
  33. end
  34. local function restoreShip()
  35. ship.isBodyActive = false
  36. ship.x = display.contentCenterX
  37. ship.y = display.contentHeight - 100
  38. -- Fade in the ship
  39. transition.to( ship, { alpha=1, time=4000,
  40. onComplete = function()
  41. ship.isBodyActive = true
  42. died = false
  43. end
  44. } )
  45. end
  46. local function onCollision( event )
  47. if ( event.phase == "began" ) then
  48. local obj1 = event.object1
  49. local obj2 = event.object2
  50. if ( ( obj1.myName == "laser" and obj2.myName == "asteroid" ) or
  51. ( obj1.myName == "asteroid" and obj2.myName == "laser" ) )
  52. then
  53. -- Remove both the laser and asteroid
  54. display.remove( obj1 )
  55. display.remove( obj2 )
  56. for i = #asteroidsTable, 1, -1 do
  57. if ( asteroidsTable[i] == obj1 or asteroidsTable[i] == obj2 ) then
  58. table.remove( asteroidsTable, i )
  59. break
  60. end
  61. end
  62. -- Increase score
  63. score = score + 100
  64. scoreText.text = "Score: " .. score
  65. elseif ( ( obj1.myName == "ship" and obj2.myName == "asteroid" ) or
  66. ( obj1.myName == "asteroid" and obj2.myName == "ship" ) )
  67. then
  68. if ( died == false ) then
  69. died = true
  70. -- Update lives
  71. lives = lives - 1
  72. livesText.text = "Lives: " .. lives
  73. if ( lives == 0 ) then
  74. display.remove( ship )
  75. else
  76. ship.alpha = 0
  77. timer.performWithDelay( 1000, restoreShip )
  78. end
  79. end
  80. end
  81. end
  82. end
  83. -- -----------------------------------------------------------------------------------
  84. -- Scene event functions
  85. -- -----------------------------------------------------------------------------------

Great job! We now have all of the scene-accessible code copied over. In the next few sections, we’ll illustrate how the Composer scene: functions tie our game together.

Creating the Scene

Now we need to actually create the scene. For the menu scene in the previous chapter, we created the background, title, and two buttons within scene:create(). For this scene, we’ll create the background, ship, and the two text objects. In addition, we’ll pause the physics engine and actually create the three display groups required for layering our game objects.

Let’s start with physics. Inside the scene:create() function, add the command physics.pause() as follows:

  1. -- -----------------------------------------------------------------------------------
  2. -- Scene event functions
  3. -- -----------------------------------------------------------------------------------
  4. -- create()
  5. function scene:create( event )
  6. local sceneGroup = self.view
  7. -- Code here runs when the scene is first created but has not yet appeared on screen
  8. physics.pause() -- Temporarily pause the physics engine
  9. end

What is the purpose of this command at this point in the scene’s life cycle? Remember that our game scene isn’t truly on screen at this point and, because we don’t want the game to start quite yet, we’ll immediately pause the physics engine. This allows us to create objects, assign their physical bodies, and position them, but they won’t be affected physically until we re-start the physics engine.

Next we need to create the three display groups for which we previously just defined forward references. Add the following highlighted lines:

  1. function scene:create( event )
  2. local sceneGroup = self.view
  3. -- Code here runs when the scene is first created but has not yet appeared on screen
  4. physics.pause() -- Temporarily pause the physics engine
  5. -- Set up display groups
  6. backGroup = display.newGroup() -- Display group for the background image
  7. sceneGroup:insert( backGroup ) -- Insert into the scene's view group
  8. mainGroup = display.newGroup() -- Display group for the ship, asteroids, lasers, etc.
  9. sceneGroup:insert( mainGroup ) -- Insert into the scene's view group
  10. uiGroup = display.newGroup() -- Display group for UI objects like the score
  11. sceneGroup:insert( uiGroup ) -- Insert into the scene's view group
  12. end

Here, in addition to creating the groups, we also insert each group into the Composer scene’s view group (sceneGroup) using sceneGroup:insert(). This is how we instill our original display groups into the scene.

As you recall, most Solar2D display object APIs accept a valid display group variable as a convenient inline shortcut for inserting the object into that group. However, display.newGroup() is one of the exceptions to this shortcut — you can’t simply supply an inline group reference to insert the new display group into an existing group. Instead, you must use the object:insert() command.

With the groups in place, let’s create the background:

  1. -- Set up display groups
  2. backGroup = display.newGroup() -- Display group for the background image
  3. sceneGroup:insert( backGroup ) -- Insert into the scene's view group
  4. mainGroup = display.newGroup() -- Display group for the ship, asteroids, lasers, etc.
  5. sceneGroup:insert( mainGroup ) -- Insert into the scene's view group
  6. uiGroup = display.newGroup() -- Display group for UI objects like the score
  7. sceneGroup:insert( uiGroup ) -- Insert into the scene's view group
  8. -- Load the background
  9. local background = display.newImageRect( backGroup, "background.png", 800, 1400 )
  10. background.x = display.contentCenterX
  11. background.y = display.contentCenterY
  12. end

Note

If you inspect the scene-accessible code area near the top of game.lua, you’ll notice that we did not include a forward reference to background via local background. This is because, once we create and insert the background object into the scene’s view, we’ll never need to access it elsewhere in the code. Thus, we simply create it as a local object inside the scene:create() function.

Now let’s create the ship and both text objects:

  1. -- Load the background
  2. local background = display.newImageRect( backGroup, "background.png", 800, 1400 )
  3. background.x = display.contentCenterX
  4. background.y = display.contentCenterY
  5. ship = display.newImageRect( mainGroup, objectSheet, 4, 98, 79 )
  6. ship.x = display.contentCenterX
  7. ship.y = display.contentHeight - 100
  8. physics.addBody( ship, { radius=30, isSensor=true } )
  9. ship.myName = "ship"
  10. -- Display lives and score
  11. livesText = display.newText( uiGroup, "Lives: " .. lives, 200, 80, native.systemFont, 36 )
  12. scoreText = display.newText( uiGroup, "Score: " .. score, 400, 80, native.systemFont, 36 )
  13. end

Unlike the background, we create these three objects using the variable forward references — ship, livesText, and scoreText — that we included earlier in the scene-accessible code section. This is because other functions will need to know about these objects as the game runs.

Essentially, you can create a forward reference in the scene-accessible area, assign an actual object to that reference inside a scene: function, and then other functions will associate the reference with the new object.

Notice that we are still inserting objects into their proper display groups such as backGroup, mainGroup, and uiGroup. This is the correct procedure because all of those groups were inserted into the scene’s view group and they are now children of that parent group.

Moving onward — remember how we deferred adding the ship’s "tap" and "touch" event listeners in the scene-accessible section because ship didn’t yet exist as an actual object? Now that the ship does exist, let’s add its event listeners:

  1. -- Display lives and score
  2. livesText = display.newText( uiGroup, "Lives: " .. lives, 200, 80, native.systemFont, 36 )
  3. scoreText = display.newText( uiGroup, "Score: " .. score, 400, 80, native.systemFont, 36 )
  4. ship:addEventListener( "tap", fireLaser )
  5. ship:addEventListener( "touch", dragShip )
  6. end

That’s it! Our initial scene objects will now be created — albeit off screen — immediately before Composer proceeds to the next function in the scene’s life cycle, scene:show().

Showing the Scene

In the menu scene, we didn’t need to use scene:show() nor its companion function scene:hide(), but in this game scene we do. At this point, there are still some essential aspects which we haven’t copied over from main_original.lua — primarily, we have not yet enabled collision detection or started the game loop to spawn asteroids. Fortunately, scene:show() can be used to put everything in motion!

Transition Effects

While we didn’t utilize one for the menu scene, the composer.gotoScene() command allows you to specify a transition effect such as fading in, sliding in from a screen edge, cross-fading from the previous scene, etc. Naturally, there is a time duration associated with the start and finish of scene transitions, and this is where scene phases come into play.

Scene Phases

An important factor to understand (in contrast to scene:create()) is that Composer calls the scene:show() function twice. Of course it’s imperative to know when each of these calls occurs so that we can take the proper actions at the proper time. This distinction is provided via event.phase within scene:show(). Basically, scene:show() calls/phases work like this:

-

The first call occurs when the scene is ready to be shown, essentially after every command within scene:create() has been executed. In this case, event.phase is "will", effectively indicating that the scene “will show” and the transition effect is about to occur.

-

The second call occurs immediately after the scene has shown — basically, when the scene transition has completed. In this case, event.phase is "did", meaning the scene “did show” and the transition effect completed.

Notice that the scene template already contains a conditional statement to check for each phase of scene:show():

  1. -- show()
  2. function scene:show( event )
  3. local sceneGroup = self.view
  4. local phase = event.phase
  5. if ( phase == "will" ) then
  6. -- Code here runs when the scene is still off screen (but is about to come on screen)
  7. elseif ( phase == "did" ) then
  8. -- Code here runs when the scene is entirely on screen
  9. end
  10. end

Now that you understand this concept, simply paste code from your main_original.lua file into the proper conditional clause. For this game, we basically want to start the game running — spawning asteroids, detecting collisions, etc. — once the scene is fully on screen (the "did" phase). So, in your game.lua file, within the scene:show() function, add the three highlighted commands:

  1. -- show()
  2. function scene:show( event )
  3. local sceneGroup = self.view
  4. local phase = event.phase
  5. if ( phase == "will" ) then
  6. -- Code here runs when the scene is still off screen (but is about to come on screen)
  7. elseif ( phase == "did" ) then
  8. -- Code here runs when the scene is entirely on screen
  9. physics.start()
  10. Runtime:addEventListener( "collision", onCollision )
  11. gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )
  12. end
  13. end

Essentially, these commands accomplish the following:

  • Re-start the physics engine with physics.start() (remember that we paused it in scene:create()).
  • Start collision detection.
  • Start the game loop with our original timer.performWithDelay() command.

Let’s check the result of our code! Save your modified game.lua file and then relaunch the Simulator. As expected, you’ll be presented with the menu screen, but now we can actually proceed. Tap/click the Play button and, assuming you did everything up to this point correctly, Composer should proceed to the game.lua scene which plays identically to our original version of the game.

Hiding the Scene

At this point there’s a significant flaw in the game. When the player runs out of lives, the asteroids will continue to build up and there is no way to restart the game. This means that we need to adapt our game.lua code so that Composer exits the scene when the player runs out of lives.

When we intend to exit the game scene, remember that the gameLoopTimer timer will still be running, spawning asteroids and removing off-screen asteroids. In addition, the physics engine will still be moving asteroids about. All of these things should be stopped inside the scene:hide() function.

Similar to scene:show(), scene:hide() will be called twice and the distinction is once again provided via event.phase. Basically, scene:hide() calls/phases work like this:

-

The first call occurs when the scene is about to be hidden (transition off screen). In this case, event.phase is "will", effectively indicating that the scene “will hide” and the transition effect is about to occur.

-

The second call occurs immediately after the scene is fully off screen. In this case, event.phase is "did", meaning the scene “did hide” and the transition effect completed.

For this scene, in the "will" and "did" phase conditions of scene:hide(), we’ll “undo” some things by adding three commands:

  1. -- hide()
  2. function scene:hide( event )
  3. local sceneGroup = self.view
  4. local phase = event.phase
  5. if ( phase == "will" ) then
  6. -- Code here runs when the scene is on screen (but is about to go off screen)
  7. timer.cancel( gameLoopTimer )
  8. elseif ( phase == "did" ) then
  9. -- Code here runs immediately after the scene goes entirely off screen
  10. Runtime:removeEventListener( "collision", onCollision )
  11. physics.pause()
  12. end
  13. end

These commands essentially reverse what we did in the scene:show() function:

  • Stop the game loop by canceling the timer associated with gameLoopTimer.
  • Stop collision detection by removing the runtime event listener.
  • Pause the physics engine with physics.pause().

Note how we’re intentionally using both phases of scene:hide(). The first command can occur before the scene begins to hide (phase == "will") since we don’t need to generate new asteroids once the game is over. For the other two commands, we defer them until after the scene is fully off screen because we don’t want the remaining asteroids to suddenly stop detecting collisions or stop moving (physics.pause()) when the scene transition begins.

Our scene:hide() function is now complete, but we need to take a few final steps to make sure it actually gets called. Logically, a scene will never be hidden unless you intentionally cause it to hide, and this occurs when you tell Composer to go to a different scene (composer.gotoScene()).

In our game, we want to return to the menu — or eventually go to the high scores scene — when the player dies and has no remaining lives. This condition has already been accounted for partially in our previous code, so all we need to add is an additional command in the same place.

In game.lua, find your onCollision function and, toward its end, locate this conditional block:

  1. -- Update lives
  2. lives = lives - 1
  3. livesText.text = "Lives: " .. lives
  4. if ( lives == 0 ) then
  5. display.remove( ship )
  6. else
  7. ship.alpha = 0
  8. timer.performWithDelay( 1000, restoreShip )
  9. end
  10. end
  11. end
  12. end
  13. end

Now, directly below the display.remove( ship ) command, add the following highlighted command:

  1. if ( lives == 0 ) then
  2. display.remove( ship )
  3. timer.performWithDelay( 2000, endGame )
  4. else
  5. ship.alpha = 0
  6. timer.performWithDelay( 1000, restoreShip )
  7. end
  8. end
  9. end
  10. end
  11. end

The timer.performWithDelay() command is nothing new — this instance simply calls a function endGame() after 2000 milliseconds (2 seconds). Of course, we haven’t actually written the endGame() function yet, so let’s write it now. Directly above your onCollision() function, add this new function:

  1. local function endGame()
  2. composer.gotoScene( "menu", { time=800, effect="crossFade" } )
  3. end
  4. local function onCollision( event )
  5. if ( event.phase == "began" ) then

For now, this function simply returns to the menu by calling composer.gotoScene().

This time we’re including an additional, optional table containing parameters for a scene transition effect. Inside this table, we specify an effect duration (time) of 800 milliseconds and an effect property of "crossFade". This built-in Composer effect will cause the game scene to fade out while, concurrently, the menu scene fades in.

Scene Cleanup

Hopefully players will want to play the game again! By default, Composer caches scenes in memory to save processing power when the scene is revisited. So, even though it’s hidden at this point, your game scene remains basically as you left it. If you play the game again, the scene comes back into view and new asteroids begin spawning. Unfortunately, there are some problems:

  • The asteroids from the previous game are still in the scene.
  • Your previous score still appears and lives remain at zero.
  • The ship isn’t showing!

Depending on the game, cleaning up a scene to restart fresh can involve some effort. In this game, we would need to “undo” some things we did in scene:create() as well as remove the references to old asteroids contained in the asteroidsTable table. We would also need to reset score, lives, and the ship’s visibility within scene:show(). None of this is exceptionally complicated, but wouldn’t it be convenient to have an easier way to reset a scene? Fortunately, Composer offers one:

  1. composer.removeScene( "game" )

Essentially, this command removes and destroys the game.lua scene as if it never existed. By doing so, you lose the caching benefit mentioned above, but for most scenes it’s not worth the effort to programmatically reset each aspect individually.

With this simplified approach, let’s modify our scene:hide() function:

  1. -- hide()
  2. function scene:hide( event )
  3. local sceneGroup = self.view
  4. local phase = event.phase
  5. if ( phase == "will" ) then
  6. -- Code here runs when the scene is on screen (but is about to go off screen)
  7. timer.cancel( gameLoopTimer )
  8. elseif ( phase == "did" ) then
  9. -- Code here runs immediately after the scene goes entirely off screen
  10. Runtime:removeEventListener( "collision", onCollision )
  11. physics.pause()
  12. composer.removeScene( "game" )
  13. end
  14. end

This addition shoud be clear — we simply call composer.removeScene( "game" ) within the "did" phase of scene:hide(), effectively destroying the scene after it transitions fully off screen.

Time to test our changes! Save your modified game.lua file and then relaunch the Simulator. Now you should be able to play the game repeatedly and get a clean reset each time.

This chapter was a bit more detailed and required many things to be copied over to very specific places within game.lua. If your code isn’t working as expected, please compare it to the game.lua file bundled with this chapter’s source files.

Chapter 5 — 转换为 Composer - 图1

Extra Credit

Earlier in this chapter, you learned about scene transition effects. We already applied one to the composer.gotoScene() command within the endGame() function, so now let’s apply one when the game scene comes into view.

If you recall, the game scene is only accessed via the Play button in the menu scene, so we’ll modify that file:

-

Open the menu.lua file within your StarExplorer folder.

-

Find the gotoGame() function and add a "crossFade" effect with a duration of 800 milliseconds to the composer.gotoScene() command:

  1. local function gotoGame()
  2. composer.gotoScene( "game", { time=800, effect="crossFade" } )
  3. end
  • Although we haven’t yet created it, the highscores.lua scene should also appear with a transition effect. Since menu.lua is already open, modify the composer.gotoScene() command within the gotoHighScores() function immediately following gotoGame(): ``` local function gotoGame() composer.gotoScene( “game”, { time=800, effect=”crossFade” } ) end

local function gotoHighScores() composer.gotoScene( “highscores”, { time=800, effect=”crossFade” } ) end ```

  • Save your modified menu.lua file.

Great! Now the game scene — and the high scores scene, once we create it — will transition in/out with a nice cross-fade effect.

Chapter Concepts

Here’s a summary of the concepts we covered in this chapter:

Command/Property Description
physics.pause() Pauses the physics engine.
object:insert() Inserts an object into a group.
timer.cancel() Cancels a timer operation initiated with timer.performWithDelay().
object:removeEventListener() Removes an event listener from an object.
composer.removeScene() Removes a specific Composer scene.

#

⟨Chapter 4 — Creating Scenes|Chapter 6 — Implementing High Scores⟩

© 2020-2026 Solar2D. All Rights Reserved.

Help us help you! If you notice a problem with this page, please report it.

Report an Issue