|
How to handle game state changes? |
kenmasters1976
Member #8,794
July 2007
|
Say, for example, that in my game I have a splash screen, a title screen, an options screen, then there's the actual game mode and in-game menus that pause the game mode. What's the best way to handle the communication between these, and possibly other, different game states?. Currently, I'm using ALLEGRO_USER_EVENT to emit custom events into the game's event queue, so I do something like: ALLEGRO_EVENT event; event.type = OPEN_TITLE_SCREEN; ... al_emit_user_event(&user_event_source, &event, NULL); And then every object in the game contains a section in their logic to respond to each possible event. This approach works but I feel like there might be better ways to do it. Any ideas? Thanks.
|
torhu
Member #2,727
September 2002
|
If you have multiple things happening in your game at the same time, you already do this. You just need a hierachy of objects. Let's say you have a Game object, a Menu object, a Chat object, and a Main object. So maybe you can open a menu in a multiplayer game, while still watching the game unfold, while also chatting with someone in the in-game chat system. It's just a matter of abstraction, and how you organize that. |
Mark Oates
Member #1,146
March 2001
|
The approach I prefer (if I have my codebase setup correctly in that project) is to have a global pointer that points to the current_screen. A "screen" is a type of object that is fed allegro events like mouse movements, keyboard inputs and the like, and dispatches them to do certain sub-objects and manages that coordination internally. When a new screen is created (title screen goes to gameplay screen), that pointer is deleted, a new different screen object is created, and assigned to the pointer. All the dependencies are injected into the newly created screen. Here's an example of how it gets switched out: https://github.com/MarkOates/DragonWrath/blob/master/src/DragonWrath/ScreenManager.cpp#L135-L199 (note that index_of_level_to_start should be named something different, enum_val_of_screen_to_start or something like that.) Overall, what I've really found is how you do it really depends on the complexity of game, the programmers on you team, the standards of your codebase, blah blah. Each codebase has different priorities, so I think you should be flexible with how you approach it and use that to judge your architecture's goals. -- |
Edgar Reynaldo
Major Reynaldo
May 2007
|
I manage this with an abstract base class called Scene. Each game screen derives from this and implements certain functions in the base class. After that, you can manage your scenes any way you want. You can have as many or as few scenes as you want, at the same time, or in a certain order, or w/e. 1enum GAME_STATE {
2 READY = 0,
3 RUNNING = 1,
4 COMPLETE = 2
5};
6
7class Scene {
8public :
9 bool Init()=0;
10 void Destroy()=0;
11
12 GAME_STATE HandleEvent(ALLEGRO_EVENT e)=0;
13 void Update(double dt)=0;
14 void Display()=0;
15};
16
17class Intro : public Scene {
18 double tsec;
19 ALLEGRO_BITMAP* bg;
20public :
21 bool Init() {
22 tsec = 3.0;
23 bg = al_load_bitmap("BG.png");
24 assert(bg);
25 return bg;
26 }
27 void Shutdown() {al_destroy_bitmap(bg);bg = 0;}
28
29 GAME_STATE HandleEvent(ALLEGRO_EVENT e) {if (e.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {return COMPLETE;} else if (tsec < 0.0) {return COMPLETE;} else {return RUNNING;}
30 void Update(double dt) {tsec -= dt;}
31 void Display() {
32 al_draw_bitmap(bg);
33 }
34};
35
36class SceneManager {
37 std::list<unique_ptr<Scene> > scene_list;
38public :
39 bool Init();
40 void Shutdown();
41
42 GAME_STATE HandleEvent(ALLEGRO_EVENT e);
43 void Update(double dt);
44 void Display(ALLEGRO_DISPLAY* d);
45}
My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
kenmasters1976
Member #8,794
July 2007
|
Thanks for your replies, these are some interesting ideas; definitely could help in making the game state/screen/scene changes to be handled by a single object rather than my current method where every object in the game has to respond to these changes. Thanks a lot, will surely be useful.
|
Ariesnl
Member #2,902
November 2002
|
I use a state stack, which is a stack of functionpointers. So you can go from game to menu to options etc in any order end easily go back to where you were. Perhaps one day we will find that the human factor is more complicated than space and time (Jean luc Picard) |
Mark Oates
Member #1,146
March 2001
|
Ariesnl said: I use a state stack, which is a stack of functionpointers. So you can go from game to menu to options etc in any order end easily go back to where you were. Interesting. So all of your states are created in memory at the beginning of the game? Have you run into any complicated scenarios with this approach (timers, sound/music, etc?) -- |
Edgar Reynaldo
Major Reynaldo
May 2007
|
It might be a little more versatile to have a list of screens that are visible and active. That way you can have one draw over the other and filter input to the top one. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Chris Katko
Member #1,881
January 2002
|
One mistake people often make: There's nothing wrong with pre-allocating the max memory instead of using dynamic allocations. "But it's a waste" ... and? If someone's computer can't run the game with an array of MAX_OBJECTS, congrats, it was going to crash when they hit MAX_OBJECTS anyway. And with memory fragmentation, dynamic allocations are going to take more space for the same maximum. Unless you're making a game like Factorio which can have literally millions of objects and increases CPU requirements the more you do, there's no need. Paper Mario knows exactly how many enemies can be on the screen at a time by design. -----sig: |
Mark Oates
Member #1,146
March 2001
|
I remember a long time ago looking some of Johan Peitz's code for one of his games. He would create the new enemies at run-time and for some reason that was mind-blowing to me. I had always tried to pre-allocate the entire block of enemies, hold them in memory, and have those objects either in a "paused" state before being shown, or "recycled" into new objects after being destroyed. But, his approach was so much simpler to reason about: When a creature needs to appear in the game, it's created at that time in the system. I had gone so deep into the computer and memory management and performance and all this that it never occurred to me that it didn't really matter to the user. -- |
Chris Katko
Member #1,881
January 2002
|
Nowadays you can get away with allocating every frame. It's just wasteful if you're using POD (plain-old-data) objects because you're invoking the entire operating system for checking page permissions. It's like storing all your files in a folder, verses in a single archive. The reason files in a folder is so insanely slow (see Factorio, KSP, other games with modding) is because it's invoking the entire operating system permissions checks for every single access. Reading 20,000 files is 20,000 permissions checks. Reading 1 archive with 20,000 files, is 1 check. If using new and delete makes it easier to conceptualize your program and get it out the door, by all means do it. Just keep in mind that it "could" become a bottleneck. Tons of people were/are worried that because D uses a garbage collector, the GC may cause stuttering or slowdowns. I was afraid too, and then I got wise and actually bothered to test it--in the worst case. I used the garbage collector to allocate every. single. particle. for a particle engine (bullets, explosions and smoke) for a 2d plane game. I still held 60 FPS on my humble netbook. Now I still prefer static arrays where possible, but it's good to know the GC isn't really the bottleneck people are afraid of. On the otherhand, there's other benefits to static allocation. You can nuke the entire thing with a single memset because it's contiguous. So you can have a block for menu related data, a block for round data, and a block for credits. And when you switch rounds, you delete the main one with a single memset (instead of remembering every single possible object and subobject). You can also have two "game" datasets and switch between them ala page flipping, to do level transitions where the first one is still running while the second one fades in. (*watch out for dangling pointers!!!) Also, if you have dead objects and live objects sharing an array (because it's a static buffer), you could theoretically have a helper thread that runs along every frame, scans a few dozen objects, and sorts them into two buckets of dead vs alive. (ala alive come first in the array then dead.) Even if they're not completely organized, the reduced "switching" of if(alive) means the CPU cache is going to go from "always take this branch" to "never take this branch" instead of flipping back and forth. -----sig: |
Ariesnl
Member #2,902
November 2002
|
<quote name="Mark Oates""> I use a state stack, which is a stack of functionpointers. So you can go from game to menu to options etc in any order end easily go back to where you were. Interesting. So all of your states are created in memory at the beginning of the game? Have you run into any complicated scenarios with this approach (timers, sound/music, etc?) Only the states Playing, Paused, Menu, Options, not the states of playing the game istelf. This causes no problems with threads, timers or anything like that. You can even let the game run while in another state giving a realtime experience while looking at a map by running ( processing) two states ( game and map) and rendering just one (the map) Perhaps one day we will find that the human factor is more complicated than space and time (Jean luc Picard) |
kenmasters1976
Member #8,794
July 2007
|
Chris Katko, that actually makes a lot of sense. Thanks, will keep that in mind. Currently, I use dynamic allocation in my project but, hopefully, it is structured enough to allow for adapting it with little effort to use static allocation instead. [EDIT:] On topic, I'm implementing a basic scene manager. Right now, I've decided that every object in the game communicates with the scene manager only by setting some flags in the current scene. If an action is to be performed, the scene manager is the one in charge of the action and it can manipulate all the objects in the current scene, this minimizes the need for every object to know about other objects in the scene as I was doing it before. This seems to work fine and every object only needs to set the current scene with an ACTION_REQUIRED flag and set a pointer to the object that originated the action. There are two ways to set this flag, either to manipulate the current scene directly or to generate an ALLEGRO_EVENT to be consumed by the scene manager. Both have effectively the same effect so I guess it's just a matter of what method I like more?.
|
|