Corona Reference: Composer

The simplest way to create an app with multiple scenes (which would include nearly all apps) is to create a separate lua file for each scene, and use the Composer API to switch from one scene to another. This requires some rearrangement of our code, but single page apps can be quickly adapted to a multi-scene app.

Note: Your app project folder will now contain multiple lua files, but you still need one called "main.lua". This file may not contain a lot of text, but it will minimally need a function that tells Composer which scene you'd like to start with when your app is opened. You can include some universal options and global variables in the main.lua file that will be inherited by the rest of your scenes.

Another note: there are older, deprecated libraries available for scene changing with Corona. One of them was included with Corona, and was called Storyboard. This is no longer supported or under development (Composer replaced it in January 2014) but will still work if you'd prefer to use it or have a sample app that uses it. Another was created by a third party, and was called Director. This was the default scene changing class for many years until Corona released their own. However, it is not compatible with the newer graphics library, and it is recommended that you avoid it.

Composer Template

Any scene that will utilize composer (which will be all of them!) will need to include the following two lines at the top of the lua file:
local composer = require( "composer" )
local scene = composer.newScene()
This will call the Composer library (just as we had to call the physics library for any scene using physics) and create a variable called "scene" that we'll be referencing throughout the file. Since it's a local variable, we can use the same name for every scene.

Following these two lines, we'll list any local variables that we'll be using in the scene, being sure to declare them as local. This is particularly important now - if we have two scenes that both include a circle called "myCircle" which is not specified to be local, the functions from one scene could refer to the object within the other scene. We do not need to define the variables yet, just list them as forward references.

You can also create some functions here that can be called by the scene event handlers we'll start creating next. These event handlers are triggered by listeners included at the bottom of the scene (more on that below). Each of these handlers tells Corona what should be done at various stages of the scene being opened, viewed, closed, removed, etc. A full Composer scene template is below:
local composer = require( "composer" )
local scene = composer.newScene()

-- name all local variables here
-- defined local (scene) functions here

function scene:create( event )
   local sceneGroup = self.view
   -- your code here; define display objects, sprites, physics bodies, etc - but don't play any sounds or animations yet.
end

function scene:show( event )
   local sceneGroup = self.view
   local phase = event.phase
   
   if ( phase == "will" ) then
      -- any code placed here will run when the scene is still "off-screen", but about to be displayed to the user. In many cases, this will be empty.
   elseif ( phase == "did" ) then
      -- any code placed here will run as soon as the scene is displayed on screen. This is where you would start any animations, start playing background audio, start timers, etc.
   end
end

function scene:hide( event )
   local sceneGroup = self.view
   local phase = event.phase
   
   if ( phase == "will" ) then
      -- any code placed here will run when the scene is still on screen, but is about to go off screen. This is where you would stop timers, audio, and animations that you created in the show event.
   elseif ( phase == "did" ) then
      -- any code placed here will run as soon as the scene is no longer visible. In many cases, this will be empty.
   end
end

function scene:destroy( event )
   local sceneGroup = self.view
   -- any code placed here will run as the scene is being removed. Remove display objects, set variables to nil, etc.
end

scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

return scene
The purpose of the handlers is explained next.

Composer Events

create

After calling the Composer library and declaring your variables, you'll include a function that will look like this:
function scene:create( event )
   local sceneGroup = self.view
   -- your code here; define display objects, sprites, physics bodies, etc - but don't play any sounds or animations yet.
   -- add all display objects to sceneGroup 
end
Any code included in this function will be run as soon as the scene is created. This is a good place to add your display objects and add any touch event listeners on those objects, as well as defining sprites and sounds (but not playing them yet). The event handlers for these events can go within the create function. You could also open any text or database files you're planning to use within the scene here.

All display objects must be added to sceneGroup. A possible create scene could be
function scene:create( event )
   local sceneGroup = self.view
   
   myRectangle = display.newRect( 200, 300, 50, 60 )
   sceneGroup:insert( myRectangle )
end
We didn't declare myRectangle to be local - this is because we've already done that before the handler. Each time you create a new variable, be sure to go back and add it to your list of variables near the top of your file.

