OOP, global variables, and large projects

Started by
11 comments, last by bjle 19 years ago
This is kind of a big, and abstract, question (and probably there is no "correct" answer) so I can't expect a complete answer but any tips or explanations would be appreciated. I have consistantly run into trouble with games and programs I've made where they just get too complicated after a while, so I'm trying to change my programming style. The books and tutorials about game-programming I've read don't seem to match up with what I have read about things like avoiding global variables, and I always have a hard time expanding what is in the tutorials to make something else. The main question I have is, what sort of basic structure works well for a game? To give a specific example, say I want to make a game using C++ windows and Direct3D. I have always made the main D3D Device global because many different functions need it. To process messages from the WndProc correctly, I've needed to make my HWNDs global as well. Then I need a global bool to tell whether the game is minimized or active, etc. etc... I realize this is "bad style" but I can't think of a way to fix it. Is throwing all these things into some global "g_app" class any better? Even more specifically, say I want to make a breakout game. I understand that going the OOP route, I'd make classes for Paddle, Ball, and Brick. These would contain information about the objects in the game, and something like an Update() function. But where in my program should I declare these? If I don't declare Ball, Brick, and Paddle globally, it doesn't seem that I could do collision detection properly without a ridiculous amount of parameter-passing. The same goes for rendering...how do I render each object from within its own class (if that makes sense) without either a global D3D device or a copy of my device in every single instance of each object? What would be most helpful is an example of a "good style" framework for that breakout game (or any game) including what sort of things are "allowed" to be global, how complicated the main function should be, how classes and variables should be divided amongst .h and .cpp files, what should #include what, how inheritance should be used, or anything else I'm missing. Basically, how to avoid insane increase in complexity as the game gains functionality. Or if anyone knows of any articles focusing on my problem on this or any other site I would like to know :) Thanks
Advertisement
I can tell you this much: you should keep up to the advices about "pretty programming" as long as it doesn't make your programming too difficult or too slow. In game programming it is important to make things optimized as possible, so you should keep the most important stuff as global variables IMHO.
If you write in OOP style, then I suggest you do like this: have an abstract parent class which does nothing, but every other object inherits from it. It should look like this:

class CObject{  public:    int x,y,width,height;    virtual ~CObject() { }    virtual DoAction() = 0;    virtual DoDraw() = 0;}; 

Now if you have a CTank, then it will inherit from CObject. In the DoAction() function you should unite all the other functions which are about "action", while in DoDraw() everything that has something to do with drawing. It is up to you to make a distinction what goes where.

Of course, you should add more functions if needed ;)

And not all objects should inherit CObject. For example, if you have a matrix which represents a map. It does not need to inherit CObject cause it has nothing in common with it. A few things should be separated from CObject.

Anyway, in the end you'll have tanks, airplanes, submarines and infantry all placed into one list<CObject*> MyUnits, sorted by the y coordinate. All you have to do now is
for (list<CObject*>::iterator i = MyUnits.begin(); i != MyUnits.end(); i++){   (*i)->DoAction();   (*i)->DoDraw();}


Warning: this is an ideal case, most games will look somewhat different ;)
This is a very good question. Far too many books these days leave this questions out, and I believe it is far more important that how to use graphics APIs, etc.

As far as the class design for your Breakout game, you're on the right track. As for keeping things such as the Direct3D device and window handles global -- it will work, but it is considered bad programming practice. As you yourself suggested, it is common to make a static class (or a singleton) that stores all of this stuff, and provides accessor methods for various objects to use them. For example:

// This is the application class -- it stores any thing at the application level // statically.class Application{  public:     // All methods are made static so they can be accessed throughout the entire code base     static void Init();     static void Shutdown();     /*These methods provide access to the class members. Having accessor                     methods such as these isn't totally necessary and provides no real benefits -- however it is commonly considered an excellent practice, and it can increase code readability.*/     static LPDIRECT3DDEVICE9 GetD3DDevice() { return s_D3DDevice; }     static HWND GetWindowHandle() { return s_WndHandle; }  private:     /* Member variables are also kept static -- ensuring there is ever only once instance of them, and also so that the static accessor methods can access them. */     static LPDIRECT3DDEVICE9 s_D3DDevice;     static HWND s_WndHandle;     // ...};// Later on in the games source:// To access the render device, do this:LPDIRECT3DDEVICE9 D3DDevice = Application::GetD3DDevice();// We must use the scope operator (::) because the method is static and does not belong to a specific object (instance) of the class


This is the way I've typically seen this problem handled.

Also, you asked about inheritance, and a Breakout game seems like an excellent candidate to showcase it:

