General Game Design

Started by
7 comments, last by m0ng00se 16 years, 2 months ago
I've been programming for about 3-4 years now, and have finally worked up the determination to make a polished game. However, almost as soon as I started, I was met with quite a few problems, mainly dealing with how to design the game modularly. To put it simply, my game design skills are severely lacking. Most of my game ideas are relatively simple, consisting of a few different "states," such as the game, the menu, the virtual store, etc... My question is this: How do you design your code so that everything works together? I used to end up with large Switch-Case statements to check which state the program was in, and drawing/updating everything according to that. There must be a better way, though. My next idea was to try a system of "Screens," each one representing a different game state. Each separate state had a class extending a basic Screen class, containing a Render function and an Update function. All objects that were used in that screen were fields of the class (for instance, the Menu Screen would have the GUI elements, the Game Screen would have the player, bullets, enemies, etc..). However, this soon led to problems. How was I to effectly transition between screens? How would I pass the instance of a player class from the Game Screen to the Store Screen, where its abilities would be upgraded? As you can see, I didn't get very far. Any help is greatly appreciated. Links to articles and such would be great, too. Thanks!
Advertisement
You forgot to mention what language you program in :P.

If C++ use classes.
If C... Then i don't know :P.
Quote:My question is this: How do you design your code so that everything works together?

By using the simplest method that works.

Quote:I used to end up with large Switch-Case statements to check which state the program was in, and drawing/updating everything according to that. There must be a better way, though.

"Better" is hard to define. Did your switch statement accomplish your goal? If so, then it was a good design. Design is good, and thinking about better ways to build your programs is good, but creating complexity for the sake of complexity is not good. The first step is to decide what about your current design (huge switch statement) is holding you back. Then think about how to solve that particular problem. Do you really have so many different states that you need to create a more complex structure to handle the states? The solution could be as simple as restructuring the switch statement so that it is easier to read (ie. instead of lots of code for each case, just a single function call that actually does the work).
You can go as far as you want with design and do some pretty clever stuff, but the goal should be to reduce the amount of time you have to spend writing and maintaining your code. Otherwise you are just designerbating (did I coin a phrase?).
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
Quote:You forgot to mention what language you program in :P.


I'm not sure that this really matters, as the general design of a game could be implemented in a lot of languages. In any case, I began the game using BlitzMax, and am currently looking at C++ and HGE as an alternative.

Quote:By using the simplest method that works.


If only I could find the simplest method! It seems I sacrifice modularity for simplicity. Surely there's a place in between? My "simple" methods usually end up messy and inefficient, which leads me to believe I'm doing something wrong.

Quote:You can go as far as you want with design and do some pretty clever stuff, but the goal should be to reduce the amount of time you have to spend writing and maintaining your code. Otherwise you are just designerbating (did I coin a phrase?).


Hmm, when I think about it, perhaps I am "designerbating." It seems that ever since I got into the mindset that everything should be modular and reusable, I haven't managed to get anything done.

I'll try for more simplicity. Thanks!





Quote:Surely there's a place in between? My "simple" methods usually end up messy and inefficient, which leads me to believe I'm doing something wrong.

When you look at some code and think, "man, what was I thinking there", it means you probably just need to go back and refactor that part of your code (not necessary redesign from scratch).

Quote:Hmm, when I think about it, perhaps I am "designerbating." It seems that ever since I got into the mindset that everything should be modular and reusable, I haven't managed to get anything done.

That is the danger. Don't get me wrong. I am not saying to just make a hack job of everything and never put any thought into it. But, if you are are spending all of your time on design and never actually getting around to making anything, then you are definitely going down the wrong road. Understand the problem you are trying to solve, implement it to the best of your current understanding, analyze the results, and refactor where necessary. Over time an elegant "design" will simply emerge. The number one goal however is to get something done!
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
Quote:Original post by Torrente
My next idea was to try a system of "Screens," each one representing a different game state. Each separate state had a class extending a basic Screen class, containing a Render function and an Update function. All objects that were used in that screen were fields of the class (for instance, the Menu Screen would have the GUI elements, the Game Screen would have the player, bullets, enemies, etc..).


This is very common and well-accepted, and the problems are easily addressed:

Quote:How was I to effectly transition between screens?


