Overview
The Console allows us to do a great number of things. We can issue various commands to our game, change game engine variables, etc. all while the game is still running. While on the surface the Console appears to be quite smart, under the hood it is actually not that intelligent. When a command has been entered into the Console, the Console simply passes the text on to the CommandManager to handle. Also, any game code that wishes can “print” to the Console. The Console simply displays everything that has been printed to it. There is very little validation at all in the Console. The majority of functionality within the Console is to handle the rendering of the Console. To see how the Console plugs into the CommandManager at design-time, please refer to the design section of the prior article.
Implementation
Since rendering is going to take up most of the article, let's go ahead and dive into the bones of the Console class. First of all, we have a couple of attributes to manage all the lines that have been printed to the Console, as well as attributes to manage details of our rendering capabilities.
public sealed class Console : IDisposable
{
// Inner Types
private enum ConsoleState
{
Closed,
Closing,
Opening,
Open
}
// Attributes/Properties
private float _currentConsolePercentage = 0.0f;
private float _maxConsolePercentage = 30.0f;
// no property accessors
private ArrayList _entries = new ArrayList();
// no property accessor
private StringBuilder _entryLine = new StringBuilder(">");
// no property accessor
private ConsoleState _state = ConsoleState.Closed;
// no property accessor
private System.Drawing.Font _windowsFont;
private Font _font;
// no property accessors
public bool IsVisible
{
get { return _state != ConsoleState.Closed; }
}
public int LineHeight
{
get { return (int)_windowsFont.Size * 2; }
}
The ConsoleState enumeration is defined as an inner type because it is only used within the Console class itself. All the outside world knows about the Console is that its State can be toggled via ToggleState. _currentConsolePercentage and _maxConsolePercentage are the percentages that the console is currently open, and the maximum percentage it can open to, respectively. _entries are all the lines that have been printed to the Console and _entryLine is like the command line, where we can input various commands.
Notice that our fonts are not initialized in the constructor. This is because we cannot guarantee that our instance will be created before our Renderer instance. If the Console instance is created before the Renderer instance, our font initialization code would throw an “object reference not set“ error because the Device property of the Renderer would be null. Also in the Initialize function, we will initialize our commands like we did in the CommandManager.
// Singleton Implementation
public static readonly Console Instance = new Console();
private Console()
{
}
public void Initialize()
{
_windowsFont = new System.Drawing.Font("Arial", 8.0f);
_font = new Font(BattleEngine.Instance.Renderer.Device, _windowsFont);
// add our needed commands
CommandManager.Instance.AddCommand("consolesize", new CommandCallback(this.SetConsoleSize));
CommandManager.Instance.AddCommand("clear", new CommandCallback(this.Clear));
}
I think the various Print functions are pretty self explanatory. The few comments I have is that we only store the last 50 lines that were printed, and we only print print characters and strings to the entry line if we are visible (this does not prevent us from printing to the Console in general though). The PrintFormat function is provided simply for convenience.
public void Print(string text)
{
_entries.Insert(0, text);
while(_entries.Count > 50)
{
_entries.RemoveAt(49); // 49 because of zero-based index
}
}
public void PrintFormat(string text, params object[] args)
{
Print(string.Format(text, args));
}
public void PrintCharacterToEntryLine(char character)
{
if (IsVisible)
_entryLine.Append(character);
}
public void PrintStringToEntryLine(string text)
{
if (IsVisible)
_entryLine.Append(text);
}
public void Backspace()
{
if (_entryLine.Length > 1)
_entryLine.Remove(_entryLine.Length - 1, 1);
}
If the Console is visible, and we hit enter, we tell the Console to process whatever is currently on the entry line. When we process the entry line, we simply pass off the text to the CommandManager's buffer and reset our entry line.
public void ProcessEntryLine()
{
string text = _entryLine.ToString().Trim().Remove(0, 1);
CommandManager.Instance.Buffer.AddLine(text);
_entryLine = new StringBuilder(">");
}
Next we want to implement the command callbacks for the commands we added in our initialization above (”clear” and “consolesize”). The “consolesize” command will allow us to control the maximum percentage of the window that the Console will take up when it is fully opened. Currently, the minimum is 5% and the maximum is 50% (the default is 30%). The “clear” commands deletes all entries that are currently in the Console and resets the entry line.
#region Commands
private void SetConsoleSize(string[] args)
{
try
{
float newSize = float.Parse(args[0]);
if (newSize > 50.0f || newSize < 5.0f)
{
Print("consolesize must be between 5 and 50");
}
else
{
_maxConsolePercentage = newSize;
_currentConsolePercentage = _maxConsolePercentage;
}
}
catch
{
Print("consolesize must be between 5 and 50");
}
}
private void Clear(string[] args)
{
_entries.Clear();
_entryLine = new StringBuilder(">");
}
#endregion
The reason that the SetConsoleSize method is surrounded by a try/catch block is because we do not wish to throw an error if the parameter is not valid (for example, the catch will catch (no pun intended) any errors that occur because the parameter is not a valid float). We will just print the usage of the command and ignore the error.
Now for the fun stuff. The first thing we want to ensure is that we are not rendering the Console when it is not visible. Once this has been ensured, then we can update the current percentage of the window that is displayed. We are incrementing the the current percentage slowly so the Console slides open rather than appearing out of thin air.
public void Render()
{
if (IsVisible)
{
// update console state
if (_state == ConsoleState.Opening && _currentConsolePercentage <= _maxConsolePercentage)
{
_currentConsolePercentage += 0.5f;
if (_currentConsolePercentage >= _maxConsolePercentage)
{
_currentConsolePercentage = _maxConsolePercentage;
_state = ConsoleState.Open;
}
}
else if (_state == ConsoleState.Closing && _currentConsolePercentage >= 0.0f)
{
_currentConsolePercentage -= 0.5f;
if (_currentConsolePercentage <= 0.0f)
{
_currentConsolePercentage = 0.0f;
_state = ConsoleState.Closed;
}
}
Pretty simple. Next we will try to render the Console. I am going to dissect this a piece at a time so I can explain everything that is going on properly. First, we are going to verify that the Console is opened far enough for a single line to display. If it is not, then we will not render. The line variable will store where on the Y-axis our current line to render is.
// render background
try
{
int line = (int)((_currentConsolePercentage / 100.0f) * BattleEngine.Instance.TargetControl.Height) - this.LineHeight;
if (line > 5)
{
The reason that we divide by 100 is to get a true float percentage value (for example, since we are specifying 30% we need to multiply by 0.30 to get the correct percentage). So, the line variable stores where on the Y-axis our current position is minus the LineHeight of our font.
Next we figure out the current Console height as well as the height of the visible portion of the Console. Using these values, we will also need to determine where the rectangle for the background of the Console will be renderer (x position, y position, width, and height).
float consoleHeight = BattleEngine.Instance.TargetControl.Height * (_maxConsolePercentage / 100.0f);
float visibleHeight = BattleEngine.Instance.TargetControl.Height * (_currentConsolePercentage / 100.0f);
float x = 0;
float y = BattleEngine.Instance.TargetControl.Height + (consoleHeight - visibleHeight);
float width = BattleEngine.Instance.TargetControl.Width;
float height = consoleHeight;
Next we will actually render the background of the Console. The Rectangle class will be defined in a later article. All the rectangle class basically is, is a wrapper around a vertex buffer. With the Rectangle class, you just specify where you would like the rectangle drawn, how big it should be, and what color it should be. Remember, for this first delivery, we will only be dealing with programmer graphics. Once the game is operational, we will start adding our eye candy.
// draw background
Battle.Shapes.Rectangle consoleBackground = new Battle.Shapes.Rectangle(x, y, width, height, System.Drawing.Color.White);
Battle.Shapes.Rectangle consoleDivider = new Battle.Shapes.Rectangle(0, BattleEngine.Instance.TargetControl.Height - line + 2, width, 1, System.Drawing.Color.Black);
consoleBackground.Render();
consoleDivider.Render();
The only objects left to render are the entry line and the various stored entries that are currently in the Console. As we render the various lines, we adjust the line position as we go so that the text is rendered in the correct place.
// draw entry line
_font.DrawText(_entryLine.ToString(), new System.Drawing.Rectangle(2, line, 0, 0), DrawTextFormat.None, System.Drawing.Color.Black);
line -= this.LineHeight;
// draw stored entries
foreach(string entry in _entries)
{
if (line > 5)
{
_font.DrawText(entry, new System.Drawing.Rectangle(2, line, 0, 0), DrawTextFormat.None, System.Drawing.Color.Black);
line -= this.LineHeight;
}
}
}
}
catch
{
Console.Instance.Print("could not render console");
}
}
}
The only thing left to implement functionality-wise in the Console class is the ability to toggle the state the console is in.
public void ToggleState()
{
if (_state == ConsoleState.Closed || _state == ConsoleState.Closing)
_state = ConsoleState.Opening;
else
_state = ConsoleState.Closing;
}
#endregion
The last thing we need to do in our Console class is to dispose of the fonts that we use to draw the text on the screen.
public void Dispose()
{
_font.Dispose();
_windowsFont.Dispose();
}
}
Now that wasn't too hard was it? Now we just need to hook it into our current engine. All we need to do is update the Initialize and Render functions on the BattleEngine class in order to initialize and render the Console, respectively of course.
public void Initialize(System.Windows.Forms.Control targetControl)
{
_targetControl = targetControl;
// create subsystems
_renderer = new Renderer(targetControl);
// initialize subsystems
CommandManager.Instance.Initialize();
Console.Instance.Initialize();
}
private void Render()
{
_renderer.BeginScene();
Console.Instance.Render();
_renderer.EndScene();
}
It's nothing special, but our game now has a console!

And another article has drawn to a close. I hope you all are having as much fun as I am having. All that is left for our input system in this simple 2D game is to gather keyboard input. In fact, keyboard input is our next article, so stay tuned. Same Bat Time, Same Bat Channel.