The create handler is only called with the scene did not previously exist. This will, of course, be the case the first time the user opens your app and gets to that scene. It will also happen if the user returns to the scene, and it had previously been deleted (not just removed). If the scene is not deleted, then Corona will keep it in memory (as long as there's memory available). This means if the user revisits the scene, the create scene function will NOT be called, because the scene was already created and in memory.

show

The next scene handler you can include will look like this:
function scene:show( event )
   local sceneGroup = self.view
   local phase = event.phase
   
   if ( phase == "will" ) then
      -- any code placed here will run when the scene is still "off-screen", but about to be displayed to the user. In many cases, this will be empty.
   elseif ( phase == "did" ) then
      -- any code placed here will run as soon as the scene is displayed on screen. This is where you would start any animations, start playing background audio, start timers, etc.
   end
end
Note that there are two phases to this scene, one when the scene has not yet been displayed, and one that is triggered as soon as the scene is visible. Neither of these will typically include creating any display objects (though they could). Instead, you'll generally use the "did" phase to start any action in your scene, such as sounds and sprites (that have already been created in the create event above), and begin timers or Runtime events.

If you're using any native objects (we have not seen these in class yet), you'll need to create them within the "did" phase of the handler, and NOT in the create handler. This is because native objects (like keyboards) cannot be added to the scene group, so they would not remain in the scene when it was loaded again.

hide

The next scene handler you can include will look like this:
function scene:hide( event )
   local sceneGroup = self.view
   local phase = event.phase
   
   if ( phase == "will" ) then
      -- any code placed here will run when the scene is still on screen, but is about to go off screen. This is where you would stop timers, audio, and animations that you created in the show event.
   elseif ( phase == "did" ) then
      -- any code placed here will run as soon as the scene is no longer visible. In many cases, this will be empty.
   end
end
This event can be thought of as the opposite of the show event - it's where you'll stop any timers, animations, sounds, etc. It's also where you'd remove native objects, if you have any.

destroy

Finally, you'll create an event that runs when you're done with the scene:
function scene:destroy( event )
   local sceneGroup = self.view
   -- any code placed here will run as the scene is being removed. Remove display objects, set variables to nil, etc.
end
This event can be thought of as the opposite of the create event - it's where you'll remove any display objects, sounds, and other objects, as well as closing any database or text files you've opened.

To speed up performance when changing scenes, Corona will attempt to save scenes, even when the user has changed to another one. This means that the destroy function will only be called if you've called the removeScene function (more on this later), or if memory is getting low. If a scene would not be revisited any time soon (such as a Level 1 scene once the user made it to Level 2), you may want to force removal to free up the memory used by the scene.

The Listeners

After creating the scene event handlers above, you'll need to listen for the scene phase you're currently in and call the appropriate handler. The last few lines of your scene file will look like this:
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

return scene

Changing Scenes

Suppose that your project folder contains three files: "main.lua" (required!), "scene1.lua", and "scene2.lua". All of them are structured similarly to the Composer template above. The first file that will be opened will always be the main.lua file, but we need a way to get to scene1. To do so, we'll use the gotoScene function of the Composer API. This function can be quite simple and take only a single argument - the name of the scene we'd like to go to. This will be passed as a string equal to the name of the lua file containing the scene, but without the .lua extension. For instance, our main.lua file might contain the following:
composer.gotoScene( "scene1" )
The gotoScene function could appear in an event listener, a physics collision handler, in a function called by a timer, or just about anywhere you need it. For instance, you might have a button that says Settings, with a tap event listener. When tapped, the event handler will use a gotoScene function to switch to a Settings menu scene.

Options

There are two optional arguments for the gotoScene function. One is the transition effect used to switch scenes, and the other is the amount of time it should take to change from one scene to the next. If neither option is specified, then a scene change will be instantaneous and without any special effects.

The full list of transition effects is below. For explanation purposes, I'll assume we're changing from scene1 to scene2:
  • "fade" - scene1 will fade to alpha 0, and when it is done, scene2 will fade in to replace it. There is a moment in the middle of the transition when the display is blank.
  • "crossFade" - scene1 will fade to alpha 0, and scene2 will fade in at the same time. There is no moment when the display is empty.
  • "zoomOutIn" - scene1 will shrink to a singularity, and scene2 will expand from a point to replace it.
  • "zoomOutInFade" - scene1 will shrink to a singularity while fading, and scene2 will expand from a point to replace it while fading.
  • "zoomInOut" - scene1 will expand and go off screen, scene2 will appear large and shrink to fit the screen.
  • "zoomInOutFade" - scene1 will expand and go off screen while fading, scene2 will appear large and shrink to fit the screen while fading.
  • "flip" - scene1 will contract to a vertical line, scene2 will expand from the same line to fill the screen. The overall effect is that scene2 is one side of a piece of paper, scene2 is printed on the back, and you're spinning the paper around a vertical axis to reveal the other side.
  • "flipFadeOutIn" - similar to flip, with fading effects.
  • "zoomOutInRotate" - scene1 will shrink to a point while rotating, scene2 expands from a point while rotating to replace scene1.
  • "zoomOutInFadeRotate" - similar to zoomOutInRotate, but with fading effects.
  • "zoomInOutRotate" - scene1 will expand while rotating, scene2 shrinks while rotating to replace scene1.
  • "zoomInOutFadeRotate" - similar to zoomInOutRotate, but with fading effects.
  • "fromRight" - scene2 slides over scene1 from the right (scene1 doesn't appear to move)
  • "fromLeft" - scene2 slides over scene1 from the left (scene1 doesn't appear to move)
  • "fromTop" - scene2 slides over scene1 from above (scene1 doesn't appear to move)
  • "fromBottom" - scene2 slides over scene1 from below (scene1 doesn't appear to move)
  • "slideRight" - scene2 slides onto screen from the right while pushing scene1 to the left.
  • "slideLeft" - scene2 slides onto screen from the left while pushing scene1 to the right.
  • "slideDown" - scene2 slides onto screen from above while pushing scene1 down.
  • "slideUp" - scene2 slides onto screen from below while pushing scene1 towards the top.
To use an effect, include a table within the gotoScene function with your preferred options:
composer.gotoScene( "scene2", { effect = "flip", time = 500 })
As usual, time is in milliseconds.

Passing Values Between Scenes

You'll typically need some values created in one scene to be present in the next. If a player reaches level 2, you'll want to keep the score they achieved in level 1, for instance. This can be done with external text or database files, but this means opening and closing the file within each scene. However, if you have a lot of data to pass from one scene to another, this is probably what you'll want to do. In particular, it prevents this data from being lost when the app is closed.

If you only have a few pieces of information to transfer from one scene to the next, you can include a list of parameters within the gotoScene function. This is done by including a table within the options table:
composer.gotoScene( "scene2", { effect = "flip", time = 500, params = { score = 45, health = 8, level = 3 })

Another solution is to use global variables.

Removing Scenes

Corona will store anything that appears in the create scene handler until memory gets low. However, it doesn't always make sense to do this. If you're leaving a scene that you don't expect to return to in the near future, you can help free up memory by removing the scene. This would typically be done after you've gone to the next scene. So if you start in Scene 1, then switch to Scene 2, the code for Scene 2 might include a remove function that destroys everything in Scene 1. This certainly doesn't mean that you can't return to Scene 1 again. It just means that when you do, the create function of Scene 1 will be run again, and all display objects will be recreated. This may cause a momentary delay when returning to Scene 1, but the overall performance of the app will be improved with consistent "clean-up".

In theory, this is not a necessary step. Corona is supposed to handle memory issues for you, and scenes will be deleted as necessary.

Removing a scene requires the removeScene function, which calls the destroy event handler and takes up to two arguments. The first specifies the name of the scene you'd like to remove. Just as in changing scenes, this is a string equal to the name of the lua file containing the scene, without the .lua extension. The second argument is optional, and decides just how much will be removed. If the second argument does not exist, or is set to false, then all display objects in the scene will be removed, and the scene group itself will also be removed. This is the "deepest" removal option.
composer.removeScene( "scene1" )	-- scene is destroyed; objects and group removed
If you pass a second argument of true, then the scene will be "recycled". This means display objects are destroyed, but the scene group itself is preserved in memory:
composer.removeScene( "scene1", true )	-- scene is recycled; objects removed but group remains

Global Variables

Lua has a global table (called "_G") to which you can add a global variable by adding the prefix "_G." to the variable name. These are available to any scene in your app, which makes them a convenient option for preserving data. For instance, you could define
_G.score = 100
Then in another scene of your app, you could have a line such as
_G.score = _G.score + 500
Finally, in a third scene, if you run
print( _G.score )
you'll see 600 in the terminal. The value of the variable is maintained throughout the scenes.

However, it's also very easy to run into problems, such as interfering with Lua's build in values. It is highly recommended that you avoid defining global variables using Lua's _G table.

Instead, you can create your own 'global table'. Create a lua file that contains nothing but this table, some contents of the table, and the command 'return'. For instance, the following might be the contents of a file called "myGlobal.lua":
local MG = { }

return MG
In any scene you need to access or modify the data in your table, include the line
local myGlobals = require( "myGlobal" )
Then, add a value to your table:
myGlobals.score = 100
In the next scene (after you've required the "myGlobal" file), you can refer to "myGlobals.score", which will have a value of 100 until you change it.

For a more complicated example, we can setup a lua file that will contain our preferred style for the entire app, such as colors, fonts, and any other universal settings we want to apply. We'll call the file "settings.lua", which will look like this:
local set = {
	["bgColor"] = { 0.2, 0.3, 0.4, 1 },
	["font"] = "Arial",
	["textSize"] = 20,
	["textColor"] = { 0.8, 0.9, 1, 1 }
}

return set
Now, a scene in our app might include
local settings = require( "settings" )

local sampleText = display.newText( "Hello World", 100, 200, settings.font, settings.textSize )
	sampleText:setFillColor( settings.textColor[1], settings.textColor[2], settings.textColor[3] )
This makes it much easier to have an app with a uniform appearance from one scene to the next, and also to change the look of the entire app. If you refer only to these global settings in your code, then a simple change to a color in the settings.lua file will cause the color to change in every scene throughout the app, without having to change every individual object.

There is a catch: Every time the app is closed and reopened, anything in these 'global' files will be reset. That means if you're saving a player's current score in a global table, it will be lost when the app is restarted. Global tables and variables should only be used for data that is only needed for a single session.