Defining Game State – Scenes

Now that we have a lot of the basic pieces in place to make a simple game, we need to think about how to keep track of game state.  For example, how do you know to start with a start menu?  How can you transition to the actual gameplay?  What if user wants to pause the game or quit?

We do this by defining each one of these game states to be a Scene.  Some examples are:

  • Start menu
  • Settings dialogs
  • Credits scroll
  • Game level
  • Game map
  • Pause menu

Each of these things needs to be able to define the following behaviors:

  • Initialize whatever it needs
  • Draw itself
  • Take user input and respond to it
  • Cleanup resources when it is no longer needed

Lets take an example – the gameplay screen.  Here is what each of those items would entail:

  • Initialize – load up the level data and image data, process them
  • Draw – show all the player and enemy sprites
  • Update – check if the user touched something and react.  Also move characters around and resolve their interactions.
  • Cleanup – Unload all the image data, clean up memory

Here is the actual code to define a base Scene class:

Scene = CreateClass()

function Scene:initialize()
  --debug layer - every scene can have one
  self.debugLayer = MOAILayer2D.new()
  self.debugLayer:setViewport(Viewport)
end

function Scene:cleanup()
end

function Scene:update()
end

function Scene:getLayers()
  return {}
end

It is pretty basic – you just need to create a subclass and fill out those four functions in order to define your Scene’s behavior.  initialize() defines a layer that every scene will have to draw debug information.  If we standardize this across all Scenes, it becomes easier to toggle on/off all debug data across your game.  cleanup()/update() are pretty straight forward, but what happened to draw()?  In Moai, you don’t actually draw stuff to the screen every time – instead you just need to define some visual shapes (Props) and then tell the Moai rendering system what you want to draw.  It will then run at 60fps and render what you defined.  If you move those shapes around during your update() function, it will be reflected when Moai draws the next frame.  In order to tell Moai which shapes to draw, you really tell it which layers to draw, and then each layer contains all the Props.

Since many gameplay scenes would use Box2D, I also created a Scene subclass that handles Box2D setup for you.  It has 2 things it needs to accomplish – create the MOAIBox2DWorld, and keep track of Box2D bodies, which need to be cleaned up at the end.

Box2DScene = CreateClass(Scene)

function Box2DScene:init()
  self.bodies = {}  
end

function Box2DScene:addBody(type)
  local body = self.box2dworld:addBody(type)
  self.bodies[body] = true
  return body
end

function Box2DScene:destroyBody(body)
  --remove it from the master list of bodies
  self.bodies[body] = nil
  body:destroy()
end

function Box2DScene:initialize(gravity)
  Scene.initialize(self)

  if gravity == nil then
    gravity = -9.8
  end

  self.box2dworld = MOAIBox2DWorld.new()
  self.box2dworld:setGravity(0, gravity)
  self.box2dworld:setUnitsToMeters(1/30)
  self.box2dworld:start()
  self.debugLayer:setBox2DWorld(self.box2dworld)  
end

function Box2DScene:cleanup()
  self.box2dworld:stop()

  --cleanup any leftover bodies still in the simulation
  if self.bodies ~= nil then
    for k,v in pairs(self.bodies) do
      if k ~= nil and v == true then
        k:destroy()
      end
    end
  end

  self.box2dworld = nil

  Scene.cleanup(self)
end

Now that we have that, it is pretty simple to create Scenes that use Box2D:

SpritePhysicsTestScene = CreateClass(Box2DScene)

function SpritePhysicsTestScene:initialize()
  Box2DScene.initialize(self)
  
  --a layer to hold everything we want Moai to render
  self.spriteLayer = MOAILayer2D.new()
  self.spriteLayer:setViewport(Viewport)

  --Box2D body/fixture setup.  Note that we use self (Box2DScene)'s addBody function
  local staticBody = self:addBody( MOAIBox2DBody.STATIC )
  staticBody:setTransform( 0, -100 )
  local dynamicBody = self:addBody( MOAIBox2DBody.DYNAMIC )
  dynamicBody:setTransform( 65, 60 )

  local floorFixture = staticBody:addRect(-200, -50, 200, -60)
  local circleFixture = dynamicBody:addCircle( 0, 0, 20 )

  circleFixture:setDensity(1)
  circleFixture:setRestitution(0.9)
  dynamicBody:resetMassData()

  --create sprites
  local prop1 = MOAIProp2D.new()
  prop1:setDeck(ResourceManager.getDeck("sprites/bear.png", -19, -19, 19, 19))
  self.spriteLayer:insertProp(prop1)
  prop1:setParent(dynamicBody)
end

function SpritePhysicsTestScene:cleanup()
  self.spriteLayer = nil

  Box2DScene.cleanup(self)
end

function SpritePhysicsTestScene:getLayers()
  return { self.spriteLayer }
end

Some important things to note:

  • We define a new layer to hold everything we want to draw, and then return that in the getLayers() function.
  • When we create Box2D bodies, we use the Box2DScene’s addBody() function, which keeps track of it for cleanup.
  • When we override these functions, we always have to call the parent class’s version of the function so it can run its logic.
  • Since Box2D is handling our physics updates, and our Prop is attached to it, we don’t actually need to create a update() function here to do anything.

Next time I’ll cover the SceneManager class, which will keep track of these Scenes and actually manage the game state.

Leave a Reply