Tutorial: Building a Level Selection Scene

Tutorial: Building a Level Selection Scene

Today’s tutorial illustrates how to build a level select scene for a game. Level select screens are common in games which are divided into levels from which the player can resume play or choose to replay for maximum score.

levselect

Setup

This tutorial module is built atop the Corona Composer API. It assumes that we have a scene module game.lua which is loaded when a particular level is selected, along with a scene named menu.lua which will be shown if the player decides not to play a level.

We also need a common “data” table which can be accessed in multiple scenes. The following code loads a mydata.lua file which mimics the simple data module outlined in the Goodbye Globals! tutorial:

This looks more complex than it actually is. The bottom half just pre-populates the table for purposes of this tutorial — this will allow us to see the results without building a functioning game around it. The key table members are at the top:

  • .maxLevels — The maximum number of levels for the game (to be included in the level select screen).
  • .settings — Data table that will hold various player data and otherwise.
  • .settings.currentLevel — Level that the player is currently playing.
  • .settings.unlockedLevels — Highest level attained (completed) by the player.
  • .settings.soundOn / .settings.musicOn — Booleans for player audio preferences.
  • .settings.levels — Table that tracks the player’s per-level progress. For this tutorial, only .stars is important, but we could also track the player’s per-level score.

This entire table can be easily saved using the loadsave module saveTable() call:

Scene Setup

Now that the basic data module is configured, let’s look at the core scene:

Looking in Depth

All of this is based on the standard Composer scene. Although the code is commented, let’s examine each part and its functionality:

  1. To keep this tutorial simple, there are no required graphical assets. Instead, we use the display.newPolygon() API to generate vector stars. This API requires an array of vertices, so we define it in the scene’s main chunk in case there’s a need to use it elsewhere.
  2. The next two functions handle button events. Since our buttons use the onEvent handler, we need to test for the phase to ensure that the code is only executed once. In each handler function, we simply use composer.gotoScene() to go to the appropriate scene.
  3. Inside the handleLevelSelect() function, we set the currentLevel value within the myData.settings table so that it’s easy to access from the game.lua scene. We also call composer.removeScene() to make sure that the game scene loads fresh each time.
  4. The rest of the magic happens in the scene:create() event handler. First, we create a background, set its fill to white, and center it on screen. Then we create a widget.newScrollView() to hold the buttons in case we have more than one full screen of them. As designed, we can fit around 20 buttons on the screen without needing to scroll.
  5. Next, we initialize some variables that are used to compute each button’s location on the screen (within the scroll view). These include the x and y of the button (xOffset and yOffset) and a count of the current number of buttons in the row (cellCount). The cellCount variable starts at 1, and this tutorial will place 5 buttons in each row.
  6. For the max number of levels in the game (myData.maxLevels), we perform a loop and generate a widget.newButton() for each button. This tutorial uses the vector-based method of generating the button shape — in this case, a rounded rectangle.
  7. In the loop, we also set the button’s text label as the index of the array, thus numbering them in sequential order. We use this same value to set the button’s id value which is used when the player selects the level.
  8. The next block of code tests whether the number of unlocked levels has been passed in. Unlocked levels will be shown in full color with the button’s touch handler enabled, whereas locked levels will be faded out and disabled.
  9. If we want to show the number of stars earned per level, we can generate them now. In an actual produced game, we would likely use an image instead of a star-shaped polygon. Either way, we read the number of earned stars from the saved data (myData.settings.level.stars) and generate one star for each. Each star is positioned relative to the button’s x and y values.
  10. Next, we increment xOffset to position the next button further to the right. We also increment the number of buttons in the row and, if that value exceeds the number of buttons for each row, we reset xOffset to the first position in the row, increment the cellCount variable, and add the number of pixels at which to draw the next row (yOffset).
  11. Once the loop finishes, we insert the entire scroll view into the scene’s view group and center it on the screen.

Conclusion

While some of this module may seem complex, most of the process is straightforward. Of course, for an actual produced game, certain adjustments to variables and data structures would be necessary. In the meantime, if you want to experiment with the code base in this tutorial, please download it here.

Tags:
Rob Miracle
[email protected]

Rob is the Developer Relations Manager for Corona Labs. Besides being passionate about helping other developers make great games using Corona, he is also enjoys making games in his spare time. Rob has been coding games since 1979 from personal computers to mainframes. He has over 16 years professional experience in the gaming industry.

