by Chris "Kiwidog" Hargrove
Download this week's code files:cotc9src.zip (90k)
Be sure to read the license agreement before using the code.
"We cannot always control our thoughts, but we can control our words" - Florence Scovel Shinn
Back again! After last week's little respite from the trials and tribulations of game code (don't laugh :) we return this week to the guts of the column, the project.
Not necessarily the same topic within the project, though. I was working on the rest of the DirectDraw code only to find that my coworker and partner in crime Nick Shaffner was writing some similar code relating to DirectDraw for an unrelated task, and I realized that before I start digging into that block of code anymore than I already have, I might want to work out some issues that could benefit from his experiments. Not to mention the fact that since I hate DirectDraw, I'd prefer to procrastinate working with it yet again for as long as I can hold out. :) So the DirectDraw stuff can be stalled for a bit until I know what I release to you guys is actually worth its salt (it's easy to get DirectDraw working, evident by the code I already put in the project. Getting it working at high efficiency is another matter entirely). So I'm not obeying a rigid schedule here, but hey, this is a recreational project; I can afford it.
In the meantime, I figured I'd dig one of the random subsystems (in the "random subsystem bin") which would need addressing at some point, and knock it out of the way. Fortunately this particular subsystem is something I tend to get quite a few requests for, so I hope nobody's disappointed by the diversion...
Today we'll add the basis of a Quake-style in-game "console" to the system.
Keep in mind that since the video subsystem is not complete, the actual user interface to the console (via a drop-down box or whatever) will not be graphical for our initial tests. No biggie though; that aspect of the console is not exactly a major task though, and in reality the user interface has very little to do with the "guts" of a console subsystem. We'll get to some explanation in just a moment.
A Couple Adjustments
I've noticed over the past few code articles that the text has become increasingly boring to read. I think I'm spending too much code-related text space mentioning specific functions or other code trivialities, and losing focus from what I originally intended to do in the article text, i.e. discuss the logic behind the code. So from this point on I'm not going to go over code specifics at the structure/variable/function/etc level unless something is worthy of note. That kind of writing just becomes very monotonous to everybody involved, and you guys are all perfectly competant enough to read through the code on your own without me needing to babysit the path your eyes take. So I'll refrain from that from here on out. I'll just mention the modules that were added, and you can read through at your leisure for the details. This'll also help keep the article size down, which with the new loonygames format is not exactly a bad thing.
Also, a quick Q&A snippet, probably the most esoteric code question I've gotten in the past two weeks (and I've gotten it three times from three different people, I don't know if there's a connection):
Anyway, back to goodies :)
It's Not A Spectator Sport
With the complexity of games these days it's become very difficult to develop them in a vacuum. Debugging has become a very interactive process, so much so that external debuggers are often simply not effective enough for many situations. The more that can be monitored and tweaked inside the game itself, the better. Since the rise of Quake, the "console" has become an integral (and now public) part of many games. Some consoles are left open to the user, while others are enabled only during development. Regardless, they're now an extremely common facility in the midst of game creation. Why? Because they're really, really handy, that's why. Ever have a value somewhere in your code that you'd love to keep tabs on (like in a debugger's "watch" window) but find yourself unable to do so? A console may be perfect for you.
So how difficult is an in-game console to write? It looks tougher than it is, actually. There are a whole lot of different ways to do it, but if you take one of the simpler approaches (which most developers tend to), you won't suck up more than a thousand lines or so on the core of one, tops. The one I've tossed in to our project (within con_main.h and con_main.cpp) is right around 800 or so lines at this point, and it already has much of what's necessary. So what's the trick?
There is no trick. Once you get down to it, a console is nothing more than a redirector which turns a string into a variable access or a call to a custom-format function. All a console subsystem really needs is an "execute" function that takes a command line for it to dispatch so it can do its job. The job of typing in that line (via a box on the screen or whatever) is totally irrelevant to the console; all it needs is a string for it to work.
So there's no trick to the dispatch, either? Nope. All you need for that is a couple of linked lists. Say you want a console which has two primary ways of using it... console functions, and console variables. On the internal side, a console function is associated with some kind of function body that should be called, while a console variable is a value that can be manipulated. Both of these can be accessed easily if each "console function" or "console variable" is kept in its own structure above the raw function/variable being dealt with. This structure holds stuff like the name of the function or variable, the function to call or the variable to change, etc... and then a pointer to the next console function or variable, respectively. In our case we actually have a third list to hold variable handlers, but they're technically console functions so the issue is the same.
Now you may be thinking that dealing with such a structure (or a registration system which deals with the structure in the same fashion) may be a pain. Not so. This is a situation where C++ becomes extremely handy over C, since it allows constructors. With only a little bit of preprocessor abuse, macros can be written which completely hide the variable/function registration process, making it extremely easy to add functions and variables into the system. Whenever you create a console function body or declare a variable with these macros, the hidden list entry structure can be initialized and registered completely behind the scenes within the body of a safely hidden constructor. Look at the macros declared at the top of the header file to see what I'm referring to.
Is this the only way to do it? Hardly. But it's a darn good place to start. The example for this week uses the debugging window as a text interface to the console, via a small input receiver. This receiver is very similar to the one we'll actually end up using inside the game itself, and in reality, hardly anything will need to change for it except the visuals for text output. I've thrown in a few small console functions (as well as a variable or two) to start the foundation so you can see how it all works.
Over and Out
Since the next article falls pretty close to Christmas, I'm not quite sure yet what the topic will be. Something fun, most likely. Networking? Sound? Something else entirely? The DirectDraw stuff will likely fall another article past the next one, so I'll have to fill in with something. I guess it'll be a Christmas surprise, won't it. :)
Until next time,
- Chris"Kiwidog" Hargrove is a programmer at 3D Realms Entertainment working on Duke Nukem Forever.
Code on the Cob is © 1998 Chris Hargrove.
Reprinted with permission.