Writing a console within an application Writing a console within an application shell shell

Writing a console within an application


I implemented the following as a console for an opengl game I was writing a while ago. It is by no means a definitive answer to you question but it worked for me and you might get something useful out of it.

The 2 files are at this bottom of this post. The code is unlikely to run directly as there is one for 2 library header files that I'm not going to include. If you want the full source let me know.

Basically though the console class allows you to add variable pointers to it that can be changed at run time. It accepts input from the windows event messages. (The actual handling of input is done elsewhere) The command parsing is done in the ProcessInput() method and variables are updated in the ChangeVariable() method.

A word of warning. This method essentially gives the console users direct access to the memory locations of the individual variable. This requires good input validation to ensure you the user cant cause the application to crash at runtime. If I ever sat down and tried to make another console I would likely do things slightly different. However I hope this gives you a little help.

The header file:

#ifndef CONSOLE_H#define CONSOLE_H#include <vector>#include <map>#include <string>#include "Singleton.h"#include <Windows.h>#include "Enumerations.h"#include "StringConversion.h"class Console{public:    Console();    ~Console();    void Update(std::vector<WPARAM> pressedKeys);    void AddInt(std::string varName, int *ptrToInt);    void AddFloat(std::string varName, float *ptrToFloat);    void AddLong(std::string varName, long *ptrToLong);    void AddBool(std::string varName, bool *ptrToBool);    const std::string &GetCurrentText();    const std::vector<std::string> &GetPreviousText();private:    std::map<std::string, int *> m_Ints;    std::map<std::string, float *> m_Floats;    std::map<std::string, long *> m_Longs;    std::map<std::string, bool *> m_Bools;    std::map<std::string, std::string> m_Variables;    std::vector<std::string> m_PrevConsoleText;    std::string m_CurrInput;    int m_PrevSelection;    bool ProcessInput();    void ChangeVariable(const std::string &varName, const std::string &value);};typedef Singleton<Console> g_Console;#endif // CONSOLE_H

The cpp file:

