Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » How to handle game state changes?

This thread is locked; no one can reply to it. rss feed Print
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
avatar

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
avatar

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.

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

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.

#SelectExpand
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}

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
avatar

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.
Easy to implement and works great :-)

Perhaps one day we will find that the human factor is more complicated than space and time (Jean luc Picard)
Current project: [Star Trek Project ] Join if you want ;-)

Mark Oates
Member #1,146
March 2001
avatar

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?)

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Chris Katko
Member #1,881
January 2002
avatar

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:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Mark Oates
Member #1,146
March 2001
avatar

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.

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

Chris Katko
Member #1,881
January 2002
avatar

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:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Ariesnl
Member #2,902
November 2002
avatar

<quote name="Mark Oates"">
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?)
</quote>

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)
Current project: [Star Trek Project ] Join if you want ;-)

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?.

Go to: