|
How do you structure multiple game screens? |
Chris Katko
Member #1,881
January 2002
|
You start a game. You get a menu system. You move in and out of forms. Then you start the game. Now you're on the level select / campaign screen and things are moving around. Then you click go, you play a round of C&C or whatever and beat the level. You go back to the level select, and play another. How do you typically go about structuring that stuff? Is there a single game loop that changes states based on what "screen" you're in? Or is there multiple game loops for each screen, and a master controller above them? Lastly, how do you go about de-coupling and designing interfaces between those sections? Clearly, you don't want a hodge-podge mess of everything touching everything. Without good encapsulation, it would be a nightmare, so how do you go about architecting that? -----sig: |
MiquelFire
Member #3,110
January 2003
|
For XNA, Microsoft had this package or something in which you define scenes. Each scene would be the screen on your post. You would have an active list, and you add remove scenes as needed, so the main menu would be a scene, the level select would be a scene, etc. I don't remember the details, but I think the class structure would have functions that the main loop could call when the scene needed to do something. The way they handled it however, a scene could be transparent, so the top most scene would have focus, but other scenes could do updates. So the pause menu could be a scene, but the game could still animate stuff behind the pause menu. --- |
jmasterx
Member #11,410
October 2009
|
Echoing this, I use a base Scene class, and all Scenes inherit this. But I can only run one scene. Then I have a SceneManager that is responsible for scene switching, and calling things like input, logic, and render() on the scene. When a scene is done, it sends a message to the scene manager and on the next frame, the scene manager calls the scene end methods, cleanup is performed and the new scene is loaded. The reason you don't want to kill the scene right away is because things will crash hard if you do. Here is the full source of my scene manager. It does a little more than described but the idea is the same. 1#include "Game/Engine/SceneManager.hpp"
2#include "Game/Scene/InitialScene.hpp"
3#include "Game/Scene/GameScene.hpp"
4#include "Game/Scene/LobbyScene.hpp"
5#include "Game/Scene/LoginScene.hpp"
6#include "Game/Utility/StringUtil.hpp"
7
8namespace cge
9{
10 SceneManager::SceneManager( DeviceManager* settings, DynamicUIManager* dynamicUImanager )
11 : m_devices(settings),m_gameIsRunning(true), m_nextScene(LOGIN),
12 m_currentScene(NULL),m_needsRedraw(true),m_frameRate(60.0f),
13 m_dynamicUI(dynamicUImanager), m_currentSceneType(NO_SCENE),
14 m_needsResize(false),m_newScreenHeight(0),m_newScreenWidth(0),
15 m_transitioning(false),m_transitionOpacity(1.0f),m_transitionRate(0.05f), m_needsTransition(false),
16 m_guiImageManager("res/gui/gui_image_mapping.conf","res/gui/")
17 {
18 queue = al_create_event_queue();
19 m_gameTimer = al_create_timer(1.0 / m_frameRate);
20 registerEventSources();
21 //Instance the first scene
22 processMessages();
23 Log::write("Scene Manager","Scene Manager Started");
24
25 m_g.setBuffering(settings->getDisplay()->getMaxTextureSize() >= 2048);
26 }
27
28 SceneManager::~SceneManager(void)
29 {
30 //delete the current scene
31 if(m_currentScene)
32 {
33 m_currentScene->sceneEnd();
34 delete m_currentScene;
35 m_currentScene = NULL;
36 }
37
38 al_destroy_timer(m_gameTimer);
39 al_destroy_event_queue(queue);
40 Log::write("Scene Manager","Scene Manager Destroyed");
41
42 }
43
44 void SceneManager::run()
45 {
46 m_devices->setSceneMessenger(this);
47
48 al_start_timer(m_gameTimer);
49
50 //is the event handled?
51 bool handled = false;
52 ALLEGRO_EVENT next;
53 int numTicks = 0;
54
55 //main loop
56 while(m_gameIsRunning)
57 {
58
59 handled = false;
60 al_wait_for_event(queue,&evt);
61
62 bool hasNext = al_peek_next_event(queue,&next);
63 if(hasNext && next.type == ALLEGRO_EVENT_TIMER)
64 {
65 al_drop_next_event(queue);
66 }
67 //render the scene
68 if(m_needsRedraw && al_is_event_queue_empty(queue))
69 {
70 m_g.begin();
71 m_currentScene->render();
72 if(m_needsTransition)
73 {
74 m_needsTransition = false;
75 m_transitioning = true;
76 }
77 if(m_transitioning)
78 m_g.drawTintedSprite(m_g.getBuffer(),
79 Color(m_transitionOpacity,m_transitionOpacity,m_transitionOpacity,m_transitionOpacity),0,0,0);
80 m_g.end();
81
82 m_needsRedraw = false;
83 }
84
85 defaultBeginEventHandler(&evt);
86 m_currentScene->processEvent(&evt,handled);
87
88 //do default behavior if event was not handled by the scene
89 if (!handled)
90 {
91 defaultEndEventHandler(&evt);
92 }
93 processMessages();
94 }
95 }
96
97 void SceneManager::registerEventSources()
98 {
99 al_register_event_source(queue, al_get_keyboard_event_source());
100 al_register_event_source(queue, al_get_mouse_event_source());
101 al_register_event_source(queue, al_get_timer_event_source(m_gameTimer));
102 al_register_event_source(queue, al_get_display_event_source(m_devices->getDisplay()->getContext()));
103 Log::write("Scene Manager","Event sources registered");
104
105 }
106
107 void SceneManager::sendSceneChangeMessage( SceneEnum scene )
108 {
109 m_nextScene = scene;
110 }
111
112 void SceneManager::sendQuitMessage()
113 {
114 m_gameIsRunning = false;
115 }
116
117 void SceneManager::processMessages()
118 {
119 changeScene();
120 }
121
122 void SceneManager::changeScene()
123 {
124 if(m_nextScene != NO_SCENE)
125 {
126
127 //delete the current scene
128 if(m_currentScene)
129 {
130 if(m_g.isBuffering())
131 {
132 startTransition();
133 }
134
135 m_currentScene->sceneEnd();
136 m_currentScene->deinitBaseScene();
137 delete m_currentScene;
138 m_currentScene = NULL;
139 Log::write("Scene Manager","Scene Ended");
140 }
141
142 m_currentSceneType = m_nextScene;
143
144 //instance the requested scene
145 switch(m_nextScene)
146 {
147 case SceneManagerMessage::GAME:
148 m_currentScene = new GameScene();
149 Log::write("Scene Manager","Changed to Game Scene");
150 break;
151 case SceneManagerMessage::LOBBY:
152 m_currentScene = new LobbyScene();
153 Log::write("Scene Manager","Changed to Lobby Scene");
154 break;
155 case SceneManagerMessage::LOGIN:
156 m_currentScene = new LoginScene();
157 Log::write("Scene Manager","Changed to Login Scene");
158 break;
159 }
160
161 m_nextScene = NO_SCENE;
162
163 //set some class pointers
164 m_currentScene->setGraphics(&m_g);
165 m_currentScene->setDeviceManager(m_devices);
166 m_currentScene->setDynamicUIManager(m_dynamicUI);
167 m_currentScene->setGuiImageManager(&m_guiImageManager);
168 m_currentScene->setSceneMessenger(this);
169 m_currentScene->setGameTimer(m_gameTimer);
170 m_currentScene->initBaseScene();
171 m_currentScene->sceneBegin();
172 m_currentScene->resizeEvent(
173 m_devices->getDisplay()->getResolution().getX(),
174 m_devices->getDisplay()->getResolution().getY());
175 m_currentScene->initialResizeCompleted();
176 m_currentScene->sceneLogic();
177 m_currentScene->logic();
178
179
180 }
181 }
182
183 void SceneManager::defaultEndEventHandler( ALLEGRO_EVENT* evt )
184 {
185 switch(evt->type)
186 {
187 case ALLEGRO_EVENT_DISPLAY_CLOSE:
188
189 sendQuitMessage();
190 break;
191 default:
192 break;
193 }
194 }
195
196 void SceneManager::defaultBeginEventHandler( ALLEGRO_EVENT*evt )
197 {
198
199 m_currentScene->processGuiInputEvent(evt);
200
201 if(evt->type == ALLEGRO_EVENT_TIMER && evt->timer.source == m_gameTimer)
202 {
203 if(m_transitioning)
204 {
205 m_transitionOpacity -= m_transitionRate;
206
207 if(m_transitionOpacity <= 0)
208 {
209 m_transitionOpacity = 0;
210 m_transitioning = false;
211 }
212 }
213 m_needsRedraw = true;
214 m_devices->getAudioManager()->logic();
215 m_currentScene->sceneLogic();
216 m_currentScene->processGuiLogic();
217 m_currentScene->logic();
218 m_devices->getNetClient()->tick();
219 }
220 else if(evt->type == ALLEGRO_EVENT_DISPLAY_RESIZE)
221 {
222 m_needsResize = true;
223 m_newScreenWidth = evt->display.width;
224 m_newScreenHeight = evt->display.height;
225 sendResizeMessage(m_newScreenWidth,m_newScreenHeight);
226 }
227 else if(m_needsResize)
228 {
229 m_needsResize = false;
230
231 }
232 }
233
234 void SceneManager::sendResizeMessage( int w, int h )
235 {
236 //this is a bug in Allegro
237 if(w == 0 && h == 0)
238 {
239 return;
240 }
241
242 al_acknowledge_resize(m_devices->getDisplay()->getContext());
243 m_g.resizeBuffer(w,h);
244 //stop transition
245 m_transitioning = false;
246 m_transitionOpacity = 0.0f;
247 m_currentScene->processGuiResizeEvent();
248 m_currentScene->resizeEvent(
249 w,h);
250 Log::write("Scene Manager","Resize Event: Width:" + StringUtil::toString(w) +
251 " Height:" + StringUtil::toString(h));
252 }
253
254 SceneManagerMessage::SceneEnum SceneManager::getCurrentScene() const
255 {
256 return m_currentSceneType;
257 }
258
259 bool SceneManager::willSceneChange() const
260 {
261 return m_nextScene != NO_SCENE;
262 }
263
264 void SceneManager::startTransition()
265 {
266 m_g.setTargetToBuffer();
267 m_g.begin();
268 m_currentScene->render();
269 m_g.setTargetToBackbuffer();
270 m_transitioning = false;
271 m_needsTransition = true;
272 m_transitionOpacity = 1.0f;
273 }
274
275}
Agui GUI API -> https://github.com/jmasterx/Agui |
Thomas Fjellstrom
Member #476
June 2000
|
I'd probably do something similar to the above. Though the limit of only being able to run a single "scene" at once is a bit arbitrary though it saves a bit on work, and allows certain assumptions. I'd probably have an active "stack". So you could have the game running, but then call up the settings screen which would overlay the game, and not kill the game scene. Or you can get fancier and have an actual scene graph, and have the game view just be a regular view in your graph. -- |
pkrcel
Member #14,001
February 2012
|
I've seen the "state stack" model applied in some game remakes aroudn the web, but I can't say I'm a fan of it. What jmsterx supplied is more along the lines fo how I was doing it, albeit I never managed to get through. It is unlikely that Google shares your distaste for capitalism. - Derezo |
Thomas Fjellstrom
Member #476
June 2000
|
pkrcel said: I've seen the "state stack" model applied in some game remakes aroudn the web, but I can't say I'm a fan of it. And is that the fault of the underlying concept, or the implementation? -- |
jmasterx
Member #11,410
October 2009
|
What I'm currently doing could technically work with multiple scenes in a stack. Much like how it works in iPhone programming, I allow for a single 'RootViewController' and that thing could be a NavigationVC that keeps a stack of scenes in itself. But at that point, might as well have that in the first place via SceneManager. The way I did it works fine for me, but a scene graph sounds way cooler. When I started my game project 4 years ago, I had no idea how to do scenes, and what you see there is what I came up with without researching anything. I might do it differently now if I started over though. The design forced me to create a class called ClientShared where I keep data structures of the state of the lobby, chat etc while the player is in a game so that I don't have to send the client the full state of the lobby when they return. Agui GUI API -> https://github.com/jmasterx/Agui |
pkrcel
Member #14,001
February 2012
|
Thomas Fjellstrom said: And is that the fault of the underlying concept, or the implementation? Ah more about the concept itself, implementaiton mileage may vary....and of cours eI am not experienced enuff to go over what's basicalli my impression. Sure enough, I'd prefer for example a graph like you suggest which in my mind might handle better "scene concurrency" and "realtime passing by". Of course if I think about games of the past (I am nostalgic ) , a scene is actually a "screen" and you VERY RARELY get more than one at a time, and maybe the most common case regardless of game "age" is that you basically have more often than not just two states: game and menu . In such situations I guess popping and pushing states might make sense and be actually easy to implement and maintain, though when I see a moltitude of "screens" and behind the scenes the engine is preparing->pushing->showing->processing->popping states, I find it difficult to follow along the code, which in my mind makes maintenance (by a third party, which I usually am ) a pain. I am aware that I haven't said anything really valuable on the technical point of view It is unlikely that Google shares your distaste for capitalism. - Derezo |
beoran
Member #12,636
March 2011
|
I would normally use a single game loop combined with a state machine for keeping track of the different game states such as start menu, settings, play, pause, etc. If needed the state machine can have a stack to keep track of "previous" states and return to them. For the detailed state, I maintain the GUI state, the engine state and the game state separately in different structs or in the scripting engine. |
Thomas Fjellstrom
Member #476
June 2000
|
beoran said: I would normally use a single game loop combined with a state machine for keeping track of the different game states such as start menu, settings, play, pause, etc. If needed the state machine can have a stack to keep track of "previous" states and return to them. IMO that's the same as a regular old scene state. Just a bit inside out. -- |
Audric
Member #907
January 2001
|
State machine is not adequate if the same screen can be reached from several ways, ex: A lot of game menu systems can be nicely represented by stack: |
beoran
Member #12,636
March 2011
|
Yes, in such a case a stack is needed, combined with a state machine to keep track of the actual state and to forbid disallowed state transitions. |
Thomas Fjellstrom
Member #476
June 2000
|
Which ends up being about the same as a scene stack -- |
Ariesnl
Member #2,902
November 2002
|
You can also have "in game" screens and "out game" screens. Perhaps one day we will find that the human factor is more complicated than space and time (Jean luc Picard) |
|