#include "Console.h"Console::Console(){    m_PrevSelection = 0;}Console::~Console(){}void Console::AddInt(std::string varName, int *ptrToInt){    m_Ints[varName] = ptrToInt;    m_Variables[varName] = "int";}void Console::AddFloat(std::string varName, float *ptrToFloat){    m_Floats[varName] = ptrToFloat;    m_Variables[varName] = "float";}void Console::AddLong(std::string varName, long *ptrToLong){    m_Longs[varName] = ptrToLong;    m_Variables[varName] = "long";}void Console::AddBool(std::string varName, bool *ptrToBool){    m_Bools[varName] = ptrToBool;    m_Variables[varName] = "bool";}void Console::ChangeVariable(const std::string &varName, const std::string &value){    //*(m_Bools[varName]) = value;    std::string temp = m_Variables[varName];    if(temp == "int")    {        //*(m_Ints[varName]) = fromString<int>(value);    }    else if(temp == "float")    {        //*(m_Floats[varName]) = fromString<float>(value);    }    else if(temp == "long")    {        //*(m_Longs[varName]) = fromString<long>(value);    }    else if(temp == "bool")    {        if(value == "true" || value == "TRUE" || value == "True")        {            *(m_Bools[varName]) = true;        }        else if(value == "false" || value == "FALSE" || value == "False")        {            *(m_Bools[varName]) = false;        }    }}const std::string &Console::GetCurrentText(){    return m_CurrInput;}void Console::Update(std::vector<WPARAM> pressedKeys){    for(int x = 0; x < (int)pressedKeys.size(); x++)    {        switch(pressedKeys[x])        {        case KEY_A:            m_CurrInput.push_back('a');            break;        case KEY_B:            m_CurrInput.push_back('b');            break;        case KEY_C:            m_CurrInput.push_back('c');            break;        case KEY_D:            m_CurrInput.push_back('d');            break;        case KEY_E:            m_CurrInput.push_back('e');            break;        case KEY_F:            m_CurrInput.push_back('f');            break;        case KEY_G:            m_CurrInput.push_back('g');            break;        case KEY_H:            m_CurrInput.push_back('h');            break;        case KEY_I:            m_CurrInput.push_back('i');            break;        case KEY_J:            m_CurrInput.push_back('j');            break;        case KEY_K:            m_CurrInput.push_back('k');            break;        case KEY_L:            m_CurrInput.push_back('l');            break;        case KEY_M:            m_CurrInput.push_back('m');            break;        case KEY_N:            m_CurrInput.push_back('n');            break;        case KEY_O:            m_CurrInput.push_back('o');            break;        case KEY_P:            m_CurrInput.push_back('p');            break;        case KEY_Q:            m_CurrInput.push_back('q');            break;        case KEY_R:            m_CurrInput.push_back('r');            break;        case KEY_S:            m_CurrInput.push_back('s');            break;        case KEY_T:            m_CurrInput.push_back('t');            break;        case KEY_U:            m_CurrInput.push_back('u');            break;        case KEY_V:            m_CurrInput.push_back('v');            break;        case KEY_W:            m_CurrInput.push_back('w');            break;        case KEY_X:            m_CurrInput.push_back('x');            break;        case KEY_Y:            m_CurrInput.push_back('y');            break;        case KEY_Z:            m_CurrInput.push_back('z');            break;        case KEY_0:            m_CurrInput.push_back('0');            break;        case KEY_1:            m_CurrInput.push_back('1');            break;        case KEY_2:            m_CurrInput.push_back('2');            break;        case KEY_3:            m_CurrInput.push_back('3');            break;        case KEY_4:            m_CurrInput.push_back('4');            break;        case KEY_5:            m_CurrInput.push_back('5');            break;        case KEY_6:            m_CurrInput.push_back('6');            break;        case KEY_7:            m_CurrInput.push_back('7');            break;        case KEY_8:            m_CurrInput.push_back('8');            break;        case KEY_9:            m_CurrInput.push_back('9');            break;        case KEY_QUOTE:            m_CurrInput.push_back('\"');            break;        case KEY_EQUALS:            m_CurrInput.push_back('=');            break;        case KEY_SPACE:            m_CurrInput.push_back(' ');            break;        case KEY_BACKSPACE:            if(m_CurrInput.size() > 0)            {                m_CurrInput.erase(m_CurrInput.end() - 1, m_CurrInput.end());            }            break;        case KEY_ENTER:            ProcessInput();            break;        case KEY_UP:            m_PrevSelection--;            if(m_PrevSelection < 1)            {                m_PrevSelection = m_PrevConsoleText.size() + 1;                m_CurrInput = "";            }            else            {                m_CurrInput = m_PrevConsoleText[m_PrevSelection - 1];            }            break;        case KEY_DOWN:            if(m_PrevSelection > (int)m_PrevConsoleText.size())            {                m_PrevSelection = 0;                m_CurrInput = "";            }            else            {                m_CurrInput = m_PrevConsoleText[m_PrevSelection - 1];            }            m_PrevSelection++;            break;        }    }}bool Console::ProcessInput(){    int x;    std::string variable = "NULL", value;    bool ok = false;    std::string::iterator it;    //Split up the input from the user.    //variable will be the variable to change    //ok will = true if the syntax is correct    //value will be the value to change variable to.    for(x = 0; x < (int)m_CurrInput.size(); x++)    {        if(m_CurrInput[x] == ' ' && variable == "NULL")        {            variable = m_CurrInput.substr(0, x);        }        else if(m_CurrInput[x] == '=' && m_CurrInput[x - 1] == ' ' && m_CurrInput[x + 1] == ' ')        {            ok = true;        }        else if(m_CurrInput[x] == ' ')        {            value = m_CurrInput.substr(x + 1, m_CurrInput.size());        }    }    if(ok)    {        m_PrevConsoleText.push_back(m_CurrInput);        m_PrevSelection = m_PrevConsoleText.size();        if(m_PrevConsoleText.size() > 10)        {            m_PrevConsoleText.erase(m_PrevConsoleText.begin(), m_PrevConsoleText.begin() + 1);        }        m_CurrInput.clear();        ChangeVariable(variable, value);    }    else    {        m_PrevConsoleText.push_back("Error invalid console syntax! Use: <variableName> = <value>");        m_CurrInput.clear();    }    return ok;}const std::vector<std::string> &Console::GetPreviousText(){    return m_PrevConsoleText;}

