Game Engine 2007
Well, here I go again. I've started making a graphics engine again, but this time, I took a different strategy. C++ has a strange habit of making me think I need to make things perfectly the first time, so I hit stumbling blocks and end up stopping. This time, I started with Visual Basic, and just made a large form with a lot of buttons, each of which allows me to start up small experiments. Slowly, over a couple months, it built over time, and I ended up getting into multitexturing, rendered some decent looking terrain and even delved into a portal engine complete with wall decals and dynamic lighting. Having one program that allowed me to develop little pieces and dabble in them all in a single app kept the creative process fresh and moving.
Source code:
Here are some videos that show off what I've done:
Next up (in no particular order):
Eventually I'd like to turn it into a simple dungeon crawl, complete with outside terrain, a small town with a quest NPC, a building with windows, and a cave with hallways and doors to explore.
Articles (sort of)
Pitfalls
I've started and stopped a lot of attempts at game and graphics engines over the years. I'll start one up, it will get complicated, and I'll just stop because to improve it, things would start to get convoluted. Then, when I gets ideas for one again, I end up starting from scratch, barely ever re-using code from the old projects. It's not a great way to work, and probably more a matter of my own perception than the quality of what I've done before.
Start with a good game/window architecture
Should you make a window that calls the game code from its idle loop? Or should you make a game structure that keeps the primary loop and calls a sort of 'DoEvents()' so that the window can accept inputs?
Previously, I took the idle loop approach, and it just seemed cloogy to me. It made much more sense for the game code to command the primary loop, then go off and tell Windows to process its messages, then come back. When I developed software for a business using Borland C++, the ProcessMessages() command was used to allow updating a progress bar while data loaded, or even clicking a Cancel button to stop the loading. Visual basic has a similar command called DoEvents. C++ doesn't really have one, but it's simple enough to write one:
MSG
oMSG;
while(::PeekMessage(&oMSG, NULL, 0, 0, PM_NOREMOVE))
{
if(::GetMessage(&oMSG, NULL, 0, 0))
{
::TranslateMessage(&oMSG);
::DispatchMessage(&oMSG);
}
else
{
break;
}
}
As far as I know, this chunk of code allows Windows to process all relevent messages for any window that is running, including the one you are using to run your program. When it returns, any Windows controls, keyboard or mouse events on your window will have been picked up and processed by your WindowProc routine. Basically, the DoEvents() says 'go accept and process inputs in my window', and in the WindowProc, you can code to watch for mouse, keyboard and button events and provide those results to your game code via your own homegrown event list, keyboard state array, etc.
Using this architecture in Visual Basic, I set up a spinning cube demo like this:
done
= false
while not done
..Set Viewport
..Clear Screen
..Transform Viewpoint
..Render
Cube
..Swap Buffers
..DoEvents
wend
and when the Done button is clicked, all it does is set done = true, and the animation ends.
To me, that architecture is more intuitive than having to set up an idle loop to call a variable chunk of code, not to mention having to preserve all the variables necessary for the specific routine running, since I would have to leave my procedure every tick, which loses all the local variables. I could define them all as static, but on the first run I would need to set some flag to initialize them... bleh, headache. If I'm going to run a looping demo, I should only need the variables required to run that chunk of code in the procedure with the loop, which I can conveniently define at the beginning of the procedure. Anyone looking at my code can then quickly see what kinds of variables I need for that loop instead of looking them up in an encapsulating class, and if they were to look at the list there, they would have to wonder which demo loops each was actually used for. Using the DoEvents(), pretty much the only common variables are done, keyDown[] and a couple others to facilitate what to do when the render window is clicked.
I've seen people say using the DoEvents() indicates a design flaw. I heartily disagree. If your program is entirely windows control driven, then you don't need a DoEvents() very often except when maybe a dialog has an animated progress bar with a cancel button and you'd like to keep the progress bar updated and have the option of cancelling the loop operation. I'm sure there are plenty of other circumstances. But games aren't window event driven. They are logic driven. DoEvents() allows me the architecture that is much more intuitive for me, and my Visual Basic code for this engine wouldn't have developed as far as it did without it. Adding a bunch of extra background bookkeeping to allow an idle loop (or worse yet, a Timer control on a VB form to simulate an idle loop) just piles on junk you don't need.
I can't stress enough that the ability to just slap on another button to make a simple demo loop in the same app, without having to convolute a class with extra variables or make another project just to see something new, really kept my productivity up.
Polymorphism and derived classes are nice (and necessary in some cases), but don't overdo it
Those of you who have worked enough with C++ to get into polymorphism very likely saw the holy light when you realized the kinds of things that could be done with it. If you were taught about the subject, you might have been given a simple example like a base class named CDog with a virtual function named Bark(), and two classes derived from CDog named CStBernard and CChihuahua. The CStBernard::Bark() routine printed "WOOF!" and the CChihuahua::Bark() routine printed "Yip!". It illustrates polymorphism's capacity, but can be misleading when it comes to when to use it and when not to.
Some time ago, I was experimenting with AI game behaviors and was trying to figure out a way to structure them more intuitively. I attempted to created a CBehavior class with virtual functions and derived other behavior classes from it. Then I could just assign a pointer to a variable made with those behavior classes to game objects, and viola, they took on that behavior. But it became a debugging nightmare. When a problem arose, I had to open so many source files to find the problem that I slapped myself when I finally found it (I couldn't figure out why a sound stopped when the object got a certain distance away from the viewpoint. It turns out in a previous experimentation, I had actually coded to behavior to stop the sound when it got close just to see if it would work, and I didn't change it back before taking a long break).
I also wrote a template linked list class with deleted node functionality. I derived other list classes from that, and in the derived class's EmptyList() routines and DeleteNode() routines, I would perform special operations on the nodes if need be before passing control on to the CLinkedList::EmptyList() and CLinkedList::DeleteNode() routines. This worked quite well, since some nodes had allocated data assigned to them that I wanted to get rid of before moving the node to the deleted list. This is a good use of derived classes (it's not polymorphism, since no virtual functions are involved).
The example with the game behavior above would probably have been better serviced with a switch() statement with a behavior type variable rather than separating all possible behaviors into their own class. The number of class names it would have added to the project list would have been another headache. The linked list functionality, however, couldn't be handled with a switch statement unless I was willing to add another variable to the CLinkedList class. The way I handled it with derived classes, however, makes the class behave in a much more transparent way and keeps me from having to do extra coding (setting the switch-required listType variable and what-not). It also makes sure that construction and destruction is handled properly and automatically.
My Render class is a mostly virtual function class that I can point to either an OpenGL object or a DirectX object. Using only the Render class's functions, I get identical results in each API and hide the guts of working with the API's from the game coders.
OpenGL/DirectX pitfalls