by J. A. Whye
Digital Ringmaster
http://ThreeRingRanch.com
Copyright 2020 by Three Ring Ranch • All rights reserved.
Quick but IMPORTANT note! Solar2D used to be called Corona SDK so if you see mention of Corona in sample code or screen shots, just know that’s the reason and you’re still in the right place. 😉
You can read this entire book online, but if you’d like a copy of your own, fee free to grab a copy from Amazon just to say thanks. 😉
(The book still says Corona SDK, but only until I can search-replace the name Solar2D — all the content is still the same!)
Foreword
I hate “how to program” books that list page after page of solid code — it’s a waste of space because nobody’s going to type in a huge listing. But a programming book really does need to show source code, so I’ve kind of split it down the middle…
…I’ve included just the pertinent chunks of code in the instructions and also am providing a sample project with which you can start.
The sample code is available here:
http://ThreeRingRanch.com/files/ComposerSceneManagement.zip
The Videos
This course on using Composer can be used all by itself, but it works best when used in conjunction with the video tutorials I sell here:
https://gamedev.teachable.com/p/using-solar2d-composer/
The same material is covered in this ebook and the videos — but with the videos you get to see exactly what I’m doing, every step of the way. For people who learn better by watching, I recommend going that way. (There is a small fee for the video course.)
Code Clarification
In order to make long lines more readable, I typically break them up in the code chunks you’ll see below. For example, instead of this:
1 |
[crayon-67532b6151c2c524457547 ]local title = display.newText(grp, "<NameOfScene>", display.contentCenterX, display.contentCenterY, "Helvetica", 48) |
[/crayon]
…you’ll see this:
1 2 3 4 5 6 7 8 |
[crayon-67532b6151c2f250829924 ]local title = display.newText( grp, "<NameOfScene>", display.contentCenterX, display.contentCenterY, "Helvetica", 48 ) |
[/crayon]
It’s the same thing since Lua doesn’t really care about whitespace, and while the second example takes more vertical space on the screen, it’s much easier to read — and to tweak values later while you’re testing.
Now, let’s dive into Solar2D’s Composer library…
What Is Composer?
Composer is a part of the Solar2D framework that’s not technically required in order to make games and apps, but it sure makes dealing with certain aspects of development less prone to causing insanity. Specifically, scene management.
What’s scene management?
In a typical game you’ll have a main menu, a credits or about screen, a way to choose levels, the actual playing screen, etc. Each one of those screens, or scenes, can be more easily managed with Composer.
Want to slide the options in from the left?
Composer.
Want to zoom the main menu out while zooming the level selector in?
Composer.
Every scene in your game or app is actually a separate Lua file.
That’s the good news.
The bad news is that at first glance (and possibly even second and third glance) you might think Composer is really hard to understand. And I get that — it’s what I thought the first time I saw Composer.
The way to use Composer is by requiring the Composer library in your code and then creating a scene, like this:
1 2 |
[crayon-67532b6151c31434698942 ]local composer = require( "composer" ) local scene = composer.newScene() |
[/crayon]
Now you can do things with that scene object, and that seems really straightforward, but the following is where people get tripped up… with the Composer scene template file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
[crayon-67532b6151c33427667124 ]local composer = require("composer") local scene = composer.newScene() ---- -- All code outside of the listener functions will only be executed -- ONCE unless "composer.removeScene()" is called. ---- -- local forward references should go here ---- -- "scene:create()" function scene:create(event) local sceneGroup = self.view -- Initialize the scene here. -- Example: add display objects to "sceneGroup", add touch listeners, etc. end -- "scene:show()" function scene:show(event) local sceneGroup = self.view local phase = event.phase if (phase == "will") then -- Called when the scene is still off screen but about to come on screen elseif (phase == "did") then -- Called when the scene is now on screen. -- Insert code here to make the scene come alive. -- Example: start timers, begin animation, play audio, etc. end end -- "scene:hide()" function scene:hide(event) local sceneGroup = self.view local phase = event.phase if (phase == "will") then -- Called when the scene is on screen (but is about to go off screen). -- Insert code here to "pause" the scene. -- Example: stop timers, stop animation, stop audio, etc. elseif (phase == "did") then -- Called immediately after scene goes off screen. end end -- "scene:destroy()" function scene:destroy(event) local sceneGroup = self.view -- Called prior to the removal of scene's view ("sceneGroup"). -- Insert code here to clean up the scene. -- Example: remove display objects, save state, etc. end ---- -- Listener setup scene:addEventListener("create", scene) scene:addEventListener("show", scene) scene:addEventListener("hide", scene) scene:addEventListener("destroy", scene) ---- return scene |
[/crayon]
That’s a lot of code to mess with before you even start putting your actual game or app code in. And while you don’t have to use Composer to make your different screens and move between them, it’s a part of the SDK that actually makes game and app development a lot easier.
In the next section we’ll take a look at the different Composer scene template sections and understand what they’re used for.
Understanding the Scene Template
While the Composer scene template file can be intimidating at first glance, it’s really pretty neat. There are three main sections:
- The beginning where you’d import libraries with require() statements, create local variables, and put in the code functions you write. Here’s a chunk from the beginning of a typical scene:
1 2 3 4 5 6 7 8 9 |
[crayon-67532b6151c42071505630 ]local composer = require( "composer" ) local scene = composer.newScene() local aliens = {} -- some variables for my game local score = 0 local function addToScore(num) -- a function I wrote for my game score = score + num end |
[/crayon]
- Next are special scene-based functions that are called when certain events happen. Here’s what the create function looks like before we put any of our code into it:
1 2 3 4 |
[crayon-67532b6151c45262262974 ]function scene:create( event ) local sceneGroup = self.view -- more code goes down here... end |
[/crayon]
- Finally, there are event listeners that wait for specific events to be triggered and when that happens they call the correct listener function. Here are the event listeners in a typical scene file:
1 2 3 4 |
[crayon-67532b6151c47608696375 ]scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) |
[/crayon]
You don’t have to worry about actually triggering those event listeners — Solar2D automatically handles that as part of the Composer library.
When you break it down like that it’s not too bad — it mostly makes sense. Your local variables go at the top, all the different functions your code will call go below that, and so on. What needs a little more explanation are the scene-based functions:
- scene:create()
- scene:show()
- scene:hide()
- scene:destroy()
Let’s take a quick look at those to see what they’re used for.
The Composer Scene Functions
scene:create()
First is the scene:create() function and that’s the first function called when a scene object is created. That’s the place in the file where you actually create the display objects that will be shown in the scene. There are other places you can do that, but this is the most logical place and there are reasons (that we won’t get into now) for why this is the best location for creating at least most of the display objects. This is also a great place to preload sound files.
After the scene:create() function is finished the display objects will have been created but nothing will be seen because the scene’s view is hidden by default. Because it’s hidden you don’t want to start any timers or physics-based activity at this time.
scene:show()
Next is the scene:show() function and this one’s a little more complicated than the last one. Simply because this function is triggered twice when you enter the scene. One of the properties of the event table that’s passed in to scene:show() is called phase. When event.phase == “will” then the scene has been completely created and is just about to be shown. When event.phase == “did” that means the once-hidden scene has been moved into place and is now visible. The base code inside scene:show() will look like this:
1 2 3 4 5 6 7 |
[crayon-67532b6151c4e342668828 ]local phase = event.phase if (phase == "will") then -- Called when the scene is off screen but about to come on elseif (phase == "did") then -- Called when the scene is now on screen. -- Add code here to start things. Ex: timers, animations, audio, etc. end |
[/crayon]
It’s not required that you put code in both spots. In the “will” location you may want to reposition display objects when you’re restarting a level, but that would only happen if you’re reloading the entire scene to restart a level and there are better ways to handle that scenario. In many cases you’ll leave that blank and only worry about the “did” phase.
scene:hide()
Next we have the scene:hide() function and just like the last one it’s split into two phases: “will” when the scene is about to go off screen and “did” right after the scene is hidden from view. In the “will” phase you’ll do things like pause physics, cancel any timers you may have started, cancel transitions that have not completed, etc. Also, if you have audio that was started in the scene:show() function, this is the place to stop the audio playing.
scene:destroy()
Finally, there’s the scene:destroy() function that’s called right before Composer cleans up the display objects in the scene. Here you can take care of things that were initiated in the scene:create() function, such as closing a database that was opened. If you loaded scene-specific audio files in the scene:create() function, this destroy() function is a great place to dispose of that audio.
Composer Scene Template
I’m going to close out this section with a copy of the Composer scene template. In the following sections when I say to copy and paste the scene template code, I’m talking about what you see below.
(You may want to copy the code below and paste it into a file called composer-master.lua — then you can just duplicate that file whenever you need a new scene file.)
In the next section we’ll take a look at how to use this template to actually create a Composer-based app.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
[crayon-67532b6151c56379798075 ]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()" -- ----------------------------------------------------------------------------------- -- ----------------------------------------------------------------------------------- -- 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 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 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) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene |
[/crayon]
Setting Up Composer
The simplest Composer-based app consists of two files: main.lua and a file consisting of a Composer scene template. Using the template code from the last section, create an empty file called menu.lua and paste that code into it. Save it to your project folder.
Then create a file called main.lua and open that for editing.
First thing we’re going to do is pull the Composer library into our project with this line:
1 |
[crayon-67532b6151c59678081733 ]local composer = require("composer") |
[/crayon]
If you look at the top of the Composer scene file you’ll see that same line, and right afterwards we create a scene object. We don’t have to do that in this case — there’s no need for a scene object in the main.lua file — all we need is Composer active and we now have that.
The next line in main.lua is going to be:
1 |
[crayon-67532b6151c62719490362 ]composer.gotoScene("menu") |
[/crayon]
We’re accessing the gotoScene() function of the Composer library and passing in the name of the scene to which we want to go. The parameter we’re passing in is the name of the file you created a few minutes ago, minus the .lua suffix.
That’s about all we need in main.lua for this example (although as seen in the screen shot I also hid the status bar).
Save main.lua and launch this little two-file project in the Solar2D Simulator. You should see a black screen. If you see any errors, look at your code again to see if you accidentally included a typo.
It doesn’t look like much (“Look Ma, a black screen!” “That’s nice, honey. sigh“) but behind the scenes Composer is doing just what it’s designed to do — it’s switching to a new scene and displaying it. The scene just happens to have no display objects to show. D’oh!
Back to the Menu Scene File
Just to make sure it’s really happening, let’s add some code to that menu.lua scene file. Open it in the editor and scroll down to the scene:show() function. Inside the “will” phase block, add this line of code:
1 |
[crayon-67532b6151c65204195862 ]print("menu scene:show will") |
[/crayon]
And right under that in the “did” phase block, add this line:
1 |
[crayon-67532b6151c67706502584 ]print("menu scene:show did") |
[/crayon]
Save the file and run the project again. In the terminal window you should see the two print statements show up.
Proof!
Creating the Play Scene
Now create a new file called play.lua and paste the Composer scene template code in that one, too. Up toward the top, right after the two lines that create the composer and scene objects, add these two lines of code:
1 2 |
[crayon-67532b6151c69262819932 ]local widget = require("widget") widget.setTheme ( "widget\_theme\_ios" ) |
[/crayon]
We’re going to need some simple buttons to switch from one scene to another and we may as well go the easy route and use the widget buttons.
In the scene:create() function insert this code (make sure you put it after the line that’s already there where sceneGroup is being declared):
1 2 3 4 5 6 7 8 |
[crayon-67532b6151c6f569301042 ]local bg = display.newRect( display.contentCenterX, display.contentCenterY, display.viewableContentWidth, display.viewableContentHeight ) bg:setFillColor(0.57, 0.16, 1) sceneGroup:insert(bg) |
[/crayon]
That display.newRect line (which is actually multiple lines on the screen, but just one statement) creates a rectangle the size of the screen and centers it. The next line changes the color of that rectangle to a deep purple, and the final line inserts the object into the scene’s display group.
Note! For the Composer library to handle your graphics correctly, every display object must be inserted into the current scene’s view. You can see at the beginning of each of the scene functions a variable named sceneGroup is created:
1 |
[crayon-67532b6151c72791725945 ]local sceneGroup = self.view |
[/crayon]
Now inside those functions you can insert display objects into the sceneGroup display group. If you create display objects outside of the scene functions (and you probably will) remember to pass that sceneGroup parameter to your function, or just use self.view as the group to insert into (that’s the easiest way).
Many of the beginner mistakes I’ve seen when using Composer are related to this — if you neglect to insert the display objects into the scene display group, weird things will happen. Most commonly you’ll switch scenes and some of the display objects in the previous scene won’t go away.
Under the previous lines of code, add the following:
1 2 3 4 5 6 |
[crayon-67532b6151c74372252718 ]local backBtn = widget.newButton ({label="Back", id="menu", onRelease=goSomewhere}) backBtn.anchorX = 0 backBtn.anchorY = 1 backBtn.x = screenLeft + 10 backBtn.y = screenBottom - 10 sceneGroup:insert(backBtn) |
[/crayon]
Nothing new and exciting here, but notice I’m setting the anchorX and Y to the lower-left corner of the button. That makes it easier to space it 10 pixels from the side and bottom.
Also, you’ll notice the button has an ID set to “menu” and it needs to call the goSomewhere() function when it’s pressed and released, but that function doesn’t exist yet. I wanted to leave it for last because it includes new code directly related to Composer, so let’s look at that now.
Go back up in the code right before the scene:create() function and add this function:
1 2 3 4 5 |
[crayon-67532b6151c76316524429 ]local function goSomewhere(event) local goto = event.target.id local options = {effect="crossFade", time=300} composer.gotoScene( goto, options ) end |
[/crayon]
When that function is called it’s passed in an event table that contains a .target property. The target equals the object that was tapped (that’s normal for pretty much all event listeners), and since we created an ID property in the button we can grab that value and put it in the goto variable. We know it’s going to be the word “menu” which is the name of the other Composer scene. That value could have been hard-coded, but by passing values to functions you’re able to make code that can more easily be reused. (In fact, you’re going to be able to reuse that exact function in all of your Composer scene files.)
After we grab the destination we set up a Lua table containing options for the gotoScene command we’re getting ready to execute. The effect property is what kind of transition we want between one scene and the next, and the time property is how long the transition will take, in milliseconds. The following transition effects are available for use with Composer:
- “fade”
- “crossFade”
- “zoomOutIn”
- “zoomOutInFade”
- “zoomInOut”
- “zoomInOutFade”
- “flip”
- “flipFadeOutIn”
- “zoomOutInRotate”
- “zoomOutInFadeRotate”
- “zoomInOutRotate”
- “zoomInOutFadeRotate”
- “fromRight” — over current scene
- “fromLeft” — over current scene
- “fromTop” — over current scene
- “fromBottom” — over current scene
- “slideLeft” — pushes current scene off
- “slideRight” — pushes current scene off
- “slideDown” — pushes current scene off
- “slideUp” — pushes current scene off
In the example code above I’m using crossFade but after you’ve seen how that works, try plugging in some of the other values to see how they look. You can also play around with the time value to see how longer or shorter transitions affect the look and feel of your app.
Adding the Code to the Menu Scene
Go ahead and save the play.lua file and open menu.lua for editing. Copy the goSomewhere() function and the code that creates the background rectangle into the menu.lua file (making sure to put them into their correct spots).
In the code that makes the background rectangle, change the color of the background in menu.lua so it will be easy to see when we’ve switched scenes:
1 |
[crayon-67532b6151c7c026025886 ]bg:setFillColor(1, 0.38, 0.13) |
[/crayon]
Inside the scene:create() function instead of a Back button we’re going to add a button that will take us to the play scene:
1 2 3 4 5 6 7 8 |
[crayon-67532b6151c7f096765019 ]local playBtn = widget.newButton ({ label="Play Game", id="play", onRelease=goSomewhere }) playBtn.x = centerX playBtn.y = centerY sceneGroup:insert(playBtn) |
[/crayon]
That will place the button in the center of the screen. Save the file and run the project. You should now be able to use the Play button to switch scenes and then use the Back button there to switch back.
That’s the foundation of using Composer to manage scenes in a Solar2D game or app.
A Challenge for You
Before going on to the next section, I want to issue a challenge. Take a few minutes and try this:
- Create a new scene file called about.lua (make sure to copy the template code into it).
- Add another button in the Menu scene that will take you to the new About scene.
- Add a button in the About scene that will take you back to the Menu scene.
Now try out the new project and see if it works the way you think it should. To make sure you’re going to the correct scene either change the background color or add print statements in the code that you can see in the terminal window.
And One More for Extra Credit 😉
Grab a picture of a space ship, your Mom, or whatever you have handy, and display it in the play scene. You’ll want to create the object during the create phase of the scene, but there are good reasons to not just throw the display.newImage() code inside scene:create() — instead, make it part of a new function and then call that function from inside scene:create() — either way, play around with it.
Building a Composer Framework
It’s usually easier to create a new game or app if you don’t have to start from scratch each time, so let’s make a Composer-based framework we can use as a starting point whenever we want a new project. The example I’m going to create here will be aimed at a game framework, but if you’re doing business apps the process is the same. Instead of a Choose Level scene, you might have a scene where the user searches for a database record.
So from a game standpoint, what are some typical scenes that most games will have?
- Menu
- About the Game
- Options
- Choose Level
- Play
That’s enough to get us started and as you’ll see, most of the files are almost exactly the same, so making a Tutorial, Feedback, or other kind of scene will be very easy.
Creating the Scene Files
Let’s start by creating the main.lua file — as seen in the last section, that’s only going to consist of a straight jump to the menu scene. You can copy the main.lua file from the last example to use for this new framework.
Create a new file called menu.lua and paste in the Composer scene template code. Add the widget lines at the top so we can use the buttons in our framework:
…and like we did last time, create the goSomewhere() function above the scene:create() function:
1 2 3 4 5 |
[crayon-67532b6151c87421067713 ]local function goSomewhere(event) local goto = event.target.id local options = {effect="crossFade", time=300} composer.gotoScene( goto, options ) end |
[/crayon]
If you’d like a different transition effect than crossFade, go ahead and put that in here since we’re going to use this file as the base for our other files.
Instead of putting the background rectangle inside the scene:create() function we’re going to create a new function for that and other display objects and call that function during the create phase.
Create a new function above scene:create() that looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
[crayon-67532b6151c89008710162 ]local function setUpDisplay(grp) local bg = display.newRect( grp, display.contentCenterX, display.contentCenterY, display.viewableContentWidth, display.viewableContentHeight ) bg:setFillColor(0.57, 0.16, 1) local playBtn = widget.newButton ({ label="Play", id="chooselevel", onRelease=goSomewhere }) playBtn.x = centerX playBtn.y = centerY - 50 grp:insert(playBtn) local aboutBtn = widget.newButton ({ label="About", id="about", onRelease=goSomewhere }) aboutBtn.x = centerX aboutBtn.y = centerY + 100 grp:insert(aboutBtn) end |
[/crayon]
This creates a colored background (go ahead and change that color to whatever you want for your default color) and two buttons — one that will take us to the Choose Level screen and one to our About screen.
Notice that when we create playBtn the label says “Play” but the id=”chooselevel” means it will go to the chooselevel.lua scene and not the play.lua scene. That’s a stylistic choice — if you want the button to say “Choose Level” just change the label. In most level-based games the Play button will go to where you choose a level first, then on to the play scene.
The setUpDisplay() function has one parameter, the scene display group for inserting the display objects that are created. We’ll pass that in from the scene:create() function now. Go there and add this code after the sceneGroup variable is defined:
1 |
[crayon-67532b6151c90304434579 ]setUpDisplay(sceneGroup) |
[/crayon]
We pass in the display group that all display objects need to be inserted into.
That’s all we need to do for this file. Save it and then we’ll use it as the base for the rest of the scene files.
The Rest of the Scene Files
Duplicate that menu.lua file four times and name the new files:
- about.lua
- chooselevel.lua
- options.lua
- play.lua
Open each one for editing and make the following changes…
In the setUpDisplay() function remove the code for the two buttons and add this instead:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[crayon-67532b6151c93464337037 ]local title = display.newText( grp, "<NameOfScene>", display.contentCenterX, display.contentCenterY, "Helvetica", 48 ) title:setFillColor(0, 0, 0) local backBtn = widget.newButton ({ label="Back", id="menu", onRelease=goSomewhere }) backBtn.anchorX = 0 backBtn.anchorY = 1 backBtn.x = display.screenOriginX + 10 backBtn.y = display.screenOriginY - 10 grp:insert(backBtn) |
[/crayon]
This will add a text object so we can see which scene we’re in, and a back button that will return us to the menu scene. Notice in the first line you’ll need to change <NameOfScene> to the actual name of the scene; About, Choose Level, etc. After making the changes save all the files and launch it in the simulator to try out the new framework.
Once you’ve seen that it all works, save the project and use it as the “master” for new projects. You’ll make a copy of the folder and contents, rename it for your new project, and start editing those files. The master files are left alone to be copied for future projects.
One thing to think about is code that you’ll often use — putting that in the framework so it’s available for every new project might be a good idea. That’s what I do by adding the most used screen coordinate variables (centerX, screenLeft, etc.) to the top of the Composer scene template. As you write more and more code you’ll create functions that are generic in nature and are always handy — those are the kind of things to add to your master framework.
Another Extra Credit Section
At this point you can go from the menu to the about scene, the options scene, or the choose level scene and back again. You can’t, though, go from the choose level scene to the play scene since we don’t actually have levels to choose from (something that’s beyond the scope of this lesson).
But since you already know how to add buttons to a scene you could easily add 3 buttons to the chooselevel.lua file that lead to the play scene. How would you know which level was chosen? Since each level scene is going to go to the play scene you could set the id parameter for each button to the level number, such as id=”1″, id=”2″, and so on. You’ll also have to change the goSomewhere() function in that file. It should grab event.target.id as the level the user is wanting to play, and then switch to the play scene.
Play around with that idea and see what you can come up with.
The Composer Overlay
With the Composer scene manager you typically switch from one scene to another. But Composer allows you to have one “overlay” scene at a time that appears above the parent scene, leaving that parent scene active behind it. I’ve found overlay scenes great for pause screens during a game or about screens for business apps.
An overlay scene uses the default Composer scene template code — it’s created the same way — but you call it a little differently. Instead of calling gotoScene() you call showOverlay() and pass the name of the scene to use. When the overlay scene is loaded it acts the same way as a normal scene — you don’t have to learn anything new. Woot! 🙂
Let’s create an overlay scene that can be used as a pause screen in a game (you could also use it as pop-up help or an about box in a business app). Create a file called pause.lua, open it up, and paste in the default Composer scene template code.
Inside the scene:create() function of this new file, add a call to the setUpDisplay() function, passing in the scene group:
1 |
[crayon-67532b6151c9a877083694 ]setUpDisplay(sceneGroup) |
[/crayon]
Now move up above the scene:create() function and create that setUpDisplay() function as we did before (be sure and include grp as the parameter that’s passed into the function) and inside that new function create a background — but this background won’t cover the whole screen:
1 2 3 4 5 6 7 8 |
[crayon-67532b6151c9d027559110 ]local bg = display.newRect( grp, display.contentCenterX, display.contentCenterY, display.viewableContentWidth - 100, display.viewableContentHeight - 100 ) bg:setFillColor ( 0.53, 0.06, 0 ) |
[/crayon]
We’re making a background that is the size of the device screen minus 100 pixels (technically not pixels because of the dynamic nature of Solar2D graphics, but we can think of them in that way). When we center that on the screen we get a block that looks like this:
Right under the code that makes that background block add the code for a Resume button:
1 2 3 4 5 6 7 8 |
[crayon-67532b6151ca3766948753 ]local resumeBtn = widget.newButton ({ label="Resume", id="resume", onRelease=goSomewhere }) resumeBtn.x = contentCenterX resumeBtn.y = contentCenterY + 50 grp:insert(resumeBtn) |
[/crayon]
One more piece and that’s the goSomewhere() function. We’ll create that above the setUpDisplay() function in the code and it will be very short:
1 2 3 |
[crayon-67532b6151ca6237185668 ]local function goSomewhere(event) composer.hideOverlay( "slideUp", 300 ) end |
[/crayon]
In our other scenes the goSomewhere() function actually moved us to a different scene — in this case it’s just hiding the current overlay scene. If you want to rename that function to something more specific, such as hideOverlay() or ditchOverlay(), feel free. Just remember that in the following text I’ll be referring to it by the original name.
Later we’ll add a little more code, but technically that’s all that’s needed to make it work right now. Well, except we have no way to get to the overlay scene, so let’s fix that by opening the play.lua file and adding a button in the setUpDisplay() function:
1 2 3 4 5 6 7 8 9 10 |
[crayon-67532b6151ca8361761250 ]local pauseBtn = widget.newButton ({ label="Pause", id="pause", onRelease=goSomewhere }) pauseBtn.anchorX = 1 pauseBtn.anchorY = 0 pauseBtn.x = display.viewableContentWidth - 10 pauseBtn.y = display.screenOriginY + 10 grp:insert(pauseBtn) |
[/crayon]
That button will appear in the upper-right corner of the screen and will pass “pause” to the goSomewhere() function so we need to edit that — right now it’s set to only do a gotoScene() but that won’t work for an overlay scene.
Scroll up to the goSomewhere() function in the play.lua file and change it so it looks like this:
1 2 3 4 5 6 7 8 9 10 11 |
[crayon-67532b6151caa632926805 ]local function goSomewhere(event) local goto = event.target.id local options = {} if goto == "pause" then options = {effect="fromTop", time=300, isModal=true} composer.showOverlay( goto, options ) else options = {effect="crossFade", time=300} composer.gotoScene( goto, options ) end end |
[/crayon]
We first grab the id that will show us which scene we need and then create an empty options table. After that we have an if/else block and depending on whether we hit the Back button or Pause button (we know that from the id property) we fill the options table appropriately and then call either composer.showOverlay() or composer.gotoScene().
Overlays in a Nutshell
You create an overlay scene file with the default Composer scene template code. You make it appear with the composer.showOverlay() call and ditch it with the composer.hideOverlay() call. That’s really all you need to know to get started using it.
A Tip for Non-Beginners
The scene:destroy() function in a scene file isn’t usually called when switching from one scene to another — the scene.view and such is left in memory in case you switch back to that scene — that way it won’t have to start from scratch in creating the scene. Unless memory runs low or you explicitly tell Composer to remove the scene from memory, it hangs around, off in the wings.
Except for overlay scenes.
When you hide an overlay scene the entire scene object is removed from memory. There is a parameter you can use to override that if you know you’ll be calling the overlay again. The call would look like this:
1 |
[crayon-67532b6151cb2195863059 ]composer.hideOverlay( true, "slideUp", 300 ) |
[/crayon]
That first parameter, true, tells Composer to recycle the scene view, but leave the scene object hanging around for later use.
Using Your New Framework
If you’ve created a master framework for Composer-based projects, starting a new game or app is now much easier than it used to be. Make a duplicate of the framework folder and rename it as your new project. Now you can start editing.
In some cases you’ll have more scenes than you need — maybe you’re making a game that doesn’t have distinct levels, in which case you can delete the chooselevel.lua file and then change the id parameter of the Play button in the menu.lua file. Or maybe you need a tutorial, so you can duplicate one of the scene files and rename it.
If you’re making a business app you might be going with the default look of the buttons, but if you’re making a game you’ll probably be adding the code to show graphical buttons. So add your background images, change the look of the buttons, and within minutes you have the base of your game done and can start building the actual gameplay mechanics.
Same for business apps — in just a few minutes you can have the foundation of your app working, with buttons that move you from scene to scene.
Retrofit An Existing Game
Taking an existing project and converting it to use Composer is mostly just copy/paste stuff, but sometimes you have to juggle things around a little bit. I’m going to convert my demo game, Attack of the Cuteness, to use Composer. And like the best high wire acts, I won’t be using a net — instead of doing this ahead of time and the recreating it for the tutorial, I’m doing this for the first time right now, so you’ll see exactly what potholes I hit.
(I recorded the process below for the video version of this tutorial. If you have access to that, watching what I’m doing rather than reading will probably be more valuable, but I’ve included every step below so even just reading should be helpful.)
When you retrofit your own project to use Composer the details will be different, but the idea is the same, so taking a quick turn through this section should help make your job easier.
Attack of the Cuteness was designed to show just a few features of Solar2D so it’s very simple. You tap the screen to start and then tap the rocket ships before they hit the planet. If the planet gets hit 5 times it vanishes and the game is over. To convert this game we’re going to use main.lua and just three Composer scene files. Here’s a list of the files we need to copy from our master framework:
- main.lua
- menu.lua
- play.lua
- pause.lua
The easiest way to make the changes are to copy the files into the Attack of the Cuteness folder, so first we need to rename the original main.lua file. Name it something like old-main.lua and then copy the new files from the framework into the project folder.
Since we don’t have levels in this game I’m going to open menu.lua and change the id of the Play button widget so clicking it will go directly to that scene:
1 2 3 4 5 |
[crayon-67532b6151cba275406727 ]local playBtn = widget.newButton ({ label="Play", id="play", onRelease=goSomewhere }) |
[/crayon]
I’m also going to delete the code that creates the About and Options buttons since we’re not using those in this example.
Once that’s done and the changes are saved I’m going to open play.lua in my editor and the Attack of the Cuteness old-main.lua file (the one that was renamed) in a different editor so I can see both files at the same time, the original and the new Composer version.
Attack of the Copy/Paste (Mostly)
First thing to do is see if there are any libraries being required for this game. Nothing there in the original file, so we go on to local variables. This game does have a list of variables and forward references for functions so we’ll copy those and paste into the play.lua Composer file.
1 2 3 4 5 6 7 |
[crayon-67532b6151cbd534691552 ]local spawnEnemy local gameTitle local scoreTxt local score = 0 local hitPlanet local planet local speedBump = 0 |
[/crayon]
Now we see some code that preloads audio which we’ll be playing later. Those lines should be moved into the scene:create() function and since this demo game was a single screen the original code doesn’t ever dispose of the audio. Now that we’re using Composer we want to take care of that.
First, we change the lines that load the audio so we’re using variables that can be accessed from more than one location. We remove the local keyword from the lines that load the audio:
1 2 3 |
[crayon-67532b6151cbf058102431 ]sndKill = audio.loadSound("audio/boing-1.wav") sndBlast = audio.loadSound ( "audio/blast.mp3" ) sndLose = audio.loadSound ( "audio/wahwahwah.mp3" ) |
[/crayon]
And then we add these forward references to those at the top of the code:
1 2 3 |
[crayon-67532b6151cc6437920374 ]local sndKill local sndBlast local sndLose |
[/crayon]
Now we can access sndKill, etc., from anywhere in this Composer scene file. Since we loaded the audio inside the scene:create() function the right place to dispose of it is inside the scene:destroy() function. Add these lines inside that function (after the line that’s already there):
1 2 3 4 5 6 |
[crayon-67532b6151cc8374573145 ]audio.dispose( sndKill ) audio.dispose( sndBlast ) audio.dispose( sndLose ) sndKill = nil sndBlast = nil sndLose = nil |
[/crayon]
That’s the only new code we need to add, now let’s go back to copy-pasting.
We can now copy all the functions from the original file and paste them into the Composer scene template, right above the scene:create() function. After that there’s only one line of code left in the original file:
1 |
[crayon-67532b6151cca533111221 ]createPlayScreen() |
[/crayon]
That should be pasted into the scene:create() function, either right before or right after the lines that load the audio files (it doesn’t matter which).
Save the file and then launch the project. Hitting the Play button from the menu should allow the game to launch and you can start playing.
While the game does play, there are two things wrong with the way this is structured. First, there’s no way to get back to the main menu from the play scene, and second, we’re handling some things in the create phase of our scene that should actually be done in the show phase. Let’s fix that last problem, first.
Breaking Out Create and Show Pieces
The createPlayScreen() function is called from inside scene:create() and that’s good — it’s building the display objects for the scene. But that function is also starting some transitions and when scene:create() is being handled the scene view isn’t even visible. We also have the background image fading in, and since that visual effect is now going to be handled by Composer switching scenes, we can ditch that code completely.
At the top of the createPlayScreen() function let’s delete the line that sets the alpha to 0:
1 |
[crayon-67532b6151ccc812369162 ]background.alpha = 0 |
[/crayon]
And then down a few more lines delete the call to transition.to() that changes the alpha of the background — that’s what was used to fade in the alpha of that image.
1 |
[crayon-67532b6151cce770019700 ]transition.to( background, { time=2000, alpha=1, y=centerY, x=centerX } ) |
[/crayon]
Now we’re going to move the showTitle() function that’s inside there outside of the createPlayScreen() function. Just select it, cut, and then paste it down below outside of the createPlayScreen() function.
Finally, cut the other transition.to() line from inside createPlayScreen() and move it down into the scene:show() function. Put it inside the block of code for the “did” phase, like this:
1 2 3 4 5 6 7 8 |
[crayon-67532b6151cd0492514498 ]elseif ( phase == "did" ) then transition.to( planet, { time=2000, alpha=1, y=display.contentCenterY, onComplete=showTitle }) end |
[/crayon]
When you run the project now you won’t see any difference, but coding things right from the beginning can save you a lot of time and hassles later on.
Adding Pause and Exit
Finally, let’s add a pause button to the Play scene and for that we’ll use the pause.lua file as an overlay. We’ll also include an Exit Game button in that overlay so we can quit playing when we’ve had enough rocket blasting.
For the Pause button we’re actually going to repurpose the Back button. Let’s change the widget button code in play.lua so it looks like this (you could put it all on one line; I broke it up so it’s easier to read):
1 2 3 4 5 6 7 8 |
[crayon-67532b6151cd7806423597 ]local pauseBtn = widget.newButton ({ label="||", id="pause", width=20, height=20, labelXOffset=5, onRelease=goSomewhere }) |
[/crayon]
Notice that I changed the name of the variable we’re creating from backBtn to pauseBtn just to be consistent with what the button does. So be sure and change the few lines directly following to refer to the correct variable name.
I changed the label to two “pipe” characters because that resembles the pause symbol — in real life you’d probably want to use an actual pause symbol graphic that matches the look and feel of your game or app. I also used the width and height parameters so the button would be nice and small, and then used labelXOffset to position the label in the center of the button.
We need to change the goSomewhere() function in play.lua because the only option now is to pop up the pause screen:
1 2 3 4 5 |
[crayon-67532b6151cd9698202045 ]local function goSomewhere(event) local goto = event.target.id local options = {effect="fromTop", time=300, isModal=true} composer.showOverlay( goto, options ) end |
[/crayon]
We’re adding the isModal parameter to the options table to make sure taps don’t “fall through” when the pause scene is on top of the play scene — we only want taps to be seen by the overlay scene itself. And we’re also calling showOverlay() instead of the gotoScene() function. That leaves the parent scene, play in this case, active instead of unloading it.
Now when we tap the pause button in the play scene the pause scene drops down from the top of the screen. Let’s fix that so we have two options — Resume, which gets rid of the overlay and puts us back into the play scene, or Exit Game which gets rid of the overlay and sends us back to the main menu.
Open the pause.lua file and scroll down to the setUpDisplay() function. We’re going to copy the code for the Resume widget button, paste it in right below that, and tweak it to make it an Exit Game button. The tweaks should look like this:
1 2 3 4 5 6 7 8 |
[crayon-67532b6151cdb673028835 ]local exitBtn = widget.newButton ({ label="Exit Game", id="exit", onRelease=goSomewhere }) exitBtn.x = display.contentCenterX exitBtn.y = display.contentCenterY - 50 grp:insert(exitBtn) |
[/crayon]
Now scroll back up to the goSomewhere() function and we’ll change that so it can take a different action based on which button is clicked. The code inside that function should look like this:
1 2 3 4 5 |
[crayon-67532b6151ce1211149936 ]if event.target.id == "exit" then composer.gotoScene( "menu", {effect="crossFade"}) else composer.hideOverlay( "slideUp", 300 ) end |
[/crayon]
The button that was tapped is in event.target so we grab the id property of that and see if it’s “exit” and if so, we do a gotoScene() to the menu scene. Otherwise we just hide the overlay which takes us back to the play scene.
Just a quick note — if Exit Game is chosen and we go straight to the menu, the hide phases of the play scene are still activated. So if you have code in those areas to save the progress of the player, that code will still be triggered even if you leave from this pause scene.
That’s all we should have to do. Run the project and go to the Play scene. Hit the pause button and the Pause scene becomes active. You can still see the buttons on the Play scene but you shouldn’t be able to tap them due to the isModal property we set when we showed the overlay. Tapping the Resume button will take you back to Play and tapping the Exit Game button will take you back to the menu.
Wrapping Up the Retrofit
The goal of this section was to take a non-Composer game and make it scene-based. We did that pretty easily, and even added a pause feature to the game.
The main idea of retrofitting an existing project with Composer is to throw all your functions into the scene template and then figure out what items need to be created or started in either the scene:create() or scene:show() functions.
Reloading Scenes
What happens if the player doesn’t collect enough stars, or points, or for some reason wants to replay a level? You need to reload the scene!
Complete honesty up front — I dislike the way most people reload scenes because I think it reinforces bad habits. In fact, an actual “reload” of a scene is not even necessary.
However, I’ll discuss how it’s done in this part of the tutorial because many of the examples you see in forums, etc., do it this way (which bugs me to no end). I will also show what I think is the better way to reload scenes. You can use whichever way you want and even if you choose the “wrong” way I won’t think you’re a bad person. Just a person making bad decisions. 😉
Reload with an Intermediate Scene
Let’s say Sarah is playing your game and doesn’t get a high enough score, and so wants to play that level over again. Common sense would tell you that a simple button that would do a composer.gotoScene() to the play level would just reset things.
Except common sense lets us down in this case. You can’t reload the current scene and have things work the way they should. The way the Composer library works is very smart in conserving memory and running efficiently, but it means we have to jump through a small hoop.
Note! Most of this tutorial is “how to make it work” rather than “this is why it works” but let me take a moment to explain what’s happening. When Solar2D loads a Composer scene the code in the main part of the file (everything outside of the scene:create(), scene:show(), etc., functions, is executed once. Later when you leave that scene it’s actually left hanging around in memory in case you need to come back to it again — if that happens, it can be loaded much faster than the first time because it doesn’t execute everything again. But this means some code may be skipped when you return to a scene. That’s why you can’t just reload a scene.
Fortunately, the solution is pretty simple — create an intermediate Composer scene and use it as a stepping stone between playing a level and playing it again.
Play Scene >>> Intermediate Scene >>> Play Scene
That intermediate scene could recap the score of the level and show buttons to Retry or go Back to the main menu or level chooser.
There’s just one “special thing” you need to do in that intermediate scene — make a call to remove the scene you’re heading back to.
1 |
[crayon-67532b6151ce9447843450 ]composer.removeScene(“play”) |
[/crayon]
You need to do that so Composer completely removes the scene and will execute all lines of code in that file again when it’s relaunched. That line of code could go inside a retryLevel() function and the parameter you use is the name of the scene you’re wanting to remove (go back to) — in this example “play” is the name of the Composer scene.
1 2 3 4 5 6 |
[crayon-67532b6151cec197916321 ]-- This would be in the intermediate scene file local function retryLevel() local options = {effect="crossFade", time=300} composer.removeScene(“play”) composer.gotoScene( “play”, options ) end |
[/crayon]
If you don’t want the user to have to hit a button to replay the level, you could call the retryLevel function automatically after you enter the scene. For example, inside scene:show() (where phase == “did”) you could add this line of code:
1 |
[crayon-67532b6151cee087245845 ]timer.performWithDelay(10, retryLevel) |
[/crayon]
After 10/1000ths of a second (don’t blink!) retrylevel() will be called and the play scene will be launched again.
The Un-Reload Method
So what’s this magic method that I like so much? I’ll explain it very quickly, but without code, because it’s not something that needs a specific chunk of code to work. It’s a way of writing your code inside a Composer scene file.
It boils down to this — don’t put any more code in the scene:create(), scene:show(), etc., functions than you have to. For example, instead of putting the code that shows the background image and UI elements in the scene:create() function, put that code in a function called setUpBackground() and then inside scene:create() make a call to that function.
Inside the scene:show() function, don’t put the code that positions game sprites and starts them moving — put those in their own function and call that function from inside scene:show().
Basically, whatever you need to do to be able to start a level, make it so that code can be called without the need to call one of those Composer scene functions.
What does that kind of code separation give us?
It allows us to recreate the background, reset the game pieces, etc., without having to trigger those regular Composer scene functions. It means we can replay a scene without having to do any scene reloading trickery. And a nice side benefit is that in most cases it allows us to easily use a single Composer scene file no matter how many levels we have in our game. (That technique is outside the scope of this tutorial, but it’s one I use in all of my games. Single scene file — multiple levels.)
I said I wouldn’t show you any code, but let me give you an example of what it might look like to replay a scene:
1 2 3 4 5 |
[crayon-67532b6151cf5855581804 ]local function replay() deleteGameSprites() setUpGameSprites() startAudioAndAnimations() end |
[/crayon]
Those are all things that might have been placed inside the scene:create() and scene:show() functions, but because they’re broken out into their own functions I’m able to call them without reloading the scene.
For your game you may decide that using the “official” reload method (with the intermediate file) is best for your needs — if so, go for it. But play around with the technique discussed here — you can break out your code into their own functions and still use an intermediate file, so you can work towards this method in stages.
Misc Composer Tips
Most Composer scenes don’t “live in a vacuum” — they depend on values from other scenes. Whether it’s the level number you want to load or the number of lives left for the player, passing variables from one scene to another is vital. Fortunately, there are several great ways to pass values to and from Composer scenes.
The first two methods below are talked about with no real code specifics given — just so you know they exist. We’ll spend more time on the last two simply because they’re built in to the Composer library.
Global Variables
The first and (hands down) easiest way to pass values from one scene to another is to put that value in a global variable. For example, instead of using “local score = 0” to define a score variable in your main file, you would use “score = 0” (without the local keyword). Now it’s a global variable and every scene in the project can access it directly.
So why not do that? Well, if you have a tiny project and know what you’re doing, go ahead and use a global variable or three. But for more information on why using globals is usually a poor practice, see the following tutorial on the Corona Labs site:
Goodbye Globals! https://docs.coronalabs.com/tutorial/basics/globals/index.html
Global ‘Require’ File
Toward the bottom of the blog post shown above there’s mention of a method (Data Module) that’s often used in place of global variables. In fact, it’s what I usually do instead of creating actual global variables. Look for the Data Module heading toward the end of that article to see how it works. I’m not going to go into detail with it here because the two following methods of passing variables to Composer scenes are more relevant to what we’re playing with here.
(But do take a look at that method in the blog post because it can come in very handy at times.)
Passing Variables in the Options Parameter
When you use the composer.gotoScene() function one of the parameters (optional) is a table:
1 |
[crayon-67532b6151cff549019133 ]composer.gotoScene( sceneName [, options] ) |
[/crayon]
That table can hold three different things: the visual effect to use when switching to the next scene, the time the transition should take, and then one called params, which is itself a table.
In previous code we’ve used gotoScene() with two parameters in the options table:
1 2 |
[crayon-67532b6151d01583651875 ]local options = {effect="crossFade", time=300} composer.gotoScene( goto, options ) |
[/crayon]
Let’s say that we want to pass the current score and the player’s name (stored in variables named currScore and playerName) to the next scene. Our code would look like this:
1 2 3 4 5 6 7 8 9 |
[crayon-67532b6151d03533597893 ]local options = { effect="crossFade", time=300, params={ score=currScore, pName=playerName } } composer.gotoScene( goto, options ) |
[/crayon]
When we get to the next scene we’re able to access those variables inside the scene:create() and/or scene:show() functions, like this:
1 2 |
[crayon-67532b6151d05530889985 ]local theScore = event.params.score local thePlayerName = event.params.pName |
[/crayon]
If you want to use those values in the next scene you must grab them from the create() or show() function because those are the only two places where the event record is passed in, and that event record holds the params table.
Using Composer’s setVariable() and getVariable() Methods
Finally, there are two API calls in the Composer library that are specifically for passing variables.
To store a variable use the setVariable() call like this in one Composer scene file:
1 2 |
[crayon-67532b6151d07925174425 ]composer.setVariable(“score”, currScore) composer.setVariable(“pName”, playerName) |
[/crayon]
The first parameters is the name of the variable, and the second parameter is the actual value. This is typically referred to as a key-value pair. Remember that the first parameter is not an actual variable, but the name of a variable, that’s why it’s enclosed in quote marks.
In the next Composer scene file (or even two or three scenes later — you don’t have to grab them immediately when using this method) you can retrieve the variables like this:
1 2 |
[crayon-67532b6151d0d972998717 ]local theScore = composer.getVariable(“score”) local thePlayerName = composer.getVariable(“pName”) |
[/crayon]
Again, the parameters you’re passing is the name of the variable, in quotes. That’s the “key” and you’ll get back the “value” that matches the key. Using this method you don’t have to grab the values from inside the create() or show() functions — you can use those Composer methods from anywhere in your scene file. Which makes this method the most versatile.
Any of these ways of passing values will work for regular scenes as well as overlay scenes.
Almost everything in the prior sections relates to both games and business apps. But there are a couple things that are business app-specific that I want to go over.
Tab Bars
Tab bars are a special beast because while you switch from scene to scene in a business app, usually the tab bar stays at the bottom — it doesn’t do the transition effects. That means you probably don’t want to insert it into the Composer scene (self.view) or the tab bar will move on and off the screen when scenes transition. If you create the tab bar in your main.lua file and then start messing with Composer scenes, your tab bar will remain on top of everything else and will stay in place when transitioning from one scene to another.
Native Objects
It’s important that you insert the display objects you create into the scene’s display group (self.view), but if you’re creating native objects such as a text field or a map you know those don’t act like the normal Solar2D display objects. They can’t be inserted into display groups, and that includes the Composer scene group.
That means you can’t create those objects in the scene create phase. Instead, you need to create them inside the scene:show() “did” phase. Which means they won’t be seen as the scene swoops onto the screen, but there’s no way around that.
Sidebar: No easy, fool-proof way, anyway. You could create fake native objects that are replaced after the scene is onscreen — make a display.newRect() that looks like a native textfield and add that inside the create function. Then when you get the scene:show() “did” phase remove that object and replace it with the real native textfield. No, not elegant, but kind of tricky. Worth the hassle? That’s up to you and what you’re trying to accomplish.
You’ll also need to manually remove those objects — Composer won’t handle that for you. It only knows about the display objects that are inserted into its scene group. You’ll want to remove the native objects in the “will” phase of the scene:hide() function.
Almost The End
I hope you’ve enjoyed this tutorial and it’s helped you get started using the Composer library for Solar2D. If you’re more visually-oriented and would like the video version of this tutorial, it’s available for a very small price here:
Three Ring Ranch Video Tutorials
https://gamedev.teachable.com/p/using-solar2d-composer/
Also, following are some links to the official Composer library documentation from Corona Labs — it’s always a good idea to acquaint yourself with all the API calls that are available for the library/framework you’re using. You never know when you’ll come across something that I don’t think is all that vital (so didn’t cover), but that will solve a problem you’ve been banging your head against!
Composer Library API
https://docs.coronalabs.com/api/library/composer/index.html
Composer Library Guide
https://docs.coronalabs.com/guide/system/composer/index.html