20 Comments
  • julien
    Posted at 15:21h, 12 August

    Won’t you need to remove the background and the buttons in the scene:destroy ?

    That would imply to declare them outside the create, wouldn’t it ?

    • Rob Miracle
      Posted at 17:49h, 12 August

      Composer (and formerly Storyboard for those still using it) will automatically remove display objects that have been added to the scene’s view group (i.e. self.view or as localized “sceneGroup”. If the scene gets removed, then those objects will be freed up for you. You only need to worry about timers, native.* objects, transitions that may still be running, audio that may still be playing and have a call back and Runtime event listeners. Simple touch and tap listeners that are part of a display object like this are handled with the objects themselves.

      You might need to access these items outside of create scene if your code needs it, but for this example, there isn’t any thing you need to do that would need that. For instance, the touched button object is passed to the event handler as event.target, so I can still access the individual button from that function.

      Rob

  • Mo
    Posted at 21:32h, 12 August

    FANTASTIC tutorial! Thank you Rob. This is the type of things we all need to implement for games. can I suggest another tutorial that could be helpful to people making games? In addition to this level select tutorial, i would love a tutorial that explain how to make one of those menus that you select items by sweeping left or right. They are usually used to choose either a powerups or a world. Usually they are multiple box that aligned horizontally and you can scroll left/right with a sweep of you finger.

    Not sure If I am making any sense 🙂

    But thank you so much for this GREAT tutorial!

    Mo.

    • Rob Miracle
      Posted at 16:55h, 13 August

      Great idea Mo! I’ll see what I can come up with.

      Rob

  • Peter Dwyer
    Posted at 02:12h, 13 August

    Irony seems to be a weird theme in my life lately. I literally wrote one of these over the last few weeks because I wanted some way to select levels and refused to buy some pre-made pack to do what is essentially a simple task. Of course if you’re a game designer and developer you soon manage to turn simple into a war of attrition. Adding every feature under the sun from level groups to free roaming selection maps a-la candy crush and plants vs zombies 2.

    >_<

  • Mo
    Posted at 23:37h, 14 August

    @Rob. Super cool, thank you!

    Mo

  • Jeff Zivkovic
    Posted at 16:52h, 17 August

    Awesome Tutorial.

    But I’m thinking I missed some very basics about using composer. I don’t know the syntax to create the intended scene that would trigger the listener. I’ve been looking all over for an example where composer is used, so I can see the syntax and usage to actually create the scene and get this level selection rolling.

    • Jeff Zivkovic
      Posted at 16:53h, 17 August

      Can somebody please direct me to a finished app example where composer is used?

      Thanks a bunch,

      Jeff

      • Jeff Zivkovic
        Posted at 16:58h, 17 August

        Sorry. Nevermind. I found the example that came with the Corona download.

        Feeling sheepish.

  • Jeff Zivkovic
    Posted at 06:07h, 18 August

    Okay. Got it basically working.

    But I can’t figure out why I can’t see the button strokes and fill. I can see the stars and the word cancel. And the level number shows up momentarily when clicked (so, when it’s in “over” mode). But the rounded rectangles of the buttons are white-on-white. Any ideas?

    Thanks,

    Jeff

    • Rob Miracle
      Posted at 15:33h, 18 August

      Please ask this in the forums. We are probably going to need you to post code and the forum comments isn’t a good place to do that.

      Rob

  • Lori
    Posted at 07:58h, 10 October

    I was able to get the buttons set up but I am having trouble getting them to go to the correct level. The game board for each level is unique in my game so I have scene’s for each: level1.lua, level2.lua etc. I am trying to modify the button handler code to do this. Below is the code I believe needs to be modified. I’ve tried if statements such as if buttons.id == 1 then…I would change the word “game” with “level1”. I would appreciate any help anyone can provide

    — Button handler to go to the selected level
    local function handleLevelSelect( event )
    if ( “ended” == event.phase ) then
    — ‘event.target’ is the button and ‘.id’ is a number indicating which level to go to.
    — The ‘game’ scene will use this setting to determine which level to load.
    — This could be done via passed parameters as well.
    myData.settings.currentLevel = event.target.id

    — Purge the game scene so we have a fresh start
    composer.removeScene( “game”, false )

    — Go to the game scene
    composer.gotoScene( “game”, { effect=”crossFade”, time=333 } )
    end
    end

    • Rob Miracle
      Posted at 19:46h, 10 October

      try:

      composer.gotoScene( “level” .. tostring(event.target.id), { effect=”crossFade”, time=333 } )

  • Sobh
    Posted at 16:03h, 13 June

    hi
    thanks for the tut

    i am facing one issue . i am trying to change the fillColor to use an image using
    defaultFile = “lock.png”,
    overFile = “lock.png”,

    to be able to put a lock or unlock image according to if the level is passed or not

    but whatever i do , the defaultFile and overFile didnot work .

    and i removed the fillColor but still it show white !

  • Sobh
    Posted at 04:38h, 19 June

    Ok i find the issue
    for any one who face this
    you can remove the shape line and create a rect image with the lock shape on it and use it as the default file .

  • Guest
    Posted at 05:57h, 05 June

    I have downloaded files, when I open it in simulator error message apper:
    “ERROR: Runtime error
    14:54:00.051 module ‘utility’ not found:
    14:54:00.051 no field package.preload[‘utility’]”

    You did not say anything about it in tutorial?

    • Rob Miracle
      Posted at 11:15h, 05 June

      In game.lua there is a line: local utility = require(“utility”)
      Simply remove that line and you will be all set.

  • Jonas
    Posted at 08:59h, 27 August

    Hello! Thank you for this great tutorial! I’m having a great time programming my games! 🙂
    I did this in my game, the functionality is exactally the same, I copied and pasted the code, the only thing I changed was the “id” variable that is equal to “i”, so now it is a number, not a string . But something is going wrong: When I click a level button, say 3, it stores the id in myData.settings.currentLevel, it becomes 3. But when game.lua is loaded, all the variables in gameData.lua are reset to the original ones in the file. So gameData.settings.currentLevel becomes 1 again. Any idea of what I’m doing wrong? Thank you!

    • Jonas
      Posted at 09:02h, 27 August

      Sorry, there is a typo in my comment, the second time I said “gameData” instead of “myData”, I meant “myData”.

    • Rob Miracle
      Posted at 11:08h, 28 August

      This is probably a question best asked in the forums. First, changing it to .i instead of .id to make it a number was an unnecessary step. You could have just set the id to a number. Are you by any change reading the saved settings in game.lua and perhaps not saving the settings in levelselect.lua? This would cause this problem. Generally you only need to load the settings in main.lua since the myData table will persist through the app. You however should save the settings every time you make a change you need to keep.