Advertisement

Game State Staging

Started by April 15, 2022 07:06 PM
16 comments, last by C-Research 2 years, 6 months ago

Question: What is standard practice for organizing game code modularity according to Game State:

Game State Staging

Splash Screen - loading and initialization / Main Menu / Game Configuration Menu - opponent side has been chosen in Main Menu, further options for this side are selected here / Game HUD - ingame options / Exit window, score, rewards, and credits

I am using CMake, C libraries, cpp, and soon adding LUA next to script the game and handle UI management.

I noticed that some indie game developers are using several source files (main.cpp, game.cpp, menu.cpp, etc..)

Others are sticking all the cpp in a single main.cpp and with the single header file to wrap it all up. This seems fine for smaller games, but that does not seem to be massively scalable.

Where I am: Know how to use a graphics library and other libs to render 3D in a window, make a simple menu, do very simple scripting of 3D object, and dabbling in different styles of coding modularity. Using CMake for configuring Make files, I know how to create submodules / subdirectories, classes, pointers, define objects, etc.. (though using pointers sparingly, for known reasons), installing on Linux and creating an installer for Windows… … pretty much the fundamentals of cpp programming.

All hail C, Alfather of the Codes

First let me preface this with a warning, I'm not a professional Gamedev (working as System Developer/Programmer Measurement Tech) and only worked on my own time on games. And English is not my Native tongue.

After a few years of trying different methods I arrived at this method and prefer it (every game state got its own h and cpp files):
- Firstly abstract the Game State so you can inherit from it
- put the base game state files in a Game State folder, or how you want to call it.
- create subfolder for your inherited Game State and drop them in there
- then decide if you need access to all Game States always, in other words do you need to #include all inherited Game States in an separate header file so you can just include 1 file and got all states (can bloat your program size for obvious reasons)
- if you use the consolidated header, drop it in the same folder as your base game state

I hope this helps at least as inspiration.
Have a nice day o/

“It's a cruel and random world, but the chaos is all so beautiful.”
― Hiromu Arakawa

Advertisement

This varies wildly by game kind, but there are some standard patterns.

A game loop of any kind typically has three phases:

  1. Receive input (controller/keyboard/mouse/network)
  2. Update simulation
  3. Render/present/play sounds – this may in turn be two steps, “update animation state” and “draw stuff.”

Typically, there will be a stack of active … things, that receive these callbacks. Thus, you may have a main game mode, and then a character-equipment menu on top of that, and then an in-game settings menu on top of that. Each of them would receive each callback, but have the ability to mutate the arguments passed along to the next state. For example, an in-game menu might set the “time to advance physics by” to 0, to pause the game while in the menu. Also, it may consume and zero out any input events, so they only go to control the menu, not the underlying game.

Additionally, these receivers will typically want to know when they “become active” and “become inactive.”

So, the “major game state manager” API might look something like:

class IGameState {
public:
    virtual void OnBecomeActive(IGameState *parent) = 0;
    virtual void OnBecomeTopmost() = 0;
    virtual void OnBecomeObscured() = 0;
    virtual void OnBecomeInactive() = 0;
    virtual void OnReceiveInput(KeyboardInput &kb, MouseInput &mi, ControllerInput &ci, NetworkInput &ni) = 0;
    virtual void OnUpdateSimulation(Time &simDeltaTime) = 0;
    virtual void OnUpdateAnimations(Time &animDeltaTime) = 0;
    virtual void OnIssueRenderingCommands(Time renderDeltaTime) = 0;

	virtual ~IGameState() {}
};

class GameStateStack {
public:
    // pop each current state on the stack, calling its OnBecomeInactive(), then
    // push this state at the bottom, calling its OnBecomeActive() with no parent.
    // Then call OnBecomeTopmost() on it.
    void SwitchToBaseState(IGameState *newBase);
    
    // call OnBecomeObscured() on the current top.
    // call OnBecomeActive() on this new state, passing the current head as parent,
    // then call OnBecomeTopmost() on it.
    void PushGameState(IGameState *newTop);
    
    // call OnBecomeInactive() on the top state, and OnBecomeTopmost() on the state
    // below it on the stack, which becomes the top.
    void PopGameState();
    
    // Call OnReceiveInput() on each state, starting at the topmost
    void SendReceiveInput(KeyboardInput &kb, MouseInput &mi, ControllerInput &ci, NetworkInput &ni);
    
    // Call OnUpdateSimulation() on each state, starting at the topmost
    void SendUpdateSimulation(Time &simDeltaTime);
    
    //	Call OnUpdateAnimations() on each state, starting at the topmost
    void SendUpdateAnimations(Time &animDeltaTime);
    
    //	Call OnIssueRenderingCommands() on each state, STARTING AT THE BOTTOM-MOST
    void SendIssueRenderingCommands(Time renderDeltaTime);
    
    std::vector<IGameState *> stateStack_;
};

Typically, you create one of each kind of screen ("main menu," “settings menu,” “matchmaking menu,” “main gameplay,” “character configuration,” …)