// This is the base class for all game entities. A game "entity" is defined// in this context to be any thing which exists in the physical game world,// is updated on a frame-by-frame basis, can be rendered to the screen, and// can interact with other entities. This type of class is called// an "abstract interface", in that it implements no functionality of it's own.// Derived classes are responsible for implementing any functionality.class IGameEntity{  public:     // All methods are pure virtual (meaning virtual and having no provided     // implementation). This is because IGameEntity is meant to be an abstract     // interface.     virtual void Init() = 0; // the '= 0;' indicates no implementation is                               // provided     virtual void Shutdown() = 0;     virtual void Update(float TimeStep) = 0;     virtual void Render() = 0;};// You would then proceed to create specialized entity classes, such as Ball and Paddle:class Ball : public IGameEntity{  public:     // We must redeclare these methods to show we intend to implement them.     // This time, however, we leave off the '= 0;' terminal because we will     // provide an implementation.     virtual void Init();     virtual void Shutdown();     // ...  private:     // Here we can store data specific to this classes implementation to the      // above methods:     Sprite* m_Sprite;     CollisionOutline* m_CollOutline;     Vector2 m_Pos;     Vector2 m_Vel;};// You would do the same for all difference game entity types (Paddle and CpuPaddle).// Another class design would be to create a base class IPaddle. However, it is unnecessary in this case, as no extra methods or data would need to be declared in that interface than what is already in IGameEntity.


Now the question is, where do we store our instances of these classes? A good question, too. Perhaps handle this in a similar way to the application data. Create a static 'Game' class, which provides methods to update the frame, render the frame, etc.

Then, in your 'WinMain()' function, you might have something like this:

// Sorry, I can't remember the exact signature for WinMain()int WinMain(){     Application::Init();     Game::Init();     while (Game::GetRunning()) {          Game::UpdateFrame();          Game::RenderFrame();     }     Game::Shutdown();     Application::Shutdown();}


Well, I hope I answered most of your questions! If not, just ask and I'll be glad to expand on what I said.
I was always wondering when have the Update() and Render() functions showed up. I was always working with DoAction() and DoDraw() and suddenly everywhere I looked it was Update() and Render() :P
In the game I'm working on now, I connect almost everything to a window (a 'form' as it's called in my IDE).
In the main 'unit' I create Stage classes. And I create general classes like TCar, TWeather, TParticle. Those classes can hold all kinds of stuff like variables. Also I define records (in C++ it is called differently) like TVector, etceteta. Records can not be destroyed, so memory is fixed, and classes can hold records.

In those Stages I can create and destroy all those general classes. If the stage is finished, I destroy it and create the next one.
The other units are like collision detection routines, fileIO, etc.

I also make global variables which are used in the whole thing and not connected to anything, like counters.

If a game gets really complicated (and this happens real quick) you'll need a good oversight in what objects, variables and other stuff you've created and how they connect. Pascal is really neat and clear in that matter, i love it;
Quote:Original post by Marmin
In the game I'm working on now, I connect almost everything to a window (a 'form' as it's called in my IDE).
In the main 'unit' I create Stage classes. And I create general classes like TCar, TWeather, TParticle. Those classes can hold all kinds of stuff like variables. Also I define records (in C++ it is called differently) like TVector, etceteta. Records can not be destroyed, so memory is fixed, and classes can hold records.

In those Stages I can create and destroy all those general classes. If the stage is finished, I destroy it and create the next one.
The other units are like collision detection routines, fileIO, etc.

I also make global variables which are used in the whole thing and not connected to anything, like counters.

If a game gets really complicated (and this happens real quick) you'll need a good oversight in what objects, variables and other stuff you've created and how they connect. Pascal is really neat and clear in that matter, i love it;


Very interesting terminology. [smile] What language are you using?
I think Delphi puts T in front of class names
It sounds like you need to object orient more. Seriously, the best thing you can do with large projects is encapsulate things so that you can code them, and then forget about them and they just work.

And just a few tidbits, from experience:

- Object oriented programming [for me] doesn't work well with actual [game] objects. It works much better with methods and procedures and structures which the game objects are mere parameters to.

- Seperate data from representation. Your game object paddle/ball/bricks shouldn't care how they're rendered. Your sprites shouldn't care what they represent. This eliminates much of the interdependancies you describe, and allows you to encapsulate things better. Once that's done you can add, change and fiddle with things without making such a large impact on the rest of the code.

- Globals are fine. If you don't need them, don't use them. If you do need them, use them. Like for the main d3d device. You maybe shouldn't make that global, but you might want to make a 'graphics' class which handles the d3d device, and all of the other configuration and "single instance" related data into a global which things access and utilize.
Here are my thoughts.

The key to reducing the complexity of software is to reduce the number of dependencies and references between the various elements in the code and data. Restricting or preventing access to private data is the most effective way of reducing complexity. Collecting global variables into a common location makes it easier to find stuff, but it does nothing to reduce complexity (since the number of dependencies and references is unchanged).

I don't think that simply having global variables affects complexity, but it does affect potential complexity. A global object can potentially be referenced by everything. What it means is that as your project grows, its complexity will grow faster with global variables than without.

Finally, you described how not using global variables would require a "ridiculous amount of parameter-passing". Well, that might be true, but now you are in the realm of optimization. Optimizing involves making assumptions, and an assumption is basically an implicit dependency -- thus optimization increases complexity in order to gain efficiency. Complexity is the bane of software development, so that's why optimization should always be done later rather than sooner.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
hide as much as possible. you want to program, so that anyone can take part of your program and say "yes, this works as a great library I will use it in my project..."

its a fine balance between will it add unnecessary complexity that can be avoided or can i encapsulate this object safely (having preference for the latter). there is too much stigma regarding global objects atm.

remember though: keep it simple, keep it safe.

This topic is closed to new replies.

Advertisement