We have a bunch of scene definitions now, but how do we keep track of them and transition between them? A solution is the SceneManager class, which maintains a stack of Scenes and handles adding/removing/updating them. Here’s what the stack might look like, with each layer being a Scene:
At each step, only the top Scene on the current stack will be updated and drawn. As Scenes are added to the stack, they are initialized, and as they are taken off, they will be cleaned up. The interface for the SceneManager will be as follows:
- pushScene(scene) – add a new scene to the top of the stack
- popScene() – remove the top scene
- update() – should be called every loop iteration, calls update() on the top Scene
Here’s the code:
SceneManager = {} --stack of current scenes local scenes = {} local showDebugLayer = true
First we declare the SceneManager class. Since there can only ever be one scene manager, I’m making this static and just creating it as a global table. There is also a “scenes” table to store the actual stack. This is declared local and it should not be accessible outside of this file, so it is essentially a private member variable. Finally we have a flag to determine whether we show the debug layer that each Scene has. This makes it a little bit easier to toggle all debug information on or off.
Next we have a helper function to set the current scene to be the one that is drawn.
local function setScene(scene) local layers = {} if scene then layers = scene:getLayers() if showDebugLayer == true then table.insert(layers, scene.debugLayer) end end if layers then MOAIRenderMgr.setRenderTable(layers) else MOAIRenderMgr.setRenderTable({}) end end
This checks the current scene, grabs its layers, and passes it to the MOAIRenderMgr. It also checks the debug flag – if it is set to true, it will also add the scene’s debugLayer to the list.
Finally we have the interface that you can call:
function SceneManager.pushScene(scene) table.insert(scenes, scene) scenes[#scenes]:initialize() setScene(scenes[#scenes]) end function SceneManager.popScene() scenes[#scenes]:cleanup() table.remove(scenes, #scenes) if #scenes > 0 then setScene(scenes[#scenes]) else setScene(nil) end end function SceneManager.showDebugLayer(show) showDebugLayer = show setScene(scenes[#scenes]) end --calls update on the visible scene function SceneManager.update() if #scenes > 0 then scenes[#scenes]:update() end end
Remember #scenes is the number of items in a table. Since Lua tables are 1-indexed, scene[#scenes] will be the last (top) Scene. Every time we add a Scene to the top, we initialize it, and when we take it off the top, we clean it up. showDebugLayer() will take either true or false, set the flag, and then update the drawn layers with the new information. You need to make sure you call update() every simulation loop so it knows to pass it on to the active Scene.
This is an example of how the SceneManager can be used in your main.lua file. This is assuming all the Moai setup has already been done. We initialize it with a Scene instance, and then call update() every loop. When we detect that after an update() is finished and there are no Scenes left, we exit out and assume the game is over.
SceneManager.pushScene(PhysicsTestScene.new()) local gameOver = false local mainLoop = MOAICoroutine.new () mainLoop:run ( function () while not gameOver do SceneManager.update() if SceneManager.sceneCount() == 0 then gameOver = true end coroutine.yield () end print("Game Over!") os.exit() end )
There is still some room for improvement here:
- If there is a Scene that is on top of another scene but doesn’t take up the entire screen, then we need to still render the one below it. To address this we can add a transparency property to Scenes that should be see-through, and then make sure we grab the layers from the next Scene down when we set it up. An example would be a pause menu that only takes up a small portion of the screen – you’d still want to see the action going on underneath. You wouldn’t want to call update() though since the pause menu should be the only one responsive.
- If there is pause functionality, we need to add pause() and unpause() functions to Scene, which would stop/start all MOAIAction related items (Box2D simulations, timers, animations). SceneManager would have to call these when necessary.
- If the start scene takes up a lot of memory or resources, we may not want to hold it in memory while the gameplay is happening. Instead of just adding on to the stack, we can pop off the start menu scene before adding on the gameplay one. We would have to remember to add it back on once the user wants to go to the main menu. We would also lose any state the main menu has, since the user is going to a brand new instance of it.