OgreConsole         A simple graphical console

Description

A graphical ingame console is an essential part of any game. Today I've desided to put together this code for a simple graphical console. This console class is a singleton class that renders a console on the auto created window. To make things simple, it takes a single parameter in the init() method and retreives all other things from the root (including scene manager and rendering window). To pull down the console you just call setVisible(true) and you call setVisible(false) to hide it. The console automatically animates when it shows and hides. That is done automatically by the console class since it registers itself as a frame listener. It also registers itself as a log listener so that it can display log messages.

Here is how the console looks ingame:

screenshot.png

Usage

In your scene initialisation, you can add something like this:

new OgreConsole;
 OgreConsole::getSingleton().init(mRoot);
 OgreConsole::getSingleton().setVisible(true);
 OgreConsole::getSingleton().addCommand("quit",&CMD_Quit);
 OgreConsole::getSingleton().addCommand("screenshot",&CMD_Screenshot);

Now you will just have to feed the input events to the onKeyPressed() method to get the console working properly. The console will not handle input event when it is closed.

ogreconsole.h

#pragma once
 #include <OgreFrameListener.h>
 #include <Ogre.h>
 #include <OIS/OIS.h>
 #include <list>
 #include <vector>
 
 using namespace Ogre;
 using namespace std;
 
 class OgreConsole: public Singleton<OgreConsole>, FrameListener, LogListener
 {
 public:
    OgreConsole();
    ~OgreConsole();
 
    void   init(Ogre::Root *root);
    void   shutdown();
 
    void   setVisible(bool visible);
    bool   isVisible(){return visible;}
 
    void   print(const String &text);
 
    virtual bool frameStarted(const Ogre::FrameEvent &evt);
    virtual bool frameEnded(const Ogre::FrameEvent &evt);
 
    void onKeyPressed(const OIS::KeyEvent &arg);
 
    void addCommand(const String &command, void (*)(vector<String>&));
    void removeCommand(const String &command);
 
    //log
    void messageLogged( const String& message, LogMessageLevel lml, bool maskDebug, const String &logName ) {print(logName+": "+message);}
 private:
    bool            visible;
    bool            initialized;
    Root         *root;
    SceneManager   *scene;
    Rectangle2D   *rect;
    SceneNode      *node;
    OverlayElement *textbox;
    Overlay      *overlay;
 
    float            height;
    bool            update_overlay;
    int               start_line;
    list<String>      lines;
    String            prompt;
    map<String,void (*)(vector<String>&)>  commands;
 };

ogreconsole.cpp


