[color="#4B0082"] Be sure to read the license agreement before using the code.[/color]
[spoiler][font="Courier New"]"Code On The Cob"
Source Code License Agreement
Copyright (c) 1998 Chris Hargrove
The licenser, Chris Hargrove, is herein referred to as the "licenser".
This license agreement, the source code provided, the executable(s)
compilable thereof, and all other contents of the distributable archive
including the archive itself are herein referred to as "covered" by this
Reading, compiling, running, or otherwise using any item covered by this
license constitutes automatic acceptance of the rules described in this
license, as precedes and follows in the remainder of this file.
All items covered by this license agreement are the sole property of the
licenser, and are licensed as-is to the public for educational use only.
No items covered by this license may be distributed in any archive other
than that which is originally provided, and said archive may only be
distributed via mediums that require no cost for retrieval of said archive
other than those required by the medium itself and its direct providers
thereof. None of the items covered may be modified or duplicated in any
form, with the sole exception of modifications and duplications made by
licensees for educational use only. Said modifications and duplications
may only be made to source code covered by this license, and all said
modifications and duplications become the immediate and sole property of the
licenser. The licenser is not responsible for any damages, direct or
indirect, that may result from usage, proper or improper, of items covered
under this license.
Any violation of the above rules must be authorized via express written
consent of the licenser, or it may be punishable by civil legal action.
THE LICENSE PARAPHRASED:
I'm writing this code so you can read it and learn from it. If you
understand it and find some bits and pieces useful as-is, feel free to make
use of them. But don't go taking credit for that which is not yours, and
don't go trying to make a buck off of my effort directly without my
permission. I'm doing this for your personal growth, not for your wallet
or your ego. In general, don't screw me and I won't screw you. The spirit
of sharing information is why I volunteer my already limited time to write
this series. That same spirit is behind a lot of what I and many others have
learned, and what we're all still learning to this day. This license is
basically my way of saying that I don't want some random guy out there to
ruin it for myself and everyone else. Be ethical.
"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.
[size="5"]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):
[bquote] In your code, you put parentheses around almost all of your return values. I was told by my professors that I didn't need to do this, since return is not a function. Why do you? [/bquote]A:
[bquote]I realize the parentheses around my return values aren't necessary, or rather that they technically shouldn't be necessary. The reason I do is because of habit, which I started a couple years ago after noticing that compiler optimization bugs seem to creep up when you least expect them, and sometimes a pair of extra parentheses can make a difference. With the return value optimizations that many compilers do, I got the sinking suspicion that some of my more esoteric return expressions may not end up compiling the way I wanted them to, and I started parenthesizing them. I've done it ever since, whether it be necessary or not.[/bquote] Anyway, back to goodies
[size="5"]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.
[size="5"]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 (C) 1998 Chris Hargrove.
Reprinted with permission.