Allegro.cc - Online Community
Post Reply

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

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.

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

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

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.

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.

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

Post Reply
Go to: