|
Game Development 101 - Back to the Basics
I had big plans for this month. I was going to put the code out there to do nifty things like get input from the user, and create a basic game. I started to write all this when I realized something - I had forgotten some of the more crucial parts of game programming. In actuality, I had been putting them off, as they aren't exactly the most exciting of subjects, but we're just about to the point where they become indispensable. So sorry to disappoint, but there won't be much graphics-specific code this month. Instead we're going to cover documentation and code architecture, along with a little bit of vector math.
Now don't get me wrong - the subjects may be a little dry, but they're crucial to your success as a game programmer, and it'd be a crime for me to not make mention of them. Documentation is the long-standing enemy of the novice coder (and can still be an annoyance to those of us who no longer consider ourselves novices), but since a lot of game development is done in a team fashion (indeed, most game teams have a person devoted solely to the practice of design and documentation) documentation is the glue that holds it all together. Data structures, while not as essential, as probably most of us learned them in a class or three several years ago, also deserve mention as there are several types of dynamic structures that we'll be using in our games, and some work better than others in different places. Finally, vector math is going to be in almost everything that we do AI-wise, as well as an essential part of collision detection (and response). It's probably been a while since physics/calculus for most of you, so I'll cover the basics of what you'll need to know.
So let's dig in. First stop:
DOCUMENTATION
To be honest, there aren't really many standards to game documentation. Don't get me wrong - the industry makes somewhat excessive use of documents, just what goes into them often varies from group to group. Either way, there are several that should be covered regardless of what they're called, which I'm going to present here.
For consistency's sake, I'm drawing a lot of my information from "Game Architecture and Design" by Andrew Rollings and Dave Morris. While the version I have isn't in print anymore, they released a new version a couple months ago that addresses some of the major typos and mistakes in this volume. This is pretty much an essential read for anyone trying to get into game development - it doesn't cover much of the hardcore game development stuff (you won't find any tricks for ray tracer engines in there), but it covers the planning portion of the project (documentation, code architecture), which is arguably more important
First is the game treatment. The game treatment is a 1-2 page document that introduces the vision of the game designer to the reader. It's the first test of your game idea - writing it all down. At this stage, you don't really need to worry about things such as technical limitations or what is actually possible; you only need to worry about your idea and where you want to take it. Cover everything about your idea that makes it seem cool to you, while getting as much of your inspiration across as possible.
The next document is where most of the disagreement happens. Many people at this point will move straight to the monolithic design document, putting everything about everything into that document. Personally, I like a bit more segmented approach. While it may not shrink the design doc much, it will provide a sense of organization. I suggest at this point you present a list of things that are bad with the idea, and attempt to solve those issues. Get outside input - we all tend to think too highly of our own work. It may end up being a bit of a reality check, but overall it's better to find out what's wrong now than to discover the problem months into the development process. Also, don't let the small things get to you here. If an issue isn't really all that vital, don't let it be the reason you scrap the entire project. One of the most important things to develop here is a thick skin - take all criticism at face value, and then try to work with that to better the overall product.
The most important document (at least for the designer) is the Design Document. This is a monstrous document, often averaging 100 pages or more, that covers every aspect of your game (and I mean EVERYTHING). This document covers everything that you could possibly conceive of in relation to your game - monsters, levels, enemies, music, story, dialogue, etc. - in one fell swoop. If your main character tends to lean a bit to the right when he swings his sword, it better be in this document. If you want your source files organized by name, it better be in this document. If you want the game to do something when the player sneezes, you're getting a little ambitious, but it BETTER BE IN THIS DOCUMENT. This document is not complete until you can hand it to someone else and say "Here's my idea, make this game" and be confident it will turn out exactly as you envisioned it. Anything that isn't covered in here is going to consume many hours of programming and development time later. When in doubt, go into too much detail (if there is such a thing). Cover your coding standards, your filenames, your class structure, model resolution, level size, real world scale comparison, music styles for each level, sound effects, textures, character names, game mood, and anything else you can thing of (most likely a lot more than just what I've listed here). Once this document is complete, then (and only then) can you be confident that your idea is solid and ready for production. You also want to make sure that this document is well organized, for two reasons:
1. Most text editors have an upper limit on document size that this document will definitely exceed, and 2. Organization provides logical breaking points for new documents.
Now I don't mean to be frightening or anything, but documentation is the key to software development, and is often an area in which game developers are lacking (the days of coding a commercial-quality game in your garage are over). One small note, though - you most likely won't be able to cover everything in the documents before you can start coding. All of these documents, especially the design document, are evolutionary things. The rule presented by Morris and Rollings is that you can really only document 80% of the project before you start development, the rest will reveal itself as you go along. So if you happen to stumble across something that you just can't figure out without busting out the IDE, put it to the side for now and come back to it later.
While there are other documents you may write, or feel the urge to write, these three are probably the most frequently occurring. Write as much as you can, and more than you need - an overabundance of documents never really hurt anyone.
With documentation out of the way, let's move to:
CODE ARCHITECTURE
There's not much I can cover here - I mean, who can tell YOU how to code, right? But there are a few things worth mentioning that I picked up in a couple books and through hard experience.
First, try to make your code structure as clean and simple as possible. Sure, you can do all sorts of cool stuff with multiple inheritance, abstract classes, and virtual functions, but just keep in mind that every layer of abstraction is another set of processor cycles, and thus a slower game. Now I'm not saying to dumb your program down to a purely procedural level, just be aware of what goes on behind the scenes. One level of inheritance is perfect, two is acceptable (and makes things a lot easier), but most architectures that go beyond that may be a little too processor-intensive for your average game. If you absolutely HAVE to have that cool class structure, keep it as far from the game loop (or other time-critical portions of your program) as possible.
Now that being said, let me bend the rule just a bit. I recommend a base class for all your drawable objects called GraphicsObject. Have all of your drawable classes inherit from this class. Why? Because it'll make things easier. All graphical objects will have a certain set of common functions - translate, rotate, scale, and draw, among others - which can be either standardized or virtual. The benefit of this is that the compiler won't have to care what kind of monster is drawing itself. All it will see is a call to GraphicsObject.Draw() - let the computer worry about the late binding. I'll cover a bit more on how to use this idea in a moment - until then, here's a sample prototype for a graphics object:
// This is the GraphicsObject class // All drawable objects should inherit from this class class GraphicsObject{ public: //This is the happy constructor GraphicsObject(); //A virtual destructor, to ease inheritance virtual ~GraphicsObject(); //Scale, rotate, and translate functions that we'll get to in a later tutorial void scale(); void rotate(); void xlate(); //Make the draw function purely virtual, so as to ensure that it is implemented virtual void draw() = 0; };
This won't be much use yet, but it will serve us greatly as it changes to meet our needs.
Second, a word on data structures. Games are dynamic by nature, so we have to make some hard decisions. By far there are three data structures you will use the most as a result of this fact - the STL Vector, the Binary Search Tree, and the Linked List (in no particular order).
Just a quick note before I hit these - there are hundreds of great tutorials on data structures on the web. The following is not meant to be complete nor exhaustive, it is just meant to present the idea of different places to use the data structures. I figure why should I reproduce all the great work that's already out there (more often than not by people smarter than me).
First, the STL Vector. The STL Vector, for those of you who don't know, is basically a dynamic array. It allows you to add any number of objects on the end, as long as they are of the specified type (this is where our GraphicsObject scheme comes in handy). Thus, if we were to classify all of our drawables as a Graphics Object, our draw loop becomes really simple:
vector world;
void draw(){
for(int i = 0; i<world.size(); i++){
world[i] -> draw();
}
glutSwapBuffers();
}
Next, the Binary Search Tree is very useful for things that we would need to search for (say, for example, the closest polygons to the player, to ease the load on the graphics processor). Basically, assign each object you'll need to sort through a value (say, distance from player), populate the tree, and start your searches. The benefit of the binary search tree is that it is one of the most efficient ways of searching out there (and one of the most commonly used, as well). Quake 3 uses a modified BST format for its level files.
Finally, the Linked List is another exercise in dynamic array fun. Allowing the user to delete or add at will, the Linked List is a valuable tool for any programmer.
I can hear the question now - "Why should I use a linked list when I have a vector already?" Well, it's a simple difference between the way you're going to use the objects. The linked list is best if you plan on adding and deleting a lot of data from the list. The vector, when something gets deleted, has to take the time to move all of the items after the deleted item towards the front, resulting in roughly n-1 deletion operations. The linked list only has to first navigate to the deletion target, then perform a couple pointer swats and a delete call - much much simpler than the vector. So it's just a matter of judgment. If you're going to have a lot of enemies flying at the player, I'd suggest a linked list approach, while if you're going to have a pretty stable world for the player to walk around in, go for the Vector.
And with data structures (briefly) covered, let's hit the last item on the agenda:
VECTOR MATH
In an earlier tutorial I said that we wouldn’t do many equations until we hit 3D graphics. Well, that was a half-truth. It's better to get into practice now, while we're fresh and ready, of using vectors to do just about everything. By vectors I mean the mathematical construct, not the data structure.
Again, there are hundreds of tutorials and libraries available on the net. One that I've worked with and liked is GMTL, available from <a href = "http://ggt.sourceforge.net"> http://ggt.sourceforge.net </a>. Of course, it's always a good idea to write one for yourself, if for no other reason than to get the basics down.
I'm just going to run through some of the sample equations here. Vectors will be noted as points (x,y,z), with the coordinate indicating a vector from the origin to the point.
First, vector addition. Real easy stuff. If you have v1 = (x1,y1,z1) and v2 = (x2, y2, z2) then v1+v2 = (x1+x2, y1+y2, z1+z2). Subtraction is opposite. Easy.
Next, basic trigonometry can be used to obtain the magnitude of the vector. For 2D vectors, the magnitude is the square root of x squared plus y squared (or sqrt(x*x + y*y)). For 3D, it's the same thing, just throw a z squared into the equation as well (sqrt(x*x + y*y + z*z)).
Third is the dot product. This is one of two methods for multiplying vectors (with the other being the cross product), and this one yields a scalar quantity (scalar means non-vector. 5 is a scalar, (5,0) is a vector). This is useful because it can help us to determine the general angle between two objects. If the dot product is greater than 0, then the angle between them is less than 90 degrees. If it is less than 0, then it's greater than 90 degrees. A dot product of zero means that the angle between the two is exactly 90 degrees. Given vector v1 = (x1,y1,z1) and vector v2 = (x2,y2,z2), the dot product of v1 and v2 is (x1* x2 + y1*y2 + z1*z2).
Fourth is the cross product. The cross product yields a vector that is normal (or perpendicular) to both of the vectors being crossed. Thus, V1 x V2 = V3, where V3 is normal to both V1 and V2. To get the cross product, using the above definitions of v1 and v2, the cross product v3 = (y1*z2 - y2*z1, z1*x2 - z2*x1, x1*y2 - x2*y1). Notice that in this equation, in a 2D situation, the X and Y resultant values become 0, this yielding a vector that is perpendicular to the X,Y plane. The cross product becomes extremely useful when producing normals for lighting, as now we can do the whole thing programmatically.
Finally, just a note about unit vectors. These become useful when doing animation and using lighting engines, and can be found by dividing each element of a vector by its magnitude.
And that's it for this month. Tune in next month when we return to the world of game code and programming (I promise this time).
|
|