Gemini Scene Management

| Comments

I have been busy adding features to Gemini and haven’t updated this blog in ages, so I wanted to add a post about some of the recent work. The two biggest changes I have made are getting physics to work (via Box2D) and adding a scene management API. I’ll talk more about physics in a later post, but for now I want to talk about scene management. Follow the jump to read more.

Once I got some of the core features working with Gemini, it quickly became obvious that I needed some sort of scene management API before anyone could do anything serious with Gemini. As an aside, the terms scene, level, and storyboard tend to be used interchangably to mean the same thing across various APIs. I am using the term scene but that was just an arbitrary choice.

I looked at various APIs that are available on different platforms such as storyboarding on iOS and found the basic design pattern to be the same on all of them. Essentially they all use an event driven system; for each scene you define a module/class/view controller or what have you that defines code that is executed when certain events are sent to the scene.

There are typically four (but possibly more) events that are defined. The first event is typically a create scene event that is received when the scene is first loaded. This is where various objects in the scene are created but not activated (no physics, motion, etc.). The second event sent to the scene is an enter scene event, which is called when the scene is about to be shown. The Third event is an exit scene event, which is called just before a scene stops showing. The final event is the destroy scene event, which cleans up the scene before it is removed entirely.

In addition to scenes and events a scene management API needs a class to manage all the scenes and transitions between them. In my case I call this the Director (specifically, GemDirector), while others call it the storyboard manager or something similar. I mentioned transitions, and the API should also provide various transitions for moving from one scene to another, so that you don’t just pop from one to the next.

Before I go into the scene management API I created based on all this, let’s look at an example. The following video shows three different scenes with transitions in between. The first scene shows some simple physics with a couple of falling boxes. It also has a blue star drawn with a thick line and animated using the enterFrame event that fires (you guessed it) at the beginning of every frame. The second scene shows a simple ferris wheel made from four square rectangles and two long rectangles. The ferris wheel is animated using the enterFrame event once again. The final scene simply shows three sprites (using the same sprite data) being animated.

The first transition is a simple page curl effect (doesn’t everything need a page curl?). This is based off a demo Dana Nuon posted on his blog. The second and third transitions are variations on a slide transistion that slides one scene out while the next slides in. This transition can slide up, down, left, or right.

These are the only two transitions I have created so far, but I have created a class heirachy that makes it easy to add transitions. I plan to add many more. All the transistions have there own set of parameters, some of which (like duration) are common to them all.

The API I have created for this is very similar to that provided by the Corona SDK with its storyboard library. My scene definition lua files are for the most part compatible with the storyboard/scene files Corona uses.

Here is the code for the second scene in the video:

Scene 2
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
----------------------------------------------------------------------------------
--
-- scene2.lua - demo of scene management api
-- This module defines a "scene" object which is managed by the Director
--
----------------------------------------------------------------------------------

---------------------------------------------------------------------------------
-- load libraries and data here
---------------------------------------------------------------------------------
local director = require( "director" )
local scene = director.newScene()
local display = require('display')
local sprite = require('sprite')
local walker = require('walker')

---------------------------------------------------------------------------------
-- Define variables local to this module here
---------------------------------------------------------------------------------
local walkerSprite
local sprite2
local sprite3

---------------------------------------------------------------------------------
-- Event handlers
---------------------------------------------------------------------------------

-- Called when the scene is first loaded
function scene:createScene( event )