Then you'd push/pop the appropriate state at the appropriate time, and they'd each take care of doing the right thing. You might also want to add some asserts that a state that's already on the stack, can't be pushed on the stack again, and such.

When switching “major mode” (such as selecting “exit game” from the in-game pause menu,) you'll use SwitchToBaseState() to nuke all the states that are on the current stack, to jump straight to the new main state.

If you want fancy transitions with slide-in/slide-out, background asynchronous loading, and so on, you might want to make this API fancier; specifically, you might want a queue of states that are transitioning out, and they'd get a special “OnRenderTransitionOut()” callback until they're done. Transition-in could be handled by the main render function, or you could have a callback for that, too. All of those bits can be done many ways, and might not be necessary for Your First Game, though!

enum Bool { True, False, FileNotFound };

Ultraporing said:
then decide if you need access to all Game States always, in other words do you need to #include all inherited Game States in an separate header file so you can just include 1 file and got all states (can bloat your program size for obvious reasons)

Previously, I considered this as a way to #include Lua scripts for game scripting in an external lib. My reasoning is to search for an efficient way for a Lua game programmer to affect game state “in real time” (meaning, no need to recompile nor even restart the game, for the script programmer to access game state content).

I have a vague ability to make a simple external lib, accessed by Dear ImGUI, to affect main game state, but so far only in C++. After a few weeks of research, I am still searching for a way to enable Lua programming of game script (instead of wordy C++) without the game programmer having to even look at the underlying CPP code. Intuitively, I believe this can be connected in a header file. The CPP side of the #include, I understand; it is the Lua side which eludes me.

All hail C, Alfather of the Codes

@hplus0603 Wow. I learned a lot on the first reading of your post and much more the second time. Over the weekend, I revisited my focus on stacks - interesting coincidence here. My goal is to limit my game state stacks to a total of 3, even if I want to scale massively in the future.

Is this an unreasonable expectation by me? I should mention that I so far have put each type of game content in its own stack. For example, 3D objects have their own stack with calls on textures, images such as billboards have their own stack, sounds have their own stack. At this point, my very simple game program has no need for dynamic allocation, but as it grows it will!

I know almost nothing about dynamic allocation, but obviously it is on the horizon and I am approaching it fast. My main concern is not the coding details but how it relates to game state. Keep in mind that the search for Lua real time game script programming is at the forefront with a few other major issues (with no need to recompile nor even restart the game in order to program in game script, but merely return to menu).

My plan for this week is to look at Lua game scripting documentation and examples all week. By the weekend I will probably now if I need to purchase a Lua game scripting publication or not. I prefer to blaze my way forth with more independence than that, but I shall see soon enough.

All hail C, Alfather of the Codes

C-Research said:

Previously, I considered this as a way to #include Lua scripts for game scripting in an external lib. My reasoning is to search for an efficient way for a Lua game programmer to affect game state “in real time” (meaning, no need to recompile nor even restart the game, for the script programmer to access game state content).

I have a vague ability to make a simple external lib, accessed by Dear ImGUI, to affect main game state, but so far only in C++. After a few weeks of research, I am still searching for a way to enable Lua programming of game script (instead of wordy C++) without the game programmer having to even look at the underlying CPP code. Intuitively, I believe this can be connected in a header file. The CPP side of the #include, I understand; it is the Lua side which eludes me.

I found 2 different Guides/Projects for C++ Lua bindings.
Blog & GitHub Project: https://github.com/Lunderberg/lua-bindings​

Blog from the Git Project: http://www.lunderberg.com/2015/03/31/lua-bindings_part-1_global-variables/

Short Geeks for Geeks Tut: https://www.geeksforgeeks.org/integrating-lua-in-cpp/

This small c++ gameengine with lua component scripting on Github looks like a great resource for research too: https://github.com/0xc0dec/solo

Sorry About that, I fixed the post and links. this stupid editor hates me and messes my posts up sometimes on save.​

“It's a cruel and random world, but the chaos is all so beautiful.”
― Hiromu Arakawa

Advertisement

@Ultraporing I found 2 different Guides/Projects for C++ Lua bindings.

EDITED:

I speed read the readme and noticed:

LuaState::LoadLibs and LuaState::LoadSafeLibs

Instantly, I knew that LoadLibs could be used by me in the backend development with the game engine, while the resulting enablement of LoadSafeLibs allows the game script programmer access to external libs which call on allocated asset stacks.

Just what I need!

The lib binds Lua to C++ 11. I have been using C++14 mostly and about to use some C++17 for a particular library. Compiling different versions of C++ by separate .cpp files has been the way which I integrated them in the game engine. The main.cpp of the engine is in C++14 because so many libraries are exactly supported in it.

Does anybody reading this have any thoughts about the preferred versions of C++ for the game states? I settled on C++ 14 for the game engine main and the game loop main.

I will need to make a flowchart just to figure out how versions of C++ & Lua with corresponding libraries of my choice all relate to game states. The game state manager API might be a tricky one in terms of selecting a version of C++. The header files solve the issue of connecting different versions of CPP if I continue using designated .cpp files, so no concern there.

