🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

04.09 - Game States and Variable Scope

Started by
21 comments, last by Teej 23 years ago
From Demos to Games Throughout the last eight articles, we''ve taken a tour of different game elements, each an integral part of game development. Of course, what we''ve written thus far could hardly be considered games. But let''s face it -- you can''t write games unless you can display/animate images, control timing, understand resolutions and colors, and harness DirectX to make all of these work together. Now that we have some skill in these departments, what''s missing? Continuity. That''s what we''re missing. Games don''t just start with players running around and shooting. There''s intro screens, menus, multiple levels, intros/outros and cutscenes, etc. In other words, a game consists of many different components, and all we''ve got so far is our single entry point, Game_Main(). Obviously if we want to write anything robust, we''re going to need a little help. Life in Game_Main() We know that Game_Main() is executed continually, and the approach we''re taking is to render a single frame of animation each time. We also know that the only way to remind ourselves in Game_Main() of what we''re supposed to be doing each frame is by keeping track of everything with static/global variables and other data structures. All that we need to do is add an additional level of organization so that our game not only knows ''how'' to do things, but ''when'' to do things. As an example, say our game consists of an intro screen (something that says, "Press SPACE to begin"), and the actual game where our sprites are moving around, etc. We''d start by thinking of these two components as separate, and assign a new global variable to keep track of where the user is:

enum GameState {GS_INTRO, GS_GAME};
 
// Our G structure in GLOBALS.H
struct
{
    ...
    GameState gameState;
    ...
} G;
 
Somewhere in Game_Initialize() we would initialize this variable like so:

G.gameState = GS_INTRO;
 
...and in Game_Main(), we could branch-off depending on the game state:

void Game_Main()
{
    if (G.gameState == GS_INTRO)
    {
        // Show the intro screen
    }
    else
    {
        // The game -- move sprites
    }
}
 
This is about the simplist mechanism for game states that I could think of. Of course, for this particular example it works fine, so there would be no need to go further. In reality though, we''ll have more than two game states, and this if/else structure could quickly become large and unweildy. How else could we implement this type of ''filter''? How about this:

void Game_Main()
{
    switch (G.gameState)
    {
    case GS_INTRO: GS_Intro(); break;
    case GS_GAME:  GS_Game();  break;
    }
}
 
This approach definitely seems a lot smarter, as it calls a corresponding game function for each possible game state. And, let me tell you, you will not want to be writing entire games in Game_Main() unless they''re very small... Sometimes you might want to use a combination of the two -- a main game state that determines which function gets called, and a secondary, or sub-state variable to act as a filter once in the proper game function. Here''s an example of what I mean:

enum MainGS {MGS_INTRO, MGS_GAME};
enum SubGS  {SGS_MOVING, SGS_SHOOTING, SGS_DYING};
 
// Our G structure in GLOBALS.H
struct
{
    ...
    MainGS gameState;
    SubGS  gameSubState;
    ...
} G;
 
Here, we could use gameState to call the appropriate game function (e.g. using the switch construct), and we could use gameSubState from within the chosen game function as a helper. What you get is two levels of indirection that can help you better organize what code needs to be executed when in order for your game to work properly. Another technique involves using function pointers, which is a powerful programming technique that''s covered in Tricks and Techniques. Sufficive to say, we don''t need anything of these calibre for our immediate purposes. Tracing Your Footsteps The use of enumerated game states is definitely going to help us organize a simple game, and there''s one more game state that we should be keeping track of to make life a little easier on us. It turns out that it''s often important to know not only what state we''re currently in, but what state we''re coming from. To validate this need, take a look at the following:

...
if (G.gameState == GS_DEAD)
{
    // Code to animate player''s death
}
...
 
Let''s assume that the code for this block is responsible for an animation spanning multiple sprite frames controlled by a timer. You see, this block of code is going to be executed repeatedly because the game state GS_DEAD will be the current game state for the whole time the player is seen crouching over and dying, and this means that there will be variables that need to be initialized. One way to accomplish this is to add a little logic like so:

