Table of contents
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:
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.