Game from Scratch C++ Edition Part 2

 

 

Alright, code time! In this part we are going to setup the general framework for our application. Pang! is going to be state driven, which means at any given time the game is in exactly one state. It is by changing states that we control program flow. It also makes certain tasks like pausing the game trivially simple. On the bright side, it’s all actually pretty easy to understand.

 

 

First off we want to open pang.cpp that Visual C++ automatically generated for us and make a few very small changes.

 

 

// pang.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "Game.h" int _tmain(int argc, _TCHAR* argv[]) { Game::Start(); return 0; }

Click here to download Pang.cpp

 

 

Add lines 6 and 10 to your copy of pang.cpp. Line 6 simply says to include the “Game.h” include file, we will be creating in just a few seconds. Line 10 calls the static method Start() which we are also about to create in a few minutes.

 

 

Optional information

 

But lets look at a couple Microsoftisms before we continue.

 

First, notice that your project already includes a stdafx.cpp file and on line 5 includes stdafx.h? This is Microsoft’s way of supporting precompiled headers.

 

 

What are precompiled headers?

 

Some of the header files you include in your project such as Windows.h or the various SFML .hpp files can become rather large but rarely ever change. To speed up the compilation process, you put all of these header files in stdafx.h and then include stdafx.h as the very first item in every cpp file. It actually makes header management a bit easier and makes the compilation process a whole bunch faster. If you don’t want to use stdafx, you can turn it off as a project setting ( or don’t select “Precompiled Headers” when you create your project ). For the record, with a file extension change, GNU C++ supports stdafx, so portability shouldn’t be an issue.

 

Second, you may notice that main looks different than what you expect. _tmain is a Microsoft provided macro that enables you to support UNICODE if possible or falls back to traditional main if you don’t. If you wish, you can replace _tmain with the more traditional

 

int main(int argc, char** argv)

 

if you prefer. If you aren’t compiling using a compiler other than Visual C++, there really is no point. One last quick note, if you ever see a function prefixed with an underscore such as _tmain, that indicates a vendor specific or non-standard extension that may not be portable cross compilers.

 

 

Now that you’ve made those changes, we need to create Game.cpp and Game.h.

 

 

In Solution Explorer right click Source Files and select Add->New Item… in the dialog that popped up select C++ File (.cpp) , fill in the name as Game.cpp and click Add.

 

 

Now we create the header file using almost exactly the same process. Right click Header Files and select Add-New Item.. , this time selecting Header File (.h) , fill in the name as Game.h and click Add.

 

 

Optional information

 

 
Why didn't we use Add Class?

We could have but the truth is, it is pretty buggy. First and foremost, it reserves all of the MFC datatype names even if you aren't using MFC. Want to see it for yourself, Right click Source Files and choose Add Class and try to add one named MenuItem. Annoying.

 

 

 

Alright, now that we have our two new source files, open up Game.h and enter the following code:

 

#pragma once #include "SFML/Window.hpp" #include "SFML/Graphics.hpp" class Game { public: static void Start(); private: static bool IsExiting(); static void GameLoop(); enum GameState { Uninitialized, ShowingSplash, Paused, ShowingMenu, Playing, Exiting }; static GameState _gameState; static sf::RenderWindow _mainWindow; };
 
 
 
First thing you might notice is that the class is entirely static.  You may be asking yourself, doesn’t that make it global?  Well frankly, yes it does.  Every public member of Game is globally available and this is actually what I want.
 
 
 
The reasons I made it static are:
  • I needed a global interface of some sort
  • I had no need to defer initialization so a Singleton is pretty much a waste of time
  • If Game fails to initialize, there’s no recovering anyways
  • There is going to be exactly one “instance” of this object
  • I find it cleaner

 

 

 

If you wanted, you could make Game a traditional class, instantiated one, passed it around and destroyed it. Personally, I simply find this solution cleaner.  You can think of static functions and member variables like the Highlander of C++ programming, there can be only one and they’ve been there forever.

 

 

 

The rest of the code is fairly straight forward, on line 10 we declare the single public method Start() that we called on line 10 of pang.cpp.  On line 16 we defined an enum type called GameState which represent the various states that our game can be in.  We will cover this a little bit later.

 

 

 

Line 19 and 20 we declare our class’s member variables.  One is an instance of the GameState enum we just defined.  The other is a (poorly named) RenderWindow, perhaps the single most important class in SFML.  We will cover this too in more detail later.

 

 

 

The one other thing you may have noticed is the #pragma once line.  By their nature, you should be wary of pragma’s, as they are quite often not portable across compilers.  #pragma once guarantees that the code is included only once and takes the place of a traditional include guards. #pragma once is pretty well supported by major compilers, so it should be safe to use.

 

Optional information
 

The Rules of using static in classes

When dealing with static inside your classes there are a few things you need to be aware of.

First off, static methods can only access static member variables.

Second, there is no this pointer in a static method.

Third, you cannot set a static variable in a class constructor.

Fourth, you need to initialize your static variables outside of your class.

Finally, your variables are initialized at runtime.

 

 

That’s about it. They appear more complicated than they really are.

Optional information

What's an enum?


An enum is a user defined type consisting of a set of constants. Put in plainer ( and less accurate English ), it's a way for you to assign a range of labels to a sequence of data. By default, the first value of an enum is assigned 0, but this can be overridden. Before the enum existed, it would generally have been implemented as a series of consts or worse, using #defines.
 
 
If this all seems rather confusing, see here for a much better and much more detailed explanation of enums.  Generally when you have a set list of values that never change in value or composition, you may want to consider using an enum. If you find yourself using a lot of consts and #defines in your code, you should probably look into the enum with more detail.  They are handy and make your code much cleaner to read and maintain.

 

Now open up game.cpp and add the following:

 

#include "stdafx.h" #include "Game.h" void Game::Start(void) { if(_gameState != Uninitialized) return; _mainWindow.Create(sf::VideoMode(1024,768,32),"Pang!"); _gameState = Game::Playing; while(!IsExiting()) { GameLoop(); } _mainWindow.Close(); } bool Game::IsExiting() { if(_gameState == Game::Exiting) return true; else return false; } void Game::GameLoop() { sf::Event currentEvent; while(_mainWindow.GetEvent(currentEvent)) { switch(_gameState) { case Game::Playing: { _mainWindow.Clear(sf::Color(255,0,0)); _mainWindow.Display(); if(currentEvent.Type == sf::Event::Closed) { _gameState = Game::Exiting; } break; } } } } Game::GameState Game::_gameState = Uninitialized; sf::RenderWindow Game::_mainWindow;

Click here to download Game.cpp

 

 

 

 

First and most confusing, take a look at lines 52 and 53.  These are Game’s two member variables.  As a quirk of being static, they need to be instanced manually. That said, since we don’t have a constructor available, this is a convenient time to set a default value Unitialized to _gameState.  This means when Game is created, its _gameState value will start as unitialized, which is useful in a minute.

 

 

 

 

Now take a look at Game::Start().  This currently is the only publically exposed method and is what is called in pang.cpp to get the game started.  Since Game::Start() is available globally we need to be careful that it isn’t called more than once, which would cause all kinds of bad things to happen.  This is why we check to see if the _gameState is currently Unitialized, which it will only be when starting.  If it is unitialized, simply return for now, although throwing some kind of error would be the right behavior.  Considering that when you return from Game::Start() the program exits, returning an error would be pretty redundant.

 

 

 

 

Next up, Game::Start() tells SFML to create the main window at a resolution of 1024x768 at 32bpp colour with the title "Pang!". It then switches the game to the  Playing state.  Next it starts looping over and over until the game is set to an exiting state, calling the critical function Game::GameLoop() each loop through.  When the game is moved to an exiting state, IsExiting() will return true, ending the loop ( and thus exiting the game ).

 

 

 

 

Game::GameLoop() is easily the most important function in the program as this is where everything happens and it will get much more complex as we add features.  Remember, GameLoop is called over and over millions of times, depending on the speed of your computer.  SFML uses a polling based system for events which is why we call GetEvent.  If there is an event, it returns true and assigns it to currentEvent, if there are no new events it returns false.  It is called in a while loop because there can be multiple events in the queue at a time, so it will loop over all available events until there are none left to process.

 

 

 

Next up is a switch statement, which determines what code runs depending on what our current gameState is.  Right now we only handle a single gameState ( Running ) but that will change shortly.  The only thing you need to remember for now is that the game is only ever in a single state at a time.  For now, the running state simply clears the screen to a bright red colour then displays it on screen.  Then it checks to see if there was an event, and if that event was the Closed event ( someone closed the window ), it switches the game state to Exiting which causes isExiting() to return true on the next pass through the game loop, causing our game loop to end.

 

 

 

 

So here it is, our program in action!

 

 

image

 

 

 

 

Granted not that exciting yet, but it has the framework of almost everything a game needs.  Graphics, input and an event loop.  In the next part, we will add a bit more functionality, including a menu and splashscreen.

 

 

 

You can download the complete project source with everything you need to this point right here. 

 

 

 

EDIT 02/27/2012  Massive omission!

 

All this time later, I realized I made a massive omission in this chapter, that if you aren’t following along using the included sample project that it would cause major confusion.  I mentioned earlier about pre-compiled headers and the stdafx files, but I forgot to include them!  Essentially it allows you to include all the various header files you are going to need and precompile them to speed things up.  In a nutshell, you put any #includes in stdafx where the code is large and not going to change.  This means headers like Windows.h or SFML headers are ideal to be located in stdafx.h.

 

This was such a glaring omission because this is what actually causes SFML to be included!   Oops.  The file included below is actually the stdafx.h from a much later chapter, so there are some includes that aren’t needed yet, but shortly will be.  If you are downloading the sample project,  you will notice that your copy is slightly different.

 

So, lets take a look at those two missing files.  First up is stdafx.cpp

// stdafx.cpp : source file that includes just the standard includes // pang.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" // TODO: reference any additional headers you need in STDAFX.H // and not in this file

Click here to download stdafx.cpp

 

 

 

This file simply exists to make sure stdafx.h is called.  Now lets take a look at stdafx.h

 

 

// stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently // #pragma once #include <stdio.h> // TODO: reference additional headers your program requires here #include <SFML/System.hpp> #include <SFML/Graphics.hpp> #include <SFML/Window.hpp> #include <SFML/Audio.hpp> #include <map> #include <iostream> #include <cassert>

Click here to download stdafx.h

 

 

This file simply consists of includes for all the various header files that we are going to need through-out the project.  This prevents you from having to include them in all your various files ( you get them included when you added #include “stdafx.h” at the top of your file ).  More importantly, it prevents the compiler from having to process the contents over and over, which can speed compilation up a fair bit.

 

 

Sorry for the omission…

 

 

Back to Part One Forward to Part Three




blog comments powered by Disqus