All hail C, Alfather of the Codes

@hplus0603 At first thought, it seems that the logical place to put the game state (manager) API would be in the game engine main and linked to internal libraries of game states in the game main directory. Is this in line with what you are saying?

Of course, no need to show me code - I got that covered. I see from some online examples of game state APIs that some coders put them in the game main and some who program engines, too, put them in the game engine main but others do not do this.

C++ is extremely flexible, technically allowing any one of these, but I am always concerned with the scalability of game state. Preventing memory leaks and avoiding “spaghetti coding” are high priority. This is why I asked.

You guys are HUGE help so far, by the way!

All hail C, Alfather of the Codes

C-Research said:

@hplus0603 At first thought, it seems that the logical place to put the game state (manager) API would be in the game engine main and linked to internal libraries of game states in the game main directory. Is this in line with what you are saying?

Of course, no need to show me code - I got that covered. I see from some online examples of game state APIs that some coders put them in the game main and some who program engines, too, put them in the game engine main but others do not do this.

C++ is extremely flexible, technically allowing any one of these, but I am allows concerned with the scalability of game state. Preventing memory leaks and avoiding “spaghetti coding” are high priority. This is why I asked.

You guys are HUGE help so far, by the way!

Here are some thoughts.

I would put the game state manager and API into the same lib as my generic engine or just create a new lib for it which only handles game states and binding related stuff.
Both have pros and cons.

The modular approach of splitting the bindings and manager from the rest of the game and the game lib is in my opinion easier to maintain in the long run because it is not deeply integrated into the rest of the engine and its functionalities. But can make it difficult to access engine/game specific things and needs a solution if this is needed.
Just an idea never did this so I got no experience but you could maybe export the memory addresses of the specific functions ect you need on compile of the engine/game and load it in as a file or something so you can just drop in a new mapping file and your LUA scripts can use it without you explicitly exposing them. Autogenerating Wrapper files exposing structures/functions/ect you flagged in the game/engine code, to compile in your Manager lib for less work when maintaining and updating based on new changes.

The more integrated approach of putting it into your game or its lib which contains the bulk of the logic that needs accessing. As you can probably guess already, this makes it easier to expose and access game related functionality. But you may have to rebuild the game/engine every time you extend what your LUA/Game manager can do.

C-Research said:
The lib binds Lua to C++ 11. I have been using C++14 mostly and about to use some C++17 for a particular library. Compiling different versions of C++ by separate .cpp files has been the way which I integrated them in the game engine. The main.cpp of the engine is in C++14 because so many libraries are exactly supported in it. Does anybody reading this have any thoughts about the preferred versions of C++ for the game states? I settled on C++ 14 for the game engine main and the game loop main. I will need to make a flowchart just to figure out how versions of C++ & Lua with corresponding libraries of my choice all relate to game states. The game state manager API might be a tricky one in terms of selecting a version of C++. The header files solve the issue of connecting different versions of CPP if I continue using designated .cpp files, so no concern there.

If I were in your shoes I would try to figure out which C++ version you need and prefer and try to recompile if possible the external libraries which use an older version to the newer one. Even If I would need to update it by hand, just so all the code is consistent and you don't have a flowchart hell^^.

“It's a cruel and random world, but the chaos is all so beautiful.”
― Hiromu Arakawa

@Ultraporing The great thing about standard C / C++ compilers is that they will grab any version of source CPP and let you know exactly where you are using a different version of CPP, usually with a coding suggestion or hint from the editor or IDE, too. Sometimes code correction in editor fixes it automatically if it is a small difference between versions. Nice how compilers and editors compliment each other that way!

When a large amount of version differences are involved, obviously some other major solution is needed. The challenge is to use third party libraries which were developed on older CPP compared with newer versions of C++. I lack enough experience to reconcile differences in some cases, though I learned very earlier to be more prevention minded. Much of this involves selecting third party libraries which are based on C99 (still generally a subset of any version of C++ since 1999, maybe with a few little bugs to fix), or be biased toward C++ libraries where the developer claims standard (C-like) CPP.

Until now, no major problems but I see them coming if I stay on the same strategy.

What I have done recently is to stay in line with the documentation, readme's, and tutorials, since using a different version of C++ sometimes breaks my code with no fix after much reading of errors and debugging. If I had more of my very limited time available, then I would work through all of them. Of course, this is not a problem if I only have a couple or so. As my source codes become larger, the risks are growing exponentially.

One time I got around 700 error messages from problems in only 3 blocks of code! I was fortunate to solve most of it by changing the version of CMake, otherwise it would have probably caused me to delete a module and start coding again from the beginning. I sort of dodged a bullet of my own making! It was a lesson in experience that I need to pay attention to version of C++ more closely in the future.

Summary of this post, it is very challenging to use a different version of C++ than was used by the developer of a library! Added to the complexity, some libraries were based on different versions of CPP.

This is one of many reasons why C++ developers want the growth of the language to freeze and actually did so a long time ago. I still find ways to very productive in the language.

All hail C, Alfather of the Codes

This topic is closed to new replies.

Advertisement