#include "Console.h"
 
 template<> OgreConsole *Singleton<OgreConsole>::ms_Singleton=0;
 
 #define CONSOLE_LINE_LENGTH 85
 #define CONSOLE_LINE_COUNT 15
 OgreConsole::OgreConsole(){
    start_line=0;
 }
 
 OgreConsole::~OgreConsole(){
    
 }
 
 void OgreConsole::init(Ogre::Root *root){
    if(!root->getSceneManagerIterator().hasMoreElements())
       OGRE_EXCEPT( Exception::ERR_INTERNAL_ERROR, "No scene manager found!", "init" );
 
    this->root=root;
    scene=root->getSceneManagerIterator().getNext();
    root->addFrameListener(this);
 
    height=1;
 
    // Create background rectangle covering the whole screen
    rect = new Rectangle2D(true);
    rect->setCorners(-1, 1, 1, 1-height);
    rect->setMaterial("console/background");
    rect->setRenderQueueGroup(RENDER_QUEUE_OVERLAY);
    rect->setBoundingBox(AxisAlignedBox(-100000.0*Vector3::UNIT_SCALE, 100000.0*Vector3::UNIT_SCALE));
    node = scene->getRootSceneNode()->createChildSceneNode("#Console");
    node->attachObject(rect);
 
    textbox=OverlayManager::getSingleton().createOverlayElement("TextArea","ConsoleText");
    textbox->setCaption("hello");
    textbox->setMetricsMode(GMM_RELATIVE);
    textbox->setPosition(0,0);
    textbox->setParameter("font_name","Console");
    textbox->setParameter("colour_top","1 1 1");
    textbox->setParameter("colour_bottom","1 1 1");
    textbox->setParameter("char_height","0.03");
    
    overlay=OverlayManager::getSingleton().create("Console");   
    overlay->add2D((OverlayContainer*)textbox);
    overlay->show();
    LogManager::getSingleton().getDefaultLog()->addListener(this);
 }
 
 void OgreConsole::shutdown(){
    if(!initialized)
       return;
    delete rect;
    delete node;
    delete textbox;
    delete overlay;
 }
 void OgreConsole::onKeyPressed(const OIS::KeyEvent &arg){
    if(!visible)
       return;
    if (arg.key == OIS::KC_RETURN)
    {
       //split the parameter list
       const char *str=prompt.c_str();
       vector<String> params;
       String param="";
       for(int c=0;c<prompt.length();c++){
          if(str[c]==' '){
             if(param.length())
                params.push_back(param);
             param="";
          }
          else
             param+=str[c];
       }
       if(param.length())
          params.push_back(param);
 
       //try to execute the command
       map<String,void(*)(vector<String>&)>::iterator i;
       for(i=commands.begin();i!=commands.end();i++){
          if((*i).first==params[0]){
             if((*i).second)
                (*i).second(params);
             break;
          }
       }
 
       print(prompt);
       prompt="";
    }
    if (arg.key == OIS::KC_BACK)
       prompt=prompt.substr(0,prompt.length()-1);
    if (arg.key == OIS::KC_PGUP)
    {
       if(start_line>0)
          start_line--;
    }
    if (arg.key == OIS::KC_PGDOWN)
    {
       if(start_line<lines.size())
          start_line++;
    }
    else
    {
       char legalchars[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890+!\"#%&/()=?[]\\*-_.:,; ";
       for(int c=0;c<sizeof(legalchars);c++){
          if(legalchars[c]==arg.text){
             prompt+=arg.text;
             break;
          }
       }
    }
    update_overlay=true;
 }
 bool OgreConsole::frameStarted(const Ogre::FrameEvent &evt){
    if(visible&&height<1){
       height+=evt.timeSinceLastFrame*2;
       textbox->show();
       if(height>=1){
          height=1;
       }
    }
    else if(!visible&&height>0){
       height-=evt.timeSinceLastFrame*2;
       if(height<=0){
          height=0;
          textbox->hide();
       }
    }
 
    textbox->setPosition(0,(height-1)*0.5);
    rect->setCorners(-1,1+height,1,1-height);
 
    if(update_overlay){
       String text;
       list<String>::iterator i,start,end;
       
       //make sure is in range
       if(start_line>lines.size())
          start_line=lines.size();
 
       int lcount=0;
       start=lines.begin();
       for(int c=0;c<start_line;c++)
          start++;
       end=start;
       for(int c=0;c<CONSOLE_LINE_COUNT;c++){
          if(end==lines.end())
             break;
          end++;
       }
       for(i=start;i!=end;i++)
          text+=(*i)+"\n";
       
       //add the prompt
       text+="] "+prompt;
 
       textbox->setCaption(text);
       update_overlay=false;
    }
    return true;
 }
 
 void OgreConsole::print(const String &text){
    //subdivide it into lines
    const char *str=text.c_str();
    int start=0,count=0;
    int len=text.length();
    String line;
    for(int c=0;c<len;c++){
       if(str[c]=='\n'||line.length()>=CONSOLE_LINE_LENGTH){
          lines.push_back(line);
          line="";
       }
       if(str[c]!='\n')
          line+=str[c];
    }
    if(line.length())
       lines.push_back(line);
    if(lines.size()>CONSOLE_LINE_COUNT)
       start_line=lines.size()-CONSOLE_LINE_COUNT;
    else
       start_line=0;
    update_overlay=true;
 }
 
 bool OgreConsole::frameEnded(const Ogre::FrameEvent &evt){
 
    return true;
 }
 
 void OgreConsole::setVisible(bool visible){
    this->visible=visible;
 }
 
 void OgreConsole::addCommand(const String &command, void (*func)(vector<String>&)){
    commands[command]=func;
 }
 
 void OgreConsole::removeCommand(const String &command){
    commands.erase(commands.find(command));
 }

console.material


material console/background
 {
     technique
     {
         pass
         {
             scene_blend alpha_blend
             diffuse 0 0 1 0.5
         }
 
     }
 
 }

And finally the font:

console.fontdef

Console
 {
     type         truetype
     source         console.ttf
     size         32
     resolution     55
 }

You can find the font file here.

TODO

  • Command history
  • Cursor (I haven't figured out yet how to draw underlined characters)
  • Colored text quake style with ^# prefixes to indicate colors (I haven't figured this out either; the only thing I can do right now is to set the color of the whole textarea and not individual characters in it) - see this wiki article
  • Support for command callbacks as pointers to member functions of a class (boost?)

About

This OgreConsole was originally posted by PixL in this Ogre Forum topic:
Ogre graphical console

New version

A newer GUI toolkit called Gorilla now includes a new version of this console. It's the same code as shown in this article, but ported to a new and more expansive GUI system.