Edit 1: Added DrawConsole()I just get the text from the console class render an image that looked similar to the source engine console window found in any recent valve game and then the text gets drawn in the appropriate places.

void View::DrawConsole(){    Square console;    std::vector<std::string> temp;    temp = g_Console::Instance().GetPreviousText();    console.top = Vector3f(0.0, 0.0, 1.0);    console.bottom = Vector3f(640, 480, 1.0);    g_Render::Instance().SetOrthographicProjection();    g_Render::Instance().PushMatrix();    g_Render::Instance().LoadIdentity();    g_Render::Instance().BindTexture(m_ConsoleTexture);    g_Render::Instance().DrawPrimative(console, Vector3f(1.0f, 1.0f, 1.0f));    g_Render::Instance().DisableTexture();    g_Render::Instance().SetOrthographicProjection();    //Draw the current console text    g_Render::Instance().DrawString(g_Console::Instance().GetCurrentText(), 0.6f, 20, 465);    //Draw the previous console text    for(int x = (int)temp.size(); x > 0; x--)    {        g_Render::Instance().DrawString(temp[x-1], 0.6f, 20, (float)(425 - (abs((int)temp.size() - x) * 20)));    }    g_Render::Instance().SetPerspectiveProjection();    g_Render::Instance().PopMatrix();    g_Render::Instance().SetPerspectiveProjection();}


There are a few things to this. First you want some kind of line editing support. There are libraries for this, for example NetBSD's editline http://www.thrysoee.dk/editline/

Then you somehow need to process keypresses. Now this is where the fun begins. Instead of trying to process the key events directly, I'd feed them into a anonymous pipe created using the pipe on (POSIX) / CreatePipe on Windows. Then on the other end you can read them of, as if they came in from stdin. A second anonymous pipe doubles the function of stdout and gets its output displayed on the in-game console. I'd call the resulting pair of FDs consolein and consoleout. I'd also add a consoleerr FD for urgent error messages; the console may display them in another color or filter them.

The nice thing about this approach is, that you can use all the nice standard library features to talk to your console then. You can use fprintf(consoleout, ...), fscanf(consolein, ...) and so on; it also works with C++ iostreams, of course. But more importantly you can directly attach it to libraries like the aforementioned editline.

Finally you need to process the commands the user typed into the console. There I'd go the lazy route and just embed a scripting language interpreter, one that supports interactive operation. Like Python, or very widespread throughout games, Lua. You can also implement your own command interpreter of course.


Well, what you probably want if you want it to feel more like a console is:

  • Being able to switch it on and off with one button press, probably something like ~, which is used a lot.
  • Give the line you're typing on a background color, maybe transparent, but at least make sure it's not just text floating on the RenderWindow. If the output of the command is multiple lines, make sure they're all visible or that people could at least scroll through the history.
  • Make sure the commands are easy to understand and consistent. For example, if I'm not mistaken, a lot of games on the source engine use the cl_ prefix for anything rendering related. See cl_showfps 1 for example.
  • Traditional terminal input would be a nice touch. Up shows you the previous command you filled in. Maybe if you're feeling adventurous, use Tab for completion.
  • If you have some time left, a way to show available commands through --help for example would also be nice. Depending on how complicated your game is of course.

For the rest, look at how other games did this. You mentioned Quake, which has a great example of an ingame terminal. I for one think the one in a lot of Source games is also easy to use ( see Half Life 2, Counter Strike Source, Team Fortress 2, Left 4 Dead, etc. ). I don't think there are any standard libraries for this, not including another framework like OGRE or IrrLicht.