-- Always create at least one layer for your scene or objects you add will be added
-- to the defualt layer, which is usually not what you want
  local layer1 = display.newLayer(1)
  layer1:setBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  
  -- Layers you create must be added to this scene or they will be part of the "default" scene
  self:addLayer(layer1)

  -- create a dipslay group that we can use to spin all our boxes at once
  scene.group = display.newGroup()
    layer1:insert(scene.group)

    -- draw four boxes (and cross bars) and put them in the group
    local x_center = 240
    local y_center = 160

    local hbar = display.newRect(x_center,y_center, 250,15)
    hbar:setFillColor(0.0,0.0,1.0,1.0)
    hbar.rotation = 45.0
    scene.group:insert(hbar)

    local vbar = display.newRect(x_center,y_center, 15, 250)
    vbar:setFillColor(0.0,0.0,1.0,1.0)
    vbar.rotation = 45.0
    scene.group:insert(vbar)

    for j=0,1 do
      local y = y_center - 75 + j * 150
      for i=0,1 do
        local x = x_center - 75 + i * 150
        local rectangle = display.newRect(x,y,50,50)
        rectangle:setFillColor(1.0,0,0,1.0)
        rectangle:setStrokeColor(1.0,1.0,1.0,1.0)
        rectangle.strokeWidth = 3.0
        scene.group:insert(rectangle)
      end
    end

    scene.group.xReference = x_center
    scene.group.yReference = y_center
    scene.group.x = x_center
    scene.group.y = y_center


end


-- Called when scene is about to be onscreen
function scene:enterScene( event )
  
  -----------------------------------------------------------------------------
  -- Start timers, event listeners, etc. here to begin the scene
  -----------------------------------------------------------------------------

  -- preload the next scene
    director.loadScene('scene4')

  -- Define an event listener to handle "enterFrame events".  Make this an entry in the 
  -- data table for this scene.  This is not strictly necessary but makes it easy to refer
  -- back to it when we want to remove it.
   scene.groupListener = function(event)
     -- rotate our rectangles about the group center (reference points) and about each recs center
     scene.group.rotation = scene.group.rotation + 1.0
     for i=3,6 do
       local rec = scene.group[i]
       rec.rotation = rec.rotation - 1.0
     end
   end
   -- the "enterFrame" event fires at the beginning of each render loop
   Runtime:addEventListener("enterFrame", scene.groupListener)

   -- Define another event listener, this time to hande a timer event to make us
   -- transition to the next scene.
   local function listener(event)
   -- transition to the next scene using a "slide" transition
       director.gotoScene(
           "scene4",
           {transition="GEM_SLIDE_SCENE_TRANSITION", duration=2.5, direction="left"})
   end
   -- start a timer to call our listener defined above after ten seconds.
   timer.performWithDelay(10000, listener)

end


-- Called when scene is about to be moved offscreen
function scene:exitScene( event )
  

  -----------------------------------------------------------------------------

  -- stop timers, remove event listeners, etc.

  -----------------------------------------------------------------------------

   Runtime:removeEventListener("enterFrame", scene.groupListener)

end


-- Called when the scene is about to be deallocated
function scene:destroyScene( event )
  

  -----------------------------------------------------------------------------

  -- Do whatever you need here to free resources

  -----------------------------------------------------------------------------

end

---------------------------------------------------------------------------------
-- End of event handlers
---------------------------------------------------------------------------------

---------------------------------------------------------------------------------
-- Scene name definition - used to identify scene for logging purposes, etc.
---------------------------------------------------------------------------------
scene.name = "scene4"

---------------------------------------------------------------------------------
-- The event handlers defined above are bound to scene events here
---------------------------------------------------------------------------------
-- "createScene" event is dispatched if scene's view does not exist
scene:addEventListener( "createScene", scene )

-- the "enterScene" event is received when the scene transition has finished
scene:addEventListener( "enterScene", scene )

-- the "exitScene" event is dispatched before a transition to a new scene begins
scene:addEventListener( "exitScene", scene )

-- "destroyScene" event is dispatched before a view is deallocated via a "destroyScene" call
scene:addEventListener( "destroyScene", scene )

return scene

Essentially this code is event driven and simply defines and binds event listeners for the various scene events described above. The typical pattern for using this API is to use this template to define all the scenes in your game (one file per scene), as well as a main.lua file that sets everything up and calls your first scene via the director API. Any global variables defined in the main.lua files are avaiable to all the modules.

Well, that is a basic introduction to scene management in Gemini. This is probably just the start, as I can envision other events that might be useful, like pauseScene to pause animation, etc. For now, though, this should be a strong start.

Comments