Archived

This topic is now archived and is closed to further replies.

Teej

04.09 - Game States and Variable Scope

Recommended Posts

Teej    176
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.

Share this post


Link to post
Share on other sites
brandon6684    123
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.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
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.

Share this post


Link to post
Share on other sites
Zhuge_Liang    122
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."

Share this post


Link to post
Share on other sites
Carlos26    122
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

Share this post


Link to post
Share on other sites
Zhuge_Liang    122
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."

Share this post


Link to post
Share on other sites
RandomGamer    122
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.

Share this post


Link to post
Share on other sites
leggyguy    128
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.

Share this post


Link to post
Share on other sites
Anthracks    122
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

Share this post


Link to post
Share on other sites
DracosX    128
In response to the Image format question,

Bitmaps are native to windows, and are directly supported by the Win32 API. I think Teej is just using them to make it simpler to understand the workings of the actual Game without worrying about the details of image loading with other formats.

Myself, I use PNG images for most of my programming needs. If you're interested in PNG, check out http://www.libpng.org/pub/png/ (I think) I'll change that link if I'm wrong, (It's been a while since I downloaded it.)

Seriously though, if you're just wanting to learn how to make the game, just stick with bitmaps for now, since you won't need any extra libraries or any other details of loading different formats. If you're feeling up to it though, give it a shot. That's really the only way to learn, right?



Good luck,

DracosX

      // Edit:  The link was wrong.  My bad. :P    


<Edit>
<Irony>
So quickly I boast. Just today, my little brother deleted my PNGLoader library along with libpng AND my source!

I guess now, I'm forced to re-work it out.
</Irony>
</Edit>



Edited by - DracosX on July 4, 2001 3:35:57 AM

Share this post


Link to post
Share on other sites
ManOCheese    122
I have an intersting (I think) question. If we have a gif of 150k and a bmp of 400k am I right in thinking that we are only saving disk space and not memory? Unless we''re compressing to drop our overall colour depth?

Just a thought while were waiting for more instruction

I''m really enjoying doing this btw Teej and thanx to everyone else whose joining in.

Share this post


Link to post
Share on other sites
Colba    122
Just wondering why it is we are doing everything in C and then trying to give it the "look and feel of C++ classes". I''m assuming its the classic C vs C++ performance arguement - but does it really hold and does the performance improvement justify the coding complexity? Apart from that, having a blast and keep up the great work Teej

Colba Juby Woka Peterson

Share this post


Link to post
Share on other sites
videns    122
You know Teej, I find putting an enum in every module that I'm going to use globals.h in to be a bit of a pain. I could make it global scope in globals, but if you do that, you get a "defined multiple times" error during linking. So, what I found worked was to put the enum definition as part of the G struct and then reference it as G.GS_INTRO. Am I overlooking something simple here? Have others had this problem as well?

videns

Edited by - videns on July 2, 2001 10:52:01 PM

Share this post


Link to post
Share on other sites
FragLegs    122
Videns,

Try this:

#ifndef GLOBALS_OWNERSHIP
extern
#endif

enum GAME_STATE {GAME_NULL, GAME_INTRO, GAME_PLAY, GAME_END};

The ifndef/extern trick should solve your multiple definitions problem.

Share this post


Link to post
Share on other sites
Lowas    122

Videns, I think your solution is great.
It''s better to put the enum in the G struct than just globally.

You might even think of making a new Global struct of enum GAMESTATES and enum of LEVELS, INTROSCREEN, CREDITS, PAUSE, SAVE, LOAD or whatever =).

Share this post


Link to post
Share on other sites
videns    122
FragLegs,
Yep I have this in Globals.h:
#ifndef GLOBALS_OWNERSHIP
extern
#endif
enum GameState {GS_INTRO, GS_GAME};

and then use the define in GameMain.cpp:
#define GLOBALS_OWNERSHIP

but here''s what I see during the link:
GameMain.obj : error LNK2005: "struct __unnamed G" (?G@@3U__unnamed@@A) already defined in Ball.obj

which I get for every module that I include a Globals.h in.
I''m beginning to wonder if my linker has an incorrect flag set.

Lowas,
That''s a cool idea. It might be nice to then put those in one .h file to simplify the whole dealie.

Share this post


Link to post
Share on other sites
DracosX    128
quote:
Original post by ManOCheese
I have an intersting (I think) question. If we have a gif of 150k and a bmp of 400k am I right in thinking that we are only saving disk space and not memory? Unless we're compressing to drop our overall colour depth?



You Are correct! But then again, when you have a lot of image files, it kinda works out to be worth it though.

In my last game, I had 13 Megs of Bmp images. After switching to PNG, I only had about 600k. Nice.

quote:

FragLegs,
Yep I have this in Globals.h:
#ifndef GLOBALS_OWNERSHIP
extern
#endif
enum GameState {GS_INTRO, GS_GAME};

and then use the define in GameMain.cpp:
#define GLOBALS_OWNERSHIP