...
if (G.gameState == GS_DEAD)
{
    if (G.prevGameState != GS_DEAD)
    {
        // Initialize local variables
        // ...
        G.prevGameState = GS_DEAD;
    }
    
    // Code to animate player''s death
}
...
 
The first time this code is executed, it knows that it''s the first time and therefore can initialize any local variables it needs to render the player''s death properly. Once this code has completed the sequence, it will change the current game state to something other than GS_DEAD. The new game state can then in turn utilize this initialization technique as well. Of course, there are other ways to do this:

...
if (G.gameState == GS_DEAD)
{
    static bool bInit = FALSE;
    
    if (bInit == FALSE)
    {
        // Initialize local variables
        // ...
        bInit = TRUE;
    }
    
    // Code to animate player''s death
    
    // Are we done with this game state?
    if (bDeathSequenceComplete == TRUE)
    {
        bInit = FALSE;
        G.gameState = GS_SOMETHINGELSE;
    }
}
...
 
Here we''re using a static variable, just like we did with Game_Main() in earlier articles. When this game state is complete, it''s important to reset the bInit flag so that later on in the game if GS_DEAD starts again, it will once again be initialized properly. Variable Scope When talking about game states, I alluded to the fact that large games need more than one main function. By the same token, large games should use more than one source code file for their game code as well. Luckily for us, our global G structure is designed so that any source file (module) can automatically ''see'' these global variables. All that you have to do is create a new source file and make sure this is the top:

#include "Globals.h"
 
You can then start writing functions inside of this new source file that work with your global variables, thereby contributing to the game. When working with multiple modules, it sometimes becomes important to further organize variables, as games can end up with a lot of them. Consider this example source file:

// EXAMPLE.CPP
 
#include "Globals.h"
 
static struct
{
    int xpos, ypos;
    int spriteState;
} L;
 
void Function1()
{
    int local1;
    // ...
}
 
void Function2()
{
    bool bLocal;
    // ...
}
 
Bacause of the #include at the top of the file, this file can freely make use of our G structure. At the top of this file is another structure called L, which stands for LOCAL, and holds some variables that only functions in this file can use. There are two reasons for grouping variables in this way:
  • Using L gives us the look and feel of C++ classes, where all variables that belong to the module exist. If we combine all of the functions with a common purpose together with the variables they share, we have a clean and intuitive source code arrangement to work with.
  • When reading the source code, one can easily distinguish which variables are local to the function, and which are also used by other functions in the module (i.e. there''s an ''L.'' before them).
Putting everything together, we get a template like this:

G
{
    FILE1
    {
        L
        {
            Function1
            {
                local (stack) variables
            }
            Function2
            {
                local (stack) variables
            }
            ...
        }
    }
    FILE2
    {
        L
        {
            Function10
            {
                local (stack) variables
            }
            ...
        }
    }
    ...
}
 
This illustration shows that G is visible by everything, and L is visible to the file in which it resides. In order to realize the benefits of this system of data organization, you have to be sure to properly organize your variables:
  • If a variable is only needed by a single function, create it within the function
  • If a variable is needed by more than one function,
    1. if the functions seem like they belong to a group, group them to a file and place the variable in an L structure (i.e. file scope)
    2. if the functions are in different files and shouldn''t be moved, place the variable in G