In the main loop, you have a variable of type pointer-to-Screen. Each time through the loop, you potentially tell the pointer to ->Update(), and the function returns a pointer to the "next" screen - either itself, or a newly created instance of one of the other derived classes. To make the program end, you can have an Update() call return NULL, and check for that in the loop condition.

In C++, std::auto_ptr will help greatly with the memory management here.

class Screen {  // stuff  public:  // more stuff  virtual ~Screen() {}  virtual std::auto_ptr<Screen> Update() = 0;  virtual void Render() = 0;};class MenuScreen: public Screen {  // etc. etc. etc.  // in Update, you return 'this', or a 'new FooScreen', or NULL.  // Of course the return type is still the auto_ptr type!};// more screen classesint main() {  std::auto_ptr<Screen> current = new MenuScreen();  while (current) {    current->Render();    if (timestep_has_elapsed()) {      current = current->Update();    }  }}


Quote:How would I pass the instance of a player class from the Game Screen to the Store Screen, where its abilities would be upgraded?


Typically, you don't "pass it to the screen", and in particular you don't pass it from a screen. Instead, you pass it to the Update function, and just let it be a local variable in the game loop.

But in general, if you need that kind of functionality, you can pass it as a parameter to the constructor of the new screen, and let the new screen make its own copy. You design things in a way that lets them be copied safely, and then when the old screen gets destructed, you aren't wasting memory on copies - only the most recent copy is still around.
Go for simplicity. Windows is incredibly complex under the surface (I have seen the complete source code for Win95) but it uses CASE/SWITCH statements as main message handlers. After all that is what they were designed for. They're far better than diabolical nested IF/ELSE.

C++ is good for keeping objects in classes but you quite rightly pointed out that you can get big issues with objects being deconstructed before you've finished with them, as in you want to use them later after you've done some other action, but they've gone out of scope and destroyed themselves. It's great for managing memory correctly but very annoying in other ways. It means trying to envisage the scope of everything at design stage.

I use only C++ and I use HGE extensively amongst other SDKs. Having worked with other people's source I can say that simplicity is the key. I have seen lovely source that reduces lengthy transition code in beautiful little complex algorithms that nobody else can work out and are impossible to debug. In the end I always end up re-writing the code into something I can personally understand and work with.

My best advice is always write something that you can debug easily, even if it is longer and more tedious and doesn't seem very elegant from a design perspective. The bottom line is that it HAS to work and you have to know how to fix it if it breaks.

m0ng00se
Zahlman:

The code you posted is a new idea to me (or at least the concept of returning a screen in the Update() function), but it seems like it'd work for the most part. I still have a few questions, however.

Just to clear things up -- If, for example, the player hits "escape" while on the GameScreen, then I would create a new main-menu screen and have Update() return that, so that the menu is now being rendered and updated? What if I wanted the MenuScreen to be brought up while the game is paused? I wouldn't want to destroy the GameScreen.

I'm assuming these problems have been dealt with, but I'm not sure how it would be done within the design you provided.

The code is very helpful, thanks!


Mongoose: The Switch-Case method does seem to work, but it's not graceful at all. Perhaps I just need to plan ahead, but by the time I'm finishing up a game, it feels as though I have to hack things on.
Well somebody correct me if I'm wrong but isn't that the whole point of a CASE/SWITCH loop? So you can keep adding to it at a later date? Plus it's a one stop shop for a message handler which is why Microsoft use it in their own code.

If you've ever had to rewrite somebody else's source because it hit the Microsoft compiler nested IF/ELSE limit (100 -200 nests, can't remember directly) and that was THEIR idea of an elegant message handler!!! You start wishing if only they'd heard of CASE statements!

Same with somebody else's very elegant classic C++ OOP with such lovely classes that fall into each other and destroy themselves in such a pretty fashion just like they should. Except the whole thing is like a black box. You want to add another 2000 lines of code that has nothing specific to do with the pretty classes but you can't access half the data because it's a private member to a class and the whole class deconstructs itself just as you want to use it.

Then you HAVE to tack on real nasty code to do down and dirty hacks to do what you need. Give me open design anyday. That's why hackers like pure C. You just go... hmmm I want that so I'll just write a function to go get it right now, right here and never mind any design issues.

m0ng00se

This topic is closed to new replies.

Advertisement