When I played Quake and Quake2, one of the 'neatest' things I saw was the console. The area where you could type commands like cheat codes. Remember? Well, I had never seen that in any game before. You just typed stuff while the game was running (e.g. DOOM) and that didn't impress me much. The idea of a console in a game interested me. I have spent a long time figuring it out and in the process have failed about 4 or 5 times. Nevertheless, I started on it again, but this time, I asked for help from the guys programming the VOID 3D Engine (Gaz and Ripper). Thanks to them, I was able to pull off this console.
In this article, I will be discussing a few methods you could use to implement your own console. At the end, I will present you with a fully working demo of a console. It is written in Visual Basic 6.0, since its string manipulation is simple, and half of the work is already done. I also chose VB for this task because I could use my previously written LibX
game programming library, which would let me concentrate on the console, instead of rewriting the DirectDraw framework. Arising Conflicts
When I first started, I thought that all a console had to do was retrieve keyboard messages from the user and print them to the screen. Then, you just compare strings, right? Wrong!
When I tried to make my leap to creating the console, I encountered many annoying problems. Before we get into the problems you should consider before starting the console, I would like you to do this:
Open up the MS-DOS console in Windows, and type a few commands (even invalid commands). Notice how the DOS console responds to invalid commands by printing "Bad command or file name". Type a few copy commands to copy files from one place to another. This time, notice how the copy command accepts two "arguments" (source file and destination file). Lastly, notice that as the DOS window gets filled, the lines in the top get scrolled up and out of view. These are all things to consider, since we're trying to make a "clone" of this console in DirectDraw. Based on the observations we've done, the following could be considered problems we need to overcome:
- We need a way to receive the line input the user has typed.
- We need a way to copy the input we received into something that I call "the console buffer". A console buffer will have all the lines that user has input. When a console buffer is filled fully (i.e., maximum number of lines have been added), the first line is removed, and the first line becomes the second, second becomes third, and so on. And the last line is free to take any new input.
- We must be able to process the line input we have received from the user. This includes separating the "command" (what the user wants to do) from any supplied "arguments" (if any are required by the command). An example of this would be:
copy myfile.txt myfile2.txtmyfile.txt and myfile2.txt are arguments of the command copy.
- We also need to put a "prompt" where needed. A prompt is like the "C:\>" in the DOS console. Lines that the user enters input on start with the prompt. Lines that give feedback to the user (like "Bad command or file name") don't require a prompt.
Since we're creating the console in DirectDraw, we also need to consider the character size, number of pixels to leave after each character, and after each line. We must also keep in mind the maximum number of characters that can fit on the screen. It might sound too much, but once we find that shortcut, we will reach home quickly. So let's get started. Resolving the Conflicts
In the last section, we listed some of the conflicts we need to resolve before we can reach our goal. I approach the problem with the use of classes since classes can automate things. Even though C++ is somewhat easy, VB is much easier. Note that all things I did here could be done without using classes. However, I think it was simplified and cleared up when I used classes. You'll hopefully see what I mean later.
To wrap up this section--the first problem we mentioned above is going to be solved by having a class called "CLine". It contains functions for all sorts of line manipulations. The second problem will be resolved with our "CBuffer" class and the last two will be solved by our "CConsole" class.
You only need to use the CConsole class for implementing this console. CConsole uses the CBuffer and CLine classes to do its buffer manipulation. The way this console works is described here:
- The programmer creates an instance of the CConsole class.
- Upon the main window's KeyPress Event (WM_KEYPRESS), the programmer sends the CConsole object the corresponding ASCII key code.
- CConsole processes the key, if the console is in the active state. CConsole automatically toggles active state when the "~" (tilde) key is pressed, just like in Quake.
- If the key is processed, CConsole adds the key to its CLine class instance. CLine checks to make sure that the maximum number of characters that could be held on the screen's line is not exceeded. If the Enter key is pressed, the data in the CLine instance of CConsole is added to the CBuffer class.
- CBuffer adds the CLine object to itself, and does any line scrolling (oldest line gets scrolled out of view, and the newest line gets its place in the very bottom).
- When rendering, right before swapping the backbuffer to the screen, the programmer calls the CConsole's Render function. The Render function draws each line of CBuffer onto the backbuffer, if it is in active state.
- When done, the programmer releases the CConsole instance, which releases the CBuffer and CLine along with its font surface and background image surface.
See how simple it is? It's much more simple than any one of my older approaches at least. (Special thanks to Gaz and Ripper) Bitmapped Fonts -- Good or Bad?
Bitmapped fonts are something that can be a hassle, or something that will make your game look more attractive. Whenever you want to use bitmaps for fonts, always check to make sure you really need them. Meaning if you are looking at the fact that GDI is slow, and you need that few extra frames to increase your FPS, blitting the font from a surface is probably the best choice. However, it is worth considering that speed is not always the only issue. You might need to format your text output (like centering, italicizing, underlining, etc.) which is easier with GDI.
However most people (including myself) have some trouble using bitmapped fonts. Although there are many approaches to implement them, one method that Quake used, which changed the way I thought (creating a font editor, making my own custom font file format, etc.) was to create a font bitmap in ASCII order. Although it might seem obvious now, I had no idea that creating a font in ASCII order would be so much easier than creating a bitmap in A-Z order or keyboard order.
When you are creating a bitmapped font, always
create them in ASCII order (you can look up ASCII codes in MSDN by using the keyword: "character codes"). Since the KeyPress Event (Message WM_KEYPRESS) comes with an ASCII code, you can just use this value as a base to get the position of the character you want in the bitmap file by using this formula (Special thanks to: Benjamin Marty). Excerpt 3.1 shows this: Excerpt 3.1
'char_code: ASCII value of the key pressed.
'NUM_CHARS_PER_LINE: number of characters on each line
'FONT_START_Y: vertical pixel position for char's start.
'FONT_Y_SIZE: pixel size of each character's height.
'FONT_X_SIZE: pixel size of each character's width.
'FONT_BMP_WIDTH: width of the whole font bitmap file.
''Mod': VB's modulo operator (for those confused).
top = ( Int( (char_code-32) / NUM_CHARS_PER_LINE)
* FONT_Y_SIZE) + FONT_START_Y ' 32 is ASCII for space
left = (char_code * FONT_X_SIZE) Mod FONT_BMP_WIDTH
Pretty simple if you think about it, instead of hard-coding values which you may need to do over if you changed the file. If you already knew this technique, kudos to you. Conclusion
In conclusion, having a console in your game is pretty handy, especially if you can't or don't want to write your own GUI. In more complex games, for example an RPG or RTS, a console might not suffice. But in a regular shoot-em up, it's usually more than enough.
If you happen to use DirectInput's exclusive level keyboard access, all you need to do is look for a key input (like DIK_ESCAPE) that will enable/disable the console. When you get this input, just un-acquire your Keyboard device until the console is deactivated (look for the same key in your KeyPress Event/WM_KEYPRESS message) this time. When you receive that message, re-acquire the Keyboard device again.
On a small note, the console I provide is by no means optimized or bug-free. It is no where near being organized and was just something I made "on the fly". The code is free to modify and distribute.
The console demo I am providing you with is fully customizable 'as-is'. All you probably need to do is just change a few constants. If you do is it, you don't have
to give me any credits, royalties, or anything. If you have any comments, questions, or suggestions, don't hesitate to email me at VBDevelopr@hotmail.com
. The Console Demo
How to setup the demo to run properly: This demo uses LibX.DLL, an ActiveX DLL that requires 'registration'. First, place the LibX.DLL you receive with the ZIP in the Windows/System directory. To register properly, from the command prompt type:
and press enter. You should receive a prompt saying whether the registration succeeded or not. Click here to download the Demo
. Click here to download LibX v1.7
That's all for now, take care! Maybe a little DDraw GUI demo next time,
- Pranay Uppuluri