Hopefully this already makes logical sense to you, and I should point out that we haven''t been using this system in our template code thus far, but then again it hasn''t been necessary to either as our template is small and only has one run-time game module (GAMEMAIN.CPP). When it''s time to start working with L structures, you''ll know it -- your code will be too large and unmanageable without it. The End of the Introduction Well folks, that about wraps up this series! I was supposed to introduce everyone to a simple base code template, and that''s what I did. Furthermore, I threw in introductory material on some other important elements, so I think that if you''ve come this far, you''ve done well indeed. I wish that I had in front of me a ''master list'' of all of the skills we''ll be covering, but I don''t. Instead, we''ll have to continue picking away at things in increasing complexity, and the best way to do that is to practice by writing actual games. After all, that''s what we''re all here for. Therefore, we''ll be starting on a real game in the next article. That should make a lot of you happy I hope that everyone can appreciate the need to start small; there are people out there who tackle 3D engines as their first true game development endeavour, and I can''t help but feel sad for some of them because they''re missing the skills that makes a game developer great -- grass-roots evolutionary skill development. I thoroughly believe that if you want to really learn to develop games, you need to follow the same hard road that those before us have built with their determination, trials and tribulations. As games evolved, so too did the techniques that made them. It would seem logical that the earliest games are the simplest to analyse, and by tracing their evolution and taking on the skills that were developed along the way, we too will inheret the experiences and talent of their creators, and hopefully their fame. A special note to those who were here the first time the forum existed -- you''re going to recognize the first game we attempt, but I''ve rewritten the code and all of the articles, so it wouldn''t hurt to go through them again. Eventually, we''ll all be breaking new ground, so bear with me. Teej Questions? Comments? Please reply to this topic.
Advertisement
I''m not trying to bother everyone by posting this here, but maybe since it''s at the top I''ll get a responce. My problem is that I don''t have VC++ or the money to buy it, although I do have Dev C++ and Borland 5.5. If you have used either of these to compile to sample project successfully please e-mail me and tell me how you did it. I hope to eventually get VC++, but at the moment I don''t have the money.
Help!!!! Teej''s website with needed materials won''t pull up. It''s not just me because I tried it at work also. Anyone know what the deal is? Teej, if you see this then you, of course, would be able to provide the best answer. Hopefully you''ll check back today since this is a new topic. Any help at all would be accepted with open arms, though.
The anonymous was me.

"Those who are skilled in combat do not become angered, those who are skilled at winning do not become afraid. Thus the wise win before they fight, while the ignorant fight to win."
"Those who are skilled in combat do not become angered, those who are skilled at winning do not become afraid. Thus the wise win before they fight, while the ignorant fight to win."
Anon,

I''ve got the same problem as you: I haven''t been able to pull up Teej''s website. I always get a "Page Not Found" screen. Like yourself, I need to get the source files off the site. I hope Teej fixes that link.

Carlos
For those having trouble accessing his site, I believe Teej has moved to a new location:

http://www.angelfire.com/home/gamedev

Edited by - DTM on June 26, 2001 10:28:07 PM
Thanks a lot! I''ve got the files now.

"Those who are skilled in combat do not become angered, those who are skilled at winning do not become afraid. Thus the wise win before they fight, while the ignorant fight to win."
"Those who are skilled in combat do not become angered, those who are skilled at winning do not become afraid. Thus the wise win before they fight, while the ignorant fight to win."
good job so far Teej. I really am enjoying playing around with the information I''ve gained through this tutorial along with a few other resources. I think you''re right about not starting out with a grandiose 3D project, though I think there is something to be said for finding your own methods and/or finding time-tested methods through your own hard work, patience and/or logic. Anyway, I just wanted to reiterate that you''re doing a great job.
What is the deal with using non bmp files as our image files? I have a background (contains 296 colours) I would like to use for a program I am working on, and if I make a gif with it is around 150kbs, and if it is a bmp it is around 400 kbs, quite a difference really.

So are we really confined to using bmps or can we use any image files for our sources on DirectX.
You can use any format you want, as long as you write/find the code to load it. Bitmaps are easiest since they aren''t encoded or compressed in any way and are sort of the "official" windows format. But if you have code to load a JPG, GIF, TGA, whatever, go right ahead and plug it in.

Anthracks

This topic is closed to new replies.

Advertisement