Composer is Corona’s official scene manager. What is a scene manager? Consider the iOS “Clock” app. There’s a “tabBar” at the bottom which lets you switch between the World Clock, Alarms, Stopwatch and Timer. Each of these utilities could be considered a “scene” in Composer terms. Essentially, Composer allows you to write the code for scenes as separate
.lua files, one for each scene. Then it gives you the API calls to switch scenes when necessary.
If you’re new to Composer, you should first read the Introducing the Composer API tutorial. Once you understand the core functionality, you should create a foundation for each scene by the copying the scene template found here. This template is the minimal code which structures any Composer-based app, so you can create scene files from this template. In the “Clock” app example, you would end up with four files:
Be sure to avoid using file names that are the same as built-in Corona libraries. For example, you shouldn’t name a scene
physics.lua. In the above example, your
main.lua file would have a widget.newTabBar() and it would contain the code to call the various composer.gotoScene() calls to pick which scene to load.
For games, you might have a scene structure like this:
menu.lua— main menu.
gamecredits.lua— show who contributed to the app.
gamehelp.lua— show users the help information.
gamesettings.lua— a screen to change settings like sound and music on/off.
game.lua— the actual game code.
gameover.lua— a scene to end the game, show high scores, etc.
levelover.lua— an intermediate screen to show between levels.
In a case like this, you would probably initialize a bunch of stuff in
main.lua like key event handlers, system handlers, in-app purchases, ad services, game networks, etc. Then, you’d need just two lines of code to start using Composer and start the app with the menu scene:
local composer = require( "composer" ) composer.gotoScene( "menu" )
Once in the menu scene, you might want to draw a background and create buttons for Play, Help, Settings, Credits, and others if needed. Each button’s touch handler would then call composer.gotoScene() to the scene associated with that button. In other words, for the Help button:
composer.gotoScene( "gamehelp" )
In regards to scenes, each one begins with two essential lines: the Lua
require() for the Composer module, followed by a call to create a new
scene object. The scene object is what Corona uses to drive the Composer engine.
local composer = require( "composer" ) local scene = composer.newScene()
This code creates the actual scene object. It’s this scene object which gets returned at the end of the module. The scene is made up of three essential parts:
- The object itself which holds the state of the scene and information about it.
- A reference to its view which is merely a Corona display group. It holds the various display objects that make up the scene including images, widgets, other groups, etc.
- The final
return sceneline (this is required).
In between, you must write all of your code to make the scene work properly. This is where things can get a little confusing for people new to Composer, because it has multiple “phases” which represent the current state of the scene. Essentially. the scene can have the following phases (states):
- Not loaded — The scene file has not been
- Not created — The scene file has been
require()‘d, but the view has not been created and no display objects have been inserted into it.
- Created but not on screen — The scene isn’t yet visible but it’s ready to be shown on screen.
- Fully on screen and ready to start showing animations, using physics, etc.
- A new scene is ready to be shown and the current one is ready to be hidden.
- The scene is now fully hidden.
- The scene’s view is about to be removed.
- The scene is completely un-
Composer manages most of these states through events. There are four events which you can check for and then execute specific code:
scene:create()— Composer recognizes that you’re trying to show a scene, but its view does not exist.
scene:show()— This event is triggered twice: once when the scene is getting ready to transition/show on the screen (
"will"phase) and again when it’s fully on screen (
scene:hide()— This event is also triggered twice: once when the scene is getting ready to leave the screen (
"will"phase) and again when the scene is completely off screen (
scene:destroy()— The scene’s view is about to be destroyed.
Within the scene file, each event contains a unique function to handle it:
function scene:create( event ) local sceneGroup = self.view end function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then elseif ( phase == "did" ) then end end function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then elseif ( phase == "did" ) then end end function scene:destroy( event ) local sceneGroup = self.view end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene )
It’s your job to include the proper code in the proper place within these four functions, but if you use the
scenetemplate.lua file, the foundation is already set up for you.
What should go where?
Let’s start with the
scene:create() function. The only time this function is guaranteed to execute is if the scene has never been loaded before or if the scene’s view has been destroyed and you attempt to show it again via composer.gotoScene().
This function is where you should create display objects like the background, buttons and other UI elements, game objects, etc. It’s acceptable to add physics bodies to the objects here, and you can load audio within this function if it’s specific to your scene — just remember to dispose of the audio files in the
scene:destroy() function. For things that need touch/tap handlers, you can include them within this function and attach them to the appropriate objects.
It’s also very important that you insert all display objects into the scene’s view. If you don’t, Composer does not recognize them as part of the scene and it can’t manage them properly. So, if you want to create a background image for a scene, it might look like this:
local background = display.newImage( "background.png" ) -- IMPORTANT! sceneGroup:insert( background )
If you’re using physics, you must call physics.start() before you can add any physics bodies to objects, but since you probably don’t want objects to begin moving or interacting with each other at this point, it’s a good idea to call physics.pause() immediately and then add the necessary bodies.
As noted above, this function will fire twice — once before the scene shows on screen and again after the scene completes its transition on screen. These two events are distinguished by the
event.phase parameter with a value of either
"did". It’s important that you conditionally test for this phase to prevent code from inadvertently running twice.
"will" phase is a great time to position the elements that you created in the
scene:create() function. Why is this? If you are re-entering the scene, for example restarting the level, the
scene:create() function won’t execute again if the scene’s view still exists. Thus, if you position the scene’s display objects within this function, they will always reset to the intended location each time the scene is shown.
"did" phase executes when the scene is fully on the screen. Within that conditional phase check, it’s a good idea to start timers, transitions, sprite animations, and to resume paused physics by calling physics.start(). If you’re using scene-specific audio, this is a good time to play it. Finally, this is the correct time to start any
"Runtime" event listeners like
The scene:hide() and
scene:show() functions operate in tandem. Anything that you did in the
"did" phase should be “undone” in the
"will" phase. For example, you should remove native UI objects, remove
"Runtime" listeners, pause physics, stop audio, etc.
There usually isn’t much to worry about in the
"did" phase, although you can force removal of the scene after it transitions off screen.
This function only fires when the scene’s view is about to be removed. This could happen due to a low memory alert by the device or if you told Composer to explicitly remove the scene via composer.removeScene() or composer.removeHidden(). Note that when scenes are hidden, Composer cleans up all display objects inside its view (
If you loaded audio in
scene:create(), you should dispose of it here. Essentially, the
scene:create() functions also work in tandem, although it’s possible that
scene:destroy() will never be called, for example, if the scene never gets removed via code or a low memory event.
Hopefully this tutorial helps you better understand what Composer is, what the various event phases are, and what actions should be performed during each phase. There are, of course, more advanced scene management methods and attributes that can be set using Composer, but they are beyond the scope of this tutorial. If you wish to learn more about them, please consult the resources below.