but here's what I see during the link:
GameMain.obj : error LNK2005: "struct __unnamed G" (?G@@3U__unnamed@@A) already defined in Ball.obj

which I get for every module that I include a Globals.h in.
I'm beginning to wonder if my linker has an incorrect flag set.

Lowas,
That's a cool idea. It might be nice to then put those in one .h file to simplify the whole dealie.




Check to see if you're definine GLOBALS_OWNERSHIP in your Ball.cpp file. as well as in GameMain.cpp. You only need the definition in one of your source files.


DracosX



Edited by - DracosX on July 4, 2001 3:41:47 AM

Share this post


Link to post
Share on other sites
ManOCheese    122
Yey, I love being right. I've had a look at the PNG info and it looks like good idea. I'm gonna do the same with my sound files aswell by using FMOD, which I used in Quake2 to add mp3 support (bgm only tho).

Edited by - manocheese on July 4, 2001 5:43:36 AM

Share this post


Link to post
Share on other sites
videns    122
Got it!

I figured out my link problem.

I had this:
#ifndef GLOBALS_OWNERSHIP
extern
#endif

enum GameState {GS_INTRO, GS_GAME, GS_GAMEOVER};

struct
{
...
}G;

This is WRONG. The extern keyword evaluates (from the compilers point of view) as "extern enum GamesState" when what we really want is "extern struct..."

So I put the enum line above the #ifndef GLOBALS_OWNERSHIP line and now all is well.

Thanks,
videns

Share this post


Link to post
Share on other sites
Lord Maz    110
Hi, I''m having a bit of a problem with my code here.. I''m trying to make a simple starting screen before the game starts, the screen works but the program doesn''t respond to the keybord input.. here''s the code:

void Game_Main()
{

if (G.gameState == GS_INTRO)
{
HRESULT hRet;

EraseBackground();

DIPROPDWORD dipdw;
DIDEVICEOBJECTDATA ddod[10];
DWORD dwor = 10;

dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = 10;

G.lpDIKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

while (hRet = G.lpDIKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), ddod, &dwor, 0) == DIERR_INPUTLOST)
if (FAILED(hRet = G.lpDIKeyboard->Acquire())) break;

if (KEYDOWN(DIK_SPACE))
G.gameState = GS_GAME;

MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);

RECT rectSrc, rectDest;

rectSrc.left = 1;
rectSrc.top = 65;
rectSrc.right = rectSrc.left + STARTSC_WIDTH;
rectSrc.bottom = rectSrc.top + STARTSC_HEIGHT;

rectDest.left = SCREEN_WIDTH/2 - (STARTSC_WIDTH/2);
rectDest.top = SCREEN_HEIGHT/2 - (STARTSC_HEIGHT/2);
rectDest.right = rectDest.left + STARTSC_WIDTH;
rectDest.bottom = rectDest.top + STARTSC_HEIGHT;

G.lpDDSBack->Blt(&rectDest, G.lpDDSRes, &rectSrc, DDBLT_KEYSRC | DDBLT_WAIT, NULL);

G.lpDDSPrimary->Flip(NULL, 0);

}
else
{ //The normal game function should be here later
EraseBackground();
}
}

I guess it''s the DI stuff where something isn''t working, I''m not completly sure how buffered data works and so on.. Thank you for your help,

-Lord Maz-

Share this post


Link to post
Share on other sites
Teej    176
Lord Maz: Hmm, you seem to be mis-matching DirectInput styles. The two modes of gathering input are (1) buffered, and (2) immediate. You''re setting up a DIDEVICEOBJECTDATA array to recieve buffered data ''packets'', but then you''re checking for the SPACE bar as if you have immediate data:

if (KEYDOWN(DIK_SPACE))

The KEYDOWN() macro checks the KeyState array for which keys are currently pressed, and this array is populated by DirectInput when you call GetDeviceState() on the keyboard object.

First, I guess that you need to make a decision: buffered or immediate data. With buffered data, DirectX sends us packets for every key that''s pressed, and with immediate data, we get a snapshot of the state of each key on the keyboard. Of course, you should learn to use both, but currently we''re using immediate data in our first project, so you may want to follow along and get the hang of it first.

Like I''ve said, immediate data is the simpler method to implement. First, we create an array of 256 UCHARS (unsigned characters) in our G structure:

UCHAR KeyState[256];

Then, we initialize DirectInput just as it is in the BASECODE templates. When it''s time to gather input, we make the following call:

while (hRet = G.lpDIKeyboard->GetDeviceState(256, G.KeyState) == DIERR_INPUTLOST)
{
if (FAILED(hRet = G.lpDIKeyboard->Acquire())) break;
}

This fills in our KeyState array. Now, you can go ahead and check for keys being down with the KEYDOWN() macro.

Always remember that the DirectX SDK has very decent documentation on using DirectX components, so you should take a trip there when you have a moment to spare. Also, BASECODE templates have working immediate data code.

Hope that helps,

Teej



Share this post


Link to post
Share on other sites